<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.flightgear.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=PlayeRom</id>
	<title>FlightGear wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.flightgear.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=PlayeRom"/>
	<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/Special:Contributions/PlayeRom"/>
	<updated>2026-04-17T18:44:16Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.39.6</generator>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Pl/FlightGear&amp;diff=143883</id>
		<title>Pl/FlightGear</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Pl/FlightGear&amp;diff=143883"/>
		<updated>2026-04-03T16:43:22Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Sprzęt */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Software&lt;br /&gt;
|title             = FlightGear Flight Simulator&lt;br /&gt;
|logo              = FlightGear logo.png&lt;br /&gt;
|logosize          = 200px&lt;br /&gt;
|image             = Boeing 777-200ER cockpit.jpg&lt;br /&gt;
|alt               = W kokpicie [[Boeing 777-200ER]]&lt;br /&gt;
|developedby       = Developerzy FlightGear i współtwórcy&lt;br /&gt;
|initialrelease    = Lipec 17, 1997&lt;br /&gt;
|latestrelease     = {{current release|full}} ({{#time: j xg Y | {{current release|fulldate}} | pl }})&lt;br /&gt;
|writtenin         = C/C++/Nasal&lt;br /&gt;
|os                = Windows, Linux, macOS oraz FreeBSD&lt;br /&gt;
|platform          = wieloplatformowy&lt;br /&gt;
|developmentstatus = Aktywny (1996-)&lt;br /&gt;
|type              = Symulator Lotu&lt;br /&gt;
|license           = [[GNU General Public License]]&lt;br /&gt;
|website           = https://www.flightgear.org/&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[File:OV10A-NASA-in-action.jpg|thumb|right|270px|NASA [[OV-10]] we FlightGear 1.0]]&lt;br /&gt;
&lt;br /&gt;
'''FlightGear Flight Simulator''' (w skrócie '''FlightGear''' lub '''FGFS''') to zaawansowana, darmowa i całkowicie otwarto-źródłowa platforma symulacji lotu, stworzona przez wolontariuszy. FlightGear jest wydany na zasadach licencji [[GNU General Public License]] i jest on w większości napisany przy użyciu języka programowania C++.&lt;br /&gt;
&lt;br /&gt;
Coraz bardziej szczegółowe i zaawansowane wersje FlightGeara są corocznie wydawane od zapoczątkowania projektu w 1996 roku.&lt;br /&gt;
&lt;br /&gt;
Najnowsze publiczne wydanie dostępne jest do pobrania na [https://www.flightgear.org/download/ www.flightgear.org/download/] wraz z wersjami dla Microsoft Windows, macOS i Linux.&lt;br /&gt;
&lt;br /&gt;
== Historia ==&lt;br /&gt;
{{main article|FlightGear History}}&lt;br /&gt;
&lt;br /&gt;
Rozwój FlightGeara rozpoczął się od propozycji stworzenia otwarto-źródłowego symulatora w 1996 roku, w oparciu o własny kod renderowania grafiki 3D. Rozwój wersji opartej na [[OpenGL]] rozpoczął Curtis Olson w 1997 roku, ale także wiele innych osób miało swój wkład w ten projekt.&lt;br /&gt;
&lt;br /&gt;
FlightGear włączył inne otwarto-źródłowe zasoby, w tym model lotu LaRCsim od NASA oraz powszechnie dostępne dane o elewacji terenu. Pierwsze działające pliki binarne, wykorzystujące OpenGL dla grafiki 3D, pojawiły się w 1997 roku. Entuzjastyczny rozwój kolejnych wersji przez kilka lat zaowocował stopniowo coraz bardziej stabilnymi i zaawansowanymi wersjami. W 2001 roku zespół regularnie wydawał nowe wersje beta, a w 2005 roku dojrzałość oprogramowania doprowadziła do szerszych recenzji i wzrostu popularności. W 2007 roku nastąpiło formalne wyjście z wersji beta wraz z wydaniem wersji 1.0.0, dziesięć lat po pierwszym wydaniu FlightGeara w 1997 roku.&lt;br /&gt;
&lt;br /&gt;
[[File:FG-A-10.jpg|thumb|270px|Kokpit 3D [[A-10]] w wersji 1.0.0, rok 2008]]&lt;br /&gt;
&lt;br /&gt;
W 2008 roku, wersja 1.9.0 FlightGeara przeszła z biblioteki [[PLIB]] na [[OSG]], co spowodowało tymczasową utratę niektórych funkcji programu, takich jak wyświetlanie chmury 3D i cienie, za to nowo wprowadzone funkcje, takie jak cząsteczki, nadały symulacji kolejny stopień realizmu.&lt;br /&gt;
&lt;br /&gt;
== Program ==&lt;br /&gt;
&lt;br /&gt;
Silnik symulacji we FlightGear to [[SimGear]]. Jest on używany zarówno jako aplikacja użytkownika końcowego jak i przy pracach badawczych w środowiskach akademickich w celu rozwoju zagadnień związanych z symulacją lotu.&lt;br /&gt;
&lt;br /&gt;
Przykładem na możliwość dostosowywania FlightGeara jest szeroki wachlarz modeli dostępnych statków powietrznych, od [[:Category:Gliders|szybowców]] przez [[Helicopter|śmigłowców]], [[:Category:Airliners|liniowce]], [[Military aircraft|myśliwców odrzutowych]] do [[Space Shuttle|Wahadłowca Kosmicznego]]. Modele te zostały wykonane i dodane do projektu przez wielu ochotników ze społeczności FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Od wersji 0.9.10 statki powietrzne we FlightGear używają jednego z trzech [[Flight Dynamics Model|modeli dynamiki lotu]] (FDM): [[JSBSim]], [[YASim]] lub [[UIUC]]. Obecnie wyłącznie jeden silnik terenu jest w użyciu, jest nim [[TerraGear]]. Dostępne efekty pogodowe to między innymi chmury 3D, efekty świetlne, pory dnia i nocy. &lt;br /&gt;
&lt;br /&gt;
=== Modele Dynamiki Lotu ===&lt;br /&gt;
[[Flight Dynamics Models|Model dynamiki lotu]] (FDM) odpowiada za to w jaki sposób w programie jest symulowany lot statku powietrznego. FlightGear korzysta z własnych oraz zewnętrznych projektów modeli dynamiki lotu. Każdy statek powietrzny musi być tak zaprogramowany, aby korzystał z jednego z dostępnych modeli dynamiki lotu. Obecnie FlightGear jest jedynym, graficznym symulatorem lotu korzystającym ze wszystkich wspomnianych modeli dynamiki lotu, a UIUC i YASim zostały rozwinięte z myślą i w szczególności dla FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Wczesne wersje programu używały FDM, który był oparty na [[LaRCsim]] od NASA. W kolejnych wersjach został on zastąpiony przez bardziej elastyczne FDM.&lt;br /&gt;
&lt;br /&gt;
* [[JSBSim]] - domyślny model dynamiki lotu od 2000 roku.&lt;br /&gt;
* [[YASim]] - inny FDM, używający odmiennych metod obliczeniowych. Wprowadzony od wersji 0.7.9 w 2002 roku.&lt;br /&gt;
* [[UIUC]] - kolejny FDM, rozwijany przez UIUC Applied Aerodynamics Group z Uniwersytetu w Illinois na Urbana-Champaign, bazujący na LaRCsim. Obecnie już wycofany z wersji deweloperskiej FlightGeara.&lt;br /&gt;
* FlightGear może być tak skonfigurowany, aby przyjmował dane z zewnętrznych źródeł FDM takich jak [[MATLAB]].&lt;br /&gt;
* Inne niestandardowe FDM zostały napisane dla specyficznych typów statków powietrznych jak balony i sterowce.&lt;br /&gt;
&lt;br /&gt;
=== Zależności FlightGeara ===&lt;br /&gt;
&lt;br /&gt;
W przeciwieństwie do komercyjnych tytułów, głównym rezultatem prac nad projektem jest publikacja kodu źródłowego. Aby skorzystać z programu, można samodzielnie skompilować udostępniony kod dla docelowej platformy.&lt;br /&gt;
&lt;br /&gt;
Wykorzystywane przez FlightGeara biblioteki zmieniały się na przestrzeni czasu. Najważniejszą zależnością pozostaje SimGear, będący silnikiem symulacji dla FlightGeara. [[TerraGear]] nie stanowi bezpośredniej zależności – jest raczej nazwą domyślnego silnika odpowiedzialnego za generowanie terenu.&lt;br /&gt;
&lt;br /&gt;
Do obsługi dźwięku wykorzystywany jest OpenAL (z włączonym wsparciem dla SDL od wersji 0.9.5), natomiast wcześniej w tym celu używano biblioteki PLIB, która odpowiadała również za obsługę sprzętu. Za renderowanie grafiki 3D odpowiada [[OpenGL]] – DirectX nie jest wspierany. Z FlightGearem zintegrowano także silnik graficzny [[OpenSceneGraph]]. Do kompilacji wymagane jest również użycie biblioteki Simple Direct Media Layer (SDL).&lt;br /&gt;
&lt;br /&gt;
Zakres zależności może się różnić w zależności od docelowej platformy. Użytkownicy mogą samodzielnie kompilować kod lub – jeśli nie jest on dostępny bezpośrednio w ramach projektu – korzystać z gotowych wersji binarnych przygotowanych przez osoby trzecie.&lt;br /&gt;
&lt;br /&gt;
== Sprzęt==&lt;br /&gt;
&lt;br /&gt;
Do uruchomienia FlightGeara wymagany jest sprzęt obsługujący [[OpenGL]] oraz akcelerację 3D. Najlepsze wsparcie zapewniają karty graficzne NVIDIA. We wcześniejszych wersjach dostępna była również obsługa kart 3dfx, jednak została ona wycofana wraz ze wzrostem wymagań sprzętowych.&lt;br /&gt;
&lt;br /&gt;
[[File:Fgrun-page2.jpg|thumb|left|270px|[[FlightGear Launch Control|FlightGear Launcher]]]]&lt;br /&gt;
&lt;br /&gt;
== Dodatki i dostosowywanie ==&lt;br /&gt;
&lt;br /&gt;
Istnieją programy, które są zintegrowane z FlightGearem (zależności) lub są programami zewnętrznymi i współpracują z nim. Oprogramowanie takie może być częścią projektu FlightGear lub może być rozwijane niezależnie, ale udostępniane przez projekt FlightGear.&lt;br /&gt;
&lt;br /&gt;
Ważnym dodatkowym oprogramowaniem jest interfejs graficzny, służący do uruchomienia pliku wykonywalnego FlightGeara. We wczesnych wersjach, FlightGear mógł być uruchomiony jedynie z wykorzystaniem [[Pl/Opcje wiersza poleceń|opcji wiersza poleceń]]. Jednakże, w 2003 roku od wersji 0.9.3, został dołączony ''[[FlightGear Launch Control|FGRun]]'' - program startowy z interfejsem graficznym. Obecnie tę rolę spełnia ''[[FlightGear Qt launcher|QT Launcher]]''.&lt;br /&gt;
Podobną funkcję spełnia ''[[KFreeFlight]]'' dla środowiska KDE. ''FGTools'' jest alternatywnym front-endem dla środowiska Windows. ''FGKicker'' jest używany dla GTK+.&lt;br /&gt;
&lt;br /&gt;
Inne znaczące programy obejmują edytory i projekty związane z terenem. ''[[Atlas]]'' jest mapą dla FlightGeara; ''[[Kelpie Flight Planner]]'' to rozwijane w Javie narzędzie do planowania lotów.&lt;br /&gt;
''[[FlightGear Scenery Designer]]'' to edytor scenerii, pomocny przy pracy z danymi terenu. ''[[World Custom Scenery Project]]'' to projekt pomagający koordynować wspólne wysiłki na rzecz tworzenia scenerii. Na koniec edytor ''[[TaxiDraw]]'' do tworzenia nowych pasów startowych i dróg kołowania.&lt;br /&gt;
&lt;br /&gt;
=== Statki Powietrzne ===&lt;br /&gt;
{{Main article|Table of models}}&lt;br /&gt;
&lt;br /&gt;
Na początku, FlightGear dysponował tylko jednym statkiem powietrznym, był to Navion zawarty w projekcie LaRCsim od NASA, który w 2000 roku został zastąpiony przez Cessnę 172P. Rozwój UIUC i JSBSim przyniósł ze sobą kilka kolejnych samolotów, podobnie jak rozwój YASim, który od tego czasu stał się głównym FDM używanym we FlightGear. W wersji 2.12 dostępnych jest ponad 400 samolotów w ponad 900 unikalnych malowaniach, choć tylko kilka z nich wchodzi w skład pakietu podstawowego.&lt;br /&gt;
&lt;br /&gt;
[[File:EHAM.jpg|thumb|270px|[[Boeing 737-300|Boeing 733]] zaparkowany w scenerii [[EHAM]] ]]&lt;br /&gt;
&lt;br /&gt;
=== Sceneria ===&lt;br /&gt;
{{Main article|Scenery}}&lt;br /&gt;
&lt;br /&gt;
Projekt [[World Scenery]] dla FlightGeara zawiera dane o wysokości i klasie terenu całego świata.&lt;br /&gt;
Obiekty takie jak terminale, wiatraki i mosty są zebrane w [[FlightGear Scenery Database|bazie danych scenerii]].&lt;br /&gt;
&lt;br /&gt;
=== Sieć i wiele monitorów ===&lt;br /&gt;
&lt;br /&gt;
Istnieje kilka możliwości, które pozwalają komunikować się jednej instancji FlightGeara z inną. Dostępny jest protokół [[Multiplayer Howto|multiplayer]], co umożliwia lot w formacji lub symulowanie kontroli ruchu lotniczego w sieci lokalnej. Protokół Multiplayer zostanie wkrótce tak rozbudowany, aby także pozwalał na pracę w internecie. Dodatkową cechą możliwości sieciowych jest opcja podglądu innych pilotów na mapach Google.&lt;br /&gt;
&lt;br /&gt;
Kilka instancji FlightGeara może zostać tak zsynchronizowana, aby korzystały z wielu monitorów. Możliwe jest uzyskanie bardzo dobrej synchronizacji między monitorami jeżeli wszystkie instancje FlightGeara będą pracowały z tą samą częstotliwością wyświetlania klatek.&lt;br /&gt;
&lt;br /&gt;
== Kod FlightGeara vs wersja binarna ==&lt;br /&gt;
&lt;br /&gt;
W przeciwieństwie do komercyjnego oprogramowania, data wydania dotyczy wyłącznie kodu źródłowego, a nie wersji binarnej. Aby stworzyć wykonywalny program, kod źródłowy musi zostać skompilowany, co wymaga kilku bibliotek, włączając w to kilka ogólnych oraz specyficznych dla platfory. Jednak jest to zbyt trudne zadanie dla wielu zwykłych użytkowników, dlatego ochotnicy społeczności pracują, aby udostępnić wersje binarne dla poszczególnych platform i systemów operacyjnych. Dystrybucje te różnią się poziomem stabilności, wydajnością, zależnościami, a także tym, jak aktualne są one w stosunku do kodu źródłowego. Dla przykładu, niektóre ze starszych wersji binarnych będą pracować poprawnie pod Mac OS 9, ale nowsze wydania wymagają określonych wersji Mac OS X.&lt;br /&gt;
&lt;br /&gt;
For example, by late 2012 the latest code release was 2.10 (pre-release) and 2.8.0 (final). Binaries are generally available for the last final code release on all major platforms. [http://www.flightgear.org/download/main-program/ Click here to proceed to the flightgear binaries download page]&lt;br /&gt;
&lt;br /&gt;
Przykładowo, pod koniec 2012 roku najnowszą wersją kodu była wersja 2.10 (przedpremierowa) i 2.8.0 (finalna). Pliki binarne są ogólnie dostępne dla ostatniego finalnego wydania kodu na wszystkich głównych platformach. Przejdź do naszej oficjalnej [https://www.flightgear.org/download/ strony pobierania] aby pobrać pliki binarne FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Pliki binarne dla innych platform, takich jak IRIX, nie są już obsługiwane, chociaż wydania sprzed 1.0 mogą działać i można je znaleźć w [[FlightGear Git|repozytoriach kodu źródłowego git]].&lt;br /&gt;
&lt;br /&gt;
== Recenzje FlightGear ==&lt;br /&gt;
{{Main article|FlightGear Reviews}}&lt;br /&gt;
&lt;br /&gt;
== Łącza zewnętrzne ==&lt;br /&gt;
{{Main article|Links}}&lt;br /&gt;
* {{Wikipedia|FlightGear}}&lt;br /&gt;
* [https://www.flightgear.org Oficjalna strona]&lt;br /&gt;
* {{forum link|text=Forum}}&lt;br /&gt;
* [https://sourceforge.net/p/flightgear/codetickets/ System śledzenia błędów]&lt;br /&gt;
* [http://www.flightgear.org/proposal-3.0.1 Oryginalna propozycja stworzenia FlightGear]&lt;br /&gt;
&lt;br /&gt;
== Źródła ==&lt;br /&gt;
* [http://www.flightgear.org/proposal-3.0.1 Original Flight Gear Proposal] by David L. Murr (Revision 3.0.1)&lt;br /&gt;
* [http://en.wikipedia.org/wiki/FlightGear Wikipedia]&lt;br /&gt;
&lt;br /&gt;
[[ca:FlightGear]]&lt;br /&gt;
[[de:FlightGear]]&lt;br /&gt;
[[en:FlightGear]]&lt;br /&gt;
[[es:FlightGear]]&lt;br /&gt;
[[fr:FlightGear]]&lt;br /&gt;
[[it:FlightGear]]&lt;br /&gt;
[[nl:FlightGear]]&lt;br /&gt;
[[pt:FlightGear]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Pl/FlightGear&amp;diff=143882</id>
		<title>Pl/FlightGear</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Pl/FlightGear&amp;diff=143882"/>
		<updated>2026-04-03T16:41:59Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Zależności FlightGeara */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Software&lt;br /&gt;
|title             = FlightGear Flight Simulator&lt;br /&gt;
|logo              = FlightGear logo.png&lt;br /&gt;
|logosize          = 200px&lt;br /&gt;
|image             = Boeing 777-200ER cockpit.jpg&lt;br /&gt;
|alt               = W kokpicie [[Boeing 777-200ER]]&lt;br /&gt;
|developedby       = Developerzy FlightGear i współtwórcy&lt;br /&gt;
|initialrelease    = Lipec 17, 1997&lt;br /&gt;
|latestrelease     = {{current release|full}} ({{#time: j xg Y | {{current release|fulldate}} | pl }})&lt;br /&gt;
|writtenin         = C/C++/Nasal&lt;br /&gt;
|os                = Windows, Linux, macOS oraz FreeBSD&lt;br /&gt;
|platform          = wieloplatformowy&lt;br /&gt;
|developmentstatus = Aktywny (1996-)&lt;br /&gt;
|type              = Symulator Lotu&lt;br /&gt;
|license           = [[GNU General Public License]]&lt;br /&gt;
|website           = https://www.flightgear.org/&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[File:OV10A-NASA-in-action.jpg|thumb|right|270px|NASA [[OV-10]] we FlightGear 1.0]]&lt;br /&gt;
&lt;br /&gt;
'''FlightGear Flight Simulator''' (w skrócie '''FlightGear''' lub '''FGFS''') to zaawansowana, darmowa i całkowicie otwarto-źródłowa platforma symulacji lotu, stworzona przez wolontariuszy. FlightGear jest wydany na zasadach licencji [[GNU General Public License]] i jest on w większości napisany przy użyciu języka programowania C++.&lt;br /&gt;
&lt;br /&gt;
Coraz bardziej szczegółowe i zaawansowane wersje FlightGeara są corocznie wydawane od zapoczątkowania projektu w 1996 roku.&lt;br /&gt;
&lt;br /&gt;
Najnowsze publiczne wydanie dostępne jest do pobrania na [https://www.flightgear.org/download/ www.flightgear.org/download/] wraz z wersjami dla Microsoft Windows, macOS i Linux.&lt;br /&gt;
&lt;br /&gt;
== Historia ==&lt;br /&gt;
{{main article|FlightGear History}}&lt;br /&gt;
&lt;br /&gt;
Rozwój FlightGeara rozpoczął się od propozycji stworzenia otwarto-źródłowego symulatora w 1996 roku, w oparciu o własny kod renderowania grafiki 3D. Rozwój wersji opartej na [[OpenGL]] rozpoczął Curtis Olson w 1997 roku, ale także wiele innych osób miało swój wkład w ten projekt.&lt;br /&gt;
&lt;br /&gt;
FlightGear włączył inne otwarto-źródłowe zasoby, w tym model lotu LaRCsim od NASA oraz powszechnie dostępne dane o elewacji terenu. Pierwsze działające pliki binarne, wykorzystujące OpenGL dla grafiki 3D, pojawiły się w 1997 roku. Entuzjastyczny rozwój kolejnych wersji przez kilka lat zaowocował stopniowo coraz bardziej stabilnymi i zaawansowanymi wersjami. W 2001 roku zespół regularnie wydawał nowe wersje beta, a w 2005 roku dojrzałość oprogramowania doprowadziła do szerszych recenzji i wzrostu popularności. W 2007 roku nastąpiło formalne wyjście z wersji beta wraz z wydaniem wersji 1.0.0, dziesięć lat po pierwszym wydaniu FlightGeara w 1997 roku.&lt;br /&gt;
&lt;br /&gt;
[[File:FG-A-10.jpg|thumb|270px|Kokpit 3D [[A-10]] w wersji 1.0.0, rok 2008]]&lt;br /&gt;
&lt;br /&gt;
W 2008 roku, wersja 1.9.0 FlightGeara przeszła z biblioteki [[PLIB]] na [[OSG]], co spowodowało tymczasową utratę niektórych funkcji programu, takich jak wyświetlanie chmury 3D i cienie, za to nowo wprowadzone funkcje, takie jak cząsteczki, nadały symulacji kolejny stopień realizmu.&lt;br /&gt;
&lt;br /&gt;
== Program ==&lt;br /&gt;
&lt;br /&gt;
Silnik symulacji we FlightGear to [[SimGear]]. Jest on używany zarówno jako aplikacja użytkownika końcowego jak i przy pracach badawczych w środowiskach akademickich w celu rozwoju zagadnień związanych z symulacją lotu.&lt;br /&gt;
&lt;br /&gt;
Przykładem na możliwość dostosowywania FlightGeara jest szeroki wachlarz modeli dostępnych statków powietrznych, od [[:Category:Gliders|szybowców]] przez [[Helicopter|śmigłowców]], [[:Category:Airliners|liniowce]], [[Military aircraft|myśliwców odrzutowych]] do [[Space Shuttle|Wahadłowca Kosmicznego]]. Modele te zostały wykonane i dodane do projektu przez wielu ochotników ze społeczności FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Od wersji 0.9.10 statki powietrzne we FlightGear używają jednego z trzech [[Flight Dynamics Model|modeli dynamiki lotu]] (FDM): [[JSBSim]], [[YASim]] lub [[UIUC]]. Obecnie wyłącznie jeden silnik terenu jest w użyciu, jest nim [[TerraGear]]. Dostępne efekty pogodowe to między innymi chmury 3D, efekty świetlne, pory dnia i nocy. &lt;br /&gt;
&lt;br /&gt;
=== Modele Dynamiki Lotu ===&lt;br /&gt;
[[Flight Dynamics Models|Model dynamiki lotu]] (FDM) odpowiada za to w jaki sposób w programie jest symulowany lot statku powietrznego. FlightGear korzysta z własnych oraz zewnętrznych projektów modeli dynamiki lotu. Każdy statek powietrzny musi być tak zaprogramowany, aby korzystał z jednego z dostępnych modeli dynamiki lotu. Obecnie FlightGear jest jedynym, graficznym symulatorem lotu korzystającym ze wszystkich wspomnianych modeli dynamiki lotu, a UIUC i YASim zostały rozwinięte z myślą i w szczególności dla FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Wczesne wersje programu używały FDM, który był oparty na [[LaRCsim]] od NASA. W kolejnych wersjach został on zastąpiony przez bardziej elastyczne FDM.&lt;br /&gt;
&lt;br /&gt;
* [[JSBSim]] - domyślny model dynamiki lotu od 2000 roku.&lt;br /&gt;
* [[YASim]] - inny FDM, używający odmiennych metod obliczeniowych. Wprowadzony od wersji 0.7.9 w 2002 roku.&lt;br /&gt;
* [[UIUC]] - kolejny FDM, rozwijany przez UIUC Applied Aerodynamics Group z Uniwersytetu w Illinois na Urbana-Champaign, bazujący na LaRCsim. Obecnie już wycofany z wersji deweloperskiej FlightGeara.&lt;br /&gt;
* FlightGear może być tak skonfigurowany, aby przyjmował dane z zewnętrznych źródeł FDM takich jak [[MATLAB]].&lt;br /&gt;
* Inne niestandardowe FDM zostały napisane dla specyficznych typów statków powietrznych jak balony i sterowce.&lt;br /&gt;
&lt;br /&gt;
=== Zależności FlightGeara ===&lt;br /&gt;
&lt;br /&gt;
W przeciwieństwie do komercyjnych tytułów, głównym rezultatem prac nad projektem jest publikacja kodu źródłowego. Aby skorzystać z programu, można samodzielnie skompilować udostępniony kod dla docelowej platformy.&lt;br /&gt;
&lt;br /&gt;
Wykorzystywane przez FlightGeara biblioteki zmieniały się na przestrzeni czasu. Najważniejszą zależnością pozostaje SimGear, będący silnikiem symulacji dla FlightGeara. [[TerraGear]] nie stanowi bezpośredniej zależności – jest raczej nazwą domyślnego silnika odpowiedzialnego za generowanie terenu.&lt;br /&gt;
&lt;br /&gt;
Do obsługi dźwięku wykorzystywany jest OpenAL (z włączonym wsparciem dla SDL od wersji 0.9.5), natomiast wcześniej w tym celu używano biblioteki PLIB, która odpowiadała również za obsługę sprzętu. Za renderowanie grafiki 3D odpowiada [[OpenGL]] – DirectX nie jest wspierany. Z FlightGearem zintegrowano także silnik graficzny [[OpenSceneGraph]]. Do kompilacji wymagane jest również użycie biblioteki Simple Direct Media Layer (SDL).&lt;br /&gt;
&lt;br /&gt;
Zakres zależności może się różnić w zależności od docelowej platformy. Użytkownicy mogą samodzielnie kompilować kod lub – jeśli nie jest on dostępny bezpośrednio w ramach projektu – korzystać z gotowych wersji binarnych przygotowanych przez osoby trzecie.&lt;br /&gt;
&lt;br /&gt;
== Sprzęt==&lt;br /&gt;
&lt;br /&gt;
Sprzęt konieczny do uruchomienia FlightGeara jest ograniczony do sprzętu, który wspiera [[OpenGL]] i akcelerację 3D, przy czym najlepiej wspierana jest NVIDIA. Wczesne wersje posiadały wsparcie dla kart 3dfx, jednak wsparcie to zostało wycofane wraz ze zwiększającymi się wymaganiami sprzętowymi.&lt;br /&gt;
&lt;br /&gt;
[[File:Fgrun-page2.jpg|thumb|left|270px|[[FlightGear Launch Control|FlightGear Launcher]]]]&lt;br /&gt;
== Dodatki i dostosowywanie ==&lt;br /&gt;
&lt;br /&gt;
Istnieją programy, które są zintegrowane z FlightGearem (zależności) lub są programami zewnętrznymi i współpracują z nim. Oprogramowanie takie może być częścią projektu FlightGear lub może być rozwijane niezależnie, ale udostępniane przez projekt FlightGear.&lt;br /&gt;
&lt;br /&gt;
Ważnym dodatkowym oprogramowaniem jest interfejs graficzny, służący do uruchomienia pliku wykonywalnego FlightGeara. We wczesnych wersjach, FlightGear mógł być uruchomiony jedynie z wykorzystaniem [[Pl/Opcje wiersza poleceń|opcji wiersza poleceń]]. Jednakże, w 2003 roku od wersji 0.9.3, został dołączony ''[[FlightGear Launch Control|FGRun]]'' - program startowy z interfejsem graficznym. Obecnie tę rolę spełnia ''[[FlightGear Qt launcher|QT Launcher]]''.&lt;br /&gt;
Podobną funkcję spełnia ''[[KFreeFlight]]'' dla środowiska KDE. ''FGTools'' jest alternatywnym front-endem dla środowiska Windows. ''FGKicker'' jest używany dla GTK+.&lt;br /&gt;
&lt;br /&gt;
Inne znaczące programy obejmują edytory i projekty związane z terenem. ''[[Atlas]]'' jest mapą dla FlightGeara; ''[[Kelpie Flight Planner]]'' to rozwijane w Javie narzędzie do planowania lotów.&lt;br /&gt;
''[[FlightGear Scenery Designer]]'' to edytor scenerii, pomocny przy pracy z danymi terenu. ''[[World Custom Scenery Project]]'' to projekt pomagający koordynować wspólne wysiłki na rzecz tworzenia scenerii. Na koniec edytor ''[[TaxiDraw]]'' do tworzenia nowych pasów startowych i dróg kołowania.&lt;br /&gt;
&lt;br /&gt;
=== Statki Powietrzne ===&lt;br /&gt;
{{Main article|Table of models}}&lt;br /&gt;
&lt;br /&gt;
Na początku, FlightGear dysponował tylko jednym statkiem powietrznym, był to Navion zawarty w projekcie LaRCsim od NASA, który w 2000 roku został zastąpiony przez Cessnę 172P. Rozwój UIUC i JSBSim przyniósł ze sobą kilka kolejnych samolotów, podobnie jak rozwój YASim, który od tego czasu stał się głównym FDM używanym we FlightGear. W wersji 2.12 dostępnych jest ponad 400 samolotów w ponad 900 unikalnych malowaniach, choć tylko kilka z nich wchodzi w skład pakietu podstawowego.&lt;br /&gt;
&lt;br /&gt;
[[File:EHAM.jpg|thumb|270px|[[Boeing 737-300|Boeing 733]] zaparkowany w scenerii [[EHAM]] ]]&lt;br /&gt;
&lt;br /&gt;
=== Sceneria ===&lt;br /&gt;
{{Main article|Scenery}}&lt;br /&gt;
&lt;br /&gt;
Projekt [[World Scenery]] dla FlightGeara zawiera dane o wysokości i klasie terenu całego świata.&lt;br /&gt;
Obiekty takie jak terminale, wiatraki i mosty są zebrane w [[FlightGear Scenery Database|bazie danych scenerii]].&lt;br /&gt;
&lt;br /&gt;
=== Sieć i wiele monitorów ===&lt;br /&gt;
&lt;br /&gt;
Istnieje kilka możliwości, które pozwalają komunikować się jednej instancji FlightGeara z inną. Dostępny jest protokół [[Multiplayer Howto|multiplayer]], co umożliwia lot w formacji lub symulowanie kontroli ruchu lotniczego w sieci lokalnej. Protokół Multiplayer zostanie wkrótce tak rozbudowany, aby także pozwalał na pracę w internecie. Dodatkową cechą możliwości sieciowych jest opcja podglądu innych pilotów na mapach Google.&lt;br /&gt;
&lt;br /&gt;
Kilka instancji FlightGeara może zostać tak zsynchronizowana, aby korzystały z wielu monitorów. Możliwe jest uzyskanie bardzo dobrej synchronizacji między monitorami jeżeli wszystkie instancje FlightGeara będą pracowały z tą samą częstotliwością wyświetlania klatek.&lt;br /&gt;
&lt;br /&gt;
== Kod FlightGeara vs wersja binarna ==&lt;br /&gt;
&lt;br /&gt;
W przeciwieństwie do komercyjnego oprogramowania, data wydania dotyczy wyłącznie kodu źródłowego, a nie wersji binarnej. Aby stworzyć wykonywalny program, kod źródłowy musi zostać skompilowany, co wymaga kilku bibliotek, włączając w to kilka ogólnych oraz specyficznych dla platfory. Jednak jest to zbyt trudne zadanie dla wielu zwykłych użytkowników, dlatego ochotnicy społeczności pracują, aby udostępnić wersje binarne dla poszczególnych platform i systemów operacyjnych. Dystrybucje te różnią się poziomem stabilności, wydajnością, zależnościami, a także tym, jak aktualne są one w stosunku do kodu źródłowego. Dla przykładu, niektóre ze starszych wersji binarnych będą pracować poprawnie pod Mac OS 9, ale nowsze wydania wymagają określonych wersji Mac OS X.&lt;br /&gt;
&lt;br /&gt;
For example, by late 2012 the latest code release was 2.10 (pre-release) and 2.8.0 (final). Binaries are generally available for the last final code release on all major platforms. [http://www.flightgear.org/download/main-program/ Click here to proceed to the flightgear binaries download page]&lt;br /&gt;
&lt;br /&gt;
Przykładowo, pod koniec 2012 roku najnowszą wersją kodu była wersja 2.10 (przedpremierowa) i 2.8.0 (finalna). Pliki binarne są ogólnie dostępne dla ostatniego finalnego wydania kodu na wszystkich głównych platformach. Przejdź do naszej oficjalnej [https://www.flightgear.org/download/ strony pobierania] aby pobrać pliki binarne FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Pliki binarne dla innych platform, takich jak IRIX, nie są już obsługiwane, chociaż wydania sprzed 1.0 mogą działać i można je znaleźć w [[FlightGear Git|repozytoriach kodu źródłowego git]].&lt;br /&gt;
&lt;br /&gt;
== Recenzje FlightGear ==&lt;br /&gt;
{{Main article|FlightGear Reviews}}&lt;br /&gt;
&lt;br /&gt;
== Łącza zewnętrzne ==&lt;br /&gt;
{{Main article|Links}}&lt;br /&gt;
* {{Wikipedia|FlightGear}}&lt;br /&gt;
* [https://www.flightgear.org Oficjalna strona]&lt;br /&gt;
* {{forum link|text=Forum}}&lt;br /&gt;
* [https://sourceforge.net/p/flightgear/codetickets/ System śledzenia błędów]&lt;br /&gt;
* [http://www.flightgear.org/proposal-3.0.1 Oryginalna propozycja stworzenia FlightGear]&lt;br /&gt;
&lt;br /&gt;
== Źródła ==&lt;br /&gt;
* [http://www.flightgear.org/proposal-3.0.1 Original Flight Gear Proposal] by David L. Murr (Revision 3.0.1)&lt;br /&gt;
* [http://en.wikipedia.org/wiki/FlightGear Wikipedia]&lt;br /&gt;
&lt;br /&gt;
[[ca:FlightGear]]&lt;br /&gt;
[[de:FlightGear]]&lt;br /&gt;
[[en:FlightGear]]&lt;br /&gt;
[[es:FlightGear]]&lt;br /&gt;
[[fr:FlightGear]]&lt;br /&gt;
[[it:FlightGear]]&lt;br /&gt;
[[nl:FlightGear]]&lt;br /&gt;
[[pt:FlightGear]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Pl/FlightGear&amp;diff=143881</id>
		<title>Pl/FlightGear</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Pl/FlightGear&amp;diff=143881"/>
		<updated>2026-04-03T16:37:13Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Modele Dynamiki Lotu */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Software&lt;br /&gt;
|title             = FlightGear Flight Simulator&lt;br /&gt;
|logo              = FlightGear logo.png&lt;br /&gt;
|logosize          = 200px&lt;br /&gt;
|image             = Boeing 777-200ER cockpit.jpg&lt;br /&gt;
|alt               = W kokpicie [[Boeing 777-200ER]]&lt;br /&gt;
|developedby       = Developerzy FlightGear i współtwórcy&lt;br /&gt;
|initialrelease    = Lipec 17, 1997&lt;br /&gt;
|latestrelease     = {{current release|full}} ({{#time: j xg Y | {{current release|fulldate}} | pl }})&lt;br /&gt;
|writtenin         = C/C++/Nasal&lt;br /&gt;
|os                = Windows, Linux, macOS oraz FreeBSD&lt;br /&gt;
|platform          = wieloplatformowy&lt;br /&gt;
|developmentstatus = Aktywny (1996-)&lt;br /&gt;
|type              = Symulator Lotu&lt;br /&gt;
|license           = [[GNU General Public License]]&lt;br /&gt;
|website           = https://www.flightgear.org/&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[File:OV10A-NASA-in-action.jpg|thumb|right|270px|NASA [[OV-10]] we FlightGear 1.0]]&lt;br /&gt;
&lt;br /&gt;
'''FlightGear Flight Simulator''' (w skrócie '''FlightGear''' lub '''FGFS''') to zaawansowana, darmowa i całkowicie otwarto-źródłowa platforma symulacji lotu, stworzona przez wolontariuszy. FlightGear jest wydany na zasadach licencji [[GNU General Public License]] i jest on w większości napisany przy użyciu języka programowania C++.&lt;br /&gt;
&lt;br /&gt;
Coraz bardziej szczegółowe i zaawansowane wersje FlightGeara są corocznie wydawane od zapoczątkowania projektu w 1996 roku.&lt;br /&gt;
&lt;br /&gt;
Najnowsze publiczne wydanie dostępne jest do pobrania na [https://www.flightgear.org/download/ www.flightgear.org/download/] wraz z wersjami dla Microsoft Windows, macOS i Linux.&lt;br /&gt;
&lt;br /&gt;
== Historia ==&lt;br /&gt;
{{main article|FlightGear History}}&lt;br /&gt;
&lt;br /&gt;
Rozwój FlightGeara rozpoczął się od propozycji stworzenia otwarto-źródłowego symulatora w 1996 roku, w oparciu o własny kod renderowania grafiki 3D. Rozwój wersji opartej na [[OpenGL]] rozpoczął Curtis Olson w 1997 roku, ale także wiele innych osób miało swój wkład w ten projekt.&lt;br /&gt;
&lt;br /&gt;
FlightGear włączył inne otwarto-źródłowe zasoby, w tym model lotu LaRCsim od NASA oraz powszechnie dostępne dane o elewacji terenu. Pierwsze działające pliki binarne, wykorzystujące OpenGL dla grafiki 3D, pojawiły się w 1997 roku. Entuzjastyczny rozwój kolejnych wersji przez kilka lat zaowocował stopniowo coraz bardziej stabilnymi i zaawansowanymi wersjami. W 2001 roku zespół regularnie wydawał nowe wersje beta, a w 2005 roku dojrzałość oprogramowania doprowadziła do szerszych recenzji i wzrostu popularności. W 2007 roku nastąpiło formalne wyjście z wersji beta wraz z wydaniem wersji 1.0.0, dziesięć lat po pierwszym wydaniu FlightGeara w 1997 roku.&lt;br /&gt;
&lt;br /&gt;
[[File:FG-A-10.jpg|thumb|270px|Kokpit 3D [[A-10]] w wersji 1.0.0, rok 2008]]&lt;br /&gt;
&lt;br /&gt;
W 2008 roku, wersja 1.9.0 FlightGeara przeszła z biblioteki [[PLIB]] na [[OSG]], co spowodowało tymczasową utratę niektórych funkcji programu, takich jak wyświetlanie chmury 3D i cienie, za to nowo wprowadzone funkcje, takie jak cząsteczki, nadały symulacji kolejny stopień realizmu.&lt;br /&gt;
&lt;br /&gt;
== Program ==&lt;br /&gt;
&lt;br /&gt;
Silnik symulacji we FlightGear to [[SimGear]]. Jest on używany zarówno jako aplikacja użytkownika końcowego jak i przy pracach badawczych w środowiskach akademickich w celu rozwoju zagadnień związanych z symulacją lotu.&lt;br /&gt;
&lt;br /&gt;
Przykładem na możliwość dostosowywania FlightGeara jest szeroki wachlarz modeli dostępnych statków powietrznych, od [[:Category:Gliders|szybowców]] przez [[Helicopter|śmigłowców]], [[:Category:Airliners|liniowce]], [[Military aircraft|myśliwców odrzutowych]] do [[Space Shuttle|Wahadłowca Kosmicznego]]. Modele te zostały wykonane i dodane do projektu przez wielu ochotników ze społeczności FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Od wersji 0.9.10 statki powietrzne we FlightGear używają jednego z trzech [[Flight Dynamics Model|modeli dynamiki lotu]] (FDM): [[JSBSim]], [[YASim]] lub [[UIUC]]. Obecnie wyłącznie jeden silnik terenu jest w użyciu, jest nim [[TerraGear]]. Dostępne efekty pogodowe to między innymi chmury 3D, efekty świetlne, pory dnia i nocy. &lt;br /&gt;
&lt;br /&gt;
=== Modele Dynamiki Lotu ===&lt;br /&gt;
[[Flight Dynamics Models|Model dynamiki lotu]] (FDM) odpowiada za to w jaki sposób w programie jest symulowany lot statku powietrznego. FlightGear korzysta z własnych oraz zewnętrznych projektów modeli dynamiki lotu. Każdy statek powietrzny musi być tak zaprogramowany, aby korzystał z jednego z dostępnych modeli dynamiki lotu. Obecnie FlightGear jest jedynym, graficznym symulatorem lotu korzystającym ze wszystkich wspomnianych modeli dynamiki lotu, a UIUC i YASim zostały rozwinięte z myślą i w szczególności dla FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Wczesne wersje programu używały FDM, który był oparty na [[LaRCsim]] od NASA. W kolejnych wersjach został on zastąpiony przez bardziej elastyczne FDM.&lt;br /&gt;
&lt;br /&gt;
* [[JSBSim]] - domyślny model dynamiki lotu od 2000 roku.&lt;br /&gt;
* [[YASim]] - inny FDM, używający odmiennych metod obliczeniowych. Wprowadzony od wersji 0.7.9 w 2002 roku.&lt;br /&gt;
* [[UIUC]] - kolejny FDM, rozwijany przez UIUC Applied Aerodynamics Group z Uniwersytetu w Illinois na Urbana-Champaign, bazujący na LaRCsim. Obecnie już wycofany z wersji deweloperskiej FlightGeara.&lt;br /&gt;
* FlightGear może być tak skonfigurowany, aby przyjmował dane z zewnętrznych źródeł FDM takich jak [[MATLAB]].&lt;br /&gt;
* Inne niestandardowe FDM zostały napisane dla specyficznych typów statków powietrznych jak balony i sterowce.&lt;br /&gt;
&lt;br /&gt;
=== Zależności FlightGeara ===&lt;br /&gt;
&lt;br /&gt;
W przeciwieństwie do komercyjnych tytułów, głównym wynikiem pracy projektu jest wydanie zestawu kodów źródłowych. Aby użyć programu należy skompilować udostępniony kod dla docelowej platformy, na której ma pracować. Biblioteki użyte przez FlightGeara były różne w zależności od okresu. Główną biblioteką zależną jest SimGear, która jest silnikiem symulacji dla FlightGeara. [[TerraGear]] nie jest zależnością, a raczej tylko nazwą dla domyślnego silnika odpowiadającego za generowanie terenu we FlightGear. OpenAL jest używany dla obsługi dźwięku, włączając wsparcie dla SDL (od wersji 0.9.5). PLIB jest użyte do obsługi sprzętowej, także dla obsługi dźwięku przed wprowadzeniem OpenAL. [[OpenGL]] jest użyty dla swoich funkcji 3D, DirectX nie jest wspierany. Silnik graficzny [[OpenSceneGraph]] jest również zintegrowany z FlightGearem. Na koniec do kompilacji jest konieczna biblioteka Simple Direct Media Layer (SDL). Niektóre zależności mogą się różnić w zależności od tego na jaką platformę docelową jest kompilowany kod. Użytkownicy FlightGeara powinni sami kompilować kod lub jeżeli nie jest on dostępny w ramach projektu, korzystać z wersji binarnej udostępnionej przez osoby trzecie.&lt;br /&gt;
&lt;br /&gt;
== Sprzęt==&lt;br /&gt;
&lt;br /&gt;
Sprzęt konieczny do uruchomienia FlightGeara jest ograniczony do sprzętu, który wspiera [[OpenGL]] i akcelerację 3D, przy czym najlepiej wspierana jest NVIDIA. Wczesne wersje posiadały wsparcie dla kart 3dfx, jednak wsparcie to zostało wycofane wraz ze zwiększającymi się wymaganiami sprzętowymi.&lt;br /&gt;
&lt;br /&gt;
[[File:Fgrun-page2.jpg|thumb|left|270px|[[FlightGear Launch Control|FlightGear Launcher]]]]&lt;br /&gt;
== Dodatki i dostosowywanie ==&lt;br /&gt;
&lt;br /&gt;
Istnieją programy, które są zintegrowane z FlightGearem (zależności) lub są programami zewnętrznymi i współpracują z nim. Oprogramowanie takie może być częścią projektu FlightGear lub może być rozwijane niezależnie, ale udostępniane przez projekt FlightGear.&lt;br /&gt;
&lt;br /&gt;
Ważnym dodatkowym oprogramowaniem jest interfejs graficzny, służący do uruchomienia pliku wykonywalnego FlightGeara. We wczesnych wersjach, FlightGear mógł być uruchomiony jedynie z wykorzystaniem [[Pl/Opcje wiersza poleceń|opcji wiersza poleceń]]. Jednakże, w 2003 roku od wersji 0.9.3, został dołączony ''[[FlightGear Launch Control|FGRun]]'' - program startowy z interfejsem graficznym. Obecnie tę rolę spełnia ''[[FlightGear Qt launcher|QT Launcher]]''.&lt;br /&gt;
Podobną funkcję spełnia ''[[KFreeFlight]]'' dla środowiska KDE. ''FGTools'' jest alternatywnym front-endem dla środowiska Windows. ''FGKicker'' jest używany dla GTK+.&lt;br /&gt;
&lt;br /&gt;
Inne znaczące programy obejmują edytory i projekty związane z terenem. ''[[Atlas]]'' jest mapą dla FlightGeara; ''[[Kelpie Flight Planner]]'' to rozwijane w Javie narzędzie do planowania lotów.&lt;br /&gt;
''[[FlightGear Scenery Designer]]'' to edytor scenerii, pomocny przy pracy z danymi terenu. ''[[World Custom Scenery Project]]'' to projekt pomagający koordynować wspólne wysiłki na rzecz tworzenia scenerii. Na koniec edytor ''[[TaxiDraw]]'' do tworzenia nowych pasów startowych i dróg kołowania.&lt;br /&gt;
&lt;br /&gt;
=== Statki Powietrzne ===&lt;br /&gt;
{{Main article|Table of models}}&lt;br /&gt;
&lt;br /&gt;
Na początku, FlightGear dysponował tylko jednym statkiem powietrznym, był to Navion zawarty w projekcie LaRCsim od NASA, który w 2000 roku został zastąpiony przez Cessnę 172P. Rozwój UIUC i JSBSim przyniósł ze sobą kilka kolejnych samolotów, podobnie jak rozwój YASim, który od tego czasu stał się głównym FDM używanym we FlightGear. W wersji 2.12 dostępnych jest ponad 400 samolotów w ponad 900 unikalnych malowaniach, choć tylko kilka z nich wchodzi w skład pakietu podstawowego.&lt;br /&gt;
&lt;br /&gt;
[[File:EHAM.jpg|thumb|270px|[[Boeing 737-300|Boeing 733]] zaparkowany w scenerii [[EHAM]] ]]&lt;br /&gt;
&lt;br /&gt;
=== Sceneria ===&lt;br /&gt;
{{Main article|Scenery}}&lt;br /&gt;
&lt;br /&gt;
Projekt [[World Scenery]] dla FlightGeara zawiera dane o wysokości i klasie terenu całego świata.&lt;br /&gt;
Obiekty takie jak terminale, wiatraki i mosty są zebrane w [[FlightGear Scenery Database|bazie danych scenerii]].&lt;br /&gt;
&lt;br /&gt;
=== Sieć i wiele monitorów ===&lt;br /&gt;
&lt;br /&gt;
Istnieje kilka możliwości, które pozwalają komunikować się jednej instancji FlightGeara z inną. Dostępny jest protokół [[Multiplayer Howto|multiplayer]], co umożliwia lot w formacji lub symulowanie kontroli ruchu lotniczego w sieci lokalnej. Protokół Multiplayer zostanie wkrótce tak rozbudowany, aby także pozwalał na pracę w internecie. Dodatkową cechą możliwości sieciowych jest opcja podglądu innych pilotów na mapach Google.&lt;br /&gt;
&lt;br /&gt;
Kilka instancji FlightGeara może zostać tak zsynchronizowana, aby korzystały z wielu monitorów. Możliwe jest uzyskanie bardzo dobrej synchronizacji między monitorami jeżeli wszystkie instancje FlightGeara będą pracowały z tą samą częstotliwością wyświetlania klatek.&lt;br /&gt;
&lt;br /&gt;
== Kod FlightGeara vs wersja binarna ==&lt;br /&gt;
&lt;br /&gt;
W przeciwieństwie do komercyjnego oprogramowania, data wydania dotyczy wyłącznie kodu źródłowego, a nie wersji binarnej. Aby stworzyć wykonywalny program, kod źródłowy musi zostać skompilowany, co wymaga kilku bibliotek, włączając w to kilka ogólnych oraz specyficznych dla platfory. Jednak jest to zbyt trudne zadanie dla wielu zwykłych użytkowników, dlatego ochotnicy społeczności pracują, aby udostępnić wersje binarne dla poszczególnych platform i systemów operacyjnych. Dystrybucje te różnią się poziomem stabilności, wydajnością, zależnościami, a także tym, jak aktualne są one w stosunku do kodu źródłowego. Dla przykładu, niektóre ze starszych wersji binarnych będą pracować poprawnie pod Mac OS 9, ale nowsze wydania wymagają określonych wersji Mac OS X.&lt;br /&gt;
&lt;br /&gt;
For example, by late 2012 the latest code release was 2.10 (pre-release) and 2.8.0 (final). Binaries are generally available for the last final code release on all major platforms. [http://www.flightgear.org/download/main-program/ Click here to proceed to the flightgear binaries download page]&lt;br /&gt;
&lt;br /&gt;
Przykładowo, pod koniec 2012 roku najnowszą wersją kodu była wersja 2.10 (przedpremierowa) i 2.8.0 (finalna). Pliki binarne są ogólnie dostępne dla ostatniego finalnego wydania kodu na wszystkich głównych platformach. Przejdź do naszej oficjalnej [https://www.flightgear.org/download/ strony pobierania] aby pobrać pliki binarne FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Pliki binarne dla innych platform, takich jak IRIX, nie są już obsługiwane, chociaż wydania sprzed 1.0 mogą działać i można je znaleźć w [[FlightGear Git|repozytoriach kodu źródłowego git]].&lt;br /&gt;
&lt;br /&gt;
== Recenzje FlightGear ==&lt;br /&gt;
{{Main article|FlightGear Reviews}}&lt;br /&gt;
&lt;br /&gt;
== Łącza zewnętrzne ==&lt;br /&gt;
{{Main article|Links}}&lt;br /&gt;
* {{Wikipedia|FlightGear}}&lt;br /&gt;
* [https://www.flightgear.org Oficjalna strona]&lt;br /&gt;
* {{forum link|text=Forum}}&lt;br /&gt;
* [https://sourceforge.net/p/flightgear/codetickets/ System śledzenia błędów]&lt;br /&gt;
* [http://www.flightgear.org/proposal-3.0.1 Oryginalna propozycja stworzenia FlightGear]&lt;br /&gt;
&lt;br /&gt;
== Źródła ==&lt;br /&gt;
* [http://www.flightgear.org/proposal-3.0.1 Original Flight Gear Proposal] by David L. Murr (Revision 3.0.1)&lt;br /&gt;
* [http://en.wikipedia.org/wiki/FlightGear Wikipedia]&lt;br /&gt;
&lt;br /&gt;
[[ca:FlightGear]]&lt;br /&gt;
[[de:FlightGear]]&lt;br /&gt;
[[en:FlightGear]]&lt;br /&gt;
[[es:FlightGear]]&lt;br /&gt;
[[fr:FlightGear]]&lt;br /&gt;
[[it:FlightGear]]&lt;br /&gt;
[[nl:FlightGear]]&lt;br /&gt;
[[pt:FlightGear]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Pl/FlightGear&amp;diff=143880</id>
		<title>Pl/FlightGear</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Pl/FlightGear&amp;diff=143880"/>
		<updated>2026-04-03T16:33:53Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Software&lt;br /&gt;
|title             = FlightGear Flight Simulator&lt;br /&gt;
|logo              = FlightGear logo.png&lt;br /&gt;
|logosize          = 200px&lt;br /&gt;
|image             = Boeing 777-200ER cockpit.jpg&lt;br /&gt;
|alt               = W kokpicie [[Boeing 777-200ER]]&lt;br /&gt;
|developedby       = Developerzy FlightGear i współtwórcy&lt;br /&gt;
|initialrelease    = Lipec 17, 1997&lt;br /&gt;
|latestrelease     = {{current release|full}} ({{#time: j xg Y | {{current release|fulldate}} | pl }})&lt;br /&gt;
|writtenin         = C/C++/Nasal&lt;br /&gt;
|os                = Windows, Linux, macOS oraz FreeBSD&lt;br /&gt;
|platform          = wieloplatformowy&lt;br /&gt;
|developmentstatus = Aktywny (1996-)&lt;br /&gt;
|type              = Symulator Lotu&lt;br /&gt;
|license           = [[GNU General Public License]]&lt;br /&gt;
|website           = https://www.flightgear.org/&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[File:OV10A-NASA-in-action.jpg|thumb|right|270px|NASA [[OV-10]] we FlightGear 1.0]]&lt;br /&gt;
&lt;br /&gt;
'''FlightGear Flight Simulator''' (w skrócie '''FlightGear''' lub '''FGFS''') to zaawansowana, darmowa i całkowicie otwarto-źródłowa platforma symulacji lotu, stworzona przez wolontariuszy. FlightGear jest wydany na zasadach licencji [[GNU General Public License]] i jest on w większości napisany przy użyciu języka programowania C++.&lt;br /&gt;
&lt;br /&gt;
Coraz bardziej szczegółowe i zaawansowane wersje FlightGeara są corocznie wydawane od zapoczątkowania projektu w 1996 roku.&lt;br /&gt;
&lt;br /&gt;
Najnowsze publiczne wydanie dostępne jest do pobrania na [https://www.flightgear.org/download/ www.flightgear.org/download/] wraz z wersjami dla Microsoft Windows, macOS i Linux.&lt;br /&gt;
&lt;br /&gt;
== Historia ==&lt;br /&gt;
{{main article|FlightGear History}}&lt;br /&gt;
&lt;br /&gt;
Rozwój FlightGeara rozpoczął się od propozycji stworzenia otwarto-źródłowego symulatora w 1996 roku, w oparciu o własny kod renderowania grafiki 3D. Rozwój wersji opartej na [[OpenGL]] rozpoczął Curtis Olson w 1997 roku, ale także wiele innych osób miało swój wkład w ten projekt.&lt;br /&gt;
&lt;br /&gt;
FlightGear włączył inne otwarto-źródłowe zasoby, w tym model lotu LaRCsim od NASA oraz powszechnie dostępne dane o elewacji terenu. Pierwsze działające pliki binarne, wykorzystujące OpenGL dla grafiki 3D, pojawiły się w 1997 roku. Entuzjastyczny rozwój kolejnych wersji przez kilka lat zaowocował stopniowo coraz bardziej stabilnymi i zaawansowanymi wersjami. W 2001 roku zespół regularnie wydawał nowe wersje beta, a w 2005 roku dojrzałość oprogramowania doprowadziła do szerszych recenzji i wzrostu popularności. W 2007 roku nastąpiło formalne wyjście z wersji beta wraz z wydaniem wersji 1.0.0, dziesięć lat po pierwszym wydaniu FlightGeara w 1997 roku.&lt;br /&gt;
&lt;br /&gt;
[[File:FG-A-10.jpg|thumb|270px|Kokpit 3D [[A-10]] w wersji 1.0.0, rok 2008]]&lt;br /&gt;
&lt;br /&gt;
W 2008 roku, wersja 1.9.0 FlightGeara przeszła z biblioteki [[PLIB]] na [[OSG]], co spowodowało tymczasową utratę niektórych funkcji programu, takich jak wyświetlanie chmury 3D i cienie, za to nowo wprowadzone funkcje, takie jak cząsteczki, nadały symulacji kolejny stopień realizmu.&lt;br /&gt;
&lt;br /&gt;
== Program ==&lt;br /&gt;
&lt;br /&gt;
Silnik symulacji we FlightGear to [[SimGear]]. Jest on używany zarówno jako aplikacja użytkownika końcowego jak i przy pracach badawczych w środowiskach akademickich w celu rozwoju zagadnień związanych z symulacją lotu.&lt;br /&gt;
&lt;br /&gt;
Przykładem na możliwość dostosowywania FlightGeara jest szeroki wachlarz modeli dostępnych statków powietrznych, od [[:Category:Gliders|szybowców]] przez [[Helicopter|śmigłowców]], [[:Category:Airliners|liniowce]], [[Military aircraft|myśliwców odrzutowych]] do [[Space Shuttle|Wahadłowca Kosmicznego]]. Modele te zostały wykonane i dodane do projektu przez wielu ochotników ze społeczności FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Od wersji 0.9.10 statki powietrzne we FlightGear używają jednego z trzech [[Flight Dynamics Model|modeli dynamiki lotu]] (FDM): [[JSBSim]], [[YASim]] lub [[UIUC]]. Obecnie wyłącznie jeden silnik terenu jest w użyciu, jest nim [[TerraGear]]. Dostępne efekty pogodowe to między innymi chmury 3D, efekty świetlne, pory dnia i nocy. &lt;br /&gt;
&lt;br /&gt;
=== Modele Dynamiki Lotu ===&lt;br /&gt;
[[Flight Dynamics Models|Model dynamiki lotu]] (FDM) odpowiada za to w jaki sposób w programie jest symulowany lot statku powietrznego. FlightGear korzysta z własnych oraz zewnętrznych projektów modeli dynamiki lotu. Każdy statek powietrzny musi być tak zaprogramowany, aby korzystał z jednego z dostępnych modeli dynamiki lotu. Obecnie FlightGear jest jedynym, graficznym symulatorem lotu korzystającym ze wszystkich wspomnianych modeli dynamiki lotu, a UIUC i YASim zostały rozwinięte z myślą i w szczególności dla FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Wczesne wersje programu używały FDM, który był oparty na [[LaRCsim]] od NASA. W kolejnych wersjach został on zastąpiony przez bardziej elastyczne FDM.&lt;br /&gt;
&lt;br /&gt;
* [[JSBSim]] - domyślny model dynamiki lotu od 2000 roku.&lt;br /&gt;
* [[YASim]] - inny FDM, używający odmiennych metod obliczeniowych. Wprowadzony od wersji 0.7.9 w 2002 roku.&lt;br /&gt;
* [[UIUC]] - kolejny FDM, rozwinięty przez UIUC Applied Aerodynamics Group z Uniwersytetu w Illinois na Urbana-Champaign, bazujący na LaRCsim.&lt;br /&gt;
* FlightGear może być tak skonfigurowany tak, aby przyjmował dane z zewnętrznych źródeł FDM takich jak [[MATLAB]].&lt;br /&gt;
* Inne niestandardowe FDM zostały napisane dla specyficznych typów statków powietrznych jak balony i sterowce.&lt;br /&gt;
&lt;br /&gt;
=== Zależności FlightGeara ===&lt;br /&gt;
&lt;br /&gt;
W przeciwieństwie do komercyjnych tytułów, głównym wynikiem pracy projektu jest wydanie zestawu kodów źródłowych. Aby użyć programu należy skompilować udostępniony kod dla docelowej platformy, na której ma pracować. Biblioteki użyte przez FlightGeara były różne w zależności od okresu. Główną biblioteką zależną jest SimGear, która jest silnikiem symulacji dla FlightGeara. [[TerraGear]] nie jest zależnością, a raczej tylko nazwą dla domyślnego silnika odpowiadającego za generowanie terenu we FlightGear. OpenAL jest używany dla obsługi dźwięku, włączając wsparcie dla SDL (od wersji 0.9.5). PLIB jest użyte do obsługi sprzętowej, także dla obsługi dźwięku przed wprowadzeniem OpenAL. [[OpenGL]] jest użyty dla swoich funkcji 3D, DirectX nie jest wspierany. Silnik graficzny [[OpenSceneGraph]] jest również zintegrowany z FlightGearem. Na koniec do kompilacji jest konieczna biblioteka Simple Direct Media Layer (SDL). Niektóre zależności mogą się różnić w zależności od tego na jaką platformę docelową jest kompilowany kod. Użytkownicy FlightGeara powinni sami kompilować kod lub jeżeli nie jest on dostępny w ramach projektu, korzystać z wersji binarnej udostępnionej przez osoby trzecie.&lt;br /&gt;
&lt;br /&gt;
== Sprzęt==&lt;br /&gt;
&lt;br /&gt;
Sprzęt konieczny do uruchomienia FlightGeara jest ograniczony do sprzętu, który wspiera [[OpenGL]] i akcelerację 3D, przy czym najlepiej wspierana jest NVIDIA. Wczesne wersje posiadały wsparcie dla kart 3dfx, jednak wsparcie to zostało wycofane wraz ze zwiększającymi się wymaganiami sprzętowymi.&lt;br /&gt;
&lt;br /&gt;
[[File:Fgrun-page2.jpg|thumb|left|270px|[[FlightGear Launch Control|FlightGear Launcher]]]]&lt;br /&gt;
== Dodatki i dostosowywanie ==&lt;br /&gt;
&lt;br /&gt;
Istnieją programy, które są zintegrowane z FlightGearem (zależności) lub są programami zewnętrznymi i współpracują z nim. Oprogramowanie takie może być częścią projektu FlightGear lub może być rozwijane niezależnie, ale udostępniane przez projekt FlightGear.&lt;br /&gt;
&lt;br /&gt;
Ważnym dodatkowym oprogramowaniem jest interfejs graficzny, służący do uruchomienia pliku wykonywalnego FlightGeara. We wczesnych wersjach, FlightGear mógł być uruchomiony jedynie z wykorzystaniem [[Pl/Opcje wiersza poleceń|opcji wiersza poleceń]]. Jednakże, w 2003 roku od wersji 0.9.3, został dołączony ''[[FlightGear Launch Control|FGRun]]'' - program startowy z interfejsem graficznym. Obecnie tę rolę spełnia ''[[FlightGear Qt launcher|QT Launcher]]''.&lt;br /&gt;
Podobną funkcję spełnia ''[[KFreeFlight]]'' dla środowiska KDE. ''FGTools'' jest alternatywnym front-endem dla środowiska Windows. ''FGKicker'' jest używany dla GTK+.&lt;br /&gt;
&lt;br /&gt;
Inne znaczące programy obejmują edytory i projekty związane z terenem. ''[[Atlas]]'' jest mapą dla FlightGeara; ''[[Kelpie Flight Planner]]'' to rozwijane w Javie narzędzie do planowania lotów.&lt;br /&gt;
''[[FlightGear Scenery Designer]]'' to edytor scenerii, pomocny przy pracy z danymi terenu. ''[[World Custom Scenery Project]]'' to projekt pomagający koordynować wspólne wysiłki na rzecz tworzenia scenerii. Na koniec edytor ''[[TaxiDraw]]'' do tworzenia nowych pasów startowych i dróg kołowania.&lt;br /&gt;
&lt;br /&gt;
=== Statki Powietrzne ===&lt;br /&gt;
{{Main article|Table of models}}&lt;br /&gt;
&lt;br /&gt;
Na początku, FlightGear dysponował tylko jednym statkiem powietrznym, był to Navion zawarty w projekcie LaRCsim od NASA, który w 2000 roku został zastąpiony przez Cessnę 172P. Rozwój UIUC i JSBSim przyniósł ze sobą kilka kolejnych samolotów, podobnie jak rozwój YASim, który od tego czasu stał się głównym FDM używanym we FlightGear. W wersji 2.12 dostępnych jest ponad 400 samolotów w ponad 900 unikalnych malowaniach, choć tylko kilka z nich wchodzi w skład pakietu podstawowego.&lt;br /&gt;
&lt;br /&gt;
[[File:EHAM.jpg|thumb|270px|[[Boeing 737-300|Boeing 733]] zaparkowany w scenerii [[EHAM]] ]]&lt;br /&gt;
&lt;br /&gt;
=== Sceneria ===&lt;br /&gt;
{{Main article|Scenery}}&lt;br /&gt;
&lt;br /&gt;
Projekt [[World Scenery]] dla FlightGeara zawiera dane o wysokości i klasie terenu całego świata.&lt;br /&gt;
Obiekty takie jak terminale, wiatraki i mosty są zebrane w [[FlightGear Scenery Database|bazie danych scenerii]].&lt;br /&gt;
&lt;br /&gt;
=== Sieć i wiele monitorów ===&lt;br /&gt;
&lt;br /&gt;
Istnieje kilka możliwości, które pozwalają komunikować się jednej instancji FlightGeara z inną. Dostępny jest protokół [[Multiplayer Howto|multiplayer]], co umożliwia lot w formacji lub symulowanie kontroli ruchu lotniczego w sieci lokalnej. Protokół Multiplayer zostanie wkrótce tak rozbudowany, aby także pozwalał na pracę w internecie. Dodatkową cechą możliwości sieciowych jest opcja podglądu innych pilotów na mapach Google.&lt;br /&gt;
&lt;br /&gt;
Kilka instancji FlightGeara może zostać tak zsynchronizowana, aby korzystały z wielu monitorów. Możliwe jest uzyskanie bardzo dobrej synchronizacji między monitorami jeżeli wszystkie instancje FlightGeara będą pracowały z tą samą częstotliwością wyświetlania klatek.&lt;br /&gt;
&lt;br /&gt;
== Kod FlightGeara vs wersja binarna ==&lt;br /&gt;
&lt;br /&gt;
W przeciwieństwie do komercyjnego oprogramowania, data wydania dotyczy wyłącznie kodu źródłowego, a nie wersji binarnej. Aby stworzyć wykonywalny program, kod źródłowy musi zostać skompilowany, co wymaga kilku bibliotek, włączając w to kilka ogólnych oraz specyficznych dla platfory. Jednak jest to zbyt trudne zadanie dla wielu zwykłych użytkowników, dlatego ochotnicy społeczności pracują, aby udostępnić wersje binarne dla poszczególnych platform i systemów operacyjnych. Dystrybucje te różnią się poziomem stabilności, wydajnością, zależnościami, a także tym, jak aktualne są one w stosunku do kodu źródłowego. Dla przykładu, niektóre ze starszych wersji binarnych będą pracować poprawnie pod Mac OS 9, ale nowsze wydania wymagają określonych wersji Mac OS X.&lt;br /&gt;
&lt;br /&gt;
For example, by late 2012 the latest code release was 2.10 (pre-release) and 2.8.0 (final). Binaries are generally available for the last final code release on all major platforms. [http://www.flightgear.org/download/main-program/ Click here to proceed to the flightgear binaries download page]&lt;br /&gt;
&lt;br /&gt;
Przykładowo, pod koniec 2012 roku najnowszą wersją kodu była wersja 2.10 (przedpremierowa) i 2.8.0 (finalna). Pliki binarne są ogólnie dostępne dla ostatniego finalnego wydania kodu na wszystkich głównych platformach. Przejdź do naszej oficjalnej [https://www.flightgear.org/download/ strony pobierania] aby pobrać pliki binarne FlightGeara.&lt;br /&gt;
&lt;br /&gt;
Pliki binarne dla innych platform, takich jak IRIX, nie są już obsługiwane, chociaż wydania sprzed 1.0 mogą działać i można je znaleźć w [[FlightGear Git|repozytoriach kodu źródłowego git]].&lt;br /&gt;
&lt;br /&gt;
== Recenzje FlightGear ==&lt;br /&gt;
{{Main article|FlightGear Reviews}}&lt;br /&gt;
&lt;br /&gt;
== Łącza zewnętrzne ==&lt;br /&gt;
{{Main article|Links}}&lt;br /&gt;
* {{Wikipedia|FlightGear}}&lt;br /&gt;
* [https://www.flightgear.org Oficjalna strona]&lt;br /&gt;
* {{forum link|text=Forum}}&lt;br /&gt;
* [https://sourceforge.net/p/flightgear/codetickets/ System śledzenia błędów]&lt;br /&gt;
* [http://www.flightgear.org/proposal-3.0.1 Oryginalna propozycja stworzenia FlightGear]&lt;br /&gt;
&lt;br /&gt;
== Źródła ==&lt;br /&gt;
* [http://www.flightgear.org/proposal-3.0.1 Original Flight Gear Proposal] by David L. Murr (Revision 3.0.1)&lt;br /&gt;
* [http://en.wikipedia.org/wiki/FlightGear Wikipedia]&lt;br /&gt;
&lt;br /&gt;
[[ca:FlightGear]]&lt;br /&gt;
[[de:FlightGear]]&lt;br /&gt;
[[en:FlightGear]]&lt;br /&gt;
[[es:FlightGear]]&lt;br /&gt;
[[fr:FlightGear]]&lt;br /&gt;
[[it:FlightGear]]&lt;br /&gt;
[[nl:FlightGear]]&lt;br /&gt;
[[pt:FlightGear]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Bendix/King_KAP140_Autopilot&amp;diff=143868</id>
		<title>Bendix/King KAP140 Autopilot</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Bendix/King_KAP140_Autopilot&amp;diff=143868"/>
		<updated>2026-04-01T16:11:06Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''''Bendix/King KAP 140''''' '''Two Axis/Altitude Preselect Autopilot System''' is the [[autopilot]] of the default [[Cessna 172]], controlling the [[elevator]] and [[aileron]]s.&lt;br /&gt;
&lt;br /&gt;
== Quick Guide ==&lt;br /&gt;
[[File:KAP140.jpg|KAP140 Two Axis with Preselect Altitude]]&lt;br /&gt;
&lt;br /&gt;
Normally the autopilot boots as soon as the electrical system has power&amp;lt;ref&amp;gt;newer models simulate preflight checks, so it's not immediately available after power-on.&amp;lt;/ref&amp;gt;. It is commonly wired to an electrical avionics bus and secured by a dedicated breaker; if it stays dark, check power, avionics bus switches and the breaker.&lt;br /&gt;
&lt;br /&gt;
# To activate the autopilot in wings level (ROL) and vertical speed (VS) modes, press and hold the AP button for 0.25 seconds&amp;lt;ref&amp;gt;for older models, just press&amp;lt;/ref&amp;gt;. The autopilot will try to keep the wings level by keeping the turn rate at zero. The autopilot will also try to maintain the vertical speed at activation. Use the UP and DN buttons to set the desired vertical speed.&lt;br /&gt;
# With the autopilot active you can use the HDG button to toggle between wings level (ROL) and heading select (HDG) modes. In heading select mode the autopilot will try to maintain the heading selected by the heading bug on the directional gyro.&lt;br /&gt;
# Use NAV button to toggle between navigation mode (NAV) and wings level (ROL) mode. NAV mode is flying to NAV1 or GPS. That is one of the Heading modes in KAP140 that direction when heading bug OBS operated. Please be careful.&lt;br /&gt;
# Toggle other mode and approach (APR) mode when APR button pushed and following marker beacon, VOR, GPS and [[ILS]] (localizer and glide slope) for automatic approach. This mode is recommended for instrument approach.&lt;br /&gt;
# The REV button enables the back course mode having the autopilot flying away from the runway. This mode is like APR mode except that the direction is away from the localizer (LOC) and that glide slope (GS) is not used.&lt;br /&gt;
# Use the ALT button to toggle between vertical speed (VS) and altitude hold (ALT) modes. In altitude hold mode the UP and DN buttons change the altitude by 20 feet per press.&lt;br /&gt;
# The ARM button enables altitude preselect by the rotary knob using procedure below, pushing it again disables altitude preselect.&lt;br /&gt;
## Input the current atmospheric pressure using the BARO button and rotary knob&lt;br /&gt;
## Check that the display is showing altitude and set your desired altitude, using the rotary knob. &lt;br /&gt;
## Set your desired vertical speed using UP and DN button.&lt;br /&gt;
## Press the ARM button that is enable ARM mode.&lt;br /&gt;
# The BARO button sets the atmospheric pressure. When the BARO button is pushed, enter desired atmospheric pressure using the rotary dial/knob. When pushed long, it switches the setting display to hPa (and back)&amp;lt;ref&amp;gt;only on newer simulated models&amp;lt;/ref&amp;gt; (hint: you can do InHG&amp;lt;&amp;gt;hPa conversion easily with this)&lt;br /&gt;
# Press the AP button to deactivate the autopilot. The horizontal and vertical modes can not be activated independently.&lt;br /&gt;
&lt;br /&gt;
Please read the Pilot's Guide for complete instructions on the use of the KAP140 Autopilot system.&lt;br /&gt;
&lt;br /&gt;
== Limits ==&lt;br /&gt;
* Not certified for use below 200ft [[AGL]], below 80 or above 160 knots [[IAS]] or when alternate static port is active.&lt;br /&gt;
* Only activate when flaps are retracted.&lt;br /&gt;
* Do not override the autopilot with flight controls, instead deactivate it temporarily to make manual adjustments.&lt;br /&gt;
* Do not activate the autopilot near the ground (takeoff, landing) or at low speeds: it will mess up and may crash you into the ground.&lt;br /&gt;
* It also has no autoland capability, so it can't land you based on ILS signals.&lt;br /&gt;
* Don't activate it when in an unstable or mistrimmed flight attitude. Tough it will try(!) to stabilize the plane, this is unsafe.&lt;br /&gt;
* It will not rescue you out of a stall, so watch the airspeed regularly. If the plane slows down, the autopilot will increase pitch to maintain the VS/ALT mode ordered, putting you into a stall. Disengage the autopilot and manually stabilize your flight.&lt;br /&gt;
* Altitude catching only works if you are flying towards the desired setting. For example, if you are at 1500ft, entered 2000ft as target and then descend, it will not intercept but fly you into the ground. Similarly, if you enter an altitude below you and accidentally climb, you will eventually reach the planes service ceiling and stall. The &amp;quot;ALT ARM&amp;quot; mode does not mean &amp;quot;bring me to that altitude&amp;quot;.&lt;br /&gt;
* Like with altitude interception, intercepting a VOR radial or ILS will not work, if the heading bug was not aligned or if you try to intercept from a custom angle (ie. engaging NAV mode from ROL).&lt;br /&gt;
&lt;br /&gt;
== Altitude alert (beeping sound) ==&lt;br /&gt;
A nice feature for assisting manual flights is the altitude alert. This is the aural beeping alert when you get near the selected altitude preset:&lt;br /&gt;
* When getting near 1000ft of the selected altitude, it starts to beep five times and show a steady &amp;quot;ALERT&amp;quot; right below the altitude.&lt;br /&gt;
* When intercepting the altitude, the ALERT annunciator vanishes if you get 200ft near, and will shortly flash up when crossing the selected altitude to signal it &amp;quot;catched on&amp;quot;.&lt;br /&gt;
* When now exceeding the +-200ft band, it will alert by flashing and beeping.&lt;br /&gt;
&lt;br /&gt;
This feature just needs a calibrated baro setting and a selected altitude, thus it is also active when using the modes utilizing altitude preset described below.&lt;br /&gt;
&lt;br /&gt;
== Example workflow ==&lt;br /&gt;
This example tries to show you how to deal with the autopilot to achieve common tasks. There are more advanced techniques to achieve with the autopilot, but they are out of scope for this quick introduction. Please refer to the 'KAP 140 Pilot's Guide' which is linked below.&lt;br /&gt;
&lt;br /&gt;
For better familiarization it would be good to follow trough the following guide inside flightgear.&lt;br /&gt;
&lt;br /&gt;
{{note| Be aware of procedure differences when you have an [[HSI|Horizontal Situation Indicator (HSI)]] installed in the aircraft: In this case, the KAP 140 will not have an automatic 45°-Intercept for NAV/APR/REV modes when switching from HDG-mode and you need to select the desired intercept course manually by turning the heading bug to the desired intercept course. }}&lt;br /&gt;
&lt;br /&gt;
=== Before-takeoff preparations ===&lt;br /&gt;
* For the HDG/NAV mode it is important to calibrate the [[Avionics_and_instruments#Directional_Gyro|Directional Gyro (DG)]] to the magnetic compass (with running motor, so the DG has power): read the magnetic compass and rotate the DG so its upper bearing mark reflects that course (use the red heading bug if necessary, it makes it easier at the beginning). If your DG was not calibrated, this will not be the magnetic course and you probably will not get where you wanted to.&lt;br /&gt;
* For the ALT mode it is important to calibrate the barometric setting of the autopilot to the one of the altimeter: read the altimeters baro setting from the Kollsman window (the small window at the altimeter), press the &amp;quot;BARO&amp;quot; key at the autopilot and rotate the right &amp;quot;altitude-preselect knob&amp;quot; so the autopilot shows the same setting. If you miss to calibrate the baro setting, you will under-/overshoot altitude presets&lt;br /&gt;
* You should run through the HDG and VS modes on ground as preflight check to make sure the autopilot is operating the aileron, elevator and trim wheel&lt;br /&gt;
* If you intend to follow the runway heading after takeoff, now is a good time to adjust the heading bug.&lt;br /&gt;
* If you intend to climb to a specific altitude and let the autopilot intercept it, you should now rotate the altitude preselect knob of the autopilot to the desired altitude.&lt;br /&gt;
&lt;br /&gt;
=== After Takeoff: Hold heading, continue climbing ===&lt;br /&gt;
It is not advised to engage the autopilot immediately for takeoff as it will mess up with lower speeds. Establish a smooth climb rate and trimmed flight first.&lt;br /&gt;
&lt;br /&gt;
When you achieved a stable climb rate after takeoff, engage the autopilot by pressing &amp;quot;AP&amp;quot;. It will engage in ROL/VS mode: keeps the wings level and maintain climb at the current rate. The moment you engage the autopilot it will show &amp;quot;ROL&amp;quot;, &amp;quot;VS&amp;quot;, and at the right side the currently set climb rate for some seconds. Always visually confirm the autopilot shows the modes you expect!&lt;br /&gt;
&lt;br /&gt;
If not done yet, rotate the red heading bug at the DG to the desired heading before switching to HDG mode.&lt;br /&gt;
&lt;br /&gt;
Once you press the HGT button, the autopilot will switch from ROL to the HDG mode, and the plane now follows the desired course. When you change the heading bug, the plane will bank and follow the new heading (it will do this at standard turn rate).&lt;br /&gt;
&lt;br /&gt;
=== Climb to Altitude, then hold it (intercept altitude) ===&lt;br /&gt;
The autpilot now should be in HDG/VS mode. You should check the vertical speed the autopilot automatically set to the climb rate you had when engaging it. Using the buttons &amp;quot;UP&amp;quot; and &amp;quot;DN&amp;quot; you can adjust the vertical speed setting to the desired value.&lt;br /&gt;
&lt;br /&gt;
To let the autopilot intercept the desired altitude, you need to enter it using the knob on the right side. Turn in the desired altitude.&lt;br /&gt;
Doing this should automatically &amp;quot;arm&amp;quot; the autopilot (it shows &amp;quot;ALT ARM&amp;quot;), telling you that it waits to reach the entered altitude. If it was not arming automatically, you can arm it by manually pressing &amp;quot;ARM&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The plane will now climb to the altitude you requested. When you get at 1000ft near your setting, it will beep to let you know its close. Once you reach the altitude it will reduce the climb rate to zero, leveling out at the desired altitude: this is indicated by the &amp;quot;ALT ARM&amp;quot; vanishing.&lt;br /&gt;
If you are not at the altitude you entered, you probably forgot to calibrate the baro setting.&lt;br /&gt;
&lt;br /&gt;
=== Enroute climb / descend ===&lt;br /&gt;
In case you want to change the currently maintained altitude, you can do so by two means:&lt;br /&gt;
# ''Small adjustments'' can be made by pressing just the UP/DN buttons. Each press will adjust the set altitude for 20ft.&amp;lt;ref&amp;gt;In reality you can hold the button to trigger climb/descend at 500ft/m until you release the key. Recent KAP140 simulations implement this.&amp;lt;/ref&amp;gt; Note that we are in ALT mode, so we adjust absolute altiutde with UP/DN knobs, not climb rate! &lt;br /&gt;
# For ''larger adjustments'' you just rotate the altitude preselect knob to the new desired altitude. The autopilot will show &amp;quot;ALT ARM&amp;quot;. When you are ready to climb or descend, put the autopilot to VS mode by pressing the ALT button. Now push UP/DN buttons to climb or descend, and everything else will behave like already described above. You can adjust the desired climb rate anytime. When intercepting, the autopilot will automatically enter ALT mode again (which changes the behaviour of UP/DN buttons to absolute corrections too!).&lt;br /&gt;
# For other cases you can also use the manual ALT mode: Put the autopilot in VS mode by pressing &amp;quot;ALT&amp;quot;, adjust the climb rate to your liking and as soon as you feel the altitude is right, you can press &amp;quot;ALT&amp;quot; again. The AP will show &amp;quot;ALT&amp;quot; immediately, indicating it is holding this altitude. If your climb rate was a little higher, you probably overshoot the desired altitude, but the autopilot will soon return to it.&lt;br /&gt;
&lt;br /&gt;
Note that you can use these techniques with ROL, HDG and NAV modes.&lt;br /&gt;
&lt;br /&gt;
=== VOR interception (NAV mode) ===&lt;br /&gt;
For longer trips it is nice to let the autopilot track a [[VOR]] radial. The difference to the HDG mode is that the plane will compensate for wind drift as it seeks to keep the [https://en.wikipedia.org/wiki/Course_deviation_indicator CDI]-needle centered.&lt;br /&gt;
&lt;br /&gt;
To use the NAV mode you have to tune NAV1 to the VORs frequency and select the desired radial with the OBS knob of your [https://en.wikipedia.org/wiki/Course_deviation_indicator CDI1] (which links to NAV1).&lt;br /&gt;
Now you can intercept the radial, and you have two options to do that:&lt;br /&gt;
# ''From HDG mode'': select the radial also on your DG heading bug (the plane will try to follow that now). Then engage by pressing &amp;quot;NAV&amp;quot;. The autopilot will show &amp;quot;NAV ARM&amp;quot; and turn the plane to an 45° interception angle, but it will remain in HDG mode. Once you are close enough, the NAV-mode will kick in (&amp;quot;NAV ARM&amp;quot; vanishes and NAV shows) - the plane now follows the radial.&lt;br /&gt;
# ''From ROL mode'' (all angle intercept): It works like HDG mode, but once you push the NAV knob, the plane will intercept the radial at the angle you are currently flying. To initially bring the plane to the desired angle, use either HDG-mode with the heading bug, or fly manually, then engage ROL mode by pressing HDG.&lt;br /&gt;
Both modes will show a flashing &amp;quot;HDG&amp;quot; annunciator to remind you that you have to set the DG heading bug to the radial in both cases (the autopilot computes the needed course from that)!&lt;br /&gt;
&lt;br /&gt;
=== ILS assisted approach (APR mode) ===&lt;br /&gt;
The NAV and APR modes are really similar, but the APR mode does additionally follow also the glideslope signal from an [https://en.wikipedia.org/wiki/Instrument_landing_system ILS], and the ILS has a fixed radial (so it ignores the OBS knob setting of the CDI!).&lt;br /&gt;
&lt;br /&gt;
The interception works exactly as in the NAV mode described above. If you engage the APR mode knob the plane will start to intercept the signal, showing &amp;quot;APR ARM&amp;quot; and continue with the currently selected lateral mode (ROL or HDG with 45° intercept angle). Remember to set the DG heading bug to the desired approach course (&amp;quot;HDG&amp;quot; will flash to remember you of that).&lt;br /&gt;
&lt;br /&gt;
As soon as the plane intercepts the localizer (that drives the CDI needle left/right), it will behave like in NAV mode. &amp;quot;APR ARM&amp;quot; will vanish and switch to &amp;quot;APR&amp;quot;, showing you are in approach mode now.&lt;br /&gt;
&lt;br /&gt;
When the APR mode engages, the GS mode will try to intercept the vertical glideslope beam. As long as you stay below the beam, it will show &amp;quot;GS ARM&amp;quot;. When the vertical glideslope is intercepted, the plane will start to follow it downwards.&lt;br /&gt;
&lt;br /&gt;
'''Attention! This mode is dangerous''', because it will drive you into the ground if you don't disengage the autopilot. It is not meant to land you automatically, just to guide you close to the runway. As soon as you are near the runway you should disengage and land manually.&lt;br /&gt;
&lt;br /&gt;
=== Flying opposite direction of VOR or ILS (REV mode) ===&lt;br /&gt;
Using the REV mode allows you to fly away from an VOR or ILS localizer signal (note that glideslope is ignored).&lt;br /&gt;
It works the same as NAV or APR, just in the opposite compass direction. This is helpful if you want to fly straight away from the runway at start or to fly away from a VOR that you tuned the inbound course into the OBS.&lt;br /&gt;
&lt;br /&gt;
As APR and NAV, you can intercept the radial or localizer either from ROL or HDG mode.&lt;br /&gt;
In either case, dial in the Front Inbound course into the OBS and the DG heading bug, and not the direction you want to fly.&lt;br /&gt;
Course reversal is done from the autopilot automatically.&lt;br /&gt;
&lt;br /&gt;
== Related content ==&lt;br /&gt;
* [[Joystick Autopilot Bindings]]  Snippets for joystick.xml file that allow control of most of the autopilot functions using the joystick.&lt;br /&gt;
: {{icaution|These joystick bindings only work with the older KAP versions, not the new one with the preflight check simulated!}}&lt;br /&gt;
&lt;br /&gt;
== External links ==&lt;br /&gt;
*[https://bkx.bendixking.com/downloads/006-18034-0000_3.pdf Bendix/King KAP 140 Pilot's Guide download link] (PDF, 6.8 MB), Honywell, rev. 3, Nov 2005.&lt;br /&gt;
*[https://www.bendixking.com/content/dam/bendixking/en/documents/document-lists/downloads-and-manuals/006-18034-0000-KAP-140-Pilots-Guide.pdf Bendix/King KAP 140 Pilot's Guide], Honeywell, rev. 3, Nov 2005.&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&lt;br /&gt;
[[Category:Aircraft instruments]]&lt;br /&gt;
&lt;br /&gt;
[[de:Autopilot Bendix/King KAP140]]&lt;br /&gt;
[[es:Piloto automático Bendix/King KAP140]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_Browser&amp;diff=143843</id>
		<title>Nasal Browser</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_Browser&amp;diff=143843"/>
		<updated>2026-03-30T10:01:05Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:Nasal Namespace Browser Prototype.png|thumb|Screen shot showing the new [[Nasal Browser|Nasal Namespace Browser]] that is currently being worked on by Philosopher.]]&lt;br /&gt;
&lt;br /&gt;
{{Nasal Navigation}}&lt;br /&gt;
&lt;br /&gt;
The '''Nasal '''('''Namespace''')''' Browser''' is a dialog created by user Philosopher that allows the user to peek at the internal state of Nasal (starting from the global namespace). It displays an alphabetical list of keys mapped to values using the [[Canvas]] framework. Hashes and vectors are shown as their size and can be entered into via clicking on them; scalars' complete values are shown while ghosts and other types and represented in brackets.&lt;br /&gt;
&lt;br /&gt;
The source code can be viewed [{{gitorious url&lt;br /&gt;
| proj = fg&lt;br /&gt;
| repo = canvas-hackers-fgdata&lt;br /&gt;
| path = Nasal/nasal_browser.nas&lt;br /&gt;
| branch = topics/nasal-browser-rebased&lt;br /&gt;
| view = raw&lt;br /&gt;
}} here]&lt;br /&gt;
&lt;br /&gt;
{{Note|Per-update performance of the dialog (i.e. how fast it runs) is determined by how big the namespace is. On the developer's computer, it takes about 20-30 ms to update the global namespace, much less for much smaller namespaces. The dialog is automatically updated about every 0.6 seconds.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
Each entry is color-coded by type and scrollable via the Canvas scroll widget, sorted either by name or type and name. In a hash, each key is shown as either a string, a number, or a symbol, depending on what it really is (i.e. a scalar may be numeric but still a string; also, interned symbols are special cases of strings). Strings are shown in single quotes, symbols and numbers are shown without quotes. For strings, the brightness of the color is optionally reduced.&lt;br /&gt;
&lt;br /&gt;
If the binary includes the &amp;lt;code&amp;gt;debug.decompile()&amp;lt;/code&amp;gt; extension function (i.e. is part of [[User:Philosopher/Nasal introspection|extended-nasal]]) then functions can optionally be shown with their arguments, e.g. &amp;lt;code&amp;gt;isa = &amp;lt;func(obj,class)&amp;gt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;_setlistener = &amp;lt;internal func&amp;gt;&amp;lt;/code&amp;gt; (in the sense of a function implemented in C/C++).&lt;br /&gt;
&lt;br /&gt;
Values can be edited when they are clicked on; the edit bar then becomes activated and will capture key presses. A valid Nasal expression should be entered, which will be executed on {{key press|Enter}}.&lt;br /&gt;
&lt;br /&gt;
== 2025 Revival ==&lt;br /&gt;
&lt;br /&gt;
Since 2025, the Nasal Browser project has been revived and is now maintained as a standalone addon. As the original source code repository is no longer available, this version serves as its direct successor, updated for compatibility with modern FlightGear versions.&lt;br /&gt;
&lt;br /&gt;
The new version is hosted on GitHub: {{github source|user=PlayeRom|repo=flightgear-addon-nasal-namespace-browser|text=playerom/flightgear-addon-nasal-namespace-browser}}. It is fully integrated with the FlightGear addon system, providing a modernized interface for browsing the Nasal namespace while preserving historical functionality.&lt;br /&gt;
&lt;br /&gt;
Notably, the addon '''includes the original source code''', which can be optionally enabled for those who prefer the classic experience or wish to compare implementations.&lt;br /&gt;
&lt;br /&gt;
* '''Modernized UI:''' Improved compatibility with recent FlightGear releases.&lt;br /&gt;
* '''Easy Installation:''' Fully compatible with the FlightGear Addon system.&lt;br /&gt;
* '''Project Homepage &amp;amp; Source:''' {{github source|user=PlayeRom|repo=flightgear-addon-nasal-namespace-browser|text=playerom/flightgear-addon-nasal-namespace-browser}}&lt;br /&gt;
* '''Compatibility:''' FlightGear 2024.1+&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter&amp;diff=143839</id>
		<title>FlightGear Newsletter</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter&amp;diff=143839"/>
		<updated>2026-03-30T09:02:55Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Archive */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:magagazine.png|right]]&lt;br /&gt;
&lt;br /&gt;
The '''FlightGear Newsletter''' is intended to provide a collection of the latest developments from the worldwide [[FlightGear]] community. With so much development ongoing, it's almost impossible for anyone to keep up. The newsletter was started in July 2009, and is presented on a monthly basis.  It is a collaborative effort that is created, edited and distributed via the [[FlightGear Wiki]], and all FlightGear users, contributors and developers are invited to contribute.&lt;br /&gt;
&lt;br /&gt;
=== Archive ===&lt;br /&gt;
&amp;lt;div class=&amp;quot;noresize&amp;quot;&amp;gt;&lt;br /&gt;
{| class=&amp;quot;prettytable&amp;quot;&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2026&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2025&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2024&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2023&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2022&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2021&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2020&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2019&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2018&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2017&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2016&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2015&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2014&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2013&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2012&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2011&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2010&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2009&lt;br /&gt;
|-&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2026|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2026|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2026|March]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2025|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2025|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2025|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2025|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2025|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2025|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2025|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2025|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2025|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2025|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2025|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2025|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2024|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2024|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2024|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2024|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2024|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2024|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2024|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2024|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2024|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2024|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2024|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2024|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2023|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2023|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2023|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2023|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2023|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2023|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2023|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2023|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2023|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2023|October]] &lt;br /&gt;
* [[FlightGear Newsletter November 2023|November]] &lt;br /&gt;
* [[FlightGear Newsletter December 2023|December]] &lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2022|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2022|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2022|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2022|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2022|May]] &lt;br /&gt;
* June&lt;br /&gt;
* [[FlightGear Newsletter July 2022|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2022|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2022|September]]&lt;br /&gt;
* [[FlightGear Newsletter November 2022|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2022|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2021|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2021|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2021|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2021|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2021|May]] &lt;br /&gt;
* [[FlightGear Newsletter June 2021|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2021|July]] &lt;br /&gt;
* [[FlightGear Newsletter August 2021|August]] &lt;br /&gt;
* [[FlightGear Newsletter September 2021|September]] &lt;br /&gt;
* [[FlightGear Newsletter October 2021|October]] &lt;br /&gt;
* [[FlightGear Newsletter November 2021|November]] &lt;br /&gt;
* [[FlightGear Newsletter December 2021|December]] &lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2020|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2020|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2020|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2020|April]] &lt;br /&gt;
* [[FlightGear Newsletter May 2020|May]] &lt;br /&gt;
* [[FlightGear Newsletter June 2020|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2020|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2020|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2020|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2020|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2020|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2020|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2019|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2019|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2019|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2019|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2019|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2019|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2019|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2019|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2019|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2019|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2019|November]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2018|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2018|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2018|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2018|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2018|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2018|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2018|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2018|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2018|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2018|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2018|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2018|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2017|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2017|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2017|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2017|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2017|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2017|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2017|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2017|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2017|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2017|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2017|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2017|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2016|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2016|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2016|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2016|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2016|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2016|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2016|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2016|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2016|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2016|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2016|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2016|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2015|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2015|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2015|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2015|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2015|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2015|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2015|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2015|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2015|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2015|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2015|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2015|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2014|January]] &lt;br /&gt;
* [[FlightGear Newsletter February 2014|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2014|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2014|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2014|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2014|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2014|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2014|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2014|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2014|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2014|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2014|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2013|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2013|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2013|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2013|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2013|May]] &lt;br /&gt;
* [[FlightGear Newsletter June 2013|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2013|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2013|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2013|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2013|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2013|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2013|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2012|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2012|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2012|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2012|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2012|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2012|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2012|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2012|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2012|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2012|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2012|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2012|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2011|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2011|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2011|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2011|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2011|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2011|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2011|July]] &lt;br /&gt;
* [[FlightGear Newsletter August 2011|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2011|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2011|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2011|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2011|December]] &lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2010|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2010|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2010|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2010|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2010|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2010|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2010|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2010|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2010|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2010|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2010|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2010|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter July 2009|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2009|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2009|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2009|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2009|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2009|December]]&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Some newsletter editions have been translated in other languages:&lt;br /&gt;
* [[File:De.gif|16px|link=:De:FlightGear Newsletter]] [[:De:FlightGear Newsletter|Deutsch]]&lt;br /&gt;
* [[File:Es.gif|16px|link=:Es:FlightGear Newsletter]] [[:Es:FlightGear Newsletter|Español]]&lt;br /&gt;
* [[File:Fr.gif|16px|link=:Fr:FlightGear Newsletter]] [[:Fr:FlightGear Newsletter|Français]]&lt;br /&gt;
&lt;br /&gt;
=== You are invited to contribute! ===&lt;br /&gt;
We would like to emphasize that the monthly newsletter can not live without the contributions of FlightGear users and developers. The FlightGear Newsletter is a community-driven newsletter, which means that it is created and edited by people like ''you''. You don't need to be a long-time FlightGear user (or even a developer) to contribute to the newsletter. &lt;br /&gt;
&lt;br /&gt;
In fact, helping write the monthly FlightGear newsletter is an excellent way for getting started contributing to the FlightGear community very quickly and very easily. &lt;br /&gt;
&lt;br /&gt;
Even if you don't have to add anything yourself, just reviewing and improving additions by others is also highly appreciated, as are efforts to help translate newsletters or add screen shots (e.g. taken from the forum or mailing lists) to the newsletter. Screen shots can be uploaded at [[Special:Upload]].&lt;br /&gt;
&lt;br /&gt;
Everyone with a wiki account (free to [[Special:UserLogin|register]]) can edit the newsletter and every contribution is welcome. So if you know about any FlightGear related projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.&lt;br /&gt;
&lt;br /&gt;
A simple template providing a basic structure for each upcoming newsletter can be taken from [[Talk:Next newsletter]]. This template can be copied/pasted into each new newsletter to help users getting started providing contents.&lt;br /&gt;
&lt;br /&gt;
The draft for the upcoming newsletter can always be found at [[Next newsletter]].&lt;br /&gt;
&lt;br /&gt;
If English is not your native language, please don't be concerned about contributing to the newsletter, you can always easily ask fellow FlightGear users to review or proof-read your changes. Also, one of the easiest ways to get started is simply copying/pasting text from forum or mailing list discussions, such as announcements (e.g., new aircraft, new scenery, etc.).&lt;br /&gt;
&lt;br /&gt;
Another neat option to get started is adding links to FlightGear-related YouTube videos. For this, we have a dedicated section in each newsletter: &amp;quot;[[FlightGear Newsletter {{#time: F Y | last month }}#FlightGear on YouTube|FlightGear on YouTube]].&amp;quot;&lt;br /&gt;
&lt;br /&gt;
If you want to embed a video, please see {{mediawiki|Extension:EmbedVideo#Usage}}&lt;br /&gt;
&lt;br /&gt;
If you are looking for other ways to get involved, please see [[Volunteer]].&lt;br /&gt;
&lt;br /&gt;
[[Category:FlightGear Newsletter| ]]&lt;br /&gt;
[[Category:Community|Newsletter]]&lt;br /&gt;
[[Category:Lists|Newsletter]]&lt;br /&gt;
&lt;br /&gt;
[[de:FlightGear Newsletter]]&lt;br /&gt;
[[es:FlightGear Newsletter]]&lt;br /&gt;
[[fr:FlightGear Newsletter]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_FAQ&amp;diff=143790</id>
		<title>Nasal FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_FAQ&amp;diff=143790"/>
		<updated>2026-03-26T13:29:51Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* How to play sounds using Nasal script? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:Nasal Navigation}}&lt;br /&gt;
&lt;br /&gt;
This is meant to provide a place for '''frequently asked questions related to [[Nasal]]''' and related to using Nasal in [[FlightGear]], it's largely inspired by several related discussions on both, the FlightGear forums as well as the [[Mailing list|FlightGear mailing lists]].&lt;br /&gt;
&lt;br /&gt;
== What are the major features of Nasal? ==&lt;br /&gt;
(Based on [http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg21428.html] and [http://plausible.org/nasal/])&lt;br /&gt;
&lt;br /&gt;
=== Design ===&lt;br /&gt;
* Designed as an extension language, well suited for embedded applications&lt;br /&gt;
* Nasal can be instructed to run for only a certain number of &amp;quot;cycles&amp;quot; before returning. Later calls will automatically pick up the interpreter state where it left off. &lt;br /&gt;
* Small Interpreter, written in ANSI C, even smaller than lua&lt;br /&gt;
* Nasal's VM uses internally a stack machine model&lt;br /&gt;
* Nasal does not depend on any third party libraries other than the standard C library.&lt;br /&gt;
* Nasal provides optional bindings for libraries such as for example gtk, cairo, regex, readline, sqlite or unix &lt;br /&gt;
* Nasal does not depend on third party tools like (f)lex and yacc/bison. &lt;br /&gt;
* Nasal builds simply and easily, supports a reasonably simple extension API and cohabitates well with other code&lt;br /&gt;
* Nasal is threadsafe&lt;br /&gt;
* Nasal does not use a global interpreter lock (i.e. GIL in Python or Lua)&lt;br /&gt;
* Nasal is stackless for interpreted code&lt;br /&gt;
* Nasal has complete programmatic control over the runtime namespace for any piece of code, making &amp;quot;modules&amp;quot; a question of script coding and allowing a bunch of neat metaprogramming tricks&lt;br /&gt;
* Unicode Support&lt;br /&gt;
* Support for multiple execution contexts and subcontexts&lt;br /&gt;
&lt;br /&gt;
=== Language ===&lt;br /&gt;
* Supported Types: Vectors, Hashes and Scalars &lt;br /&gt;
* Literal numbers can be decimal, exponential, or hex constants. All numbers are stored internally as IEEE double-precision values.&lt;br /&gt;
* Nasal supports exception handling as a first-class language feature, with built-in runtime-inspectable stack trace&lt;br /&gt;
* Standard OOP syntax (obj.field) using Hashes&lt;br /&gt;
* Nasal is a true functional language with recursion, lexical scoping, runtime binding, anonymous lambda expressions and true closures.&lt;br /&gt;
* Nasal has a true garbage collector.&lt;br /&gt;
* Nasal's data model matches what you are used to from perl, python and javascript.&lt;br /&gt;
* Nasal has syntax (e.g. based on concepts borrowed from ECMAScript/JavaScript) that makes sense in the modern world and to programmers exposed to other languages like Javascript.&lt;br /&gt;
&lt;br /&gt;
== How to play sounds using Nasal script? ==&lt;br /&gt;
This works via property-fgcommands as per $FG_ROOT/Docs/README.commands:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var sound = {&lt;br /&gt;
    path  : DIRECTORYPATH, &lt;br /&gt;
    file  : FILENAME, &lt;br /&gt;
    volume: VOLUME,&lt;br /&gt;
    queue : &amp;quot;instant&amp;quot;, # Other optional choices, do not include queue for default, or user defined queue name&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
fgcommand(&amp;quot;play-audio-sample&amp;quot;, props.Node.new(sound));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You'll want to adjust the capital variables with your own path/file name and volume, e.g. using a helper class :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var Sound = {&lt;br /&gt;
    # static&lt;br /&gt;
    default_path: getprop(&amp;quot;/sim/fg-root&amp;quot;) ~ '/Sounds', &lt;br /&gt;
    &lt;br /&gt;
    # constructor&lt;br /&gt;
    new: func(filename, volume = 0.5, path = nil) {&lt;br /&gt;
        var m = Props.Node.new({&lt;br /&gt;
            path  : path or default_path,&lt;br /&gt;
            file  : filename,&lt;br /&gt;
            volume: volume,&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    },&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
fgcommand(&amp;quot;play-audio-sample&amp;quot;, Sound.new(filename: 'blade_vortex.wav', volume: 0.8));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# You could put your sound file in the same folder and load it from there, &lt;br /&gt;
# if you aren't using an existing sound:&lt;br /&gt;
&lt;br /&gt;
var node = Sound.new(&lt;br /&gt;
    filename: 'mySoundFile.wav',&lt;br /&gt;
    volume: 0.8,&lt;br /&gt;
    path: getprop(&amp;quot;/sim/fg-root&amp;quot;) ~ '/Aircraft/Foo/Sounds',&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
fgcommand(&amp;quot;play-audio-sample&amp;quot;, node);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The same way one would do animations using Nasal:The current sound model is property-driven. You can create a new sound event (see the *-sound.xml files under Aircraft for examples) and drive it from a given set of properties. You can then set those properties from Nasal.&lt;br /&gt;
&lt;br /&gt;
Adjust properties in Nasal (they may be &amp;quot;private&amp;quot; properties in your own subtree of the property list, say /tmp/&amp;lt;aircraft&amp;gt;) and let the sound configuration file act on those properties.&lt;br /&gt;
(from flightgear-devel).&lt;br /&gt;
&lt;br /&gt;
If your sounds are aircraft-specific, then the mechanism of putting it into the -sound.xml file is probably exactly what you want. &lt;br /&gt;
Things like playing sounds from the UI are more problematic from the current interface.&lt;br /&gt;
&lt;br /&gt;
Start with a single beep.wav with an XML definition like:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;sim&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;fx&amp;gt;&lt;br /&gt;
  ...&lt;br /&gt;
  &amp;lt;beep&amp;gt;&lt;br /&gt;
   &amp;lt;name&amp;gt;engstart&amp;lt;/name&amp;gt;&lt;br /&gt;
   &amp;lt;path&amp;gt;Sounds/beep.wav&amp;lt;/path&amp;gt;&lt;br /&gt;
   &amp;lt;property&amp;gt;/tmp/somewhere/beep&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;/beep&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now you can trigger the beep by toggling the property from false to true. With a little work with settimer(), you can get it to beep with different patterns and frequencies, etc... &lt;br /&gt;
&lt;br /&gt;
For additional information, you'll want to check out [[$FG_ROOT]]/Docs/README.xmlsound&lt;br /&gt;
&lt;br /&gt;
=== Using cmdarg() ===&lt;br /&gt;
cmdarg() returns the listened-to property as props.Node object, so you can use it with all its methods (see [[$FG_ROOT]]/Nasal/props.nas),&lt;br /&gt;
for example:&lt;br /&gt;
&lt;br /&gt;
  print(cmdarg().getPath(), &amp;quot; has been changed to &amp;quot;, cmdarg().getValue())&lt;br /&gt;
&lt;br /&gt;
[[Category:Nasal]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_FAQ&amp;diff=143789</id>
		<title>Nasal FAQ</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_FAQ&amp;diff=143789"/>
		<updated>2026-03-26T13:29:17Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* How to play sounds using Nasal script? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:Nasal Navigation}}&lt;br /&gt;
&lt;br /&gt;
This is meant to provide a place for '''frequently asked questions related to [[Nasal]]''' and related to using Nasal in [[FlightGear]], it's largely inspired by several related discussions on both, the FlightGear forums as well as the [[Mailing list|FlightGear mailing lists]].&lt;br /&gt;
&lt;br /&gt;
== What are the major features of Nasal? ==&lt;br /&gt;
(Based on [http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg21428.html] and [http://plausible.org/nasal/])&lt;br /&gt;
&lt;br /&gt;
=== Design ===&lt;br /&gt;
* Designed as an extension language, well suited for embedded applications&lt;br /&gt;
* Nasal can be instructed to run for only a certain number of &amp;quot;cycles&amp;quot; before returning. Later calls will automatically pick up the interpreter state where it left off. &lt;br /&gt;
* Small Interpreter, written in ANSI C, even smaller than lua&lt;br /&gt;
* Nasal's VM uses internally a stack machine model&lt;br /&gt;
* Nasal does not depend on any third party libraries other than the standard C library.&lt;br /&gt;
* Nasal provides optional bindings for libraries such as for example gtk, cairo, regex, readline, sqlite or unix &lt;br /&gt;
* Nasal does not depend on third party tools like (f)lex and yacc/bison. &lt;br /&gt;
* Nasal builds simply and easily, supports a reasonably simple extension API and cohabitates well with other code&lt;br /&gt;
* Nasal is threadsafe&lt;br /&gt;
* Nasal does not use a global interpreter lock (i.e. GIL in Python or Lua)&lt;br /&gt;
* Nasal is stackless for interpreted code&lt;br /&gt;
* Nasal has complete programmatic control over the runtime namespace for any piece of code, making &amp;quot;modules&amp;quot; a question of script coding and allowing a bunch of neat metaprogramming tricks&lt;br /&gt;
* Unicode Support&lt;br /&gt;
* Support for multiple execution contexts and subcontexts&lt;br /&gt;
&lt;br /&gt;
=== Language ===&lt;br /&gt;
* Supported Types: Vectors, Hashes and Scalars &lt;br /&gt;
* Literal numbers can be decimal, exponential, or hex constants. All numbers are stored internally as IEEE double-precision values.&lt;br /&gt;
* Nasal supports exception handling as a first-class language feature, with built-in runtime-inspectable stack trace&lt;br /&gt;
* Standard OOP syntax (obj.field) using Hashes&lt;br /&gt;
* Nasal is a true functional language with recursion, lexical scoping, runtime binding, anonymous lambda expressions and true closures.&lt;br /&gt;
* Nasal has a true garbage collector.&lt;br /&gt;
* Nasal's data model matches what you are used to from perl, python and javascript.&lt;br /&gt;
* Nasal has syntax (e.g. based on concepts borrowed from ECMAScript/JavaScript) that makes sense in the modern world and to programmers exposed to other languages like Javascript.&lt;br /&gt;
&lt;br /&gt;
== How to play sounds using Nasal script? ==&lt;br /&gt;
This works via property-fgcommands as per $FG_ROOT/Docs/README.commands:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var sound = {&lt;br /&gt;
    path  : DIRECTORYPATH, &lt;br /&gt;
    file  : FILENAME, &lt;br /&gt;
    volume: VOLUME,&lt;br /&gt;
    queue : &amp;quot;instant&amp;quot;, # Other optional choices, do not include queue for default, or user defined queue name&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
fgcommand(&amp;quot;play-audio-sample&amp;quot;, props.Node.new(sound));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You'll want to adjust the capital variables with your own path/file name and volume, e.g. using a helper class :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var Sound = {&lt;br /&gt;
    # static&lt;br /&gt;
    default_path: getprop(&amp;quot;/sim/fg-root&amp;quot;) ~ '/Sounds', &lt;br /&gt;
    &lt;br /&gt;
    # constructor&lt;br /&gt;
    new: func(filename, volume = 0.5, path = nil) {&lt;br /&gt;
        var m = Props.Node.new({&lt;br /&gt;
           path  : path or default_path,&lt;br /&gt;
           file  : filename,&lt;br /&gt;
           volume: volume,&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    },&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
fgcommand(&amp;quot;play-audio-sample&amp;quot;, Sound.new(filename: 'blade_vortex.wav', volume: 0.8));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# You could put your sound file in the same folder and load it from there, &lt;br /&gt;
# if you aren't using an existing sound:&lt;br /&gt;
&lt;br /&gt;
var node = Sound.new(&lt;br /&gt;
    filename: 'mySoundFile.wav',&lt;br /&gt;
    volume: 0.8,&lt;br /&gt;
    path: getprop(&amp;quot;/sim/fg-root&amp;quot;) ~ '/Aircraft/Foo/Sounds',&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
fgcommand(&amp;quot;play-audio-sample&amp;quot;, node);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The same way one would do animations using Nasal:The current sound model is property-driven. You can create a new sound event (see the *-sound.xml files under Aircraft for examples) and drive it from a given set of properties. You can then set those properties from Nasal.&lt;br /&gt;
&lt;br /&gt;
Adjust properties in Nasal (they may be &amp;quot;private&amp;quot; properties in your own subtree of the property list, say /tmp/&amp;lt;aircraft&amp;gt;) and let the sound configuration file act on those properties.&lt;br /&gt;
(from flightgear-devel).&lt;br /&gt;
&lt;br /&gt;
If your sounds are aircraft-specific, then the mechanism of putting it into the -sound.xml file is probably exactly what you want. &lt;br /&gt;
Things like playing sounds from the UI are more problematic from the current interface.&lt;br /&gt;
&lt;br /&gt;
Start with a single beep.wav with an XML definition like:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;sim&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;fx&amp;gt;&lt;br /&gt;
  ...&lt;br /&gt;
  &amp;lt;beep&amp;gt;&lt;br /&gt;
   &amp;lt;name&amp;gt;engstart&amp;lt;/name&amp;gt;&lt;br /&gt;
   &amp;lt;path&amp;gt;Sounds/beep.wav&amp;lt;/path&amp;gt;&lt;br /&gt;
   &amp;lt;property&amp;gt;/tmp/somewhere/beep&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;/beep&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now you can trigger the beep by toggling the property from false to true. With a little work with settimer(), you can get it to beep with different patterns and frequencies, etc... &lt;br /&gt;
&lt;br /&gt;
For additional information, you'll want to check out [[$FG_ROOT]]/Docs/README.xmlsound&lt;br /&gt;
&lt;br /&gt;
=== Using cmdarg() ===&lt;br /&gt;
cmdarg() returns the listened-to property as props.Node object, so you can use it with all its methods (see [[$FG_ROOT]]/Nasal/props.nas),&lt;br /&gt;
for example:&lt;br /&gt;
&lt;br /&gt;
  print(cmdarg().getPath(), &amp;quot; has been changed to &amp;quot;, cmdarg().getValue())&lt;br /&gt;
&lt;br /&gt;
[[Category:Nasal]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143529</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143529"/>
		<updated>2026-01-16T21:10:17Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* has_member() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the hash of the namespace &amp;quot;get_math_e&amp;quot;&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5);        # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5);     # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0);     # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1);     # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(true);     # result is a string &amp;quot;1&amp;quot;&lt;br /&gt;
var result = str(false);    # result is a string &amp;quot;0&amp;quot;&lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(-12.5);    # result is a string &amp;quot;-12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in &amp;quot;dev&amp;quot; version ==&lt;br /&gt;
The following functions have been added to the nasal core library curernty on &amp;quot;dev&amp;quot; version.&lt;br /&gt;
&lt;br /&gt;
===member()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = member(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=796|t=Source}}&lt;br /&gt;
|text = The function searches for a key in the given hash, including the &amp;lt;code&amp;gt;parents&amp;lt;/code&amp;gt;. If the key is found, its value is returned; otherwise, it returns &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash in which the key is searched.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = Key as a string whose value we want to return.&lt;br /&gt;
|example1 = &lt;br /&gt;
var Hash = {&lt;br /&gt;
    key: 12,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Hash, 'key');     # return 12&lt;br /&gt;
member(Hash, 'missing'); # return nil&lt;br /&gt;
|example2 = # member() also includes parents&lt;br /&gt;
&lt;br /&gt;
var Base = {&lt;br /&gt;
    key: 'text',&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Child = {&lt;br /&gt;
    parents: [Base],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Child, 'key');     # return 'text'&lt;br /&gt;
member(Child, 'missing'); # return nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===has_member()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = has_member(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=811|t=Source}}&lt;br /&gt;
|text = The function searches for a key in the given hash, including the &amp;lt;code&amp;gt;parents&amp;lt;/code&amp;gt;. If the key is found, &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is returned, otherwise &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash in which the key is searched.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = Key as a string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var Hash = {&lt;br /&gt;
    key: 12,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
has_member(Hash, 'key');     # return true&lt;br /&gt;
has_member(Hash, 'missing'); # return false&lt;br /&gt;
|example2 = # has_member() also includes parents&lt;br /&gt;
&lt;br /&gt;
var Base = {&lt;br /&gt;
    key: 'text',&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Child = {&lt;br /&gt;
    parents: [Base],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
has_member(Child, 'key');     # return true&lt;br /&gt;
has_member(Child, 'missing'); # return false&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143527</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143527"/>
		<updated>2026-01-16T19:37:21Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* has_member() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the hash of the namespace &amp;quot;get_math_e&amp;quot;&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5);        # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5);     # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0);     # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1);     # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(true);     # result is a string &amp;quot;1&amp;quot;&lt;br /&gt;
var result = str(false);    # result is a string &amp;quot;0&amp;quot;&lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(-12.5);    # result is a string &amp;quot;-12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in &amp;quot;dev&amp;quot; version ==&lt;br /&gt;
The following functions have been added to the nasal core library curernty on &amp;quot;dev&amp;quot; version.&lt;br /&gt;
&lt;br /&gt;
===member()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = member(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=796|t=Source}}&lt;br /&gt;
|text = The function searches for a key in the given hash, including the &amp;lt;code&amp;gt;parents&amp;lt;/code&amp;gt;. If the key is found, its value is returned; otherwise, it returns &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash in which the key is searched.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = Key as a string whose value we want to return.&lt;br /&gt;
|example1 = &lt;br /&gt;
var Hash = {&lt;br /&gt;
    key: 12,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Hash, 'key');     # return 12&lt;br /&gt;
member(Hash, 'missing'); # return nil&lt;br /&gt;
|example2 = # member() also includes parents&lt;br /&gt;
&lt;br /&gt;
var Base = {&lt;br /&gt;
    key: 'text',&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Child = {&lt;br /&gt;
    parents: [Base],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Child, 'key');     # return 'text'&lt;br /&gt;
member(Child, 'missing'); # return nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===has_member()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = has_member(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=811|t=Source}}&lt;br /&gt;
|text = The function searches for a key in the given hash, including the &amp;lt;code&amp;gt;parents&amp;lt;/code&amp;gt;. If the key is found, &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is returned, otherwise &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash in which the key is searched.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = Key as a string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var Hash = {&lt;br /&gt;
    key: 12,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Hash, 'key');     # return true&lt;br /&gt;
member(Hash, 'missing'); # return false&lt;br /&gt;
|example2 = # has_member() also includes parents&lt;br /&gt;
&lt;br /&gt;
var Base = {&lt;br /&gt;
    key: 'text',&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Child = {&lt;br /&gt;
    parents: [Base],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Child, 'key');     # return true&lt;br /&gt;
member(Child, 'missing'); # return false&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143526</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143526"/>
		<updated>2026-01-16T19:35:42Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the hash of the namespace &amp;quot;get_math_e&amp;quot;&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5);        # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5);     # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0);     # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1);     # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(true);     # result is a string &amp;quot;1&amp;quot;&lt;br /&gt;
var result = str(false);    # result is a string &amp;quot;0&amp;quot;&lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(-12.5);    # result is a string &amp;quot;-12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in &amp;quot;dev&amp;quot; version ==&lt;br /&gt;
The following functions have been added to the nasal core library curernty on &amp;quot;dev&amp;quot; version.&lt;br /&gt;
&lt;br /&gt;
===member()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = member(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=796|t=Source}}&lt;br /&gt;
|text = The function searches for a key in the given hash, including the &amp;lt;code&amp;gt;parents&amp;lt;/code&amp;gt;. If the key is found, its value is returned; otherwise, it returns &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash in which the key is searched.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = Key as a string whose value we want to return.&lt;br /&gt;
|example1 = &lt;br /&gt;
var Hash = {&lt;br /&gt;
    key: 12,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Hash, 'key');     # return 12&lt;br /&gt;
member(Hash, 'missing'); # return nil&lt;br /&gt;
|example2 = # member() also includes parents&lt;br /&gt;
&lt;br /&gt;
var Base = {&lt;br /&gt;
    key: 'text',&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Child = {&lt;br /&gt;
    parents: [Base],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Child, 'key');     # return 'text'&lt;br /&gt;
member(Child, 'missing'); # return nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===has_member()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = has_member(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=811|t=Source}}&lt;br /&gt;
|text = The function searches for a key in the given hash, including the &amp;lt;code&amp;gt;parents&amp;lt;/code&amp;gt;. If the key is found, &amp;lt;code&amp;gt;trur&amp;lt;/code&amp;gt; is returned; otherwise &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash in which the key is searched.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = Key as a string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var Hash = {&lt;br /&gt;
    key: 12,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Hash, 'key');     # return true&lt;br /&gt;
member(Hash, 'missing'); # return false&lt;br /&gt;
|example2 = # has_member() also includes parents&lt;br /&gt;
&lt;br /&gt;
var Base = {&lt;br /&gt;
    key: 'text',&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Child = {&lt;br /&gt;
    parents: [Base],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
member(Child, 'key');     # return true&lt;br /&gt;
member(Child, 'missing'); # return false&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143263</id>
		<title>FlightGear Newsletter November 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143263"/>
		<updated>2025-12-11T22:41:32Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Multiplayer events */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-11}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-11}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI ====&lt;br /&gt;
&lt;br /&gt;
[[File:Photoscenery-GUI-20251113-202721.jpg|thumb|Photoscenery-GUI Beta]]&lt;br /&gt;
&lt;br /&gt;
Adriano Bassignana has begun writing the manual for the Photoscenery GUI, which has been published at the following link:&lt;br /&gt;
https://wiki.flightgear.org/Julia_photoscenery_generator_GUI&lt;br /&gt;
The manual currently contains the essential information for correct installation.&lt;br /&gt;
&lt;br /&gt;
New features in the latest version include:&lt;br /&gt;
&lt;br /&gt;
The tool, which already managed the rational downloading of tiles in various resolutions, now includes additional features accessible via its web interface to increase operational utility. The program's initialization has been modified to enable faster startup.&lt;br /&gt;
&lt;br /&gt;
A new functionality has been introduced to read airports and radio navigation aids from files updated weekly—the same data sources used by FlightGear itself. This resolves the previous issue of airport misalignment between the tool and the simulator.&lt;br /&gt;
&lt;br /&gt;
Route management has been improved. When loading a route for display on the map, the system now simultaneously loads data on local scenery to prevent unnecessary downloads. For users with a fast internet connection, real-time image downloading is possible. A fast, low-resolution preload feature also provides a valid overview of the surrounding area's coverage.&lt;br /&gt;
&lt;br /&gt;
The interface allows users to view radio navigation aids and airports, along with their key information.&lt;br /&gt;
&lt;br /&gt;
The [create route] mode enables users to generate a flight path by clicking on map points, airports, and navigation aids. Created routes can be saved and later imported into FlightGear for use.&lt;br /&gt;
&lt;br /&gt;
Many other features will be documented on the Wiki in the future.&lt;br /&gt;
&lt;br /&gt;
https://github.com/abassign/Photoscenery-GUI.git&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=390&lt;br /&gt;
&lt;br /&gt;
=== Framework for Canvas Add-on ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki (PlayeRom on the forum) created a [https://github.com/PlayeRom/flightgear-addon-framework Framework] for creating add-ons based on Canvas dialog boxes. After creating numerous add-ons, he realized he needed to separate the common code base. This led to the creation of a separate Framework project that could be attached to any add-on. The framework includes the following features:&lt;br /&gt;
&lt;br /&gt;
# Automatic recognition and loading of add-on Nasal files into the appropriate namespaces (with an exclusion list if necessary).&lt;br /&gt;
# Ability to add a menu for restarting add-on Nasal files without having to change repository files.&lt;br /&gt;
# Ability to define keys for the multi-key command to restart add-on Nasal files without having to change repository files.&lt;br /&gt;
# A mechanism for checking whether there is a new version of your add-on to inform users about it.&lt;br /&gt;
# Base classes for Canvas windows that are created and destroyed on demand (Transient dialog), as well as created once during simulator startup (Persistent dialog).&lt;br /&gt;
# Ability to create Nasal unit tests and run them using multi-key command.&lt;br /&gt;
&lt;br /&gt;
An example project using the Framework is the [https://github.com/PlayeRom/flightgear-addon-canvas-skeleton Canvas Skeleton] project, which can be used as a basis for creating a new add-on.&lt;br /&gt;
&lt;br /&gt;
You can also see how his other add-ons are written, all based on the Framework (main branches):&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-aerotow-everywhere Aerotow Everywhere]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-logbook Logbook]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-which-runway Which Runway]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-nasal-namespace-browser Nasal Namespace Browser]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-on Menu Aggregator]&lt;br /&gt;
&lt;br /&gt;
=== Add-ons Menu Aggregator ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki has created a new &amp;quot;[https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-ons Menu Aggregator]&amp;quot; add-on. The &amp;quot;Add-ons Menu Aggregator&amp;quot; add-on solves the problem of increasing menu clutter in FlightGear, which occurs after installing multiple add-ons. Each add-on can add its own items to the main menu, causing it to become very large and, at low resolutions, extend beyond the screen. This makes it difficult for users to find functions related to specific add-ons, and the menu itself becomes unreadable.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;Add-ons Menu Aggregator&amp;quot; automatically aggregates menu entries from all installed add-ons and places them into one common menu item – &amp;quot;Add-ons.&amp;quot; Each add-on receives its own submenu here, containing its original items while maintaining full functionality and layout.&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|gNH3MBpRxjo|480px||Add-ons Menu Aggregator - Polish audio}}&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
=== Aircraft reviews ===&lt;br /&gt;
==== Laminar 13 Hang Glider for FlightGear ====&lt;br /&gt;
==== Flight Test &amp;amp; Personal Tweaks by Vons ====&lt;br /&gt;
&lt;br /&gt;
Having recently become interested in gliding and soaring in FG, I thought I'd go a (simpler) step further and merely strap a wing to myself (or me to a wing). That of course was also a good opportunity to harmonize further a few of the control inputs on the Laminar 13 for my rig/setup, as well as (subtly) to modify some of the weight shifts, drag and lift values, etc. Representative clip below for those who enjoy hang gliders, both real ones and in the world of FG.&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|tHQqhl3Sf5w|||Flight Test &amp;amp; Tweaks Laminar 13 Hang Glider for FlightGear|frame}}&lt;br /&gt;
&lt;br /&gt;
If you're interested in seeing the flight test results and accessing the tweaks, visit the forum page at: https://forum.flightgear.org/viewtopic.php?f=19&amp;amp;t=42893&amp;amp;start=45#p436600&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Finished events ===&lt;br /&gt;
==== Independence Day Event 2025 ====&lt;br /&gt;
To celebrate Poland's Independence Day, the Polish Discord channel organized an Air Show with several performances. Clips are included in the video:&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|ekiLpbSXAmU|480px||Independence Day Event 2025}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[Category:Changes after 2024.1]]--&amp;gt;&lt;br /&gt;
&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 11]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Howto:Add_aerotow_hitch_to_an_AI-aircraft&amp;diff=143216</id>
		<title>Howto:Add aerotow hitch to an AI-aircraft</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Howto:Add_aerotow_hitch_to_an_AI-aircraft&amp;diff=143216"/>
		<updated>2025-12-09T13:18:19Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;For the visualization of the tow rope, it is advantageous if a hitch position is defined for the AI tow plane.&lt;br /&gt;
&lt;br /&gt;
To set the hitch position for the AI aircraft, add the following code to the top of the &amp;lt;tt&amp;gt;{aircraft}-ai.xml&amp;lt;/tt&amp;gt; file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;nasal&amp;gt;&lt;br /&gt;
    &amp;lt;load&amp;gt;&lt;br /&gt;
      var acNode = cmdarg(); # &amp;quot;/ai/models/aircraft&amp;quot;&lt;br /&gt;
      acNode.getNode(&amp;quot;sim/hitches/aerotow/local-pos-x&amp;quot;, true).setValue(x); # forward&lt;br /&gt;
      acNode.getNode(&amp;quot;sim/hitches/aerotow/local-pos-y&amp;quot;, true).setValue(y); # left&lt;br /&gt;
      acNode.getNode(&amp;quot;sim/hitches/aerotow/local-pos-z&amp;quot;, true).setValue(z); # up&lt;br /&gt;
    &amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;unload&amp;gt;&lt;br /&gt;
      acNode.getNode(&amp;quot;sim/hitches&amp;quot;).remove();&lt;br /&gt;
    &amp;lt;/unload&amp;gt;&lt;br /&gt;
  &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  &amp;lt;!-- Rest of content here --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In place of x, y, z, enter appropriate values for the hitch position.&lt;br /&gt;
Coordinate system: x forward, y to the left, and z up. The units are meters.&lt;br /&gt;
&lt;br /&gt;
That's it! The tow rope ends at the defined position.&lt;br /&gt;
&lt;br /&gt;
== Related content ==&lt;br /&gt;
* [[Soaring]]&lt;br /&gt;
* [[Improving Glider Realism]]&lt;br /&gt;
* [[Howto:Setup winch and aerotowing for JSBSim-aircraft]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Howto]]&lt;br /&gt;
[[Category:Aircraft enhancement]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_December_2025&amp;diff=143215</id>
		<title>FlightGear Newsletter December 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_December_2025&amp;diff=143215"/>
		<updated>2025-12-09T11:35:54Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-12}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-12}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI ====&lt;br /&gt;
[[File:FG-JPS.png|thumb|278px|FG Julia Photoscenery-GUI Design proposal &amp;quot;LIBERA ET TESSELA&amp;quot;]]&lt;br /&gt;
&lt;br /&gt;
A major update has been released for the Photoscenery GUI, introducing significant accessibility enhancements.&lt;br /&gt;
&lt;br /&gt;
The key addition is full Internationalization (i18n) support, which makes the tool more accessible to a global audience. Users can now switch between languages instantly using new flag icons located in the control panel.&lt;br /&gt;
&lt;br /&gt;
What’s New:&lt;br /&gt;
&lt;br /&gt;
Multi-language Support:&lt;br /&gt;
The interface now supports multiple languages, including: English, Italian, French, German, Spanish, Portuguese, Chinese (Simplified), Japanese, Korean, Arabic (with full Right-to-Left layout support), and Russian.&lt;br /&gt;
&lt;br /&gt;
UI Improvements:&lt;br /&gt;
A new language switcher with flag icons has been implemented. Layout and font sizes have been optimized for better readability across different languages. Spacing in the control panel has been improved for a cleaner interface.&lt;br /&gt;
&lt;br /&gt;
This update is now available in the main branch on GitHub https://github.com/abassign/Photoscenery-GUI.git. Users are encouraged to test the new features and provide feedback regarding any translation errors or suggestions for additional language support.&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=420&lt;br /&gt;
&lt;br /&gt;
=== Re-Introducing the Rain Vector Editor Addon ===&lt;br /&gt;
This tool has been around for awhile, to reintroduce it there is a short video in the forum link below demonstrating its simple use. Use it to get the exact splash-x, y and z vectors that best look like rain on any specific window. Use that data to code dynamic control over the look of rain on windows and windscreens when introducing wind, prop blast and aircraft velocity through the air.&lt;br /&gt;
&lt;br /&gt;
https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/RainVectorEditor/&lt;br /&gt;
&lt;br /&gt;
https://forum.flightgear.org/viewtopic.php?f=14&amp;amp;t=43932&amp;amp;sid=20078e01f815391532dbded0a512a825&amp;amp;start=45#p436889&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143214</id>
		<title>FlightGear Newsletter November 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143214"/>
		<updated>2025-12-09T11:35:35Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-11}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-11}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI ====&lt;br /&gt;
&lt;br /&gt;
[[File:Photoscenery-GUI-20251113-202721.jpg|thumb|Photoscenery-GUI Beta]]&lt;br /&gt;
&lt;br /&gt;
Adriano Bassignana has begun writing the manual for the Photoscenery GUI, which has been published at the following link:&lt;br /&gt;
https://wiki.flightgear.org/Julia_photoscenery_generator_GUI&lt;br /&gt;
The manual currently contains the essential information for correct installation.&lt;br /&gt;
&lt;br /&gt;
New features in the latest version include:&lt;br /&gt;
&lt;br /&gt;
The tool, which already managed the rational downloading of tiles in various resolutions, now includes additional features accessible via its web interface to increase operational utility. The program's initialization has been modified to enable faster startup.&lt;br /&gt;
&lt;br /&gt;
A new functionality has been introduced to read airports and radio navigation aids from files updated weekly—the same data sources used by FlightGear itself. This resolves the previous issue of airport misalignment between the tool and the simulator.&lt;br /&gt;
&lt;br /&gt;
Route management has been improved. When loading a route for display on the map, the system now simultaneously loads data on local scenery to prevent unnecessary downloads. For users with a fast internet connection, real-time image downloading is possible. A fast, low-resolution preload feature also provides a valid overview of the surrounding area's coverage.&lt;br /&gt;
&lt;br /&gt;
The interface allows users to view radio navigation aids and airports, along with their key information.&lt;br /&gt;
&lt;br /&gt;
The [create route] mode enables users to generate a flight path by clicking on map points, airports, and navigation aids. Created routes can be saved and later imported into FlightGear for use.&lt;br /&gt;
&lt;br /&gt;
Many other features will be documented on the Wiki in the future.&lt;br /&gt;
&lt;br /&gt;
https://github.com/abassign/Photoscenery-GUI.git&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=390&lt;br /&gt;
&lt;br /&gt;
=== Framework for Canvas Add-on ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki (PlayeRom on the forum) created a [https://github.com/PlayeRom/flightgear-addon-framework Framework] for creating add-ons based on Canvas dialog boxes. After creating numerous add-ons, he realized he needed to separate the common code base. This led to the creation of a separate Framework project that could be attached to any add-on. The framework includes the following features:&lt;br /&gt;
&lt;br /&gt;
# Automatic recognition and loading of add-on Nasal files into the appropriate namespaces (with an exclusion list if necessary).&lt;br /&gt;
# Ability to add a menu for restarting add-on Nasal files without having to change repository files.&lt;br /&gt;
# Ability to define keys for the multi-key command to restart add-on Nasal files without having to change repository files.&lt;br /&gt;
# A mechanism for checking whether there is a new version of your add-on to inform users about it.&lt;br /&gt;
# Base classes for Canvas windows that are created and destroyed on demand (Transient dialog), as well as created once during simulator startup (Persistent dialog).&lt;br /&gt;
# Ability to create Nasal unit tests and run them using multi-key command.&lt;br /&gt;
&lt;br /&gt;
An example project using the Framework is the [https://github.com/PlayeRom/flightgear-addon-canvas-skeleton Canvas Skeleton] project, which can be used as a basis for creating a new add-on.&lt;br /&gt;
&lt;br /&gt;
You can also see how his other add-ons are written, all based on the Framework (main branches):&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-aerotow-everywhere Aerotow Everywhere]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-logbook Logbook]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-which-runway Which Runway]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-nasal-namespace-browser Nasal Namespace Browser]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-on Menu Aggregator]&lt;br /&gt;
&lt;br /&gt;
=== Add-ons Menu Aggregator ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki has created a new &amp;quot;[https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-ons Menu Aggregator]&amp;quot; add-on. The &amp;quot;Add-ons Menu Aggregator&amp;quot; add-on solves the problem of increasing menu clutter in FlightGear, which occurs after installing multiple add-ons. Each add-on can add its own items to the main menu, causing it to become very large and, at low resolutions, extend beyond the screen. This makes it difficult for users to find functions related to specific add-ons, and the menu itself becomes unreadable.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;Add-ons Menu Aggregator&amp;quot; automatically aggregates menu entries from all installed add-ons and places them into one common menu item – &amp;quot;Add-ons.&amp;quot; Each add-on receives its own submenu here, containing its original items while maintaining full functionality and layout.&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|gNH3MBpRxjo|480px||Add-ons Menu Aggregator - Polish audio}}&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_October_2025&amp;diff=143213</id>
		<title>FlightGear Newsletter October 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_October_2025&amp;diff=143213"/>
		<updated>2025-12-09T11:35:21Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-10}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-10}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
No News letter for October 2025&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Nasal Namespace Browser ===&lt;br /&gt;
&lt;br /&gt;
After 7 years, Roman Ludwicki resurrected [[Nasal Browser|Nasal Browser]] as an add-on. [https://github.com/PlayeRom/flightgear-addon-nasal-namespace-browser Nasal Namespace Browser] it's reimplementation of the Nasal Browser using Canvas Widgets.&lt;br /&gt;
&lt;br /&gt;
This add-on is useful for developers who want to view all the namespaces created in Nasal. It also makes it easier to understand how namespaces are organized.&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_September_2025&amp;diff=143212</id>
		<title>FlightGear Newsletter September 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_September_2025&amp;diff=143212"/>
		<updated>2025-12-09T11:35:05Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-09}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-09}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI Beta ====&lt;br /&gt;
&lt;br /&gt;
[[File:Photoscenery-GUI-Beta-20250810.png|thumb|Photoscenery-GUI Beta]]&lt;br /&gt;
&lt;br /&gt;
Adriano Bassignana Adriano continues working on the beta version of his Julia code, which provides an interactive map-based workflow for generating photographic scenery for FlightGear. The latest release now includes a feature that shows where the system places the Orthophotos and Orthophotos-save directories.&lt;br /&gt;
&lt;br /&gt;
https://github.com/abassign/Photoscenery-GUI.git&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=375&lt;br /&gt;
&lt;br /&gt;
=== Which Runway Add-on ===&lt;br /&gt;
&lt;br /&gt;
[[File:Which_Runway_main_window_v.1.3.0.png|thumb|Which Runway main window]]&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki (PlayeRom) created a new add-on names &amp;quot;[https://github.com/PlayeRom/flightgear-addon-which-runway Which Runway]&amp;quot;. This add-on uses weather data and preferred runways at the airport (ICAO.rwyuse.xml files) to indicate the best runway for takeoff or landing. It also calculates headwinds, crosswinds, and tailwinds for each airport runway (which can be useful for input into the MCDU), and provides a wealth of information about the airport and its runways, along with a graphical representation on a wind rose.&lt;br /&gt;
&lt;br /&gt;
See also [https://forum.flightgear.org/viewtopic.php?f=89&amp;amp;t=43680 forum] and [[Which_Runway_Add-on|wiki]] pages.&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FlightGear's Screenshot of the Month {{#time: F | 2025-9}} 2025 is &amp;quot;Operation Market Garden&amp;quot; by @OrbitalMartian&lt;br /&gt;
&lt;br /&gt;
[[File:Operation Market Garden by @OrbitalMartian.webp|thumb|Operation Market Garden by @OrbitalMartian]]&lt;br /&gt;
&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 09]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-095 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_August_2025&amp;diff=143211</id>
		<title>FlightGear Newsletter August 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_August_2025&amp;diff=143211"/>
		<updated>2025-12-09T11:34:52Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-08}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-08}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI Beta ====&lt;br /&gt;
&lt;br /&gt;
[[File:Photoscenery-GUI-Beta-20250810.png|thumb|Photoscenery-GUI Beta]]&lt;br /&gt;
&lt;br /&gt;
Adriano Bassignana has released a beta version of his code written in Julia. Now It provides an interactive map-based workflow for generating photographic scenery for FlightGear.&lt;br /&gt;
&lt;br /&gt;
https://github.com/abassign/Photoscenery-GUI.git&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=345&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_July_2025&amp;diff=143210</id>
		<title>FlightGear Newsletter July 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_July_2025&amp;diff=143210"/>
		<updated>2025-12-09T11:34:36Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-07}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-07}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Related Software tools and projects == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- Those not being part of FlightGear itself, like for example OpenRadar, TerreMaster or flightgear-atc.alwaysdata.net. --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-06}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-06 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-06 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 06]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-06 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_June_2025&amp;diff=143209</id>
		<title>FlightGear Newsletter June 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_June_2025&amp;diff=143209"/>
		<updated>2025-12-09T11:34:21Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-06}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-06}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
No News letter for June 2025&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Related Software tools and projects == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- Those not being part of FlightGear itself, like for example OpenRadar, TerreMaster or flightgear-atc.alwaysdata.net. --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_October_2025&amp;diff=143208</id>
		<title>FlightGear Newsletter October 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_October_2025&amp;diff=143208"/>
		<updated>2025-12-09T11:28:18Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Development news */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-05}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-05}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
No News letter for October 2025&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Nasal Namespace Browser ===&lt;br /&gt;
&lt;br /&gt;
After 7 years, Roman Ludwicki resurrected [[Nasal Browser|Nasal Browser]] as an add-on. [https://github.com/PlayeRom/flightgear-addon-nasal-namespace-browser Nasal Namespace Browser] it's reimplementation of the Nasal Browser using Canvas Widgets.&lt;br /&gt;
&lt;br /&gt;
This add-on is useful for developers who want to view all the namespaces created in Nasal. It also makes it easier to understand how namespaces are organized.&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143207</id>
		<title>FlightGear Newsletter November 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143207"/>
		<updated>2025-12-09T11:16:56Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Add-ons Menu Aggregator */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-05}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-05}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI ====&lt;br /&gt;
&lt;br /&gt;
[[File:Photoscenery-GUI-20251113-202721.jpg|thumb|Photoscenery-GUI Beta]]&lt;br /&gt;
&lt;br /&gt;
Adriano Bassignana has begun writing the manual for the Photoscenery GUI, which has been published at the following link:&lt;br /&gt;
https://wiki.flightgear.org/Julia_photoscenery_generator_GUI&lt;br /&gt;
The manual currently contains the essential information for correct installation.&lt;br /&gt;
&lt;br /&gt;
New features in the latest version include:&lt;br /&gt;
&lt;br /&gt;
The tool, which already managed the rational downloading of tiles in various resolutions, now includes additional features accessible via its web interface to increase operational utility. The program's initialization has been modified to enable faster startup.&lt;br /&gt;
&lt;br /&gt;
A new functionality has been introduced to read airports and radio navigation aids from files updated weekly—the same data sources used by FlightGear itself. This resolves the previous issue of airport misalignment between the tool and the simulator.&lt;br /&gt;
&lt;br /&gt;
Route management has been improved. When loading a route for display on the map, the system now simultaneously loads data on local scenery to prevent unnecessary downloads. For users with a fast internet connection, real-time image downloading is possible. A fast, low-resolution preload feature also provides a valid overview of the surrounding area's coverage.&lt;br /&gt;
&lt;br /&gt;
The interface allows users to view radio navigation aids and airports, along with their key information.&lt;br /&gt;
&lt;br /&gt;
The [create route] mode enables users to generate a flight path by clicking on map points, airports, and navigation aids. Created routes can be saved and later imported into FlightGear for use.&lt;br /&gt;
&lt;br /&gt;
Many other features will be documented on the Wiki in the future.&lt;br /&gt;
&lt;br /&gt;
https://github.com/abassign/Photoscenery-GUI.git&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=390&lt;br /&gt;
&lt;br /&gt;
=== Framework for Canvas Add-on ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki (PlayeRom on the forum) created a [https://github.com/PlayeRom/flightgear-addon-framework Framework] for creating add-ons based on Canvas dialog boxes. After creating numerous add-ons, he realized he needed to separate the common code base. This led to the creation of a separate Framework project that could be attached to any add-on. The framework includes the following features:&lt;br /&gt;
&lt;br /&gt;
# Automatic recognition and loading of add-on Nasal files into the appropriate namespaces (with an exclusion list if necessary).&lt;br /&gt;
# Ability to add a menu for restarting add-on Nasal files without having to change repository files.&lt;br /&gt;
# Ability to define keys for the multi-key command to restart add-on Nasal files without having to change repository files.&lt;br /&gt;
# A mechanism for checking whether there is a new version of your add-on to inform users about it.&lt;br /&gt;
# Base classes for Canvas windows that are created and destroyed on demand (Transient dialog), as well as created once during simulator startup (Persistent dialog).&lt;br /&gt;
# Ability to create Nasal unit tests and run them using multi-key command.&lt;br /&gt;
&lt;br /&gt;
An example project using the Framework is the [https://github.com/PlayeRom/flightgear-addon-canvas-skeleton Canvas Skeleton] project, which can be used as a basis for creating a new add-on.&lt;br /&gt;
&lt;br /&gt;
You can also see how his other add-ons are written, all based on the Framework (main branches):&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-aerotow-everywhere Aerotow Everywhere]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-logbook Logbook]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-which-runway Which Runway]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-nasal-namespace-browser Nasal Namespace Browser]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-on Menu Aggregator]&lt;br /&gt;
&lt;br /&gt;
=== Add-ons Menu Aggregator ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki has created a new &amp;quot;[https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-ons Menu Aggregator]&amp;quot; add-on. The &amp;quot;Add-ons Menu Aggregator&amp;quot; add-on solves the problem of increasing menu clutter in FlightGear, which occurs after installing multiple add-ons. Each add-on can add its own items to the main menu, causing it to become very large and, at low resolutions, extend beyond the screen. This makes it difficult for users to find functions related to specific add-ons, and the menu itself becomes unreadable.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;Add-ons Menu Aggregator&amp;quot; automatically aggregates menu entries from all installed add-ons and places them into one common menu item – &amp;quot;Add-ons.&amp;quot; Each add-on receives its own submenu here, containing its original items while maintaining full functionality and layout.&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|gNH3MBpRxjo|480px||Add-ons Menu Aggregator - Polish audio}}&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143206</id>
		<title>FlightGear Newsletter November 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143206"/>
		<updated>2025-12-09T11:14:10Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Related Software tools and projects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-05}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-05}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI ====&lt;br /&gt;
&lt;br /&gt;
[[File:Photoscenery-GUI-20251113-202721.jpg|thumb|Photoscenery-GUI Beta]]&lt;br /&gt;
&lt;br /&gt;
Adriano Bassignana has begun writing the manual for the Photoscenery GUI, which has been published at the following link:&lt;br /&gt;
https://wiki.flightgear.org/Julia_photoscenery_generator_GUI&lt;br /&gt;
The manual currently contains the essential information for correct installation.&lt;br /&gt;
&lt;br /&gt;
New features in the latest version include:&lt;br /&gt;
&lt;br /&gt;
The tool, which already managed the rational downloading of tiles in various resolutions, now includes additional features accessible via its web interface to increase operational utility. The program's initialization has been modified to enable faster startup.&lt;br /&gt;
&lt;br /&gt;
A new functionality has been introduced to read airports and radio navigation aids from files updated weekly—the same data sources used by FlightGear itself. This resolves the previous issue of airport misalignment between the tool and the simulator.&lt;br /&gt;
&lt;br /&gt;
Route management has been improved. When loading a route for display on the map, the system now simultaneously loads data on local scenery to prevent unnecessary downloads. For users with a fast internet connection, real-time image downloading is possible. A fast, low-resolution preload feature also provides a valid overview of the surrounding area's coverage.&lt;br /&gt;
&lt;br /&gt;
The interface allows users to view radio navigation aids and airports, along with their key information.&lt;br /&gt;
&lt;br /&gt;
The [create route] mode enables users to generate a flight path by clicking on map points, airports, and navigation aids. Created routes can be saved and later imported into FlightGear for use.&lt;br /&gt;
&lt;br /&gt;
Many other features will be documented on the Wiki in the future.&lt;br /&gt;
&lt;br /&gt;
https://github.com/abassign/Photoscenery-GUI.git&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=390&lt;br /&gt;
&lt;br /&gt;
=== Framework for Canvas Add-on ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki (PlayeRom on the forum) created a [https://github.com/PlayeRom/flightgear-addon-framework Framework] for creating add-ons based on Canvas dialog boxes. After creating numerous add-ons, he realized he needed to separate the common code base. This led to the creation of a separate Framework project that could be attached to any add-on. The framework includes the following features:&lt;br /&gt;
&lt;br /&gt;
# Automatic recognition and loading of add-on Nasal files into the appropriate namespaces (with an exclusion list if necessary).&lt;br /&gt;
# Ability to add a menu for restarting add-on Nasal files without having to change repository files.&lt;br /&gt;
# Ability to define keys for the multi-key command to restart add-on Nasal files without having to change repository files.&lt;br /&gt;
# A mechanism for checking whether there is a new version of your add-on to inform users about it.&lt;br /&gt;
# Base classes for Canvas windows that are created and destroyed on demand (Transient dialog), as well as created once during simulator startup (Persistent dialog).&lt;br /&gt;
# Ability to create Nasal unit tests and run them using multi-key command.&lt;br /&gt;
&lt;br /&gt;
An example project using the Framework is the [https://github.com/PlayeRom/flightgear-addon-canvas-skeleton Canvas Skeleton] project, which can be used as a basis for creating a new add-on.&lt;br /&gt;
&lt;br /&gt;
You can also see how his other add-ons are written, all based on the Framework (main branches):&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-aerotow-everywhere Aerotow Everywhere]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-logbook Logbook]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-which-runway Which Runway]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-nasal-namespace-browser Nasal Namespace Browser]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-on Menu Aggregator]&lt;br /&gt;
&lt;br /&gt;
=== Add-ons Menu Aggregator ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki has created a new &amp;quot;[https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-ons Menu Aggregator]&amp;quot; add-on. The &amp;quot;Add-ons Menu Aggregator&amp;quot; add-on solves the problem of increasing menu clutter in FlightGear, which occurs after installing multiple add-ons. Each add-on can add its own items to the main menu, causing it to become very large and, at low resolutions, extend beyond the screen. This makes it difficult for users to find functions related to specific add-ons, and the menu itself becomes unreadable.&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;Add-ons Menu Aggregator&amp;quot; automatically aggregates menu entries from all installed add-ons and places them into one common menu item – &amp;quot;Add-ons.&amp;quot; Each add-on receives its own submenu here, containing its original items while maintaining full functionality and layout.&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_September_2025&amp;diff=143205</id>
		<title>FlightGear Newsletter September 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_September_2025&amp;diff=143205"/>
		<updated>2025-12-09T11:09:50Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Related Software tools and projects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-05}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-05}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI Beta ====&lt;br /&gt;
&lt;br /&gt;
[[File:Photoscenery-GUI-Beta-20250810.png|thumb|Photoscenery-GUI Beta]]&lt;br /&gt;
&lt;br /&gt;
Adriano Bassignana Adriano continues working on the beta version of his Julia code, which provides an interactive map-based workflow for generating photographic scenery for FlightGear. The latest release now includes a feature that shows where the system places the Orthophotos and Orthophotos-save directories.&lt;br /&gt;
&lt;br /&gt;
https://github.com/abassign/Photoscenery-GUI.git&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=375&lt;br /&gt;
&lt;br /&gt;
=== Which Runway Add-on ===&lt;br /&gt;
&lt;br /&gt;
[[File:Which_Runway_main_window_v.1.3.0.png|thumb|Which Runway main window]]&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki (PlayeRom) created a new add-on names &amp;quot;[https://github.com/PlayeRom/flightgear-addon-which-runway Which Runway]&amp;quot;. This add-on uses weather data and preferred runways at the airport (ICAO.rwyuse.xml files) to indicate the best runway for takeoff or landing. It also calculates headwinds, crosswinds, and tailwinds for each airport runway (which can be useful for input into the MCDU), and provides a wealth of information about the airport and its runways, along with a graphical representation on a wind rose.&lt;br /&gt;
&lt;br /&gt;
See also [https://forum.flightgear.org/viewtopic.php?f=89&amp;amp;t=43680 forum] and [[Which_Runway_Add-on|wiki]] pages.&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
FlightGear's Screenshot of the Month {{#time: F | 2025-9}} 2025 is &amp;quot;Operation Market Garden&amp;quot; by @OrbitalMartian&lt;br /&gt;
&lt;br /&gt;
[[File:Operation Market Garden by @OrbitalMartian.webp|thumb|Operation Market Garden by @OrbitalMartian]]&lt;br /&gt;
&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 09]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-095 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter&amp;diff=143204</id>
		<title>FlightGear Newsletter</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter&amp;diff=143204"/>
		<updated>2025-12-09T11:03:02Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[File:magagazine.png|right]]&lt;br /&gt;
&lt;br /&gt;
The '''FlightGear Newsletter''' is intended to provide a collection of the latest developments from the worldwide [[FlightGear]] community. With so much development ongoing, it's almost impossible for anyone to keep up. The newsletter was started in July 2009, and is presented on a monthly basis.  It is a collaborative effort that is created, edited and distributed via the [[FlightGear Wiki]], and all FlightGear users, contributors and developers are invited to contribute.&lt;br /&gt;
&lt;br /&gt;
=== Archive ===&lt;br /&gt;
&amp;lt;div class=&amp;quot;noresize&amp;quot;&amp;gt;&lt;br /&gt;
{| class=&amp;quot;prettytable&amp;quot;&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2025&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2024&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2023&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2022&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2021&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2020&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2019&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2018&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2017&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2016&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2015&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2014&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2013&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2012&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2011&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2010&lt;br /&gt;
! align=&amp;quot;left&amp;quot; | 2009&lt;br /&gt;
|-&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2025|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2025|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2025|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2025|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2025|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2025|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2025|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2025|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2025|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2025|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2025|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2025|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2024|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2024|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2024|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2024|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2024|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2024|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2024|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2024|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2024|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2024|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2024|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2024|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2023|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2023|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2023|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2023|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2023|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2023|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2023|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2023|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2023|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2023|October]] &lt;br /&gt;
* [[FlightGear Newsletter November 2023|November]] &lt;br /&gt;
* [[FlightGear Newsletter December 2023|December]] &lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2022|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2022|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2022|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2022|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2022|May]] &lt;br /&gt;
* June&lt;br /&gt;
* [[FlightGear Newsletter July 2022|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2022|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2022|September]]&lt;br /&gt;
* [[FlightGear Newsletter November 2022|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2022|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2021|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2021|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2021|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2021|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2021|May]] &lt;br /&gt;
* [[FlightGear Newsletter June 2021|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2021|July]] &lt;br /&gt;
* [[FlightGear Newsletter August 2021|August]] &lt;br /&gt;
* [[FlightGear Newsletter September 2021|September]] &lt;br /&gt;
* [[FlightGear Newsletter October 2021|October]] &lt;br /&gt;
* [[FlightGear Newsletter November 2021|November]] &lt;br /&gt;
* [[FlightGear Newsletter December 2021|December]] &lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2020|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2020|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2020|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2020|April]] &lt;br /&gt;
* [[FlightGear Newsletter May 2020|May]] &lt;br /&gt;
* [[FlightGear Newsletter June 2020|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2020|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2020|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2020|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2020|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2020|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2020|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2019|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2019|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2019|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2019|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2019|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2019|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2019|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2019|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2019|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2019|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2019|November]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2018|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2018|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2018|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2018|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2018|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2018|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2018|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2018|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2018|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2018|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2018|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2018|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2017|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2017|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2017|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2017|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2017|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2017|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2017|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2017|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2017|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2017|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2017|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2017|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2016|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2016|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2016|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2016|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2016|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2016|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2016|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2016|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2016|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2016|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2016|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2016|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2015|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2015|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2015|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2015|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2015|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2015|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2015|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2015|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2015|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2015|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2015|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2015|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2014|January]] &lt;br /&gt;
* [[FlightGear Newsletter February 2014|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2014|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2014|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2014|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2014|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2014|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2014|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2014|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2014|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2014|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2014|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2013|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2013|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2013|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2013|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2013|May]] &lt;br /&gt;
* [[FlightGear Newsletter June 2013|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2013|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2013|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2013|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2013|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2013|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2013|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2012|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2012|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2012|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2012|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2012|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2012|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2012|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2012|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2012|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2012|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2012|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2012|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2011|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2011|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2011|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2011|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2011|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2011|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2011|July]] &lt;br /&gt;
* [[FlightGear Newsletter August 2011|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2011|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2011|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2011|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2011|December]] &lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter January 2010|January]]&lt;br /&gt;
* [[FlightGear Newsletter February 2010|February]]&lt;br /&gt;
* [[FlightGear Newsletter March 2010|March]]&lt;br /&gt;
* [[FlightGear Newsletter April 2010|April]]&lt;br /&gt;
* [[FlightGear Newsletter May 2010|May]]&lt;br /&gt;
* [[FlightGear Newsletter June 2010|June]]&lt;br /&gt;
* [[FlightGear Newsletter July 2010|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2010|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2010|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2010|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2010|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2010|December]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot; |&lt;br /&gt;
* [[FlightGear Newsletter July 2009|July]]&lt;br /&gt;
* [[FlightGear Newsletter August 2009|August]]&lt;br /&gt;
* [[FlightGear Newsletter September 2009|September]]&lt;br /&gt;
* [[FlightGear Newsletter October 2009|October]]&lt;br /&gt;
* [[FlightGear Newsletter November 2009|November]]&lt;br /&gt;
* [[FlightGear Newsletter December 2009|December]]&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Some newsletter editions have been translated in other languages:&lt;br /&gt;
* [[File:De.gif|16px|link=:De:FlightGear Newsletter]] [[:De:FlightGear Newsletter|Deutsch]]&lt;br /&gt;
* [[File:Es.gif|16px|link=:Es:FlightGear Newsletter]] [[:Es:FlightGear Newsletter|Español]]&lt;br /&gt;
* [[File:Fr.gif|16px|link=:Fr:FlightGear Newsletter]] [[:Fr:FlightGear Newsletter|Français]]&lt;br /&gt;
&lt;br /&gt;
=== You are invited to contribute! ===&lt;br /&gt;
We would like to emphasize that the monthly newsletter can not live without the contributions of FlightGear users and developers. The FlightGear Newsletter is a community-driven newsletter, which means that it is created and edited by people like ''you''. You don't need to be a long-time FlightGear user (or even a developer) to contribute to the newsletter. &lt;br /&gt;
&lt;br /&gt;
In fact, helping write the monthly FlightGear newsletter is an excellent way for getting started contributing to the FlightGear community very quickly and very easily. &lt;br /&gt;
&lt;br /&gt;
Even if you don't have to add anything yourself, just reviewing and improving additions by others is also highly appreciated, as are efforts to help translate newsletters or add screen shots (e.g. taken from the forum or mailing lists) to the newsletter. Screen shots can be uploaded at [[Special:Upload]].&lt;br /&gt;
&lt;br /&gt;
Everyone with a wiki account (free to [[Special:UserLogin|register]]) can edit the newsletter and every contribution is welcome. So if you know about any FlightGear related projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.&lt;br /&gt;
&lt;br /&gt;
A simple template providing a basic structure for each upcoming newsletter can be taken from [[Talk:Next newsletter]]. This template can be copied/pasted into each new newsletter to help users getting started providing contents.&lt;br /&gt;
&lt;br /&gt;
The draft for the upcoming newsletter can always be found at [[Next newsletter]].&lt;br /&gt;
&lt;br /&gt;
If English is not your native language, please don't be concerned about contributing to the newsletter, you can always easily ask fellow FlightGear users to review or proof-read your changes. Also, one of the easiest ways to get started is simply copying/pasting text from forum or mailing list discussions, such as announcements (e.g., new aircraft, new scenery, etc.).&lt;br /&gt;
&lt;br /&gt;
Another neat option to get started is adding links to FlightGear-related YouTube videos. For this, we have a dedicated section in each newsletter: &amp;quot;[[FlightGear Newsletter {{#time: F Y | last month }}#FlightGear on YouTube|FlightGear on YouTube]].&amp;quot;&lt;br /&gt;
&lt;br /&gt;
If you want to embed a video, please see {{mediawiki|Extension:EmbedVideo#Usage}}&lt;br /&gt;
&lt;br /&gt;
If you are looking for other ways to get involved, please see [[Volunteer]].&lt;br /&gt;
&lt;br /&gt;
[[Category:FlightGear Newsletter| ]]&lt;br /&gt;
[[Category:Community|Newsletter]]&lt;br /&gt;
[[Category:Lists|Newsletter]]&lt;br /&gt;
&lt;br /&gt;
[[de:FlightGear Newsletter]]&lt;br /&gt;
[[es:FlightGear Newsletter]]&lt;br /&gt;
[[fr:FlightGear Newsletter]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143203</id>
		<title>FlightGear Newsletter November 2025</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=FlightGear_Newsletter_November_2025&amp;diff=143203"/>
		<updated>2025-12-09T11:00:54Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Related Software tools and projects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--&lt;br /&gt;
&lt;br /&gt;
NOTES TO EDITORS&lt;br /&gt;
&lt;br /&gt;
* Headings&lt;br /&gt;
  * DO NOT DELETE HEADINGS prior to final cleanup&lt;br /&gt;
  * Current headings and their order is merely a suggestion based on what have been used earlier&lt;br /&gt;
  * Changes made to headings or structure should also be copied the Newsletter template http://wiki.flightgear.org/User:Skybike/Template:This_months_newsletter/Newsletter_example&lt;br /&gt;
&lt;br /&gt;
* Final cleanup before write protecting&lt;br /&gt;
  * Remove unused headings&lt;br /&gt;
  * Remove {{Appendix}} if not used.&lt;br /&gt;
  * Update &amp;quot;Category: Changes after&amp;quot; to the FG version current at the 1st of this month&lt;br /&gt;
  * Finally remove this comment&lt;br /&gt;
  * Update [[Next Newsletter]] and [[FlightGear Newsletter]]&lt;br /&gt;
&lt;br /&gt;
* Discussion, issues and suggestions&lt;br /&gt;
  * Regarding this newsletter issue, please use the discussion page&lt;br /&gt;
  * Regarding the newsletter in general, primarily use the FlightGear Newsletter discussion page (Talk:FlightGear Newsletter)&lt;br /&gt;
  * Regarding this Newsletter template, please use FIXME&lt;br /&gt;
&lt;br /&gt;
+++   {{Newsletter-header|{{#time: F | 2025-05}}}}   +++&lt;br /&gt;
--&amp;gt;{{User:Skybike/Template:Newsletter-header-translate|2025-05}}&lt;br /&gt;
{{TOC_right|limit=2}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
''We would like to emphasize that the monthly newsletter cannot live without the contributions of FlightGear users and developers. Everyone with a wiki account (free to register) is welcome to contribute to the newsletter.  If you know about any FlightGear related news or projects such as for example updated scenery or aircraft, please do feel invited to add such news to the newsletter.''&lt;br /&gt;
&lt;br /&gt;
''The new Visual Editor makes editing the wiki as simple as using a Word-processor, and even easier than using the forum as you don't even need to know the syntax for a url. Just hit the 'edit' link and start.''&lt;br /&gt;
&lt;br /&gt;
== Development news ==&lt;br /&gt;
&amp;lt;!-- News about FlightGear itself.  The FlightGear mailing list and/or core developers are a good source. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- {{Disclaimer|id=final-fixed-function-release}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Related Software tools and projects ==&lt;br /&gt;
=== Julia photoscenery generator GUI ===&lt;br /&gt;
==== Photoscenery-GUI ====&lt;br /&gt;
&lt;br /&gt;
[[File:Photoscenery-GUI-20251113-202721.jpg|thumb|Photoscenery-GUI Beta]]&lt;br /&gt;
&lt;br /&gt;
Adriano Bassignana has begun writing the manual for the Photoscenery GUI, which has been published at the following link:&lt;br /&gt;
https://wiki.flightgear.org/Julia_photoscenery_generator_GUI&lt;br /&gt;
The manual currently contains the essential information for correct installation.&lt;br /&gt;
&lt;br /&gt;
New features in the latest version include:&lt;br /&gt;
&lt;br /&gt;
The tool, which already managed the rational downloading of tiles in various resolutions, now includes additional features accessible via its web interface to increase operational utility. The program's initialization has been modified to enable faster startup.&lt;br /&gt;
&lt;br /&gt;
A new functionality has been introduced to read airports and radio navigation aids from files updated weekly—the same data sources used by FlightGear itself. This resolves the previous issue of airport misalignment between the tool and the simulator.&lt;br /&gt;
&lt;br /&gt;
Route management has been improved. When loading a route for display on the map, the system now simultaneously loads data on local scenery to prevent unnecessary downloads. For users with a fast internet connection, real-time image downloading is possible. A fast, low-resolution preload feature also provides a valid overview of the surrounding area's coverage.&lt;br /&gt;
&lt;br /&gt;
The interface allows users to view radio navigation aids and airports, along with their key information.&lt;br /&gt;
&lt;br /&gt;
The [create route] mode enables users to generate a flight path by clicking on map points, airports, and navigation aids. Created routes can be saved and later imported into FlightGear for use.&lt;br /&gt;
&lt;br /&gt;
Many other features will be documented on the Wiki in the future.&lt;br /&gt;
&lt;br /&gt;
https://github.com/abassign/Photoscenery-GUI.git&lt;br /&gt;
&lt;br /&gt;
For more information, see the discussion on the FG Forum: https://forum.flightgear.org/viewtopic.php?f=5&amp;amp;t=39066&amp;amp;start=390&lt;br /&gt;
&lt;br /&gt;
=== Framework for Canvas Add-on ===&lt;br /&gt;
&lt;br /&gt;
Roman Ludwicki (PlayeRom on the forum) created a [https://github.com/PlayeRom/flightgear-addon-framework Framework] for creating add-ons based on Canvas dialog boxes. After creating numerous add-ons, he realized he needed to separate the common code base. This led to the creation of a separate Framework project that could be attached to any add-on. The framework includes the following features:&lt;br /&gt;
&lt;br /&gt;
# Automatic recognition and loading of add-on Nasal files into the appropriate namespaces (with an exclusion list if necessary).&lt;br /&gt;
# Ability to add a menu for restarting add-on Nasal files without having to change repository files.&lt;br /&gt;
# Ability to define keys for the multi-key command to restart add-on Nasal files without having to change repository files.&lt;br /&gt;
# A mechanism for checking whether there is a new version of your add-on to inform users about it.&lt;br /&gt;
# Base classes for Canvas windows that are created and destroyed on demand (Transient dialog), as well as created once during simulator startup (Persistent dialog).&lt;br /&gt;
# Ability to create Nasal unit tests and run them using multi-key command.&lt;br /&gt;
&lt;br /&gt;
An example project using the Framework is the [https://github.com/PlayeRom/flightgear-addon-canvas-skeleton Canvas Skeleton] project, which can be used as a basis for creating a new add-on.&lt;br /&gt;
&lt;br /&gt;
You can also see how his other add-ons are written, all based on the Framework (main branches):&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-aerotow-everywhere Aerotow Everywhere]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-logbook Logbook]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-which-runway Which Runway]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-nasal-namespace-browser Nasal Namespace Browser]&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-menu-aggregator Add-on Menu Aggregator]&lt;br /&gt;
&lt;br /&gt;
== In the hangar ==&lt;br /&gt;
&amp;lt;!-- News about new and upgraded aircraft and related stuff. The official forum and other ones usually are a good source for this. --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === New aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Updated aircraft === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Liveries === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Instruments === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === Aircraft reviews === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Scenery corner ==&lt;br /&gt;
&amp;lt;!-- Scenery development news --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Scenery Models === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Airports === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Land cover === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Osm2city === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === New OSM2City areas === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Interview with a contributor == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Suggested flights == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == AI == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI traffic === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- === AI scenarios === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Community news ==&lt;br /&gt;
&amp;lt;!-- === FlightGear on YouTube === --&amp;gt;&lt;br /&gt;
&amp;lt;!-- embed video as {{#ev:youtube|VCc6PwRI1LA}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Forum news === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Wiki updates === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Article of the month === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multiplayer events ==&lt;br /&gt;
&amp;lt;!-- === Upcoming events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- === Finished events === --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == FlightGear events == --&amp;gt;&lt;br /&gt;
&amp;lt;!-- For example presence at FSWeekend --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Hardware reviews == --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Screenshot of the Month ==&lt;br /&gt;
&amp;lt;!--FlightGear's Screenshot of the Month {{#time: F | 2025-05}} 2025 is FIXME by {{usr|FIXME}}&lt;br /&gt;
ADD IMAGE --&amp;gt;&lt;br /&gt;
If you want to participate in the screenshot contest&amp;lt;!-- of {{#time: F | 2025-05 + 1month}}--&amp;gt;, you can submit your candidate to the {{forum link|title=this|f=88|t=}}. Be sure to see the first post for participation rules. For purposes of convenience and organization, at the end of the month or after 20 entries have been submitted, a new forum topic will be started containing all shots in an easy-to-view layout. The voting will then take place there.&amp;lt;!--Once the voting has finished, the best screenshot will be presented in the Newsletter edition of {{#time: F | 2025-05 + 1month}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Changes after 2024.1]]&amp;lt;!--Has a new version been released this month? Use previous version!--&amp;gt;&lt;br /&gt;
[[Category:FlightGear Newsletter|2025 05]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--[[de:FlightGear Newsletter {{#time: F Y | 2025-05 | de }}]]--&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143170</id>
		<title>Nasal library/props</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143170"/>
		<updated>2025-12-04T18:40:20Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* copy() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page contains documentation for the '''&amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace''' in [[Nasal]]. This namespace provides APIs for working with property trees (including the main [[Property Tree]]) via {{API Link|simgear|class|SGPropertyNode}}. The &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace is sourced from {{fgdata source|Nasal/props.nas}} and {{flightgear source|src/Scripting/nasal-props.cxx}}.&lt;br /&gt;
&lt;br /&gt;
== Class ==&lt;br /&gt;
=== Node ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|mode = class&lt;br /&gt;
|text = The main class, used widely for manipulating property trees.&lt;br /&gt;
}}&lt;br /&gt;
==== new() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.new([values]);&lt;br /&gt;
|text = Constructor function. Returns a new &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = values&lt;br /&gt;
|param1text = An optional hash that will be the initial property structure.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    a: 1,&lt;br /&gt;
    b: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;],&lt;br /&gt;
    c: {&lt;br /&gt;
        d: 1 * 4&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChild(name[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|a15d5829379864c7138edff621d6864c5f426d0b|t=commit}}&lt;br /&gt;
|text = Add a new, blank child to the node. Returns the newly-created node.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the node as a string.&lt;br /&gt;
|param2 = min_idx&lt;br /&gt;
|param2text = This specifies the minimum index to add the new one to. This takes precedence over '''append'''. Defaults to 0.&lt;br /&gt;
|param3 = append&lt;br /&gt;
|param3text = If there is already one or more children with the same name and this argument is true (1), the new child will be added after the child with the highest index. If false (0), the new child will be added at the lowest available index with the limit of '''min_idx'''. Defaults to true (1).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, false);&lt;br /&gt;
props.dump(node); # a[1] and a[0]&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, true);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChildren(name, count[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|7f1117a5374beabb045fb76fbb6dd8bfb0128100|t=commit}}&lt;br /&gt;
|text = Adds multiple children with the same name to this node. Returns &amp;lt;code&amp;gt;'''nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the nodes as a string.&lt;br /&gt;
|param2 = count&lt;br /&gt;
|param2text = Number of new children to add.&lt;br /&gt;
|param3 = min_idx&lt;br /&gt;
|param3text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = append&lt;br /&gt;
|param4text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[0] and a[1]&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[2]&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 0, 0);&lt;br /&gt;
props.dump(node); # a[2], a[0], and a[1]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== adjustValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.adjustValue(delta);&lt;br /&gt;
|text = Adds delta (numeric) to current value respecting the node type. &lt;br /&gt;
|param1 = delta&lt;br /&gt;
|param1text = Numeric value (can be negative) to add to current node value.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== alias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.alias(node, chainListener=0);&lt;br /&gt;
|text = Aliases this node to another one. Returns 1 on success and 0 on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = The node to alias to. Can be one of:&lt;br /&gt;
* A path to a property in the [[Property Tree]] as a string.&lt;br /&gt;
* A &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost.&lt;br /&gt;
* A &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/position/altitude-ft&amp;quot;);&lt;br /&gt;
props.dump(node); # equals the current altitude&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.34);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
props.dump(node2); # equals 2.34&lt;br /&gt;
|param2=chainListener|param2text=Specify if listeners should work on aliased properties or not. Current default is false forbackwards compatability.}}&lt;br /&gt;
&lt;br /&gt;
==== clearValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.clearValue();&lt;br /&gt;
|text = Clears the value and type of the node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.34);&lt;br /&gt;
props.dump(node); # prints &amp;quot;{DOUBLE} = 2.35&amp;quot;&lt;br /&gt;
node.clearValue();&lt;br /&gt;
props.dump(node); # prints &amp;quot;{NONE} = nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== decrement() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.decrement(n = 1);&lt;br /&gt;
|text = Decrements integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to subtract, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== equals() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.equals(node);&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{fgdata commit|d80722065f3c11e0511fa888b1f5f310dd0183e3|t=commit}}&lt;br /&gt;
|text = Checks whether the node refers to the same one as another. Returns 1 (true) if it is, and 0 (false) if otherwise.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to check against. May be either a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var n = props.Node.new();&lt;br /&gt;
var a = n;&lt;br /&gt;
print(a.equals(n)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAliasTarget() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAliasTarget();&lt;br /&gt;
|text = Returns the alias target of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 2.35);&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/test&amp;quot;);&lt;br /&gt;
var tgt = node.getAliasTarget();&lt;br /&gt;
print(tgt.getPath()); # prints &amp;quot;/test&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAttribute([rel_path, ]name);&lt;br /&gt;
props.Node.getAttribute();&lt;br /&gt;
|text = Returns an attribute. If no arguments are given, the function will return an integer specifying the attributes set for the node (see {{simgear source|simgear/props/props.hxx|l=767}} for a list). A list of attributes are below&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Return value&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} last {{!!}} The highest used attribute code (should be 128). See for {{simgear source|simgear/props/props.hxx|l=767}} the codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} children {{!!}} Number of child nodes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} listeners {{!!}} Number of listeners connected to this node.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} references {{!!}} Number of times the node has previously been referenced.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} tied {{!!}} Whether the node is tied.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} alias {{!!}} Whether the node is aliased.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = Attribute as a string. See the above table for a full list.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;children&amp;quot;)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2text = Example using relative path&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;a&amp;quot;, &amp;quot;readable&amp;quot;)); # prints &amp;quot;1&amp;quot; (node can be read from)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node);&lt;br /&gt;
print(node2.getAttribute(&amp;quot;alias&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example4 = print(props.globals.getNode(&amp;quot;/sim/signals/fdm-initialized&amp;quot;).getAttribute(&amp;quot;listeners&amp;quot;)); # prints the number of listeners&lt;br /&gt;
|example5 = print(props.globals.getNode(&amp;quot;/sim/time/elapsed-sec&amp;quot;).getAttribute(&amp;quot;tied&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning it is tied&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute(&amp;quot;writable&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning the node can be written to&lt;br /&gt;
|example7text = Example using no arguments&lt;br /&gt;
|example7 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute()); # prints &amp;quot;3&amp;quot; (true), meaning the node can be read from (1) and written to (2)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getBoolValue();&lt;br /&gt;
|text = Returns the value of a node converted to a boolean. If the node is a number type, 0  will return false, while 1 will return true. If the node is a string or unspecified type and the value is &amp;lt;code&amp;gt;&amp;quot;false&amp;quot;&amp;lt;/code&amp;gt;, false will be returned. Otherwise, true will be returned. Remember that boolean values are represented in Nasal as 1 (true) and 0 (false).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(-1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(-2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setValue(&amp;quot;false&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChild(rel_path[, idx[, create]]);&lt;br /&gt;
|text = Returns a child of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the child node as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Optional index for the child node as an integer.&lt;br /&gt;
|param3 = create&lt;br /&gt;
|param3text = If set to true (1), a new child will be created if it does not exist. If set to false (0), the function will not create a new child and the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no child exists. Defaults to false.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.getNode(&amp;quot;a[1]&amp;quot;).setDoubleValue(2.35);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
print(c.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1, true);&lt;br /&gt;
props.dump(node); # new child a[1] will have appeared&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChildren([name]);&lt;br /&gt;
|text = Returns a vector of child nodes, optionally those with a certain name, as &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instances.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of the child nodes as a string. If not given, all children will be returned.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren()); # all child nodes in the vector&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren(&amp;quot;b&amp;quot;)); # only children with the name &amp;quot;b&amp;quot; in the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getIndex() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getIndex();&lt;br /&gt;
|text = Returns the index of a node as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
print(node.getChild(&amp;quot;a&amp;quot;, 1).getIndex()); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;b&amp;quot;);&lt;br /&gt;
print(node.getChild(&amp;quot;b&amp;quot;).getIndex()); # prints &amp;quot;0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getName() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getName();&lt;br /&gt;
|text = Returns the name of the node as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var c = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
debug.dump(c.getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChild(&amp;quot;a&amp;quot;, 2).getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getNode(rel_path[, create]);&lt;br /&gt;
|text = Returns a subnode as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the subnode as a string.&lt;br /&gt;
|param2 = create&lt;br /&gt;
|param2text = If true (1), the node will be created if it does not exist. If false (0) and the node does not exist, the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;. Default to false (0).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getValue()); # prints &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {}&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.getNode(&amp;quot;c/d&amp;quot;, true).setDoubleValue(2.35);&lt;br /&gt;
props.dump(node); # c/d now exists&lt;br /&gt;
|example3 = var ac = props.globals.getNode(&amp;quot;sim/aircraft&amp;quot;);&lt;br /&gt;
print(&amp;quot;Current aircraft is: &amp;quot;, ac.getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getParent() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getParent();&lt;br /&gt;
|text = Returns the parent of a node, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there is no parent.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node.getNode(&amp;quot;c/d&amp;quot;).getParent()); # dumps &amp;quot;c&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
debug.dump(node.getParent()); # prints nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getPath() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getPath();&lt;br /&gt;
|text = Returns the path of the node as a string.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getPath()); # prints &amp;quot;/c/d&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getType() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getType();&lt;br /&gt;
|text = Returns node's type as a string. It should be one of &amp;quot;NONE&amp;quot;, &amp;quot;ALIAS&amp;quot;, &amp;quot;BOOL&amp;quot;, &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot; (long integer), &amp;quot;FLOAT&amp;quot;, &amp;quot;DOUBLE&amp;quot;, &amp;quot;STRING&amp;quot;, &amp;quot;VEC3D&amp;quot;, &amp;quot;VEC4D&amp;quot;, &amp;quot;UNSPECIFIED&amp;quot;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
print(node.getType()); # prints &amp;quot;NONE&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
print(node.getType()); # prints &amp;quot;INT&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValue([rel_path]);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getBoolValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Returns the value of the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path to a subnode as a string, which may contain '/' characters. If the subnode does not exist, we return nil.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.35);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue(&amp;quot;a&amp;quot;)); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0, 0.5, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0, 0.5, 1]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValues();&lt;br /&gt;
|text = Returns the node tree as a hash, with all the various subnodes, etc. If the node has no children, the result is equivalent to {{func link|getValue()|page=this}}. Subnodes that are indexed will be combined into one key and their values placed in a vector (see example 2).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;string&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;number&amp;quot;: 1.2,&lt;br /&gt;
    &amp;quot;subnode&amp;quot;: {&lt;br /&gt;
        &amp;quot;idx-node&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.addChild(&amp;quot;bool&amp;quot;).setBoolValue(1);&lt;br /&gt;
props.dump(node); # dump to node tree&lt;br /&gt;
debug.dump(node.getValues()); # dump the node converted to hash&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: [1, 2, 3]&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # a[0] = 1, a[1] = 2, a[2] = 3&lt;br /&gt;
debug.dump(node.getValues()); # a: [1, 2, 3]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== increment() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.increment(n = 1);&lt;br /&gt;
|text = Increments integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to add, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== initNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.initNode([path[, value[, type[, force]]]]);&lt;br /&gt;
|text = Initializes a node if it doesn't exist and returns that node as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to a subnode as a string. If not given, the node itself will be initialized.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Optional default value to initialize the node with.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Optional string that will set the type of the node. Must be one of &amp;lt;code&amp;gt;&amp;quot;DOUBLE&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;INT&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;BOOL&amp;quot;&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;&amp;quot;STRING&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = force&lt;br /&gt;
|param4text = If set to true (1), the node's type will be forced to change.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;);&lt;br /&gt;
props.dump(a); # a = 1&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setBoolValue(0);&lt;br /&gt;
props.dump(node.getChild(&amp;quot;a&amp;quot;)); # a = 0 (type: bool)&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;, true);&lt;br /&gt;
props.dump(a); # a = 0 (type: int)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isInt() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isInt();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot; or &amp;quot;LONG&amp;quot; (long integer) otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isNumeric() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isNumeric();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot;, &amp;quot;FLOAT&amp;quot; or &amp;quot;DOUBLE&amp;quot; otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== remove() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.remove();&lt;br /&gt;
|text = Removes the node and returns the removed node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).remove();&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; does not exist anymore&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeAllChildren() ====&lt;br /&gt;
{{Caution|Be careful when  using this API in conjunction with the Canvas system and element specific properties, for details please see [[Howto:Canvas Path Benchmarking]]}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeAllChildren();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|4766ed21a68230c0c15265e5604a74f4d99e0f5d|t=commit}}&lt;br /&gt;
|text = Removes all child nodes and returns the node.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeAllChildren();&lt;br /&gt;
props.dump(node); # all children have been removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChild(rel_path, idx);&lt;br /&gt;
|text = Removes a given child node child nodes and returns the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to a subnode as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Index of the subnode to remove as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; has been removed&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # just a[1] remains&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChildren([name]);&lt;br /&gt;
|text = Removes all children with a specified name. If no arguments are given, all children will be removed (see also {{func link|removeAllChildren()|page=this}}).&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of children to remove as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node); # just children named &amp;quot;b&amp;quot; remain&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren();&lt;br /&gt;
props.dump(node); # all children removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setAttribute([rel_path, ]attr, value);&lt;br /&gt;
props.Node.setAttribute(attrs);&lt;br /&gt;
|text = Sets an attribute or multiple attributes. A list of attributes and their codes are below. For a brand new node, the default attributes are ''readable'' and ''writable''. Returns an integer specifying the old attributes.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Description&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = attr&lt;br /&gt;
|param2text = Name of attribute to set as a string. See above.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Boolean value to set the property to.&lt;br /&gt;
|param4 = attrs&lt;br /&gt;
|param4text = When the function is used in its second form, this argument is used. This argument should be an integer specifying which arguments are set to true. See {{simgear source|simgear/props/props.hxx|l=767}} for the full list of codes. Simply add codes of the desired attributes together.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;readable&amp;quot;, 0);&lt;br /&gt;
var val = node.getValue();&lt;br /&gt;
debug.dump(val); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setAttribute(&amp;quot;a&amp;quot;, &amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).setIntValue(12); # will be traced&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(35); # read + write + trace-write&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setBoolValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a boolean value. If the node has no type, it will be set to a bool type. If the node is already a number type, it will be set to either 1 or 0. If it is a string, it will be set to either &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a boolean. If it is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, it will be false. If it is a string, it will be false. If it is a number, 0 will be false, while other numbers will be true. All other cases will be interpreted as 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(nil);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
node.setBoolValue(1.25);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;String&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;String&amp;quot; (type: string)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = &amp;quot;true&amp;quot;&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(12.32);&lt;br /&gt;
props.dump(node); # node = 12.32 (type: double)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = 1&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setBoolValue(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # /a = 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setDoubleValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setDoubleValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a double value. If the node has no type, it will be set to a double type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. If the node is an integer type, it will be truncated to the closest integer to zero. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a double number. It must be a valid number or a string that can be converted to a number.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(1);&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1 (type: int)&lt;br /&gt;
node.setDoubleValue(&amp;quot;-1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = -1 (type: int)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setDoubleValue(&amp;quot;a&amp;quot;, 12.2);&lt;br /&gt;
props.dump(node); # /a = 12.2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setIntValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setIntValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to an integer value. If the node has no type, it will be set to a integer type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a integer. It must be a valid number or a string that can be converted to a number. If the number is a double, it will be truncated to the closest integer to zero.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(&amp;quot;6&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 6&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(-12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setIntValue(12.5);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = &amp;quot;12&amp;quot; (type: string)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setIntValue(&amp;quot;a&amp;quot;, 12);&lt;br /&gt;
props.dump(node); # /a = 12&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValue([rel_path, ]value);&lt;br /&gt;
|text = {{hatnote|See also {{func link|setBoolValue()|page=this}}, {{func link|setDoubleValue()|page=this}}, and {{func link|setIntValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
{{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a given value. See table below for conversions and effects. Note that vec3d and vec4d types are not fully integrated into SGPropertyNode. Returns 1 (true) if the operation was successful.&lt;br /&gt;
&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align:right&amp;quot; {{!}} '''value''' type → &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Number&lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} String &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Vector&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} style=&amp;quot;text-align:left&amp;quot; {{!}} Current node&amp;lt;br&amp;gt;type ↓&lt;br /&gt;
{{!}} style=&amp;quot;text-align:right&amp;quot; {{!}} Result ↘&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} None/unspecified&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;double&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;vec*d&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Bool&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' != 0, node set to true.&lt;br /&gt;
* If '''value''' == 0, node set to false.&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' == &amp;quot;true&amp;quot;, node set to true.&lt;br /&gt;
* If '''value''' can be converted to an integer,&amp;lt;br&amp;gt;and != 0, node set to true.&lt;br /&gt;
{{!}} Node set to true.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Integer&lt;br /&gt;
{{!}} Node set to truncated '''value'''&lt;br /&gt;
{{!}} Node set to '''value ''' converted and truncated to an integer.&lt;br /&gt;
{{!}} Node set to 1.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Double&lt;br /&gt;
{{!}} Node set to '''value'''.&lt;br /&gt;
{{!}} Node set to '''value''' converted to number.&lt;br /&gt;
{{!}} Throws an error (vector is not a number).&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} String&lt;br /&gt;
{{!}} Node set to '''value''' converted to string. {{!!}} Node set to '''value'''. {{!!}} Node not set.&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to. Must be a string, a valid number, or a vector consisting of 3 or 4 numbers. See table above for conversions and effects.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setValue(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # \a = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValues(val);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getValues()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Sets the nodes property tree from a Nasal hash. Scalars will become nodes in the tree and hashes will become named subnodes. Vectors will be converted into indexed nodes, with the values in the vector becoming their values (see examples below).&lt;br /&gt;
|param1 = val&lt;br /&gt;
|param1text = A hash that will become the property tree.&lt;br /&gt;
|example1 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 100 # &amp;quot;a&amp;quot; will become the subnode's name, and 100 its value&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
|example2 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== unalias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.unalias();&lt;br /&gt;
|text = Un-aliases the node and returns it to a blank state. Returns 1 on success and 0 on failure (e.g., when used on a tied property).&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.35);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
&lt;br /&gt;
props.dump(node2); # equals 2.35&lt;br /&gt;
node2.unalias();&lt;br /&gt;
props.dump(node2); # no value or type&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== toggleBoolValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = toggleBoolValue();&lt;br /&gt;
|text = Toggle a boolean property. You have to make sure the property is of type bool!&lt;br /&gt;
|example1 = var b = props.Node.new().initNode(&amp;quot;/_test/bool&amp;quot;, 1, &amp;quot;BOOL&amp;quot;);&lt;br /&gt;
print(&amp;quot;bool &amp;quot;, b.getValue());&lt;br /&gt;
b.toggleBoolValue();&lt;br /&gt;
print(&amp;quot;after toggleBoolValue &amp;quot;, b.getValue());&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
=== compileCondition() ===&lt;br /&gt;
{{see also|Conditions}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.compileCondition(node);&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|43f8ce08706629e69984b1cc34e5e979d03ef09a|t=commit}}&lt;br /&gt;
|text = Compiles a [[conditions|condition]] property branch and returns a &amp;lt;code&amp;gt;Condition&amp;lt;/code&amp;gt; ghost object or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error. This ghost will contain a &amp;lt;code&amp;gt;test()&amp;lt;/code&amp;gt; function that will return the result of the condition as either 1 (true) or 0 (false).&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== condition() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.condition(node);&lt;br /&gt;
|text = Evaluates a [[conditions|condition]] property branch and returns the result as a boolean.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== copy() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.copy(src, dest[, attr]);&lt;br /&gt;
|text = Copies the property tree of the source into the destination node. Note that aliased properties will not be copied.&lt;br /&gt;
|param1 = src&lt;br /&gt;
|param1text = Source &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy from.&lt;br /&gt;
|param2 = dest&lt;br /&gt;
|param2text = Destination &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy to.&lt;br /&gt;
|param3 = attr&lt;br /&gt;
|param3text = If set to true (1), attributes will also be copied. Defaults to false (0).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1.5,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var src = props.Node.new(tree);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest);&lt;br /&gt;
props.dump(dest);&lt;br /&gt;
|example2 = var src = props.Node.new();&lt;br /&gt;
var a = src.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
a.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
a.setIntValue(12);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest, true);&lt;br /&gt;
print(dest.getNode(&amp;quot;a&amp;quot;).getAttribute(&amp;quot;trace-write&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== dump() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.dump(node);&lt;br /&gt;
|text = Recursively dump the state of a node into the console, showing value and type of each node. Note that as of 10/2016, the value of vec*d type nodes cannot be dumped.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to dump.&lt;br /&gt;
|example1 = var node = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # dump into console&lt;br /&gt;
|example2 = # Dump the entire Property Tree&lt;br /&gt;
# Warning! This is an intensive operation!&lt;br /&gt;
props.dump(props.globals);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== getNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.getNode();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|807062d0b6f858885547f27325e829c2bf42a12b|t=commit}}&lt;br /&gt;
|text = Shortcut for &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;props.globals.getNode()&amp;lt;/syntaxhighlight&amp;gt;. See {{func link|getNode()||Node|page=this}} for full documentation.&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft is: &amp;quot;, props.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== nodeList() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.nodeList(arg[, arg[, ...]]);&lt;br /&gt;
|text = Converts its arguments into a vector of node objects if possible and returns that vector. &lt;br /&gt;
|param1 = arg&lt;br /&gt;
|param1text = Object to operate on. Must be a node object, string, vector, hash, function, or &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost. Vectors and hashes must contain any of the other acceptable types, functions must return any of the other types, strings will be assumed to be paths to global properties, and ghosts will be converted into node objects. There may be any number of arguments.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var f = func(){&lt;br /&gt;
    var n = props.Node.new();&lt;br /&gt;
    return n._g;&lt;br /&gt;
}&lt;br /&gt;
var list = props.nodeList(node,&lt;br /&gt;
    &amp;quot;/sim/aircraft&amp;quot;,&lt;br /&gt;
    [&amp;quot;/sim/fg-root&amp;quot;],&lt;br /&gt;
    { &amp;quot;path&amp;quot;: &amp;quot;/sim/fg-home&amp;quot; },&lt;br /&gt;
    f&lt;br /&gt;
);&lt;br /&gt;
debug.dump(list); # dump list&lt;br /&gt;
|example2 = var root = &amp;quot;/sim/version/&amp;quot;;&lt;br /&gt;
var info = [&lt;br /&gt;
    root ~ &amp;quot;build-id&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;build-number&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;flightgear&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;hla-support&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;openscenegraph&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;revision&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;simgear&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
info = props.nodeList(info); # turn into list of nodes&lt;br /&gt;
foreach(var n; info){&lt;br /&gt;
    print(n.getValue()); # dump info&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== runBinding() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.runBinding(node[, module]);&lt;br /&gt;
|text = Runs a [[Bindings|binding]] element in a node object. Returns 1 (true) on success and 0 (false) on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = A {{tag|binding}} element as a node object.&lt;br /&gt;
|param2 = module&lt;br /&gt;
|param2text = Optional string specifying a module to run Nasal scripts in if the command is &amp;lt;code&amp;gt;nasal&amp;lt;/code&amp;gt;. This argument will not override any {{tag|module}} element in the '''node'''&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;dialog-show&amp;quot;,&lt;br /&gt;
    &amp;quot;dialog-name&amp;quot;: &amp;quot;map&amp;quot; # open map&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)' # prints value of math.pi&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;math&amp;quot;);&lt;br /&gt;
|example3 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)', # prints value of math.pi&lt;br /&gt;
    &amp;quot;module&amp;quot;: &amp;quot;math&amp;quot; # this is used&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;debug&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setAll() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.setAll(base, child, value);&lt;br /&gt;
|text = Sets indexed subnodes in the Property Tree with the same name to the same value.&lt;br /&gt;
|param1 = base&lt;br /&gt;
|param1text = Base path to the nodes.&lt;br /&gt;
|param2 = child&lt;br /&gt;
|param2text = Path to child nodes.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Value to set the subnodes to.&lt;br /&gt;
|example1 = # apply 50% throttle to all engines&lt;br /&gt;
props.setAll(&amp;quot;/controls/engines/engine&amp;quot;, &amp;quot;throttle&amp;quot;, 0.5);&lt;br /&gt;
|example2 = var nodes = props.globals.addChildren(&amp;quot;/test&amp;quot;, 3);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
props.setAll(&amp;quot;/test&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;); # set all children (test[*]/a) to &amp;quot;Hi&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrap() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrap(node);&lt;br /&gt;
|text = Turns &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghosts, either in a vector or single, into &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; objects.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or vector of such ghosts.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrap(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var vector = [canvas._newCanvasGhost()._node_ghost, props.Node.new()._g];&lt;br /&gt;
var nodes = props.wrap(vector);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    props.dump(node);&lt;br /&gt;
    print(&amp;quot;----&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrapNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrapNode(node);&lt;br /&gt;
|text = Turns a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost into a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost to convert.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrapNode(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Variable ==&lt;br /&gt;
=== globals ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.globals;&lt;br /&gt;
|text = Exposes the [[Property Tree]] as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft: &amp;quot;, props.globals.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
|example2text = Alternative using {{func link|getprop()}}.&lt;br /&gt;
|example2 = print(&amp;quot;Current aircraft: &amp;quot;, getprop(&amp;quot;/sim/aircraft&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143169</id>
		<title>Nasal library/props</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143169"/>
		<updated>2025-12-04T18:36:10Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* getChild() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page contains documentation for the '''&amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace''' in [[Nasal]]. This namespace provides APIs for working with property trees (including the main [[Property Tree]]) via {{API Link|simgear|class|SGPropertyNode}}. The &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace is sourced from {{fgdata source|Nasal/props.nas}} and {{flightgear source|src/Scripting/nasal-props.cxx}}.&lt;br /&gt;
&lt;br /&gt;
== Class ==&lt;br /&gt;
=== Node ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|mode = class&lt;br /&gt;
|text = The main class, used widely for manipulating property trees.&lt;br /&gt;
}}&lt;br /&gt;
==== new() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.new([values]);&lt;br /&gt;
|text = Constructor function. Returns a new &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = values&lt;br /&gt;
|param1text = An optional hash that will be the initial property structure.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    a: 1,&lt;br /&gt;
    b: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;],&lt;br /&gt;
    c: {&lt;br /&gt;
        d: 1 * 4&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChild(name[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|a15d5829379864c7138edff621d6864c5f426d0b|t=commit}}&lt;br /&gt;
|text = Add a new, blank child to the node. Returns the newly-created node.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the node as a string.&lt;br /&gt;
|param2 = min_idx&lt;br /&gt;
|param2text = This specifies the minimum index to add the new one to. This takes precedence over '''append'''. Defaults to 0.&lt;br /&gt;
|param3 = append&lt;br /&gt;
|param3text = If there is already one or more children with the same name and this argument is true (1), the new child will be added after the child with the highest index. If false (0), the new child will be added at the lowest available index with the limit of '''min_idx'''. Defaults to true (1).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, false);&lt;br /&gt;
props.dump(node); # a[1] and a[0]&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, true);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChildren(name, count[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|7f1117a5374beabb045fb76fbb6dd8bfb0128100|t=commit}}&lt;br /&gt;
|text = Adds multiple children with the same name to this node. Returns &amp;lt;code&amp;gt;'''nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the nodes as a string.&lt;br /&gt;
|param2 = count&lt;br /&gt;
|param2text = Number of new children to add.&lt;br /&gt;
|param3 = min_idx&lt;br /&gt;
|param3text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = append&lt;br /&gt;
|param4text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[0] and a[1]&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[2]&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 0, 0);&lt;br /&gt;
props.dump(node); # a[2], a[0], and a[1]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== adjustValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.adjustValue(delta);&lt;br /&gt;
|text = Adds delta (numeric) to current value respecting the node type. &lt;br /&gt;
|param1 = delta&lt;br /&gt;
|param1text = Numeric value (can be negative) to add to current node value.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== alias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.alias(node, chainListener=0);&lt;br /&gt;
|text = Aliases this node to another one. Returns 1 on success and 0 on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = The node to alias to. Can be one of:&lt;br /&gt;
* A path to a property in the [[Property Tree]] as a string.&lt;br /&gt;
* A &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost.&lt;br /&gt;
* A &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/position/altitude-ft&amp;quot;);&lt;br /&gt;
props.dump(node); # equals the current altitude&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.34);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
props.dump(node2); # equals 2.34&lt;br /&gt;
|param2=chainListener|param2text=Specify if listeners should work on aliased properties or not. Current default is false forbackwards compatability.}}&lt;br /&gt;
&lt;br /&gt;
==== clearValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.clearValue();&lt;br /&gt;
|text = Clears the value and type of the node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.34);&lt;br /&gt;
props.dump(node); # prints &amp;quot;{DOUBLE} = 2.35&amp;quot;&lt;br /&gt;
node.clearValue();&lt;br /&gt;
props.dump(node); # prints &amp;quot;{NONE} = nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== decrement() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.decrement(n = 1);&lt;br /&gt;
|text = Decrements integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to subtract, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== equals() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.equals(node);&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{fgdata commit|d80722065f3c11e0511fa888b1f5f310dd0183e3|t=commit}}&lt;br /&gt;
|text = Checks whether the node refers to the same one as another. Returns 1 (true) if it is, and 0 (false) if otherwise.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to check against. May be either a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var n = props.Node.new();&lt;br /&gt;
var a = n;&lt;br /&gt;
print(a.equals(n)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAliasTarget() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAliasTarget();&lt;br /&gt;
|text = Returns the alias target of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 2.35);&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/test&amp;quot;);&lt;br /&gt;
var tgt = node.getAliasTarget();&lt;br /&gt;
print(tgt.getPath()); # prints &amp;quot;/test&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAttribute([rel_path, ]name);&lt;br /&gt;
props.Node.getAttribute();&lt;br /&gt;
|text = Returns an attribute. If no arguments are given, the function will return an integer specifying the attributes set for the node (see {{simgear source|simgear/props/props.hxx|l=767}} for a list). A list of attributes are below&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Return value&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} last {{!!}} The highest used attribute code (should be 128). See for {{simgear source|simgear/props/props.hxx|l=767}} the codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} children {{!!}} Number of child nodes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} listeners {{!!}} Number of listeners connected to this node.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} references {{!!}} Number of times the node has previously been referenced.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} tied {{!!}} Whether the node is tied.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} alias {{!!}} Whether the node is aliased.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = Attribute as a string. See the above table for a full list.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;children&amp;quot;)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2text = Example using relative path&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;a&amp;quot;, &amp;quot;readable&amp;quot;)); # prints &amp;quot;1&amp;quot; (node can be read from)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node);&lt;br /&gt;
print(node2.getAttribute(&amp;quot;alias&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example4 = print(props.globals.getNode(&amp;quot;/sim/signals/fdm-initialized&amp;quot;).getAttribute(&amp;quot;listeners&amp;quot;)); # prints the number of listeners&lt;br /&gt;
|example5 = print(props.globals.getNode(&amp;quot;/sim/time/elapsed-sec&amp;quot;).getAttribute(&amp;quot;tied&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning it is tied&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute(&amp;quot;writable&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning the node can be written to&lt;br /&gt;
|example7text = Example using no arguments&lt;br /&gt;
|example7 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute()); # prints &amp;quot;3&amp;quot; (true), meaning the node can be read from (1) and written to (2)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getBoolValue();&lt;br /&gt;
|text = Returns the value of a node converted to a boolean. If the node is a number type, 0  will return false, while 1 will return true. If the node is a string or unspecified type and the value is &amp;lt;code&amp;gt;&amp;quot;false&amp;quot;&amp;lt;/code&amp;gt;, false will be returned. Otherwise, true will be returned. Remember that boolean values are represented in Nasal as 1 (true) and 0 (false).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(-1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(-2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setValue(&amp;quot;false&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChild(rel_path[, idx[, create]]);&lt;br /&gt;
|text = Returns a child of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the child node as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Optional index for the child node as an integer.&lt;br /&gt;
|param3 = create&lt;br /&gt;
|param3text = If set to true (1), a new child will be created if it does not exist. If set to false (0), the function will not create a new child and the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no child exists. Defaults to false.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.getNode(&amp;quot;a[1]&amp;quot;).setDoubleValue(2.35);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
print(c.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1, true);&lt;br /&gt;
props.dump(node); # new child a[1] will have appeared&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChildren([name]);&lt;br /&gt;
|text = Returns a vector of child nodes, optionally those with a certain name, as &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instances.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of the child nodes as a string. If not given, all children will be returned.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren()); # all child nodes in the vector&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren(&amp;quot;b&amp;quot;)); # only children with the name &amp;quot;b&amp;quot; in the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getIndex() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getIndex();&lt;br /&gt;
|text = Returns the index of a node as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
print(node.getChild(&amp;quot;a&amp;quot;, 1).getIndex()); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;b&amp;quot;);&lt;br /&gt;
print(node.getChild(&amp;quot;b&amp;quot;).getIndex()); # prints &amp;quot;0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getName() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getName();&lt;br /&gt;
|text = Returns the name of the node as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var c = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
debug.dump(c.getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChild(&amp;quot;a&amp;quot;, 2).getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getNode(rel_path[, create]);&lt;br /&gt;
|text = Returns a subnode as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the subnode as a string.&lt;br /&gt;
|param2 = create&lt;br /&gt;
|param2text = If true (1), the node will be created if it does not exist. If false (0) and the node does not exist, the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;. Default to false (0).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getValue()); # prints &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {}&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.getNode(&amp;quot;c/d&amp;quot;, true).setDoubleValue(2.35);&lt;br /&gt;
props.dump(node); # c/d now exists&lt;br /&gt;
|example3 = var ac = props.globals.getNode(&amp;quot;sim/aircraft&amp;quot;);&lt;br /&gt;
print(&amp;quot;Current aircraft is: &amp;quot;, ac.getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getParent() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getParent();&lt;br /&gt;
|text = Returns the parent of a node, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there is no parent.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node.getNode(&amp;quot;c/d&amp;quot;).getParent()); # dumps &amp;quot;c&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
debug.dump(node.getParent()); # prints nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getPath() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getPath();&lt;br /&gt;
|text = Returns the path of the node as a string.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getPath()); # prints &amp;quot;/c/d&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getType() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getType();&lt;br /&gt;
|text = Returns node's type as a string. It should be one of &amp;quot;NONE&amp;quot;, &amp;quot;ALIAS&amp;quot;, &amp;quot;BOOL&amp;quot;, &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot; (long integer), &amp;quot;FLOAT&amp;quot;, &amp;quot;DOUBLE&amp;quot;, &amp;quot;STRING&amp;quot;, &amp;quot;VEC3D&amp;quot;, &amp;quot;VEC4D&amp;quot;, &amp;quot;UNSPECIFIED&amp;quot;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
print(node.getType()); # prints &amp;quot;NONE&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
print(node.getType()); # prints &amp;quot;INT&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValue([rel_path]);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getBoolValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Returns the value of the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path to a subnode as a string, which may contain '/' characters. If the subnode does not exist, we return nil.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.35);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue(&amp;quot;a&amp;quot;)); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0, 0.5, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0, 0.5, 1]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValues();&lt;br /&gt;
|text = Returns the node tree as a hash, with all the various subnodes, etc. If the node has no children, the result is equivalent to {{func link|getValue()|page=this}}. Subnodes that are indexed will be combined into one key and their values placed in a vector (see example 2).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;string&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;number&amp;quot;: 1.2,&lt;br /&gt;
    &amp;quot;subnode&amp;quot;: {&lt;br /&gt;
        &amp;quot;idx-node&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.addChild(&amp;quot;bool&amp;quot;).setBoolValue(1);&lt;br /&gt;
props.dump(node); # dump to node tree&lt;br /&gt;
debug.dump(node.getValues()); # dump the node converted to hash&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: [1, 2, 3]&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # a[0] = 1, a[1] = 2, a[2] = 3&lt;br /&gt;
debug.dump(node.getValues()); # a: [1, 2, 3]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== increment() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.increment(n = 1);&lt;br /&gt;
|text = Increments integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to add, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== initNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.initNode([path[, value[, type[, force]]]]);&lt;br /&gt;
|text = Initializes a node if it doesn't exist and returns that node as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to a subnode as a string. If not given, the node itself will be initialized.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Optional default value to initialize the node with.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Optional string that will set the type of the node. Must be one of &amp;lt;code&amp;gt;&amp;quot;DOUBLE&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;INT&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;BOOL&amp;quot;&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;&amp;quot;STRING&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = force&lt;br /&gt;
|param4text = If set to true (1), the node's type will be forced to change.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;);&lt;br /&gt;
props.dump(a); # a = 1&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setBoolValue(0);&lt;br /&gt;
props.dump(node.getChild(&amp;quot;a&amp;quot;)); # a = 0 (type: bool)&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;, true);&lt;br /&gt;
props.dump(a); # a = 0 (type: int)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isInt() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isInt();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot; or &amp;quot;LONG&amp;quot; (long integer) otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isNumeric() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isNumeric();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot;, &amp;quot;FLOAT&amp;quot; or &amp;quot;DOUBLE&amp;quot; otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== remove() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.remove();&lt;br /&gt;
|text = Removes the node and returns the removed node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).remove();&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; does not exist anymore&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeAllChildren() ====&lt;br /&gt;
{{Caution|Be careful when  using this API in conjunction with the Canvas system and element specific properties, for details please see [[Howto:Canvas Path Benchmarking]]}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeAllChildren();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|4766ed21a68230c0c15265e5604a74f4d99e0f5d|t=commit}}&lt;br /&gt;
|text = Removes all child nodes and returns the node.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeAllChildren();&lt;br /&gt;
props.dump(node); # all children have been removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChild(rel_path, idx);&lt;br /&gt;
|text = Removes a given child node child nodes and returns the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to a subnode as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Index of the subnode to remove as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; has been removed&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # just a[1] remains&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChildren([name]);&lt;br /&gt;
|text = Removes all children with a specified name. If no arguments are given, all children will be removed (see also {{func link|removeAllChildren()|page=this}}).&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of children to remove as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node); # just children named &amp;quot;b&amp;quot; remain&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren();&lt;br /&gt;
props.dump(node); # all children removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setAttribute([rel_path, ]attr, value);&lt;br /&gt;
props.Node.setAttribute(attrs);&lt;br /&gt;
|text = Sets an attribute or multiple attributes. A list of attributes and their codes are below. For a brand new node, the default attributes are ''readable'' and ''writable''. Returns an integer specifying the old attributes.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Description&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = attr&lt;br /&gt;
|param2text = Name of attribute to set as a string. See above.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Boolean value to set the property to.&lt;br /&gt;
|param4 = attrs&lt;br /&gt;
|param4text = When the function is used in its second form, this argument is used. This argument should be an integer specifying which arguments are set to true. See {{simgear source|simgear/props/props.hxx|l=767}} for the full list of codes. Simply add codes of the desired attributes together.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;readable&amp;quot;, 0);&lt;br /&gt;
var val = node.getValue();&lt;br /&gt;
debug.dump(val); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setAttribute(&amp;quot;a&amp;quot;, &amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).setIntValue(12); # will be traced&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(35); # read + write + trace-write&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setBoolValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a boolean value. If the node has no type, it will be set to a bool type. If the node is already a number type, it will be set to either 1 or 0. If it is a string, it will be set to either &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a boolean. If it is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, it will be false. If it is a string, it will be false. If it is a number, 0 will be false, while other numbers will be true. All other cases will be interpreted as 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(nil);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
node.setBoolValue(1.25);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;String&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;String&amp;quot; (type: string)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = &amp;quot;true&amp;quot;&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(12.32);&lt;br /&gt;
props.dump(node); # node = 12.32 (type: double)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = 1&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setBoolValue(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # /a = 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setDoubleValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setDoubleValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a double value. If the node has no type, it will be set to a double type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. If the node is an integer type, it will be truncated to the closest integer to zero. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a double number. It must be a valid number or a string that can be converted to a number.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(1);&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1 (type: int)&lt;br /&gt;
node.setDoubleValue(&amp;quot;-1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = -1 (type: int)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setDoubleValue(&amp;quot;a&amp;quot;, 12.2);&lt;br /&gt;
props.dump(node); # /a = 12.2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setIntValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setIntValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to an integer value. If the node has no type, it will be set to a integer type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a integer. It must be a valid number or a string that can be converted to a number. If the number is a double, it will be truncated to the closest integer to zero.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(&amp;quot;6&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 6&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(-12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setIntValue(12.5);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = &amp;quot;12&amp;quot; (type: string)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setIntValue(&amp;quot;a&amp;quot;, 12);&lt;br /&gt;
props.dump(node); # /a = 12&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValue([rel_path, ]value);&lt;br /&gt;
|text = {{hatnote|See also {{func link|setBoolValue()|page=this}}, {{func link|setDoubleValue()|page=this}}, and {{func link|setIntValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
{{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a given value. See table below for conversions and effects. Note that vec3d and vec4d types are not fully integrated into SGPropertyNode. Returns 1 (true) if the operation was successful.&lt;br /&gt;
&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align:right&amp;quot; {{!}} '''value''' type → &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Number&lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} String &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Vector&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} style=&amp;quot;text-align:left&amp;quot; {{!}} Current node&amp;lt;br&amp;gt;type ↓&lt;br /&gt;
{{!}} style=&amp;quot;text-align:right&amp;quot; {{!}} Result ↘&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} None/unspecified&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;double&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;vec*d&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Bool&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' != 0, node set to true.&lt;br /&gt;
* If '''value''' == 0, node set to false.&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' == &amp;quot;true&amp;quot;, node set to true.&lt;br /&gt;
* If '''value''' can be converted to an integer,&amp;lt;br&amp;gt;and != 0, node set to true.&lt;br /&gt;
{{!}} Node set to true.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Integer&lt;br /&gt;
{{!}} Node set to truncated '''value'''&lt;br /&gt;
{{!}} Node set to '''value ''' converted and truncated to an integer.&lt;br /&gt;
{{!}} Node set to 1.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Double&lt;br /&gt;
{{!}} Node set to '''value'''.&lt;br /&gt;
{{!}} Node set to '''value''' converted to number.&lt;br /&gt;
{{!}} Throws an error (vector is not a number).&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} String&lt;br /&gt;
{{!}} Node set to '''value''' converted to string. {{!!}} Node set to '''value'''. {{!!}} Node not set.&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to. Must be a string, a valid number, or a vector consisting of 3 or 4 numbers. See table above for conversions and effects.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setValue(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # \a = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValues(val);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getValues()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Sets the nodes property tree from a Nasal hash. Scalars will become nodes in the tree and hashes will become named subnodes. Vectors will be converted into indexed nodes, with the values in the vector becoming their values (see examples below).&lt;br /&gt;
|param1 = val&lt;br /&gt;
|param1text = A hash that will become the property tree.&lt;br /&gt;
|example1 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 100 # &amp;quot;a&amp;quot; will become the subnode's name, and 100 its value&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
|example2 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== unalias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.unalias();&lt;br /&gt;
|text = Un-aliases the node and returns it to a blank state. Returns 1 on success and 0 on failure (e.g., when used on a tied property).&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.35);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
&lt;br /&gt;
props.dump(node2); # equals 2.35&lt;br /&gt;
node2.unalias();&lt;br /&gt;
props.dump(node2); # no value or type&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== toggleBoolValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = toggleBoolValue();&lt;br /&gt;
|text = Toggle a boolean property. You have to make sure the property is of type bool!&lt;br /&gt;
|example1 = var b = props.Node.new().initNode(&amp;quot;/_test/bool&amp;quot;, 1, &amp;quot;BOOL&amp;quot;);&lt;br /&gt;
print(&amp;quot;bool &amp;quot;, b.getValue());&lt;br /&gt;
b.toggleBoolValue();&lt;br /&gt;
print(&amp;quot;after toggleBoolValue &amp;quot;, b.getValue());&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
=== compileCondition() ===&lt;br /&gt;
{{see also|Conditions}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.compileCondition(node);&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|43f8ce08706629e69984b1cc34e5e979d03ef09a|t=commit}}&lt;br /&gt;
|text = Compiles a [[conditions|condition]] property branch and returns a &amp;lt;code&amp;gt;Condition&amp;lt;/code&amp;gt; ghost object or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error. This ghost will contain a &amp;lt;code&amp;gt;test()&amp;lt;/code&amp;gt; function that will return the result of the condition as either 1 (true) or 0 (false).&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== condition() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.condition(node);&lt;br /&gt;
|text = Evaluates a [[conditions|condition]] property branch and returns the result as a boolean.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== copy() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.copy(src, dest[, attr]);&lt;br /&gt;
|text = Copies the property tree of the source into the destination node. Note that aliased properties will not be copied.&lt;br /&gt;
|param1 = src&lt;br /&gt;
|param1text = Source &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy from.&lt;br /&gt;
|param2 = dest&lt;br /&gt;
|param2text = Destination &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy to.&lt;br /&gt;
|param3 = attr&lt;br /&gt;
|param3text = If set to 1 (true), attributes will also be copied. Defaults to 0 (false).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1.5,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var src = props.Node.new(tree);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest);&lt;br /&gt;
props.dump(dest);&lt;br /&gt;
|example2 = var src = props.Node.new();&lt;br /&gt;
var a = src.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
a.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
a.setIntValue(12);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest, 1);&lt;br /&gt;
print(dest.getNode(&amp;quot;a&amp;quot;).getAttribute(&amp;quot;trace-write&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== dump() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.dump(node);&lt;br /&gt;
|text = Recursively dump the state of a node into the console, showing value and type of each node. Note that as of 10/2016, the value of vec*d type nodes cannot be dumped.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to dump.&lt;br /&gt;
|example1 = var node = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # dump into console&lt;br /&gt;
|example2 = # Dump the entire Property Tree&lt;br /&gt;
# Warning! This is an intensive operation!&lt;br /&gt;
props.dump(props.globals);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== getNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.getNode();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|807062d0b6f858885547f27325e829c2bf42a12b|t=commit}}&lt;br /&gt;
|text = Shortcut for &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;props.globals.getNode()&amp;lt;/syntaxhighlight&amp;gt;. See {{func link|getNode()||Node|page=this}} for full documentation.&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft is: &amp;quot;, props.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== nodeList() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.nodeList(arg[, arg[, ...]]);&lt;br /&gt;
|text = Converts its arguments into a vector of node objects if possible and returns that vector. &lt;br /&gt;
|param1 = arg&lt;br /&gt;
|param1text = Object to operate on. Must be a node object, string, vector, hash, function, or &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost. Vectors and hashes must contain any of the other acceptable types, functions must return any of the other types, strings will be assumed to be paths to global properties, and ghosts will be converted into node objects. There may be any number of arguments.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var f = func(){&lt;br /&gt;
    var n = props.Node.new();&lt;br /&gt;
    return n._g;&lt;br /&gt;
}&lt;br /&gt;
var list = props.nodeList(node,&lt;br /&gt;
    &amp;quot;/sim/aircraft&amp;quot;,&lt;br /&gt;
    [&amp;quot;/sim/fg-root&amp;quot;],&lt;br /&gt;
    { &amp;quot;path&amp;quot;: &amp;quot;/sim/fg-home&amp;quot; },&lt;br /&gt;
    f&lt;br /&gt;
);&lt;br /&gt;
debug.dump(list); # dump list&lt;br /&gt;
|example2 = var root = &amp;quot;/sim/version/&amp;quot;;&lt;br /&gt;
var info = [&lt;br /&gt;
    root ~ &amp;quot;build-id&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;build-number&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;flightgear&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;hla-support&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;openscenegraph&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;revision&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;simgear&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
info = props.nodeList(info); # turn into list of nodes&lt;br /&gt;
foreach(var n; info){&lt;br /&gt;
    print(n.getValue()); # dump info&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== runBinding() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.runBinding(node[, module]);&lt;br /&gt;
|text = Runs a [[Bindings|binding]] element in a node object. Returns 1 (true) on success and 0 (false) on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = A {{tag|binding}} element as a node object.&lt;br /&gt;
|param2 = module&lt;br /&gt;
|param2text = Optional string specifying a module to run Nasal scripts in if the command is &amp;lt;code&amp;gt;nasal&amp;lt;/code&amp;gt;. This argument will not override any {{tag|module}} element in the '''node'''&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;dialog-show&amp;quot;,&lt;br /&gt;
    &amp;quot;dialog-name&amp;quot;: &amp;quot;map&amp;quot; # open map&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)' # prints value of math.pi&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;math&amp;quot;);&lt;br /&gt;
|example3 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)', # prints value of math.pi&lt;br /&gt;
    &amp;quot;module&amp;quot;: &amp;quot;math&amp;quot; # this is used&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;debug&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setAll() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.setAll(base, child, value);&lt;br /&gt;
|text = Sets indexed subnodes in the Property Tree with the same name to the same value.&lt;br /&gt;
|param1 = base&lt;br /&gt;
|param1text = Base path to the nodes.&lt;br /&gt;
|param2 = child&lt;br /&gt;
|param2text = Path to child nodes.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Value to set the subnodes to.&lt;br /&gt;
|example1 = # apply 50% throttle to all engines&lt;br /&gt;
props.setAll(&amp;quot;/controls/engines/engine&amp;quot;, &amp;quot;throttle&amp;quot;, 0.5);&lt;br /&gt;
|example2 = var nodes = props.globals.addChildren(&amp;quot;/test&amp;quot;, 3);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
props.setAll(&amp;quot;/test&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;); # set all children (test[*]/a) to &amp;quot;Hi&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrap() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrap(node);&lt;br /&gt;
|text = Turns &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghosts, either in a vector or single, into &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; objects.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or vector of such ghosts.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrap(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var vector = [canvas._newCanvasGhost()._node_ghost, props.Node.new()._g];&lt;br /&gt;
var nodes = props.wrap(vector);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    props.dump(node);&lt;br /&gt;
    print(&amp;quot;----&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrapNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrapNode(node);&lt;br /&gt;
|text = Turns a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost into a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost to convert.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrapNode(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Variable ==&lt;br /&gt;
=== globals ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.globals;&lt;br /&gt;
|text = Exposes the [[Property Tree]] as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft: &amp;quot;, props.globals.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
|example2text = Alternative using {{func link|getprop()}}.&lt;br /&gt;
|example2 = print(&amp;quot;Current aircraft: &amp;quot;, getprop(&amp;quot;/sim/aircraft&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143168</id>
		<title>Nasal library/props</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143168"/>
		<updated>2025-12-04T18:34:22Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* addChild() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page contains documentation for the '''&amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace''' in [[Nasal]]. This namespace provides APIs for working with property trees (including the main [[Property Tree]]) via {{API Link|simgear|class|SGPropertyNode}}. The &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace is sourced from {{fgdata source|Nasal/props.nas}} and {{flightgear source|src/Scripting/nasal-props.cxx}}.&lt;br /&gt;
&lt;br /&gt;
== Class ==&lt;br /&gt;
=== Node ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|mode = class&lt;br /&gt;
|text = The main class, used widely for manipulating property trees.&lt;br /&gt;
}}&lt;br /&gt;
==== new() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.new([values]);&lt;br /&gt;
|text = Constructor function. Returns a new &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = values&lt;br /&gt;
|param1text = An optional hash that will be the initial property structure.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    a: 1,&lt;br /&gt;
    b: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;],&lt;br /&gt;
    c: {&lt;br /&gt;
        d: 1 * 4&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChild(name[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|a15d5829379864c7138edff621d6864c5f426d0b|t=commit}}&lt;br /&gt;
|text = Add a new, blank child to the node. Returns the newly-created node.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the node as a string.&lt;br /&gt;
|param2 = min_idx&lt;br /&gt;
|param2text = This specifies the minimum index to add the new one to. This takes precedence over '''append'''. Defaults to 0.&lt;br /&gt;
|param3 = append&lt;br /&gt;
|param3text = If there is already one or more children with the same name and this argument is true (1), the new child will be added after the child with the highest index. If false (0), the new child will be added at the lowest available index with the limit of '''min_idx'''. Defaults to true (1).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, false);&lt;br /&gt;
props.dump(node); # a[1] and a[0]&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, true);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChildren(name, count[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|7f1117a5374beabb045fb76fbb6dd8bfb0128100|t=commit}}&lt;br /&gt;
|text = Adds multiple children with the same name to this node. Returns &amp;lt;code&amp;gt;'''nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the nodes as a string.&lt;br /&gt;
|param2 = count&lt;br /&gt;
|param2text = Number of new children to add.&lt;br /&gt;
|param3 = min_idx&lt;br /&gt;
|param3text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = append&lt;br /&gt;
|param4text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[0] and a[1]&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[2]&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 0, 0);&lt;br /&gt;
props.dump(node); # a[2], a[0], and a[1]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== adjustValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.adjustValue(delta);&lt;br /&gt;
|text = Adds delta (numeric) to current value respecting the node type. &lt;br /&gt;
|param1 = delta&lt;br /&gt;
|param1text = Numeric value (can be negative) to add to current node value.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== alias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.alias(node, chainListener=0);&lt;br /&gt;
|text = Aliases this node to another one. Returns 1 on success and 0 on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = The node to alias to. Can be one of:&lt;br /&gt;
* A path to a property in the [[Property Tree]] as a string.&lt;br /&gt;
* A &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost.&lt;br /&gt;
* A &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/position/altitude-ft&amp;quot;);&lt;br /&gt;
props.dump(node); # equals the current altitude&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.34);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
props.dump(node2); # equals 2.34&lt;br /&gt;
|param2=chainListener|param2text=Specify if listeners should work on aliased properties or not. Current default is false forbackwards compatability.}}&lt;br /&gt;
&lt;br /&gt;
==== clearValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.clearValue();&lt;br /&gt;
|text = Clears the value and type of the node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.34);&lt;br /&gt;
props.dump(node); # prints &amp;quot;{DOUBLE} = 2.35&amp;quot;&lt;br /&gt;
node.clearValue();&lt;br /&gt;
props.dump(node); # prints &amp;quot;{NONE} = nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== decrement() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.decrement(n = 1);&lt;br /&gt;
|text = Decrements integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to subtract, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== equals() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.equals(node);&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{fgdata commit|d80722065f3c11e0511fa888b1f5f310dd0183e3|t=commit}}&lt;br /&gt;
|text = Checks whether the node refers to the same one as another. Returns 1 (true) if it is, and 0 (false) if otherwise.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to check against. May be either a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var n = props.Node.new();&lt;br /&gt;
var a = n;&lt;br /&gt;
print(a.equals(n)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAliasTarget() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAliasTarget();&lt;br /&gt;
|text = Returns the alias target of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 2.35);&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/test&amp;quot;);&lt;br /&gt;
var tgt = node.getAliasTarget();&lt;br /&gt;
print(tgt.getPath()); # prints &amp;quot;/test&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAttribute([rel_path, ]name);&lt;br /&gt;
props.Node.getAttribute();&lt;br /&gt;
|text = Returns an attribute. If no arguments are given, the function will return an integer specifying the attributes set for the node (see {{simgear source|simgear/props/props.hxx|l=767}} for a list). A list of attributes are below&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Return value&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} last {{!!}} The highest used attribute code (should be 128). See for {{simgear source|simgear/props/props.hxx|l=767}} the codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} children {{!!}} Number of child nodes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} listeners {{!!}} Number of listeners connected to this node.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} references {{!!}} Number of times the node has previously been referenced.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} tied {{!!}} Whether the node is tied.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} alias {{!!}} Whether the node is aliased.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = Attribute as a string. See the above table for a full list.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;children&amp;quot;)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2text = Example using relative path&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;a&amp;quot;, &amp;quot;readable&amp;quot;)); # prints &amp;quot;1&amp;quot; (node can be read from)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node);&lt;br /&gt;
print(node2.getAttribute(&amp;quot;alias&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example4 = print(props.globals.getNode(&amp;quot;/sim/signals/fdm-initialized&amp;quot;).getAttribute(&amp;quot;listeners&amp;quot;)); # prints the number of listeners&lt;br /&gt;
|example5 = print(props.globals.getNode(&amp;quot;/sim/time/elapsed-sec&amp;quot;).getAttribute(&amp;quot;tied&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning it is tied&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute(&amp;quot;writable&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning the node can be written to&lt;br /&gt;
|example7text = Example using no arguments&lt;br /&gt;
|example7 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute()); # prints &amp;quot;3&amp;quot; (true), meaning the node can be read from (1) and written to (2)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getBoolValue();&lt;br /&gt;
|text = Returns the value of a node converted to a boolean. If the node is a number type, 0  will return false, while 1 will return true. If the node is a string or unspecified type and the value is &amp;lt;code&amp;gt;&amp;quot;false&amp;quot;&amp;lt;/code&amp;gt;, false will be returned. Otherwise, true will be returned. Remember that boolean values are represented in Nasal as 1 (true) and 0 (false).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(-1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(-2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setValue(&amp;quot;false&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChild(rel_path[, idx[, create]]);&lt;br /&gt;
|text = Returns a child of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the child node as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Optional index for the child node as an integer.&lt;br /&gt;
|param3 = create&lt;br /&gt;
|param3text = If set to 1 (true), a new child will be created if it does not exist. If set to 0 (false), the function will not create a new child and the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no child exists. Defaults to 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.getNode(&amp;quot;a[1]&amp;quot;).setDoubleValue(2.35);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
print(c.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1, 1);&lt;br /&gt;
props.dump(node); # new child a[1] will have appeared&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChildren([name]);&lt;br /&gt;
|text = Returns a vector of child nodes, optionally those with a certain name, as &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instances.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of the child nodes as a string. If not given, all children will be returned.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren()); # all child nodes in the vector&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren(&amp;quot;b&amp;quot;)); # only children with the name &amp;quot;b&amp;quot; in the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getIndex() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getIndex();&lt;br /&gt;
|text = Returns the index of a node as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
print(node.getChild(&amp;quot;a&amp;quot;, 1).getIndex()); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;b&amp;quot;);&lt;br /&gt;
print(node.getChild(&amp;quot;b&amp;quot;).getIndex()); # prints &amp;quot;0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getName() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getName();&lt;br /&gt;
|text = Returns the name of the node as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var c = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
debug.dump(c.getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChild(&amp;quot;a&amp;quot;, 2).getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getNode(rel_path[, create]);&lt;br /&gt;
|text = Returns a subnode as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the subnode as a string.&lt;br /&gt;
|param2 = create&lt;br /&gt;
|param2text = If true (1), the node will be created if it does not exist. If false (0) and the node does not exist, the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;. Default to false (0).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getValue()); # prints &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {}&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.getNode(&amp;quot;c/d&amp;quot;, true).setDoubleValue(2.35);&lt;br /&gt;
props.dump(node); # c/d now exists&lt;br /&gt;
|example3 = var ac = props.globals.getNode(&amp;quot;sim/aircraft&amp;quot;);&lt;br /&gt;
print(&amp;quot;Current aircraft is: &amp;quot;, ac.getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getParent() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getParent();&lt;br /&gt;
|text = Returns the parent of a node, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there is no parent.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node.getNode(&amp;quot;c/d&amp;quot;).getParent()); # dumps &amp;quot;c&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
debug.dump(node.getParent()); # prints nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getPath() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getPath();&lt;br /&gt;
|text = Returns the path of the node as a string.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getPath()); # prints &amp;quot;/c/d&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getType() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getType();&lt;br /&gt;
|text = Returns node's type as a string. It should be one of &amp;quot;NONE&amp;quot;, &amp;quot;ALIAS&amp;quot;, &amp;quot;BOOL&amp;quot;, &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot; (long integer), &amp;quot;FLOAT&amp;quot;, &amp;quot;DOUBLE&amp;quot;, &amp;quot;STRING&amp;quot;, &amp;quot;VEC3D&amp;quot;, &amp;quot;VEC4D&amp;quot;, &amp;quot;UNSPECIFIED&amp;quot;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
print(node.getType()); # prints &amp;quot;NONE&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
print(node.getType()); # prints &amp;quot;INT&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValue([rel_path]);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getBoolValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Returns the value of the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path to a subnode as a string, which may contain '/' characters. If the subnode does not exist, we return nil.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.35);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue(&amp;quot;a&amp;quot;)); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0, 0.5, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0, 0.5, 1]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValues();&lt;br /&gt;
|text = Returns the node tree as a hash, with all the various subnodes, etc. If the node has no children, the result is equivalent to {{func link|getValue()|page=this}}. Subnodes that are indexed will be combined into one key and their values placed in a vector (see example 2).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;string&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;number&amp;quot;: 1.2,&lt;br /&gt;
    &amp;quot;subnode&amp;quot;: {&lt;br /&gt;
        &amp;quot;idx-node&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.addChild(&amp;quot;bool&amp;quot;).setBoolValue(1);&lt;br /&gt;
props.dump(node); # dump to node tree&lt;br /&gt;
debug.dump(node.getValues()); # dump the node converted to hash&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: [1, 2, 3]&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # a[0] = 1, a[1] = 2, a[2] = 3&lt;br /&gt;
debug.dump(node.getValues()); # a: [1, 2, 3]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== increment() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.increment(n = 1);&lt;br /&gt;
|text = Increments integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to add, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== initNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.initNode([path[, value[, type[, force]]]]);&lt;br /&gt;
|text = Initializes a node if it doesn't exist and returns that node as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to a subnode as a string. If not given, the node itself will be initialized.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Optional default value to initialize the node with.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Optional string that will set the type of the node. Must be one of &amp;lt;code&amp;gt;&amp;quot;DOUBLE&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;INT&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;BOOL&amp;quot;&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;&amp;quot;STRING&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = force&lt;br /&gt;
|param4text = If set to true (1), the node's type will be forced to change.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;);&lt;br /&gt;
props.dump(a); # a = 1&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setBoolValue(0);&lt;br /&gt;
props.dump(node.getChild(&amp;quot;a&amp;quot;)); # a = 0 (type: bool)&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;, true);&lt;br /&gt;
props.dump(a); # a = 0 (type: int)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isInt() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isInt();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot; or &amp;quot;LONG&amp;quot; (long integer) otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isNumeric() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isNumeric();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot;, &amp;quot;FLOAT&amp;quot; or &amp;quot;DOUBLE&amp;quot; otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== remove() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.remove();&lt;br /&gt;
|text = Removes the node and returns the removed node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).remove();&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; does not exist anymore&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeAllChildren() ====&lt;br /&gt;
{{Caution|Be careful when  using this API in conjunction with the Canvas system and element specific properties, for details please see [[Howto:Canvas Path Benchmarking]]}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeAllChildren();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|4766ed21a68230c0c15265e5604a74f4d99e0f5d|t=commit}}&lt;br /&gt;
|text = Removes all child nodes and returns the node.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeAllChildren();&lt;br /&gt;
props.dump(node); # all children have been removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChild(rel_path, idx);&lt;br /&gt;
|text = Removes a given child node child nodes and returns the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to a subnode as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Index of the subnode to remove as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; has been removed&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # just a[1] remains&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChildren([name]);&lt;br /&gt;
|text = Removes all children with a specified name. If no arguments are given, all children will be removed (see also {{func link|removeAllChildren()|page=this}}).&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of children to remove as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node); # just children named &amp;quot;b&amp;quot; remain&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren();&lt;br /&gt;
props.dump(node); # all children removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setAttribute([rel_path, ]attr, value);&lt;br /&gt;
props.Node.setAttribute(attrs);&lt;br /&gt;
|text = Sets an attribute or multiple attributes. A list of attributes and their codes are below. For a brand new node, the default attributes are ''readable'' and ''writable''. Returns an integer specifying the old attributes.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Description&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = attr&lt;br /&gt;
|param2text = Name of attribute to set as a string. See above.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Boolean value to set the property to.&lt;br /&gt;
|param4 = attrs&lt;br /&gt;
|param4text = When the function is used in its second form, this argument is used. This argument should be an integer specifying which arguments are set to true. See {{simgear source|simgear/props/props.hxx|l=767}} for the full list of codes. Simply add codes of the desired attributes together.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;readable&amp;quot;, 0);&lt;br /&gt;
var val = node.getValue();&lt;br /&gt;
debug.dump(val); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setAttribute(&amp;quot;a&amp;quot;, &amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).setIntValue(12); # will be traced&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(35); # read + write + trace-write&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setBoolValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a boolean value. If the node has no type, it will be set to a bool type. If the node is already a number type, it will be set to either 1 or 0. If it is a string, it will be set to either &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a boolean. If it is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, it will be false. If it is a string, it will be false. If it is a number, 0 will be false, while other numbers will be true. All other cases will be interpreted as 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(nil);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
node.setBoolValue(1.25);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;String&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;String&amp;quot; (type: string)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = &amp;quot;true&amp;quot;&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(12.32);&lt;br /&gt;
props.dump(node); # node = 12.32 (type: double)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = 1&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setBoolValue(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # /a = 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setDoubleValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setDoubleValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a double value. If the node has no type, it will be set to a double type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. If the node is an integer type, it will be truncated to the closest integer to zero. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a double number. It must be a valid number or a string that can be converted to a number.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(1);&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1 (type: int)&lt;br /&gt;
node.setDoubleValue(&amp;quot;-1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = -1 (type: int)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setDoubleValue(&amp;quot;a&amp;quot;, 12.2);&lt;br /&gt;
props.dump(node); # /a = 12.2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setIntValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setIntValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to an integer value. If the node has no type, it will be set to a integer type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a integer. It must be a valid number or a string that can be converted to a number. If the number is a double, it will be truncated to the closest integer to zero.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(&amp;quot;6&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 6&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(-12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setIntValue(12.5);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = &amp;quot;12&amp;quot; (type: string)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setIntValue(&amp;quot;a&amp;quot;, 12);&lt;br /&gt;
props.dump(node); # /a = 12&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValue([rel_path, ]value);&lt;br /&gt;
|text = {{hatnote|See also {{func link|setBoolValue()|page=this}}, {{func link|setDoubleValue()|page=this}}, and {{func link|setIntValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
{{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a given value. See table below for conversions and effects. Note that vec3d and vec4d types are not fully integrated into SGPropertyNode. Returns 1 (true) if the operation was successful.&lt;br /&gt;
&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align:right&amp;quot; {{!}} '''value''' type → &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Number&lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} String &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Vector&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} style=&amp;quot;text-align:left&amp;quot; {{!}} Current node&amp;lt;br&amp;gt;type ↓&lt;br /&gt;
{{!}} style=&amp;quot;text-align:right&amp;quot; {{!}} Result ↘&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} None/unspecified&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;double&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;vec*d&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Bool&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' != 0, node set to true.&lt;br /&gt;
* If '''value''' == 0, node set to false.&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' == &amp;quot;true&amp;quot;, node set to true.&lt;br /&gt;
* If '''value''' can be converted to an integer,&amp;lt;br&amp;gt;and != 0, node set to true.&lt;br /&gt;
{{!}} Node set to true.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Integer&lt;br /&gt;
{{!}} Node set to truncated '''value'''&lt;br /&gt;
{{!}} Node set to '''value ''' converted and truncated to an integer.&lt;br /&gt;
{{!}} Node set to 1.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Double&lt;br /&gt;
{{!}} Node set to '''value'''.&lt;br /&gt;
{{!}} Node set to '''value''' converted to number.&lt;br /&gt;
{{!}} Throws an error (vector is not a number).&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} String&lt;br /&gt;
{{!}} Node set to '''value''' converted to string. {{!!}} Node set to '''value'''. {{!!}} Node not set.&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to. Must be a string, a valid number, or a vector consisting of 3 or 4 numbers. See table above for conversions and effects.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setValue(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # \a = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValues(val);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getValues()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Sets the nodes property tree from a Nasal hash. Scalars will become nodes in the tree and hashes will become named subnodes. Vectors will be converted into indexed nodes, with the values in the vector becoming their values (see examples below).&lt;br /&gt;
|param1 = val&lt;br /&gt;
|param1text = A hash that will become the property tree.&lt;br /&gt;
|example1 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 100 # &amp;quot;a&amp;quot; will become the subnode's name, and 100 its value&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
|example2 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== unalias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.unalias();&lt;br /&gt;
|text = Un-aliases the node and returns it to a blank state. Returns 1 on success and 0 on failure (e.g., when used on a tied property).&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.35);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
&lt;br /&gt;
props.dump(node2); # equals 2.35&lt;br /&gt;
node2.unalias();&lt;br /&gt;
props.dump(node2); # no value or type&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== toggleBoolValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = toggleBoolValue();&lt;br /&gt;
|text = Toggle a boolean property. You have to make sure the property is of type bool!&lt;br /&gt;
|example1 = var b = props.Node.new().initNode(&amp;quot;/_test/bool&amp;quot;, 1, &amp;quot;BOOL&amp;quot;);&lt;br /&gt;
print(&amp;quot;bool &amp;quot;, b.getValue());&lt;br /&gt;
b.toggleBoolValue();&lt;br /&gt;
print(&amp;quot;after toggleBoolValue &amp;quot;, b.getValue());&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
=== compileCondition() ===&lt;br /&gt;
{{see also|Conditions}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.compileCondition(node);&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|43f8ce08706629e69984b1cc34e5e979d03ef09a|t=commit}}&lt;br /&gt;
|text = Compiles a [[conditions|condition]] property branch and returns a &amp;lt;code&amp;gt;Condition&amp;lt;/code&amp;gt; ghost object or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error. This ghost will contain a &amp;lt;code&amp;gt;test()&amp;lt;/code&amp;gt; function that will return the result of the condition as either 1 (true) or 0 (false).&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== condition() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.condition(node);&lt;br /&gt;
|text = Evaluates a [[conditions|condition]] property branch and returns the result as a boolean.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== copy() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.copy(src, dest[, attr]);&lt;br /&gt;
|text = Copies the property tree of the source into the destination node. Note that aliased properties will not be copied.&lt;br /&gt;
|param1 = src&lt;br /&gt;
|param1text = Source &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy from.&lt;br /&gt;
|param2 = dest&lt;br /&gt;
|param2text = Destination &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy to.&lt;br /&gt;
|param3 = attr&lt;br /&gt;
|param3text = If set to 1 (true), attributes will also be copied. Defaults to 0 (false).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1.5,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var src = props.Node.new(tree);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest);&lt;br /&gt;
props.dump(dest);&lt;br /&gt;
|example2 = var src = props.Node.new();&lt;br /&gt;
var a = src.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
a.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
a.setIntValue(12);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest, 1);&lt;br /&gt;
print(dest.getNode(&amp;quot;a&amp;quot;).getAttribute(&amp;quot;trace-write&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== dump() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.dump(node);&lt;br /&gt;
|text = Recursively dump the state of a node into the console, showing value and type of each node. Note that as of 10/2016, the value of vec*d type nodes cannot be dumped.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to dump.&lt;br /&gt;
|example1 = var node = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # dump into console&lt;br /&gt;
|example2 = # Dump the entire Property Tree&lt;br /&gt;
# Warning! This is an intensive operation!&lt;br /&gt;
props.dump(props.globals);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== getNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.getNode();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|807062d0b6f858885547f27325e829c2bf42a12b|t=commit}}&lt;br /&gt;
|text = Shortcut for &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;props.globals.getNode()&amp;lt;/syntaxhighlight&amp;gt;. See {{func link|getNode()||Node|page=this}} for full documentation.&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft is: &amp;quot;, props.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== nodeList() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.nodeList(arg[, arg[, ...]]);&lt;br /&gt;
|text = Converts its arguments into a vector of node objects if possible and returns that vector. &lt;br /&gt;
|param1 = arg&lt;br /&gt;
|param1text = Object to operate on. Must be a node object, string, vector, hash, function, or &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost. Vectors and hashes must contain any of the other acceptable types, functions must return any of the other types, strings will be assumed to be paths to global properties, and ghosts will be converted into node objects. There may be any number of arguments.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var f = func(){&lt;br /&gt;
    var n = props.Node.new();&lt;br /&gt;
    return n._g;&lt;br /&gt;
}&lt;br /&gt;
var list = props.nodeList(node,&lt;br /&gt;
    &amp;quot;/sim/aircraft&amp;quot;,&lt;br /&gt;
    [&amp;quot;/sim/fg-root&amp;quot;],&lt;br /&gt;
    { &amp;quot;path&amp;quot;: &amp;quot;/sim/fg-home&amp;quot; },&lt;br /&gt;
    f&lt;br /&gt;
);&lt;br /&gt;
debug.dump(list); # dump list&lt;br /&gt;
|example2 = var root = &amp;quot;/sim/version/&amp;quot;;&lt;br /&gt;
var info = [&lt;br /&gt;
    root ~ &amp;quot;build-id&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;build-number&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;flightgear&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;hla-support&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;openscenegraph&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;revision&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;simgear&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
info = props.nodeList(info); # turn into list of nodes&lt;br /&gt;
foreach(var n; info){&lt;br /&gt;
    print(n.getValue()); # dump info&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== runBinding() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.runBinding(node[, module]);&lt;br /&gt;
|text = Runs a [[Bindings|binding]] element in a node object. Returns 1 (true) on success and 0 (false) on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = A {{tag|binding}} element as a node object.&lt;br /&gt;
|param2 = module&lt;br /&gt;
|param2text = Optional string specifying a module to run Nasal scripts in if the command is &amp;lt;code&amp;gt;nasal&amp;lt;/code&amp;gt;. This argument will not override any {{tag|module}} element in the '''node'''&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;dialog-show&amp;quot;,&lt;br /&gt;
    &amp;quot;dialog-name&amp;quot;: &amp;quot;map&amp;quot; # open map&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)' # prints value of math.pi&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;math&amp;quot;);&lt;br /&gt;
|example3 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)', # prints value of math.pi&lt;br /&gt;
    &amp;quot;module&amp;quot;: &amp;quot;math&amp;quot; # this is used&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;debug&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setAll() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.setAll(base, child, value);&lt;br /&gt;
|text = Sets indexed subnodes in the Property Tree with the same name to the same value.&lt;br /&gt;
|param1 = base&lt;br /&gt;
|param1text = Base path to the nodes.&lt;br /&gt;
|param2 = child&lt;br /&gt;
|param2text = Path to child nodes.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Value to set the subnodes to.&lt;br /&gt;
|example1 = # apply 50% throttle to all engines&lt;br /&gt;
props.setAll(&amp;quot;/controls/engines/engine&amp;quot;, &amp;quot;throttle&amp;quot;, 0.5);&lt;br /&gt;
|example2 = var nodes = props.globals.addChildren(&amp;quot;/test&amp;quot;, 3);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
props.setAll(&amp;quot;/test&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;); # set all children (test[*]/a) to &amp;quot;Hi&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrap() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrap(node);&lt;br /&gt;
|text = Turns &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghosts, either in a vector or single, into &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; objects.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or vector of such ghosts.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrap(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var vector = [canvas._newCanvasGhost()._node_ghost, props.Node.new()._g];&lt;br /&gt;
var nodes = props.wrap(vector);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    props.dump(node);&lt;br /&gt;
    print(&amp;quot;----&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrapNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrapNode(node);&lt;br /&gt;
|text = Turns a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost into a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost to convert.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrapNode(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Variable ==&lt;br /&gt;
=== globals ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.globals;&lt;br /&gt;
|text = Exposes the [[Property Tree]] as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft: &amp;quot;, props.globals.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
|example2text = Alternative using {{func link|getprop()}}.&lt;br /&gt;
|example2 = print(&amp;quot;Current aircraft: &amp;quot;, getprop(&amp;quot;/sim/aircraft&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143167</id>
		<title>Nasal library/props</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143167"/>
		<updated>2025-12-04T18:32:33Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* initNode() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page contains documentation for the '''&amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace''' in [[Nasal]]. This namespace provides APIs for working with property trees (including the main [[Property Tree]]) via {{API Link|simgear|class|SGPropertyNode}}. The &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace is sourced from {{fgdata source|Nasal/props.nas}} and {{flightgear source|src/Scripting/nasal-props.cxx}}.&lt;br /&gt;
&lt;br /&gt;
== Class ==&lt;br /&gt;
=== Node ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|mode = class&lt;br /&gt;
|text = The main class, used widely for manipulating property trees.&lt;br /&gt;
}}&lt;br /&gt;
==== new() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.new([values]);&lt;br /&gt;
|text = Constructor function. Returns a new &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = values&lt;br /&gt;
|param1text = An optional hash that will be the initial property structure.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    a: 1,&lt;br /&gt;
    b: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;],&lt;br /&gt;
    c: {&lt;br /&gt;
        d: 1 * 4&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChild(name[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|a15d5829379864c7138edff621d6864c5f426d0b|t=commit}}&lt;br /&gt;
|text = Add a new, blank child to the node. Returns the newly-created node.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the node as a string.&lt;br /&gt;
|param2 = min_idx&lt;br /&gt;
|param2text = This specifies the minimum index to add the new one to. This takes precedence over '''append'''. Defaults to 0.&lt;br /&gt;
|param3 = append&lt;br /&gt;
|param3text = If there is already one or more children with the same name and this argument is 1 (true), the new child will be added after the child with the highest index. If 0 (false), the new child will be added at the lowest available index with the limit of '''min_idx'''. Defaults to 1 (true).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, 0);&lt;br /&gt;
props.dump(node); # a[1] and a[0]&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChildren(name, count[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|7f1117a5374beabb045fb76fbb6dd8bfb0128100|t=commit}}&lt;br /&gt;
|text = Adds multiple children with the same name to this node. Returns &amp;lt;code&amp;gt;'''nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the nodes as a string.&lt;br /&gt;
|param2 = count&lt;br /&gt;
|param2text = Number of new children to add.&lt;br /&gt;
|param3 = min_idx&lt;br /&gt;
|param3text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = append&lt;br /&gt;
|param4text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[0] and a[1]&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[2]&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 0, 0);&lt;br /&gt;
props.dump(node); # a[2], a[0], and a[1]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== adjustValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.adjustValue(delta);&lt;br /&gt;
|text = Adds delta (numeric) to current value respecting the node type. &lt;br /&gt;
|param1 = delta&lt;br /&gt;
|param1text = Numeric value (can be negative) to add to current node value.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== alias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.alias(node, chainListener=0);&lt;br /&gt;
|text = Aliases this node to another one. Returns 1 on success and 0 on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = The node to alias to. Can be one of:&lt;br /&gt;
* A path to a property in the [[Property Tree]] as a string.&lt;br /&gt;
* A &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost.&lt;br /&gt;
* A &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/position/altitude-ft&amp;quot;);&lt;br /&gt;
props.dump(node); # equals the current altitude&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.34);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
props.dump(node2); # equals 2.34&lt;br /&gt;
|param2=chainListener|param2text=Specify if listeners should work on aliased properties or not. Current default is false forbackwards compatability.}}&lt;br /&gt;
&lt;br /&gt;
==== clearValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.clearValue();&lt;br /&gt;
|text = Clears the value and type of the node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.34);&lt;br /&gt;
props.dump(node); # prints &amp;quot;{DOUBLE} = 2.35&amp;quot;&lt;br /&gt;
node.clearValue();&lt;br /&gt;
props.dump(node); # prints &amp;quot;{NONE} = nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== decrement() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.decrement(n = 1);&lt;br /&gt;
|text = Decrements integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to subtract, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== equals() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.equals(node);&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{fgdata commit|d80722065f3c11e0511fa888b1f5f310dd0183e3|t=commit}}&lt;br /&gt;
|text = Checks whether the node refers to the same one as another. Returns 1 (true) if it is, and 0 (false) if otherwise.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to check against. May be either a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var n = props.Node.new();&lt;br /&gt;
var a = n;&lt;br /&gt;
print(a.equals(n)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAliasTarget() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAliasTarget();&lt;br /&gt;
|text = Returns the alias target of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 2.35);&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/test&amp;quot;);&lt;br /&gt;
var tgt = node.getAliasTarget();&lt;br /&gt;
print(tgt.getPath()); # prints &amp;quot;/test&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAttribute([rel_path, ]name);&lt;br /&gt;
props.Node.getAttribute();&lt;br /&gt;
|text = Returns an attribute. If no arguments are given, the function will return an integer specifying the attributes set for the node (see {{simgear source|simgear/props/props.hxx|l=767}} for a list). A list of attributes are below&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Return value&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} last {{!!}} The highest used attribute code (should be 128). See for {{simgear source|simgear/props/props.hxx|l=767}} the codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} children {{!!}} Number of child nodes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} listeners {{!!}} Number of listeners connected to this node.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} references {{!!}} Number of times the node has previously been referenced.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} tied {{!!}} Whether the node is tied.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} alias {{!!}} Whether the node is aliased.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = Attribute as a string. See the above table for a full list.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;children&amp;quot;)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2text = Example using relative path&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;a&amp;quot;, &amp;quot;readable&amp;quot;)); # prints &amp;quot;1&amp;quot; (node can be read from)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node);&lt;br /&gt;
print(node2.getAttribute(&amp;quot;alias&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example4 = print(props.globals.getNode(&amp;quot;/sim/signals/fdm-initialized&amp;quot;).getAttribute(&amp;quot;listeners&amp;quot;)); # prints the number of listeners&lt;br /&gt;
|example5 = print(props.globals.getNode(&amp;quot;/sim/time/elapsed-sec&amp;quot;).getAttribute(&amp;quot;tied&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning it is tied&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute(&amp;quot;writable&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning the node can be written to&lt;br /&gt;
|example7text = Example using no arguments&lt;br /&gt;
|example7 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute()); # prints &amp;quot;3&amp;quot; (true), meaning the node can be read from (1) and written to (2)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getBoolValue();&lt;br /&gt;
|text = Returns the value of a node converted to a boolean. If the node is a number type, 0  will return false, while 1 will return true. If the node is a string or unspecified type and the value is &amp;lt;code&amp;gt;&amp;quot;false&amp;quot;&amp;lt;/code&amp;gt;, false will be returned. Otherwise, true will be returned. Remember that boolean values are represented in Nasal as 1 (true) and 0 (false).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(-1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(-2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setValue(&amp;quot;false&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChild(rel_path[, idx[, create]]);&lt;br /&gt;
|text = Returns a child of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the child node as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Optional index for the child node as an integer.&lt;br /&gt;
|param3 = create&lt;br /&gt;
|param3text = If set to 1 (true), a new child will be created if it does not exist. If set to 0 (false), the function will not create a new child and the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no child exists. Defaults to 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.getNode(&amp;quot;a[1]&amp;quot;).setDoubleValue(2.35);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
print(c.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1, 1);&lt;br /&gt;
props.dump(node); # new child a[1] will have appeared&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChildren([name]);&lt;br /&gt;
|text = Returns a vector of child nodes, optionally those with a certain name, as &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instances.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of the child nodes as a string. If not given, all children will be returned.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren()); # all child nodes in the vector&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren(&amp;quot;b&amp;quot;)); # only children with the name &amp;quot;b&amp;quot; in the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getIndex() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getIndex();&lt;br /&gt;
|text = Returns the index of a node as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
print(node.getChild(&amp;quot;a&amp;quot;, 1).getIndex()); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;b&amp;quot;);&lt;br /&gt;
print(node.getChild(&amp;quot;b&amp;quot;).getIndex()); # prints &amp;quot;0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getName() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getName();&lt;br /&gt;
|text = Returns the name of the node as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var c = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
debug.dump(c.getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChild(&amp;quot;a&amp;quot;, 2).getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getNode(rel_path[, create]);&lt;br /&gt;
|text = Returns a subnode as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the subnode as a string.&lt;br /&gt;
|param2 = create&lt;br /&gt;
|param2text = If true (1), the node will be created if it does not exist. If false (0) and the node does not exist, the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;. Default to false (0).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getValue()); # prints &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {}&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.getNode(&amp;quot;c/d&amp;quot;, true).setDoubleValue(2.35);&lt;br /&gt;
props.dump(node); # c/d now exists&lt;br /&gt;
|example3 = var ac = props.globals.getNode(&amp;quot;sim/aircraft&amp;quot;);&lt;br /&gt;
print(&amp;quot;Current aircraft is: &amp;quot;, ac.getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getParent() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getParent();&lt;br /&gt;
|text = Returns the parent of a node, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there is no parent.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node.getNode(&amp;quot;c/d&amp;quot;).getParent()); # dumps &amp;quot;c&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
debug.dump(node.getParent()); # prints nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getPath() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getPath();&lt;br /&gt;
|text = Returns the path of the node as a string.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getPath()); # prints &amp;quot;/c/d&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getType() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getType();&lt;br /&gt;
|text = Returns node's type as a string. It should be one of &amp;quot;NONE&amp;quot;, &amp;quot;ALIAS&amp;quot;, &amp;quot;BOOL&amp;quot;, &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot; (long integer), &amp;quot;FLOAT&amp;quot;, &amp;quot;DOUBLE&amp;quot;, &amp;quot;STRING&amp;quot;, &amp;quot;VEC3D&amp;quot;, &amp;quot;VEC4D&amp;quot;, &amp;quot;UNSPECIFIED&amp;quot;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
print(node.getType()); # prints &amp;quot;NONE&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
print(node.getType()); # prints &amp;quot;INT&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValue([rel_path]);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getBoolValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Returns the value of the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path to a subnode as a string, which may contain '/' characters. If the subnode does not exist, we return nil.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.35);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue(&amp;quot;a&amp;quot;)); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0, 0.5, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0, 0.5, 1]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValues();&lt;br /&gt;
|text = Returns the node tree as a hash, with all the various subnodes, etc. If the node has no children, the result is equivalent to {{func link|getValue()|page=this}}. Subnodes that are indexed will be combined into one key and their values placed in a vector (see example 2).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;string&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;number&amp;quot;: 1.2,&lt;br /&gt;
    &amp;quot;subnode&amp;quot;: {&lt;br /&gt;
        &amp;quot;idx-node&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.addChild(&amp;quot;bool&amp;quot;).setBoolValue(1);&lt;br /&gt;
props.dump(node); # dump to node tree&lt;br /&gt;
debug.dump(node.getValues()); # dump the node converted to hash&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: [1, 2, 3]&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # a[0] = 1, a[1] = 2, a[2] = 3&lt;br /&gt;
debug.dump(node.getValues()); # a: [1, 2, 3]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== increment() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.increment(n = 1);&lt;br /&gt;
|text = Increments integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to add, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== initNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.initNode([path[, value[, type[, force]]]]);&lt;br /&gt;
|text = Initializes a node if it doesn't exist and returns that node as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to a subnode as a string. If not given, the node itself will be initialized.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Optional default value to initialize the node with.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Optional string that will set the type of the node. Must be one of &amp;lt;code&amp;gt;&amp;quot;DOUBLE&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;INT&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;BOOL&amp;quot;&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;&amp;quot;STRING&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = force&lt;br /&gt;
|param4text = If set to true (1), the node's type will be forced to change.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;);&lt;br /&gt;
props.dump(a); # a = 1&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setBoolValue(0);&lt;br /&gt;
props.dump(node.getChild(&amp;quot;a&amp;quot;)); # a = 0 (type: bool)&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;, true);&lt;br /&gt;
props.dump(a); # a = 0 (type: int)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isInt() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isInt();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot; or &amp;quot;LONG&amp;quot; (long integer) otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isNumeric() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isNumeric();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot;, &amp;quot;FLOAT&amp;quot; or &amp;quot;DOUBLE&amp;quot; otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== remove() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.remove();&lt;br /&gt;
|text = Removes the node and returns the removed node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).remove();&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; does not exist anymore&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeAllChildren() ====&lt;br /&gt;
{{Caution|Be careful when  using this API in conjunction with the Canvas system and element specific properties, for details please see [[Howto:Canvas Path Benchmarking]]}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeAllChildren();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|4766ed21a68230c0c15265e5604a74f4d99e0f5d|t=commit}}&lt;br /&gt;
|text = Removes all child nodes and returns the node.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeAllChildren();&lt;br /&gt;
props.dump(node); # all children have been removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChild(rel_path, idx);&lt;br /&gt;
|text = Removes a given child node child nodes and returns the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to a subnode as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Index of the subnode to remove as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; has been removed&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # just a[1] remains&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChildren([name]);&lt;br /&gt;
|text = Removes all children with a specified name. If no arguments are given, all children will be removed (see also {{func link|removeAllChildren()|page=this}}).&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of children to remove as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node); # just children named &amp;quot;b&amp;quot; remain&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren();&lt;br /&gt;
props.dump(node); # all children removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setAttribute([rel_path, ]attr, value);&lt;br /&gt;
props.Node.setAttribute(attrs);&lt;br /&gt;
|text = Sets an attribute or multiple attributes. A list of attributes and their codes are below. For a brand new node, the default attributes are ''readable'' and ''writable''. Returns an integer specifying the old attributes.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Description&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = attr&lt;br /&gt;
|param2text = Name of attribute to set as a string. See above.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Boolean value to set the property to.&lt;br /&gt;
|param4 = attrs&lt;br /&gt;
|param4text = When the function is used in its second form, this argument is used. This argument should be an integer specifying which arguments are set to true. See {{simgear source|simgear/props/props.hxx|l=767}} for the full list of codes. Simply add codes of the desired attributes together.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;readable&amp;quot;, 0);&lt;br /&gt;
var val = node.getValue();&lt;br /&gt;
debug.dump(val); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setAttribute(&amp;quot;a&amp;quot;, &amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).setIntValue(12); # will be traced&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(35); # read + write + trace-write&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setBoolValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a boolean value. If the node has no type, it will be set to a bool type. If the node is already a number type, it will be set to either 1 or 0. If it is a string, it will be set to either &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a boolean. If it is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, it will be false. If it is a string, it will be false. If it is a number, 0 will be false, while other numbers will be true. All other cases will be interpreted as 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(nil);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
node.setBoolValue(1.25);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;String&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;String&amp;quot; (type: string)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = &amp;quot;true&amp;quot;&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(12.32);&lt;br /&gt;
props.dump(node); # node = 12.32 (type: double)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = 1&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setBoolValue(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # /a = 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setDoubleValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setDoubleValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a double value. If the node has no type, it will be set to a double type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. If the node is an integer type, it will be truncated to the closest integer to zero. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a double number. It must be a valid number or a string that can be converted to a number.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(1);&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1 (type: int)&lt;br /&gt;
node.setDoubleValue(&amp;quot;-1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = -1 (type: int)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setDoubleValue(&amp;quot;a&amp;quot;, 12.2);&lt;br /&gt;
props.dump(node); # /a = 12.2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setIntValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setIntValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to an integer value. If the node has no type, it will be set to a integer type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a integer. It must be a valid number or a string that can be converted to a number. If the number is a double, it will be truncated to the closest integer to zero.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(&amp;quot;6&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 6&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(-12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setIntValue(12.5);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = &amp;quot;12&amp;quot; (type: string)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setIntValue(&amp;quot;a&amp;quot;, 12);&lt;br /&gt;
props.dump(node); # /a = 12&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValue([rel_path, ]value);&lt;br /&gt;
|text = {{hatnote|See also {{func link|setBoolValue()|page=this}}, {{func link|setDoubleValue()|page=this}}, and {{func link|setIntValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
{{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a given value. See table below for conversions and effects. Note that vec3d and vec4d types are not fully integrated into SGPropertyNode. Returns 1 (true) if the operation was successful.&lt;br /&gt;
&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align:right&amp;quot; {{!}} '''value''' type → &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Number&lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} String &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Vector&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} style=&amp;quot;text-align:left&amp;quot; {{!}} Current node&amp;lt;br&amp;gt;type ↓&lt;br /&gt;
{{!}} style=&amp;quot;text-align:right&amp;quot; {{!}} Result ↘&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} None/unspecified&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;double&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;vec*d&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Bool&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' != 0, node set to true.&lt;br /&gt;
* If '''value''' == 0, node set to false.&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' == &amp;quot;true&amp;quot;, node set to true.&lt;br /&gt;
* If '''value''' can be converted to an integer,&amp;lt;br&amp;gt;and != 0, node set to true.&lt;br /&gt;
{{!}} Node set to true.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Integer&lt;br /&gt;
{{!}} Node set to truncated '''value'''&lt;br /&gt;
{{!}} Node set to '''value ''' converted and truncated to an integer.&lt;br /&gt;
{{!}} Node set to 1.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Double&lt;br /&gt;
{{!}} Node set to '''value'''.&lt;br /&gt;
{{!}} Node set to '''value''' converted to number.&lt;br /&gt;
{{!}} Throws an error (vector is not a number).&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} String&lt;br /&gt;
{{!}} Node set to '''value''' converted to string. {{!!}} Node set to '''value'''. {{!!}} Node not set.&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to. Must be a string, a valid number, or a vector consisting of 3 or 4 numbers. See table above for conversions and effects.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setValue(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # \a = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValues(val);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getValues()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Sets the nodes property tree from a Nasal hash. Scalars will become nodes in the tree and hashes will become named subnodes. Vectors will be converted into indexed nodes, with the values in the vector becoming their values (see examples below).&lt;br /&gt;
|param1 = val&lt;br /&gt;
|param1text = A hash that will become the property tree.&lt;br /&gt;
|example1 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 100 # &amp;quot;a&amp;quot; will become the subnode's name, and 100 its value&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
|example2 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== unalias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.unalias();&lt;br /&gt;
|text = Un-aliases the node and returns it to a blank state. Returns 1 on success and 0 on failure (e.g., when used on a tied property).&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.35);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
&lt;br /&gt;
props.dump(node2); # equals 2.35&lt;br /&gt;
node2.unalias();&lt;br /&gt;
props.dump(node2); # no value or type&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== toggleBoolValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = toggleBoolValue();&lt;br /&gt;
|text = Toggle a boolean property. You have to make sure the property is of type bool!&lt;br /&gt;
|example1 = var b = props.Node.new().initNode(&amp;quot;/_test/bool&amp;quot;, 1, &amp;quot;BOOL&amp;quot;);&lt;br /&gt;
print(&amp;quot;bool &amp;quot;, b.getValue());&lt;br /&gt;
b.toggleBoolValue();&lt;br /&gt;
print(&amp;quot;after toggleBoolValue &amp;quot;, b.getValue());&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
=== compileCondition() ===&lt;br /&gt;
{{see also|Conditions}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.compileCondition(node);&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|43f8ce08706629e69984b1cc34e5e979d03ef09a|t=commit}}&lt;br /&gt;
|text = Compiles a [[conditions|condition]] property branch and returns a &amp;lt;code&amp;gt;Condition&amp;lt;/code&amp;gt; ghost object or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error. This ghost will contain a &amp;lt;code&amp;gt;test()&amp;lt;/code&amp;gt; function that will return the result of the condition as either 1 (true) or 0 (false).&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== condition() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.condition(node);&lt;br /&gt;
|text = Evaluates a [[conditions|condition]] property branch and returns the result as a boolean.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== copy() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.copy(src, dest[, attr]);&lt;br /&gt;
|text = Copies the property tree of the source into the destination node. Note that aliased properties will not be copied.&lt;br /&gt;
|param1 = src&lt;br /&gt;
|param1text = Source &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy from.&lt;br /&gt;
|param2 = dest&lt;br /&gt;
|param2text = Destination &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy to.&lt;br /&gt;
|param3 = attr&lt;br /&gt;
|param3text = If set to 1 (true), attributes will also be copied. Defaults to 0 (false).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1.5,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var src = props.Node.new(tree);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest);&lt;br /&gt;
props.dump(dest);&lt;br /&gt;
|example2 = var src = props.Node.new();&lt;br /&gt;
var a = src.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
a.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
a.setIntValue(12);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest, 1);&lt;br /&gt;
print(dest.getNode(&amp;quot;a&amp;quot;).getAttribute(&amp;quot;trace-write&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== dump() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.dump(node);&lt;br /&gt;
|text = Recursively dump the state of a node into the console, showing value and type of each node. Note that as of 10/2016, the value of vec*d type nodes cannot be dumped.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to dump.&lt;br /&gt;
|example1 = var node = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # dump into console&lt;br /&gt;
|example2 = # Dump the entire Property Tree&lt;br /&gt;
# Warning! This is an intensive operation!&lt;br /&gt;
props.dump(props.globals);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== getNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.getNode();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|807062d0b6f858885547f27325e829c2bf42a12b|t=commit}}&lt;br /&gt;
|text = Shortcut for &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;props.globals.getNode()&amp;lt;/syntaxhighlight&amp;gt;. See {{func link|getNode()||Node|page=this}} for full documentation.&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft is: &amp;quot;, props.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== nodeList() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.nodeList(arg[, arg[, ...]]);&lt;br /&gt;
|text = Converts its arguments into a vector of node objects if possible and returns that vector. &lt;br /&gt;
|param1 = arg&lt;br /&gt;
|param1text = Object to operate on. Must be a node object, string, vector, hash, function, or &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost. Vectors and hashes must contain any of the other acceptable types, functions must return any of the other types, strings will be assumed to be paths to global properties, and ghosts will be converted into node objects. There may be any number of arguments.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var f = func(){&lt;br /&gt;
    var n = props.Node.new();&lt;br /&gt;
    return n._g;&lt;br /&gt;
}&lt;br /&gt;
var list = props.nodeList(node,&lt;br /&gt;
    &amp;quot;/sim/aircraft&amp;quot;,&lt;br /&gt;
    [&amp;quot;/sim/fg-root&amp;quot;],&lt;br /&gt;
    { &amp;quot;path&amp;quot;: &amp;quot;/sim/fg-home&amp;quot; },&lt;br /&gt;
    f&lt;br /&gt;
);&lt;br /&gt;
debug.dump(list); # dump list&lt;br /&gt;
|example2 = var root = &amp;quot;/sim/version/&amp;quot;;&lt;br /&gt;
var info = [&lt;br /&gt;
    root ~ &amp;quot;build-id&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;build-number&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;flightgear&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;hla-support&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;openscenegraph&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;revision&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;simgear&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
info = props.nodeList(info); # turn into list of nodes&lt;br /&gt;
foreach(var n; info){&lt;br /&gt;
    print(n.getValue()); # dump info&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== runBinding() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.runBinding(node[, module]);&lt;br /&gt;
|text = Runs a [[Bindings|binding]] element in a node object. Returns 1 (true) on success and 0 (false) on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = A {{tag|binding}} element as a node object.&lt;br /&gt;
|param2 = module&lt;br /&gt;
|param2text = Optional string specifying a module to run Nasal scripts in if the command is &amp;lt;code&amp;gt;nasal&amp;lt;/code&amp;gt;. This argument will not override any {{tag|module}} element in the '''node'''&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;dialog-show&amp;quot;,&lt;br /&gt;
    &amp;quot;dialog-name&amp;quot;: &amp;quot;map&amp;quot; # open map&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)' # prints value of math.pi&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;math&amp;quot;);&lt;br /&gt;
|example3 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)', # prints value of math.pi&lt;br /&gt;
    &amp;quot;module&amp;quot;: &amp;quot;math&amp;quot; # this is used&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;debug&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setAll() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.setAll(base, child, value);&lt;br /&gt;
|text = Sets indexed subnodes in the Property Tree with the same name to the same value.&lt;br /&gt;
|param1 = base&lt;br /&gt;
|param1text = Base path to the nodes.&lt;br /&gt;
|param2 = child&lt;br /&gt;
|param2text = Path to child nodes.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Value to set the subnodes to.&lt;br /&gt;
|example1 = # apply 50% throttle to all engines&lt;br /&gt;
props.setAll(&amp;quot;/controls/engines/engine&amp;quot;, &amp;quot;throttle&amp;quot;, 0.5);&lt;br /&gt;
|example2 = var nodes = props.globals.addChildren(&amp;quot;/test&amp;quot;, 3);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
props.setAll(&amp;quot;/test&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;); # set all children (test[*]/a) to &amp;quot;Hi&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrap() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrap(node);&lt;br /&gt;
|text = Turns &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghosts, either in a vector or single, into &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; objects.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or vector of such ghosts.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrap(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var vector = [canvas._newCanvasGhost()._node_ghost, props.Node.new()._g];&lt;br /&gt;
var nodes = props.wrap(vector);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    props.dump(node);&lt;br /&gt;
    print(&amp;quot;----&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrapNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrapNode(node);&lt;br /&gt;
|text = Turns a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost into a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost to convert.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrapNode(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Variable ==&lt;br /&gt;
=== globals ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.globals;&lt;br /&gt;
|text = Exposes the [[Property Tree]] as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft: &amp;quot;, props.globals.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
|example2text = Alternative using {{func link|getprop()}}.&lt;br /&gt;
|example2 = print(&amp;quot;Current aircraft: &amp;quot;, getprop(&amp;quot;/sim/aircraft&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143166</id>
		<title>Nasal library/props</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=143166"/>
		<updated>2025-12-04T18:31:17Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* getNode() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page contains documentation for the '''&amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace''' in [[Nasal]]. This namespace provides APIs for working with property trees (including the main [[Property Tree]]) via {{API Link|simgear|class|SGPropertyNode}}. The &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace is sourced from {{fgdata source|Nasal/props.nas}} and {{flightgear source|src/Scripting/nasal-props.cxx}}.&lt;br /&gt;
&lt;br /&gt;
== Class ==&lt;br /&gt;
=== Node ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|mode = class&lt;br /&gt;
|text = The main class, used widely for manipulating property trees.&lt;br /&gt;
}}&lt;br /&gt;
==== new() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.new([values]);&lt;br /&gt;
|text = Constructor function. Returns a new &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = values&lt;br /&gt;
|param1text = An optional hash that will be the initial property structure.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    a: 1,&lt;br /&gt;
    b: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;],&lt;br /&gt;
    c: {&lt;br /&gt;
        d: 1 * 4&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChild(name[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|a15d5829379864c7138edff621d6864c5f426d0b|t=commit}}&lt;br /&gt;
|text = Add a new, blank child to the node. Returns the newly-created node.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the node as a string.&lt;br /&gt;
|param2 = min_idx&lt;br /&gt;
|param2text = This specifies the minimum index to add the new one to. This takes precedence over '''append'''. Defaults to 0.&lt;br /&gt;
|param3 = append&lt;br /&gt;
|param3text = If there is already one or more children with the same name and this argument is 1 (true), the new child will be added after the child with the highest index. If 0 (false), the new child will be added at the lowest available index with the limit of '''min_idx'''. Defaults to 1 (true).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, 0);&lt;br /&gt;
props.dump(node); # a[1] and a[0]&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChildren(name, count[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|7f1117a5374beabb045fb76fbb6dd8bfb0128100|t=commit}}&lt;br /&gt;
|text = Adds multiple children with the same name to this node. Returns &amp;lt;code&amp;gt;'''nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the nodes as a string.&lt;br /&gt;
|param2 = count&lt;br /&gt;
|param2text = Number of new children to add.&lt;br /&gt;
|param3 = min_idx&lt;br /&gt;
|param3text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = append&lt;br /&gt;
|param4text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[0] and a[1]&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[2]&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 0, 0);&lt;br /&gt;
props.dump(node); # a[2], a[0], and a[1]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== adjustValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.adjustValue(delta);&lt;br /&gt;
|text = Adds delta (numeric) to current value respecting the node type. &lt;br /&gt;
|param1 = delta&lt;br /&gt;
|param1text = Numeric value (can be negative) to add to current node value.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== alias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.alias(node, chainListener=0);&lt;br /&gt;
|text = Aliases this node to another one. Returns 1 on success and 0 on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = The node to alias to. Can be one of:&lt;br /&gt;
* A path to a property in the [[Property Tree]] as a string.&lt;br /&gt;
* A &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost.&lt;br /&gt;
* A &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/position/altitude-ft&amp;quot;);&lt;br /&gt;
props.dump(node); # equals the current altitude&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.34);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
props.dump(node2); # equals 2.34&lt;br /&gt;
|param2=chainListener|param2text=Specify if listeners should work on aliased properties or not. Current default is false forbackwards compatability.}}&lt;br /&gt;
&lt;br /&gt;
==== clearValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.clearValue();&lt;br /&gt;
|text = Clears the value and type of the node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.34);&lt;br /&gt;
props.dump(node); # prints &amp;quot;{DOUBLE} = 2.35&amp;quot;&lt;br /&gt;
node.clearValue();&lt;br /&gt;
props.dump(node); # prints &amp;quot;{NONE} = nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== decrement() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.decrement(n = 1);&lt;br /&gt;
|text = Decrements integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to subtract, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== equals() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.equals(node);&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{fgdata commit|d80722065f3c11e0511fa888b1f5f310dd0183e3|t=commit}}&lt;br /&gt;
|text = Checks whether the node refers to the same one as another. Returns 1 (true) if it is, and 0 (false) if otherwise.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to check against. May be either a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var n = props.Node.new();&lt;br /&gt;
var a = n;&lt;br /&gt;
print(a.equals(n)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAliasTarget() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAliasTarget();&lt;br /&gt;
|text = Returns the alias target of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 2.35);&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/test&amp;quot;);&lt;br /&gt;
var tgt = node.getAliasTarget();&lt;br /&gt;
print(tgt.getPath()); # prints &amp;quot;/test&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAttribute([rel_path, ]name);&lt;br /&gt;
props.Node.getAttribute();&lt;br /&gt;
|text = Returns an attribute. If no arguments are given, the function will return an integer specifying the attributes set for the node (see {{simgear source|simgear/props/props.hxx|l=767}} for a list). A list of attributes are below&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Return value&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} last {{!!}} The highest used attribute code (should be 128). See for {{simgear source|simgear/props/props.hxx|l=767}} the codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} children {{!!}} Number of child nodes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} listeners {{!!}} Number of listeners connected to this node.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} references {{!!}} Number of times the node has previously been referenced.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} tied {{!!}} Whether the node is tied.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} alias {{!!}} Whether the node is aliased.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = Attribute as a string. See the above table for a full list.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;children&amp;quot;)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2text = Example using relative path&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;a&amp;quot;, &amp;quot;readable&amp;quot;)); # prints &amp;quot;1&amp;quot; (node can be read from)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node);&lt;br /&gt;
print(node2.getAttribute(&amp;quot;alias&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example4 = print(props.globals.getNode(&amp;quot;/sim/signals/fdm-initialized&amp;quot;).getAttribute(&amp;quot;listeners&amp;quot;)); # prints the number of listeners&lt;br /&gt;
|example5 = print(props.globals.getNode(&amp;quot;/sim/time/elapsed-sec&amp;quot;).getAttribute(&amp;quot;tied&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning it is tied&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute(&amp;quot;writable&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning the node can be written to&lt;br /&gt;
|example7text = Example using no arguments&lt;br /&gt;
|example7 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute()); # prints &amp;quot;3&amp;quot; (true), meaning the node can be read from (1) and written to (2)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getBoolValue();&lt;br /&gt;
|text = Returns the value of a node converted to a boolean. If the node is a number type, 0  will return false, while 1 will return true. If the node is a string or unspecified type and the value is &amp;lt;code&amp;gt;&amp;quot;false&amp;quot;&amp;lt;/code&amp;gt;, false will be returned. Otherwise, true will be returned. Remember that boolean values are represented in Nasal as 1 (true) and 0 (false).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(-1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(-2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setValue(&amp;quot;false&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChild(rel_path[, idx[, create]]);&lt;br /&gt;
|text = Returns a child of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the child node as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Optional index for the child node as an integer.&lt;br /&gt;
|param3 = create&lt;br /&gt;
|param3text = If set to 1 (true), a new child will be created if it does not exist. If set to 0 (false), the function will not create a new child and the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no child exists. Defaults to 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.getNode(&amp;quot;a[1]&amp;quot;).setDoubleValue(2.35);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
print(c.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1, 1);&lt;br /&gt;
props.dump(node); # new child a[1] will have appeared&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChildren([name]);&lt;br /&gt;
|text = Returns a vector of child nodes, optionally those with a certain name, as &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instances.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of the child nodes as a string. If not given, all children will be returned.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren()); # all child nodes in the vector&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren(&amp;quot;b&amp;quot;)); # only children with the name &amp;quot;b&amp;quot; in the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getIndex() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getIndex();&lt;br /&gt;
|text = Returns the index of a node as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
print(node.getChild(&amp;quot;a&amp;quot;, 1).getIndex()); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;b&amp;quot;);&lt;br /&gt;
print(node.getChild(&amp;quot;b&amp;quot;).getIndex()); # prints &amp;quot;0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getName() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getName();&lt;br /&gt;
|text = Returns the name of the node as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var c = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
debug.dump(c.getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChild(&amp;quot;a&amp;quot;, 2).getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getNode(rel_path[, create]);&lt;br /&gt;
|text = Returns a subnode as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the subnode as a string.&lt;br /&gt;
|param2 = create&lt;br /&gt;
|param2text = If true (1), the node will be created if it does not exist. If false (0) and the node does not exist, the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;. Default to false (0).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getValue()); # prints &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {}&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.getNode(&amp;quot;c/d&amp;quot;, true).setDoubleValue(2.35);&lt;br /&gt;
props.dump(node); # c/d now exists&lt;br /&gt;
|example3 = var ac = props.globals.getNode(&amp;quot;sim/aircraft&amp;quot;);&lt;br /&gt;
print(&amp;quot;Current aircraft is: &amp;quot;, ac.getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getParent() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getParent();&lt;br /&gt;
|text = Returns the parent of a node, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there is no parent.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node.getNode(&amp;quot;c/d&amp;quot;).getParent()); # dumps &amp;quot;c&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
debug.dump(node.getParent()); # prints nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getPath() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getPath();&lt;br /&gt;
|text = Returns the path of the node as a string.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getPath()); # prints &amp;quot;/c/d&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getType() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getType();&lt;br /&gt;
|text = Returns node's type as a string. It should be one of &amp;quot;NONE&amp;quot;, &amp;quot;ALIAS&amp;quot;, &amp;quot;BOOL&amp;quot;, &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot; (long integer), &amp;quot;FLOAT&amp;quot;, &amp;quot;DOUBLE&amp;quot;, &amp;quot;STRING&amp;quot;, &amp;quot;VEC3D&amp;quot;, &amp;quot;VEC4D&amp;quot;, &amp;quot;UNSPECIFIED&amp;quot;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
print(node.getType()); # prints &amp;quot;NONE&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
print(node.getType()); # prints &amp;quot;INT&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValue([rel_path]);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getBoolValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Returns the value of the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path to a subnode as a string, which may contain '/' characters. If the subnode does not exist, we return nil.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.35);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue(&amp;quot;a&amp;quot;)); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0, 0.5, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0, 0.5, 1]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValues();&lt;br /&gt;
|text = Returns the node tree as a hash, with all the various subnodes, etc. If the node has no children, the result is equivalent to {{func link|getValue()|page=this}}. Subnodes that are indexed will be combined into one key and their values placed in a vector (see example 2).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;string&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;number&amp;quot;: 1.2,&lt;br /&gt;
    &amp;quot;subnode&amp;quot;: {&lt;br /&gt;
        &amp;quot;idx-node&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.addChild(&amp;quot;bool&amp;quot;).setBoolValue(1);&lt;br /&gt;
props.dump(node); # dump to node tree&lt;br /&gt;
debug.dump(node.getValues()); # dump the node converted to hash&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: [1, 2, 3]&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # a[0] = 1, a[1] = 2, a[2] = 3&lt;br /&gt;
debug.dump(node.getValues()); # a: [1, 2, 3]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== increment() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.increment(n = 1);&lt;br /&gt;
|text = Increments integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to add, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== initNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.initNode([path[, value[, type[, force]]]]);&lt;br /&gt;
|text = Initializes a node if it doesn't exist and returns that node as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to a subnode as a string. If not given, the node itself will be initialized.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Optional default value to initialize the node with.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Optional string that will set the type of the node. Must be one of &amp;lt;code&amp;gt;&amp;quot;DOUBLE&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;INT&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;BOOL&amp;quot;&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;&amp;quot;STRING&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = force&lt;br /&gt;
|param4text = If set to 1 (true), the node's type will be forced to change.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;);&lt;br /&gt;
props.dump(a); # a = 1&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setBoolValue(0);&lt;br /&gt;
props.dump(node.getChild(&amp;quot;a&amp;quot;)); # a = 0 (type: bool)&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;, 1);&lt;br /&gt;
props.dump(a); # a = 0 (type: int)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isInt() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isInt();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot; or &amp;quot;LONG&amp;quot; (long integer) otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isNumeric() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isNumeric();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot;, &amp;quot;FLOAT&amp;quot; or &amp;quot;DOUBLE&amp;quot; otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== remove() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.remove();&lt;br /&gt;
|text = Removes the node and returns the removed node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).remove();&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; does not exist anymore&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeAllChildren() ====&lt;br /&gt;
{{Caution|Be careful when  using this API in conjunction with the Canvas system and element specific properties, for details please see [[Howto:Canvas Path Benchmarking]]}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeAllChildren();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|4766ed21a68230c0c15265e5604a74f4d99e0f5d|t=commit}}&lt;br /&gt;
|text = Removes all child nodes and returns the node.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeAllChildren();&lt;br /&gt;
props.dump(node); # all children have been removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChild(rel_path, idx);&lt;br /&gt;
|text = Removes a given child node child nodes and returns the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to a subnode as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Index of the subnode to remove as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; has been removed&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # just a[1] remains&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChildren([name]);&lt;br /&gt;
|text = Removes all children with a specified name. If no arguments are given, all children will be removed (see also {{func link|removeAllChildren()|page=this}}).&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of children to remove as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node); # just children named &amp;quot;b&amp;quot; remain&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren();&lt;br /&gt;
props.dump(node); # all children removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setAttribute([rel_path, ]attr, value);&lt;br /&gt;
props.Node.setAttribute(attrs);&lt;br /&gt;
|text = Sets an attribute or multiple attributes. A list of attributes and their codes are below. For a brand new node, the default attributes are ''readable'' and ''writable''. Returns an integer specifying the old attributes.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Description&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = attr&lt;br /&gt;
|param2text = Name of attribute to set as a string. See above.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Boolean value to set the property to.&lt;br /&gt;
|param4 = attrs&lt;br /&gt;
|param4text = When the function is used in its second form, this argument is used. This argument should be an integer specifying which arguments are set to true. See {{simgear source|simgear/props/props.hxx|l=767}} for the full list of codes. Simply add codes of the desired attributes together.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;readable&amp;quot;, 0);&lt;br /&gt;
var val = node.getValue();&lt;br /&gt;
debug.dump(val); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setAttribute(&amp;quot;a&amp;quot;, &amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).setIntValue(12); # will be traced&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(35); # read + write + trace-write&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setBoolValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a boolean value. If the node has no type, it will be set to a bool type. If the node is already a number type, it will be set to either 1 or 0. If it is a string, it will be set to either &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a boolean. If it is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, it will be false. If it is a string, it will be false. If it is a number, 0 will be false, while other numbers will be true. All other cases will be interpreted as 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(nil);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
node.setBoolValue(1.25);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;String&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;String&amp;quot; (type: string)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = &amp;quot;true&amp;quot;&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(12.32);&lt;br /&gt;
props.dump(node); # node = 12.32 (type: double)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = 1&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setBoolValue(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # /a = 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setDoubleValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setDoubleValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a double value. If the node has no type, it will be set to a double type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. If the node is an integer type, it will be truncated to the closest integer to zero. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a double number. It must be a valid number or a string that can be converted to a number.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(1);&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1 (type: int)&lt;br /&gt;
node.setDoubleValue(&amp;quot;-1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = -1 (type: int)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setDoubleValue(&amp;quot;a&amp;quot;, 12.2);&lt;br /&gt;
props.dump(node); # /a = 12.2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setIntValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setIntValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to an integer value. If the node has no type, it will be set to a integer type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a integer. It must be a valid number or a string that can be converted to a number. If the number is a double, it will be truncated to the closest integer to zero.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(&amp;quot;6&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 6&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(-12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setIntValue(12.5);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = &amp;quot;12&amp;quot; (type: string)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setIntValue(&amp;quot;a&amp;quot;, 12);&lt;br /&gt;
props.dump(node); # /a = 12&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValue([rel_path, ]value);&lt;br /&gt;
|text = {{hatnote|See also {{func link|setBoolValue()|page=this}}, {{func link|setDoubleValue()|page=this}}, and {{func link|setIntValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
{{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a given value. See table below for conversions and effects. Note that vec3d and vec4d types are not fully integrated into SGPropertyNode. Returns 1 (true) if the operation was successful.&lt;br /&gt;
&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align:right&amp;quot; {{!}} '''value''' type → &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Number&lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} String &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Vector&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} style=&amp;quot;text-align:left&amp;quot; {{!}} Current node&amp;lt;br&amp;gt;type ↓&lt;br /&gt;
{{!}} style=&amp;quot;text-align:right&amp;quot; {{!}} Result ↘&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} None/unspecified&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;double&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;vec*d&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Bool&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' != 0, node set to true.&lt;br /&gt;
* If '''value''' == 0, node set to false.&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' == &amp;quot;true&amp;quot;, node set to true.&lt;br /&gt;
* If '''value''' can be converted to an integer,&amp;lt;br&amp;gt;and != 0, node set to true.&lt;br /&gt;
{{!}} Node set to true.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Integer&lt;br /&gt;
{{!}} Node set to truncated '''value'''&lt;br /&gt;
{{!}} Node set to '''value ''' converted and truncated to an integer.&lt;br /&gt;
{{!}} Node set to 1.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Double&lt;br /&gt;
{{!}} Node set to '''value'''.&lt;br /&gt;
{{!}} Node set to '''value''' converted to number.&lt;br /&gt;
{{!}} Throws an error (vector is not a number).&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} String&lt;br /&gt;
{{!}} Node set to '''value''' converted to string. {{!!}} Node set to '''value'''. {{!!}} Node not set.&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to. Must be a string, a valid number, or a vector consisting of 3 or 4 numbers. See table above for conversions and effects.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setValue(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # \a = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValues(val);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getValues()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Sets the nodes property tree from a Nasal hash. Scalars will become nodes in the tree and hashes will become named subnodes. Vectors will be converted into indexed nodes, with the values in the vector becoming their values (see examples below).&lt;br /&gt;
|param1 = val&lt;br /&gt;
|param1text = A hash that will become the property tree.&lt;br /&gt;
|example1 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 100 # &amp;quot;a&amp;quot; will become the subnode's name, and 100 its value&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
|example2 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== unalias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.unalias();&lt;br /&gt;
|text = Un-aliases the node and returns it to a blank state. Returns 1 on success and 0 on failure (e.g., when used on a tied property).&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.35);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
&lt;br /&gt;
props.dump(node2); # equals 2.35&lt;br /&gt;
node2.unalias();&lt;br /&gt;
props.dump(node2); # no value or type&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== toggleBoolValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = toggleBoolValue();&lt;br /&gt;
|text = Toggle a boolean property. You have to make sure the property is of type bool!&lt;br /&gt;
|example1 = var b = props.Node.new().initNode(&amp;quot;/_test/bool&amp;quot;, 1, &amp;quot;BOOL&amp;quot;);&lt;br /&gt;
print(&amp;quot;bool &amp;quot;, b.getValue());&lt;br /&gt;
b.toggleBoolValue();&lt;br /&gt;
print(&amp;quot;after toggleBoolValue &amp;quot;, b.getValue());&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
=== compileCondition() ===&lt;br /&gt;
{{see also|Conditions}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.compileCondition(node);&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|43f8ce08706629e69984b1cc34e5e979d03ef09a|t=commit}}&lt;br /&gt;
|text = Compiles a [[conditions|condition]] property branch and returns a &amp;lt;code&amp;gt;Condition&amp;lt;/code&amp;gt; ghost object or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error. This ghost will contain a &amp;lt;code&amp;gt;test()&amp;lt;/code&amp;gt; function that will return the result of the condition as either 1 (true) or 0 (false).&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== condition() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.condition(node);&lt;br /&gt;
|text = Evaluates a [[conditions|condition]] property branch and returns the result as a boolean.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== copy() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.copy(src, dest[, attr]);&lt;br /&gt;
|text = Copies the property tree of the source into the destination node. Note that aliased properties will not be copied.&lt;br /&gt;
|param1 = src&lt;br /&gt;
|param1text = Source &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy from.&lt;br /&gt;
|param2 = dest&lt;br /&gt;
|param2text = Destination &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy to.&lt;br /&gt;
|param3 = attr&lt;br /&gt;
|param3text = If set to 1 (true), attributes will also be copied. Defaults to 0 (false).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1.5,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var src = props.Node.new(tree);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest);&lt;br /&gt;
props.dump(dest);&lt;br /&gt;
|example2 = var src = props.Node.new();&lt;br /&gt;
var a = src.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
a.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
a.setIntValue(12);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest, 1);&lt;br /&gt;
print(dest.getNode(&amp;quot;a&amp;quot;).getAttribute(&amp;quot;trace-write&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== dump() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.dump(node);&lt;br /&gt;
|text = Recursively dump the state of a node into the console, showing value and type of each node. Note that as of 10/2016, the value of vec*d type nodes cannot be dumped.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to dump.&lt;br /&gt;
|example1 = var node = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # dump into console&lt;br /&gt;
|example2 = # Dump the entire Property Tree&lt;br /&gt;
# Warning! This is an intensive operation!&lt;br /&gt;
props.dump(props.globals);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== getNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.getNode();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|807062d0b6f858885547f27325e829c2bf42a12b|t=commit}}&lt;br /&gt;
|text = Shortcut for &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;props.globals.getNode()&amp;lt;/syntaxhighlight&amp;gt;. See {{func link|getNode()||Node|page=this}} for full documentation.&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft is: &amp;quot;, props.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== nodeList() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.nodeList(arg[, arg[, ...]]);&lt;br /&gt;
|text = Converts its arguments into a vector of node objects if possible and returns that vector. &lt;br /&gt;
|param1 = arg&lt;br /&gt;
|param1text = Object to operate on. Must be a node object, string, vector, hash, function, or &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost. Vectors and hashes must contain any of the other acceptable types, functions must return any of the other types, strings will be assumed to be paths to global properties, and ghosts will be converted into node objects. There may be any number of arguments.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var f = func(){&lt;br /&gt;
    var n = props.Node.new();&lt;br /&gt;
    return n._g;&lt;br /&gt;
}&lt;br /&gt;
var list = props.nodeList(node,&lt;br /&gt;
    &amp;quot;/sim/aircraft&amp;quot;,&lt;br /&gt;
    [&amp;quot;/sim/fg-root&amp;quot;],&lt;br /&gt;
    { &amp;quot;path&amp;quot;: &amp;quot;/sim/fg-home&amp;quot; },&lt;br /&gt;
    f&lt;br /&gt;
);&lt;br /&gt;
debug.dump(list); # dump list&lt;br /&gt;
|example2 = var root = &amp;quot;/sim/version/&amp;quot;;&lt;br /&gt;
var info = [&lt;br /&gt;
    root ~ &amp;quot;build-id&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;build-number&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;flightgear&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;hla-support&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;openscenegraph&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;revision&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;simgear&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
info = props.nodeList(info); # turn into list of nodes&lt;br /&gt;
foreach(var n; info){&lt;br /&gt;
    print(n.getValue()); # dump info&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== runBinding() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.runBinding(node[, module]);&lt;br /&gt;
|text = Runs a [[Bindings|binding]] element in a node object. Returns 1 (true) on success and 0 (false) on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = A {{tag|binding}} element as a node object.&lt;br /&gt;
|param2 = module&lt;br /&gt;
|param2text = Optional string specifying a module to run Nasal scripts in if the command is &amp;lt;code&amp;gt;nasal&amp;lt;/code&amp;gt;. This argument will not override any {{tag|module}} element in the '''node'''&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;dialog-show&amp;quot;,&lt;br /&gt;
    &amp;quot;dialog-name&amp;quot;: &amp;quot;map&amp;quot; # open map&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)' # prints value of math.pi&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;math&amp;quot;);&lt;br /&gt;
|example3 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)', # prints value of math.pi&lt;br /&gt;
    &amp;quot;module&amp;quot;: &amp;quot;math&amp;quot; # this is used&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;debug&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setAll() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.setAll(base, child, value);&lt;br /&gt;
|text = Sets indexed subnodes in the Property Tree with the same name to the same value.&lt;br /&gt;
|param1 = base&lt;br /&gt;
|param1text = Base path to the nodes.&lt;br /&gt;
|param2 = child&lt;br /&gt;
|param2text = Path to child nodes.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Value to set the subnodes to.&lt;br /&gt;
|example1 = # apply 50% throttle to all engines&lt;br /&gt;
props.setAll(&amp;quot;/controls/engines/engine&amp;quot;, &amp;quot;throttle&amp;quot;, 0.5);&lt;br /&gt;
|example2 = var nodes = props.globals.addChildren(&amp;quot;/test&amp;quot;, 3);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
props.setAll(&amp;quot;/test&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;); # set all children (test[*]/a) to &amp;quot;Hi&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrap() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrap(node);&lt;br /&gt;
|text = Turns &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghosts, either in a vector or single, into &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; objects.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or vector of such ghosts.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrap(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var vector = [canvas._newCanvasGhost()._node_ghost, props.Node.new()._g];&lt;br /&gt;
var nodes = props.wrap(vector);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    props.dump(node);&lt;br /&gt;
    print(&amp;quot;----&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrapNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrapNode(node);&lt;br /&gt;
|text = Turns a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost into a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost to convert.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrapNode(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Variable ==&lt;br /&gt;
=== globals ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.globals;&lt;br /&gt;
|text = Exposes the [[Property Tree]] as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft: &amp;quot;, props.globals.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
|example2text = Alternative using {{func link|getprop()}}.&lt;br /&gt;
|example2 = print(&amp;quot;Current aircraft: &amp;quot;, getprop(&amp;quot;/sim/aircraft&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143165</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143165"/>
		<updated>2025-12-04T12:52:53Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* range() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the hash of the namespace &amp;quot;get_math_e&amp;quot;&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5);        # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5);     # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0);     # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1);     # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(true);     # result is a string &amp;quot;1&amp;quot;&lt;br /&gt;
var result = str(false);    # result is a string &amp;quot;0&amp;quot;&lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(-12.5);    # result is a string &amp;quot;-12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143164</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143164"/>
		<updated>2025-12-04T12:46:17Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* closure() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the hash of the namespace &amp;quot;get_math_e&amp;quot;&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5); # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5); # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0); # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1); # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(true);     # result is a string &amp;quot;1&amp;quot;&lt;br /&gt;
var result = str(false);    # result is a string &amp;quot;0&amp;quot;&lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(-12.5);    # result is a string &amp;quot;-12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143163</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143163"/>
		<updated>2025-12-04T12:39:58Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* str() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the namespace of get_math_e&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5); # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5); # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0); # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1); # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(true);     # result is a string &amp;quot;1&amp;quot;&lt;br /&gt;
var result = str(false);    # result is a string &amp;quot;0&amp;quot;&lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(-12.5);    # result is a string &amp;quot;-12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143162</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143162"/>
		<updated>2025-12-04T12:39:51Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* str() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the namespace of get_math_e&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5); # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5); # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0); # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1); # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(true);     # result is a string &amp;quot;1&amp;quot;&lt;br /&gt;
var result = str(false);    # result is a string &amp;quot;0&amp;quot;&lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(-12.5);     # result is a string &amp;quot;-12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143161</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143161"/>
		<updated>2025-12-04T12:38:35Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* str() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the namespace of get_math_e&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5); # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5); # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0); # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1); # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(true);     # result is a string &amp;quot;1&amp;quot;&lt;br /&gt;
var result = str(false);    # result is a string &amp;quot;0&amp;quot;&lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143160</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143160"/>
		<updated>2025-12-04T12:35:48Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* sprintf() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the namespace of get_math_e&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5); # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5); # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0); # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1); # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23));  # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   # prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   # prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); # prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); # prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); # prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); # prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143159</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143159"/>
		<updated>2025-12-04T12:32:42Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* streq() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the namespace of get_math_e&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5); # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5); # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0); # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1); # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23)); # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   #prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   #prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); #prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); #prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); #prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); #prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===str()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = str(value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=186|t=Source}}&lt;br /&gt;
|text = Convert given value (scalar) to string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a number, it will be converted to a string. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is a string, the same string will be returned. If &amp;lt;code&amp;gt;value&amp;lt;/code&amp;gt; is not a scalar (hash, vector, nil), &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|param1 = value&lt;br /&gt;
|param1text = The value to convert to string.&lt;br /&gt;
|example1 = &lt;br /&gt;
var result = str(12);       # result is a string &amp;quot;12&amp;quot;&lt;br /&gt;
var result = str(12.5);     # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str(&amp;quot;12.5&amp;quot;);   # result is a string &amp;quot;12.5&amp;quot;&lt;br /&gt;
var result = str([1, 2]);   # result is a nil&lt;br /&gt;
var result = str({ x: 0 }); # result is a nil&lt;br /&gt;
var result = str(nil);      # result is a nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=QRadioPredict&amp;diff=143134</id>
		<title>QRadioPredict</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=QRadioPredict&amp;diff=143134"/>
		<updated>2025-12-02T20:52:57Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Availability */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{WIP}}&lt;br /&gt;
{{stub}}&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
&amp;lt;noinclude&amp;gt;{|&amp;lt;/noinclude&amp;gt;class=&amp;quot;layouttemplate licensetpl&amp;quot; cellspacing=&amp;quot;8&amp;quot; cellpadding=&amp;quot;0&amp;quot; style=&amp;quot;width:100%; clear:both; margin:0.5em auto; background-color:#FCFF9F; border:2px solid #acce79;&amp;quot;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
| &amp;lt;b&amp;gt;This project is an external project related to Flightgear.&amp;lt;/b&amp;gt;&amp;lt;br/&amp;gt; &amp;lt;b&amp;gt;This project is dedicated to humanitarian and radio amateur usage, as well as for increased realism for those who use Flightgear as a training tool.&amp;lt;/b&amp;gt; Support for any other type of usage will be granted by the author on a case by case basis.&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Image:qradiopredict1.png|thumb|Qt GUI for FGradio, standalone and APRS]]&lt;br /&gt;
This GUI tool exploits the tremendous connectivity and capabilities of Flightgear.&lt;br /&gt;
By running the telnet server of Flightgear at 100 Hz, we are pushing the limits of the props server, with great results.&lt;br /&gt;
Analysis of radio path for site planning and simulation purposes is thus possible by using one mobile station, positioned anywhere in the Flightgear world, and up to three fixed stations, each of them having the principal characteristics like transmit power, antenna gain, antenna height etc. configurable via the interface.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
[[Image:qfgradio1.png|thumb|Qt GUI for FGradio]]&lt;br /&gt;
[[Image:qfgradio3.png|thumb|Showing APRS stations]]&lt;br /&gt;
* Placing the mobile station on a 2D map provided by OpenStreetMap, or by open sattelite imagery.&lt;br /&gt;
* There is some support for proprietary data, such as Google Maps or sattelite images&lt;br /&gt;
* Placing and configuring up to three fixed stations, which will transmit a signal to the mobile station.&lt;br /&gt;
* Setting the path that the mobile station will follow, via waypoints placed on the map.&lt;br /&gt;
* Signal and transmission analysis for the radio links, running at the rate at which the simulator itself is running.&lt;br /&gt;
* The mobile station will always follow the terrain, if the waypoints have no altitude specified.&lt;br /&gt;
* Radio parameters are: transmit power, receiver sensitivity, antenna gain on both parts, cable losses, antenna heights above surrounding terrain, polarization, antenna type (provides radiation patterns, see the main FGRadio article).&lt;br /&gt;
* The results are displayed both graphically and as real numbers, on a different tab for each station.&lt;br /&gt;
* The Irregular Terrain Model is used with some modifications, as well as the new algorithm designed to calculate losses caused by terrain type (wooded area, city, town, coniferous, mixed forests etc.)&lt;br /&gt;
* The newest ITWOM v3.0 model has been GPL'ed by John A. Magliacane, and is used inside the application&lt;br /&gt;
* QRadioPredict is capable of running standalone, without a Flightgear connection, using the same data which is used to generate Flightgear terrain. Interfacing with Flightgear still remains the preferred mode of operation, but this change implies that terrain loading limits which have placed limitations on maximum reliable prediction range will be gone.&lt;br /&gt;
* QRadioPredict is also capable of using more than three fixed radio stations.&lt;br /&gt;
* APRS stations are displayed on map in real time, and also saved in history, with a configurable view time filter. This should allow practical tests of the algorithm for clutter loss.&lt;br /&gt;
* A 2D plot model showing signal quality in colours. This works in a similar way to the Splat! and Radiomobile 2D plot generation. The plot and map can be zoomed in or out, and the plotting distance is easily configurable via the interface&lt;br /&gt;
[[Image:qradiopredict.png|thumb|Plot mode]]&lt;br /&gt;
&lt;br /&gt;
== Planned features ==&lt;br /&gt;
* APRS stations displayed on map will perhaps be sent to Flightgear for signal analysis in real time.&lt;br /&gt;
* More than one mobile station, as well as messaging between fixed-mobile, fixed-fixed, mobile-mobile.&lt;br /&gt;
* Improving performance of clutter calculations in standalone mode.&lt;br /&gt;
* Part of this code will be used to contribute to integrated FGCom realistic radios&lt;br /&gt;
&lt;br /&gt;
== Availability ==&lt;br /&gt;
QRadioPredict has standalone operation capabilities. Most of its issues are being ironed out. It is intended to be portable across different operating systems, with dependencies kept to a reasonable minimum. qRadioPredict requires GDAL in order to work with terrain shapefiles. More information can be found on the project page at [https://github.com/QDeltaSoft/qradiopredict QRadioPredict]&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Radio propagation]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Logbook_Add-on&amp;diff=143133</id>
		<title>Logbook Add-on</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Logbook_Add-on&amp;diff=143133"/>
		<updated>2025-12-02T19:56:56Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Software&lt;br /&gt;
| title                  = Logbook Add-on&lt;br /&gt;
| logo                   = Fgaddonslogo202x89.png |thumb&lt;br /&gt;
| image                  = Logbook-v.1.1.0.png&lt;br /&gt;
| alt                    = Logbook add-on with main logbook window and secondary with details of single entry, with &amp;quot;dark&amp;quot; theme.&lt;br /&gt;
| developedby            = Roman Ludwicki (PlayeRom, SP-ROM)&lt;br /&gt;
| initialrelease         = December 11, 2022&lt;br /&gt;
| latestrelease          = 2.1.0, 2025-12-02&lt;br /&gt;
| writtenin              = Nasal&lt;br /&gt;
| developmentstatus      = Advanced production&lt;br /&gt;
| type                   = Addon&lt;br /&gt;
| platform               = Min FG version 2020.1.1&lt;br /&gt;
| license                = [[GNU General Public License]] v3&lt;br /&gt;
| website                = https://github.com/PlayeRom/flightgear-addon-logbook&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
'''Logbook''' is a FlightGear add-on for automatically logbook all your flights to SQLite/CSV file. The add-on also provides a Canvas graphical interface for viewing, editing the logbook and showing total statistics.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
&lt;br /&gt;
Installation is standard:&lt;br /&gt;
# [https://github.com/PlayeRom/flightgear-addon-logbook/releases Download] latest releases version of &amp;quot;Logbook&amp;quot; add-on and unzip it.&lt;br /&gt;
# In Launcher go to &amp;quot;Add-ons&amp;quot; tab. Click &amp;quot;Add&amp;quot; button by &amp;quot;Add-on Module folders&amp;quot; section and select folder with unzipped &amp;quot;Logbook&amp;quot; add-on directory (or add command line option: &amp;lt;code&amp;gt;--addon=/path/to/logbook&amp;lt;/code&amp;gt;), and click &amp;quot;Fly!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
= How it's working? =&lt;br /&gt;
&lt;br /&gt;
The add-on tries to automatically detect if an aircraft has taken off by checking the Weight on Wheels. Then the add-on starts collecting information about the flight. This means that if you are parked, taxiing, etc., it is not yet included in the flight log. If you are in the air, the add-on tries to detect if you have landed also by testing Weight on Wheels. Thus, logging takes place from the moment the aircraft is lifted off the ground until it is put back on the ground.&lt;br /&gt;
&lt;br /&gt;
If the aircraft has no wheels, only the floats, then the add-on will also try to recognize if the floats are resisting the water (if the aircraft uses JSBSim), thus recognizing whether you are in the air or not.&lt;br /&gt;
&lt;br /&gt;
The add-on also recognizes the moment of launch of the Space Shuttle from the starting position, which required a separate consideration due to the different launch.&lt;br /&gt;
&lt;br /&gt;
= Logbook file =&lt;br /&gt;
&lt;br /&gt;
For FlightGear version 2024.1 and later, the logbook is saved to a database file &amp;lt;code&amp;gt;logbook.sqlite&amp;lt;/code&amp;gt;. For older versions, up to 2020.3, it is a &amp;lt;code&amp;gt;logbook-v5.csv&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
You can find these files in the &amp;lt;code&amp;gt;[[$FG_HOME]]/Export/Addons/org.flightgear.addons.logbook/&amp;lt;/code&amp;gt; directory, where [[$FG_HOME]] on Windows is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;C:\Users\{user name}\AppData\Roaming\flightgear.org\&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
on macOS:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;/Users/{user name}/Library/Application Support/FlightGear/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
on Linux:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;/home/{user name}/.fgfs/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For CSV file, you can always open it and edit by any spreadsheet program like LibreOffice Calc, MS Excel, etc. However please don't put the characters &amp;lt;code&amp;gt;,&amp;lt;/code&amp;gt; in the cells, because the Logbook parser will recognize them as a column separator, which will cause errors in the operation of the add-on. It is safer to edit the log data through the GUI in the simulator.&lt;br /&gt;
&lt;br /&gt;
The SQLite file can also be edited using special database software such as &amp;quot;DB Browser for SQLite&amp;quot; (DB4S) or &amp;quot;DBeaver&amp;quot;. To obtain data for further processing in a spreadsheet, use the &amp;quot;Logbook&amp;quot; -&amp;gt; &amp;quot;Export to CSV&amp;quot; menu (see Export database to CSV file.)&lt;br /&gt;
&lt;br /&gt;
= Migrating CSV to SQLite =&lt;br /&gt;
&lt;br /&gt;
When you run Logbook version 2.x on FlightGear version 2024.1.x or later first time, it will automatically migrate your log data from the CSV file to the SQLite database file. This doesn't require any intervention.&lt;br /&gt;
&lt;br /&gt;
= Data structure =&lt;br /&gt;
&lt;br /&gt;
The following information is logged into the file:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;code&amp;gt;Real date &amp;amp; time&amp;lt;/code&amp;gt; – aircraft take-off date and time. This is the time taken from your OS, not the time in the simulator. This date and time is displayed in the GUI as default. In the settings you can choose what date and time you want to display in the Logbook window.&lt;br /&gt;
# &amp;lt;code&amp;gt;Sim UTC date &amp;amp; time&amp;lt;/code&amp;gt; – aircraft take-off date and time as UTC time in simulator. This date and time is available only in version with SQLite (2024.1+).&lt;br /&gt;
# &amp;lt;code&amp;gt;Sim local date &amp;amp; time&amp;lt;/code&amp;gt; – aircraft take-off date and time as local time in simulator. This date and time is available only in version with SQLite (2024.1+).&lt;br /&gt;
# &amp;lt;code&amp;gt;Aircraft&amp;lt;/code&amp;gt; – the code name of the aircraft.&lt;br /&gt;
# &amp;lt;code&amp;gt;Variant&amp;lt;/code&amp;gt; – the code name of the aircraft as its variant. Some aircraft are available in several variants, such as the default &amp;quot;Cessna 172P&amp;quot;, which includes different variants like &amp;quot;Cessna 172P Float&amp;quot;. If you select &amp;quot;Cessna 172P,&amp;quot; you will see &amp;lt;code&amp;gt;c172p&amp;lt;/code&amp;gt; in the &amp;lt;code&amp;gt;Aircraft&amp;lt;/code&amp;gt; as well as &amp;lt;code&amp;gt;Variant&amp;lt;/code&amp;gt; column. If you select the float variant (&amp;quot;Cessna 172P Float&amp;quot;), you will see &amp;lt;code&amp;gt;c172p&amp;lt;/code&amp;gt; in the Aircraft column, but &amp;lt;code&amp;gt;c172p-float&amp;lt;/code&amp;gt; in the &amp;lt;code&amp;gt;Variant&amp;lt;/code&amp;gt; column. This way you have the main group of aircraft in the &amp;lt;code&amp;gt;Aircraft&amp;lt;/code&amp;gt; column, and its variants in the &amp;lt;code&amp;gt;Variant&amp;lt;/code&amp;gt; column. This will allow you to extract &amp;lt;code&amp;gt;Totals&amp;lt;/code&amp;gt; statistics for a general group of aircraft no matter what variant (filtering by &amp;lt;code&amp;gt;Aircraft&amp;lt;/code&amp;gt;), as well as more precisely for a specific variant of a given aircraft (filtering by &amp;lt;code&amp;gt;Variant&amp;lt;/code&amp;gt;).&lt;br /&gt;
# &amp;lt;code&amp;gt;Type&amp;lt;/code&amp;gt; – aircraft type as one of following values: &lt;br /&gt;
#* &amp;quot;heli&amp;quot; (helicopter), &lt;br /&gt;
#* &amp;quot;balloon&amp;quot; (also airship), &lt;br /&gt;
#* &amp;quot;space&amp;quot; (space ship), &lt;br /&gt;
#* &amp;quot;seaplane&amp;quot; (also amphibious), &lt;br /&gt;
#* &amp;quot;military&amp;quot;, &lt;br /&gt;
#* &amp;quot;glider&amp;quot;, &lt;br /&gt;
#* &amp;quot;turboprop&amp;quot;, &lt;br /&gt;
#* &amp;quot;bizjet&amp;quot;, &lt;br /&gt;
#* &amp;quot;airliner&amp;quot;, &lt;br /&gt;
#* &amp;quot;ga-single&amp;quot; (small piston single-engine general aviation), &lt;br /&gt;
#* &amp;quot;ga-multi&amp;quot; (small piston multi-engine general aviation), &lt;br /&gt;
#* &amp;quot;others&amp;quot; (undefined or not recognized).&lt;br /&gt;
# &amp;lt;code&amp;gt;Callsign&amp;lt;/code&amp;gt; – your callsign set for multiplayer.&lt;br /&gt;
# &amp;lt;code&amp;gt;From&amp;lt;/code&amp;gt; – the ICAO code of the airport from which you have taken off. If you are starting immediately in the air, this field will remain blank.&lt;br /&gt;
# &amp;lt;code&amp;gt;To&amp;lt;/code&amp;gt; – the ICAO code of the airport where you landed. If you did not land (e.g. by closing FG in flight) or by landing at an adventurous location, this field will remain blank.&lt;br /&gt;
# &amp;lt;code&amp;gt;Landing&amp;lt;/code&amp;gt; – if you landed anywhere, a 1 will be entered here. If the flight ended without landing or the add-on was unable to detect a valid landing, this field will be left blank.&lt;br /&gt;
# &amp;lt;code&amp;gt;Crash&amp;lt;/code&amp;gt; – if the add-on recognizes an aircraft crash, a 1 will be entered here, otherwise this field will be left blank.&lt;br /&gt;
# &amp;lt;code&amp;gt;Day&amp;lt;/code&amp;gt; – the number of hours spent flying during the day.&lt;br /&gt;
# &amp;lt;code&amp;gt;Night&amp;lt;/code&amp;gt; – number of hours spent flying during the night.&lt;br /&gt;
# &amp;lt;code&amp;gt;Instrument&amp;lt;/code&amp;gt; – the number of hours flown during the IMC (Instrument Meteorological Conditions).&lt;br /&gt;
# &amp;lt;code&amp;gt;Multiplayer&amp;lt;/code&amp;gt; – the number of flight hours when connecting to a multiplayer server.&lt;br /&gt;
# &amp;lt;code&amp;gt;Swift&amp;lt;/code&amp;gt; – number of flight hours when connecting to swift.&lt;br /&gt;
# &amp;lt;code&amp;gt;Duration&amp;lt;/code&amp;gt; – total duration of the flight in hours, as the sum of &amp;lt;code&amp;gt;Day&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Night&amp;lt;/code&amp;gt;. The &amp;lt;code&amp;gt;Instrument&amp;lt;/code&amp;gt; is not added up here, as it is simply counted separately, regardless of whether it was day or night. &amp;lt;code&amp;gt;Duration&amp;lt;/code&amp;gt; is calculated in real time, so if you speed up or slow down the simulation time, it will not be affected.&lt;br /&gt;
# &amp;lt;code&amp;gt;Distance&amp;lt;/code&amp;gt; – total distance flown from take-off to landing, in nautical miles.&lt;br /&gt;
# &amp;lt;code&amp;gt;Fuel&amp;lt;/code&amp;gt; – total amount of fuel burned in flight, in U.S. gallons.&lt;br /&gt;
# &amp;lt;code&amp;gt;Max Alt&amp;lt;/code&amp;gt; – maximum altitude, in feet, reached during flight.&lt;br /&gt;
# &amp;lt;code&amp;gt;Max groundspeed&amp;lt;/code&amp;gt; – maximum groundspeed, in knots, reached during flight.&lt;br /&gt;
# &amp;lt;code&amp;gt;Max Mach&amp;lt;/code&amp;gt; – maximum speed in Mach number reached during flight.&lt;br /&gt;
# &amp;lt;code&amp;gt;Note&amp;lt;/code&amp;gt; – notes, by default the full name of the aircraft. '''This is a good place to enter your own notes as well'''.&lt;br /&gt;
&lt;br /&gt;
= Viewing the logbook =&lt;br /&gt;
&lt;br /&gt;
The add-on also provides the ability to view the entire flight logbook from the simulator. You should select &amp;quot;Logbook&amp;quot; -&amp;gt; &amp;quot;Logbook&amp;quot; from the menu. The main window will open with the entire logbook in tabular form.&lt;br /&gt;
&lt;br /&gt;
[[File:Logbook Main-window.png|thumb|Logbook Main-window]]&lt;br /&gt;
&lt;br /&gt;
The last row signed &amp;quot;Totals&amp;quot;, contains a summary, not only of the visible entries on a given page, but of the entire logbook. The same &amp;quot;Totals&amp;quot; row is visible on every page. The exception for totals is the Max Alt, Max GS and Max Mach columns, in which you don't have the sum of all values, but the highest one.&lt;br /&gt;
&lt;br /&gt;
At the very bottom there is a row of buttons, mainly for moving through the log pages:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;code&amp;gt;|&amp;lt;&amp;lt;&amp;lt;/code&amp;gt; – button for moving to the first page,&lt;br /&gt;
# &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; – moving to the previous page,&lt;br /&gt;
# &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; – moving to the next page,&lt;br /&gt;
# &amp;lt;code&amp;gt;&amp;gt;&amp;gt;|&amp;lt;/code&amp;gt; – moving to the last page.&lt;br /&gt;
&lt;br /&gt;
In the middle there is text information in the format &amp;lt;code&amp;gt;{on which page you are} / {number of all pages}&amp;lt;/code&amp;gt; (number of entries in the log). On the right is the &amp;lt;code&amp;gt;dark/light&amp;lt;/code&amp;gt; button to switch between window styles. The &amp;lt;code&amp;gt;≡&amp;lt;/code&amp;gt; button opens a windows with settings, and the last &amp;lt;code&amp;gt;?&amp;lt;/code&amp;gt; button opens a window with help (the same as from the &amp;quot;Logbook&amp;quot; -&amp;gt; &amp;quot;Help&amp;quot; menu).&lt;br /&gt;
&lt;br /&gt;
== Data filtering ==&lt;br /&gt;
&lt;br /&gt;
The addon allows you to filter some columns in the main log window. At the moment you can filter by the &amp;quot;Date&amp;quot; (as a year), &amp;quot;Aircraft&amp;quot;, &amp;quot;Variant&amp;quot;, &amp;quot;Type&amp;quot;, &amp;quot;Callsign&amp;quot;, &amp;quot;From&amp;quot;, &amp;quot;To&amp;quot;, &amp;quot;Landing&amp;quot; and &amp;quot;Crash&amp;quot; columns. To use filtering, hover the mouse cursor over a column name (it will be highlighted) and click it. A new window will appear with a choice of values.&lt;br /&gt;
&lt;br /&gt;
[[File:Logbbook Filtering.png|thumb|Logbbook Filtering data]]&lt;br /&gt;
&lt;br /&gt;
For filtering on the &amp;quot;Aircraft&amp;quot; column, these will be the IDs of aircraft you have flown before. For filtering by the &amp;quot;Type&amp;quot; column, these will be the names of aircraft types, etc. Each window with filters also has the &amp;quot;All&amp;quot; position, which means that the filter will be turned off and all items will be shown. When the filter is enabled, an asterisk (&amp;lt;code&amp;gt;*&amp;lt;/code&amp;gt;) sign will be shown next to the filtered column to warn that the filter has been used.&lt;br /&gt;
&lt;br /&gt;
After using the filter, the &amp;quot;Totals&amp;quot; row will also be updated with the filtered data. In this way, you can see statistics for a specific aircraft or types of aircraft.&lt;br /&gt;
&lt;br /&gt;
== Details of entry logbook ==&lt;br /&gt;
&lt;br /&gt;
Each log entry in the main window can be hovered over and clicked. Then an additional window will open presenting the details of the given entry.&lt;br /&gt;
&lt;br /&gt;
[[File:Logbook Details.png|thumb|Logbook Details entity view]]&lt;br /&gt;
&lt;br /&gt;
In general, you have the same information here as in the main window, except:&lt;br /&gt;
&lt;br /&gt;
# you can see three dates and times of aircraft takeoff:&lt;br /&gt;
#* real date &amp;amp; time from your OS,&lt;br /&gt;
#* UTC date &amp;amp; time from the simulator,&lt;br /&gt;
#* local date &amp;amp; time from the simulator;&lt;br /&gt;
# ICAO airport codes include their names in parentheses;&lt;br /&gt;
# with numerical data, you are given the units in which these values are presented with conversions to other units;&lt;br /&gt;
# at the very bottom you have an additional Note field, which is not displayed in the main window, due to the possibility of placing any length of text here.&lt;br /&gt;
&lt;br /&gt;
== Editing and deleting data ==&lt;br /&gt;
&lt;br /&gt;
Each logbook entry can be edited from the simulator. You need to select &amp;quot;Logbook&amp;quot; -&amp;gt; &amp;quot;Logbook&amp;quot; from the menu. The main window with the entire logbook will open. Here you can search for the entry you want to edit and click on it. The details window for the entry will open. Now you can click on the specific entry you want to edit. Another window with a text field will open. Just enter the new value and confirm with the &amp;quot;Save&amp;quot; button. The change will immediately be saved to a file.&lt;br /&gt;
&lt;br /&gt;
[[File:Logbook Editing entry.png|thumb|Logbook Editing entry]]&lt;br /&gt;
&lt;br /&gt;
At the bottom of the details window there is a Delete button, with which you can permanently delete the selected entry.&lt;br /&gt;
&lt;br /&gt;
== Flight Analysis ==&lt;br /&gt;
&lt;br /&gt;
The details window of logbook also contains an &amp;lt;code&amp;gt;Analysis&amp;lt;/code&amp;gt; button. Once you click on it, a new window will open with the flight analysis.&lt;br /&gt;
&lt;br /&gt;
[[File:Logbook Flight-analysis.png|thumb|Logbook Flight-analysis window]]&lt;br /&gt;
&lt;br /&gt;
The window is divided into two parts, the upper one with the map (lateral navigation) and the lower one with the profile (vertical navigation). The path along which the flight was made is drawn in blue. The brown path on the profile is the terrain elevation.&lt;br /&gt;
&lt;br /&gt;
Flight analysis can also be opened from the &amp;quot;Logbook&amp;quot; -&amp;gt; &amp;quot;Current Flight Analysis&amp;quot; menu. The difference is that this analysis always comes from your current session. The difference in operation is that in &amp;quot;Current Flight Analysis&amp;quot; you cannot change the zoom level for the vertical profile.&lt;br /&gt;
&lt;br /&gt;
Flight analysis from the logbook will only be available for flights made in FlightGear 2024.1 and later. Current session analysis is available from FlightGear 2020.&lt;br /&gt;
&lt;br /&gt;
The flight analysis window is resizable, but remember that if it is too small it will not be able to draw the map or vertical profile correctly.&lt;br /&gt;
&lt;br /&gt;
At the bottom we have a number of elements to control the flight analysis:&lt;br /&gt;
&lt;br /&gt;
=== Zoom ===&lt;br /&gt;
&lt;br /&gt;
You can change the vertical profile zoom using the &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; buttons. You can also do this using the mouse wheel when the cursor is over the graph. The zoom range is: 1x (the entire graph is visible), 2x (the graph is divided into 2 parts), 4x (division into 4 parts), 8x (division into 8 parts) and a maximum of 16x (division into 16 parts). The current zoom level is indicated by the text between the buttons. However, please note that the maximum zoom may be reduced if the number of recorded track points is too small.&lt;br /&gt;
&lt;br /&gt;
The map view can also be zoomed using the mouse wheel. There are no buttons here but there are zoom level indicators in the upper left corner of the map. The zoom range for map is from 3 to 14. The default zoom level is set to 10. If the flight path does not fit on the map, you must zoom out of the map view or move the airplane along the path by click on the path or by using the &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; buttons to see the rest of the path.&lt;br /&gt;
&lt;br /&gt;
=== Frame ===&lt;br /&gt;
&lt;br /&gt;
Information about where the aircraft icon is currently located on the flight path, among all recorded flight path points. The currently selected point is marked with the airplane icon both on the map and the profile. By default, the aircraft icon is positioned at the first point, i.e. at the beginning of the flight.&lt;br /&gt;
&lt;br /&gt;
=== Player ===&lt;br /&gt;
&lt;br /&gt;
Next we have a series of buttons to control the animation and movement of the aircraft icon along the recorded flight path points:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;|&amp;lt;&amp;lt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;gt;&amp;gt;|&amp;lt;/code&amp;gt; – jump to start or end point.&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;&amp;lt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; – move back or forward 10 points.&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt; – move back or forward 1 point.&lt;br /&gt;
* &amp;lt;code&amp;gt;Play&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;Stop&amp;lt;/code&amp;gt; – start/stop flight animation of airplane icon.&lt;br /&gt;
&lt;br /&gt;
To change airplane icon position, you can also click on the map near to the fly path or click on the profile graph. Then the airplane icon will be moved to the closest point to the click location.&lt;br /&gt;
&lt;br /&gt;
=== Speed ===&lt;br /&gt;
&lt;br /&gt;
With the &amp;quot;Speed&amp;quot; option you can change the animation speed of the airplane icon movement, where &amp;lt;code&amp;gt;1x&amp;lt;/code&amp;gt; is the real-time flight speed, up to a maximum of &amp;lt;code&amp;gt;32x&amp;lt;/code&amp;gt; faster. This option is only available from FlightGear version 2024.1. Older versions use a fixed animation speed of &amp;lt;code&amp;gt;16x&amp;lt;/code&amp;gt; (because the GUI doesn't include a combobox.)&lt;br /&gt;
&lt;br /&gt;
=== Profile mode ===&lt;br /&gt;
&lt;br /&gt;
At the bottom right there is also an option to change the profile drawing mode. In FlightGear version 2024.1, this is a combobox with the options &amp;quot;distance&amp;quot; and &amp;quot;time&amp;quot;. This option defines whether the X-axis should be drawn based on time or distance traveled.&lt;br /&gt;
&lt;br /&gt;
When based on time, the graph will be evenly and linearly distributed, even when the aircraft is stationary or hovering because time is always moving forward. So the graph won't show where the flight was faster or slower, but you will avoid overlapping points.&lt;br /&gt;
&lt;br /&gt;
When based on distance, the points will be drawn close to each other or overlapping when the aircraft is stationary or flying slowly, but they will be more spread out when flying fast, making it possible to recognize places where the flight was performed at higher speeds and where at lower ones. In FlightGear versions older than 2024, this option is presented as a check box. When you check it, you will enable &amp;quot;time&amp;quot; mode (default is &amp;quot;distance&amp;quot; mode).&lt;br /&gt;
&lt;br /&gt;
=== Information about each point ===&lt;br /&gt;
&lt;br /&gt;
To the left of the map, there is recorded information about the given path point where the airplane icon is currently located, and these are:&lt;br /&gt;
&lt;br /&gt;
* geographical coordinates of the point,&lt;br /&gt;
* altitude MSL and AGL at which the aircraft was located,&lt;br /&gt;
* true and magnetic heading that the aircraft was flying at the given point,&lt;br /&gt;
* airspeed and groundspeed in knots that the aircraft was flying at the given point,&lt;br /&gt;
* direction from which the wind is blowing and its speed in knots,&lt;br /&gt;
* flight time at the given point,&lt;br /&gt;
* distance traveled at the given point.&lt;br /&gt;
&lt;br /&gt;
=== Map view ===&lt;br /&gt;
&lt;br /&gt;
The map shows the aircraft's position and heading.&lt;br /&gt;
&lt;br /&gt;
The path the aircraft flew is drawn with a blue line.&lt;br /&gt;
&lt;br /&gt;
In the upper left corner you have information about the map zoom level. Below are the following buttons:&lt;br /&gt;
&lt;br /&gt;
* OpenStreetMap – change map provider to OpenStreetMap&lt;br /&gt;
* OpenTopoMap – change map provider to OpenTopoMap&lt;br /&gt;
&lt;br /&gt;
In the upper right corner you have information about wind direction and speed (wind barbs).&lt;br /&gt;
&lt;br /&gt;
Click near the path on the map to move the airplane icon there.&lt;br /&gt;
&lt;br /&gt;
Scroll on the map to change map zoom level.&lt;br /&gt;
&lt;br /&gt;
=== Vertical profile graph view ===&lt;br /&gt;
&lt;br /&gt;
The vertical profile shows a side view of our flight, where the vertical axis shows altitude in feet, the horizontal axis contains two values. The first is time in hours, the second below is distance in NM.&lt;br /&gt;
&lt;br /&gt;
The blue line is the flight path, the brown line is the elevation of the terrain.&lt;br /&gt;
&lt;br /&gt;
Click on the graph to move airplane icon position.&lt;br /&gt;
&lt;br /&gt;
Scroll on the graph to change zoom level.&lt;br /&gt;
&lt;br /&gt;
= Settings =&lt;br /&gt;
&lt;br /&gt;
When you click on the &amp;lt;code&amp;gt;≡&amp;lt;/code&amp;gt; button in the Logbook view, the settings window will open.&lt;br /&gt;
&lt;br /&gt;
[[File:Logbook Settings.png|thumb|Logbook Settings window]]&lt;br /&gt;
&lt;br /&gt;
Here you can configure the following options:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;code&amp;gt;Date and time displayed in the Logbook view&amp;lt;/code&amp;gt; – Logbook window shows only one &amp;lt;code&amp;gt;Date&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Time&amp;lt;/code&amp;gt; item, but triple dates and times are logged, so here you can choose which one you want to display in Logbook window, by default it is real time, taken from your operating system. These options are only available for FG &amp;gt;= 2024.1.&lt;br /&gt;
# &amp;lt;code&amp;gt;Columns to display in the Logbook view&amp;lt;/code&amp;gt; – here you can specify which columns are to be displayed in the Logbook window. Columns such as &amp;lt;code&amp;gt;Date&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;Time&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Aircraft&amp;lt;/code&amp;gt; are always displayed, the Note column will never be displayed and this cannot be changed. These options are only available for FG &amp;gt;= 2024.1.&lt;br /&gt;
# &amp;lt;code&amp;gt;Map provider&amp;lt;/code&amp;gt; – here you can specify default map tile provider in the Flight Analysis view. Available providers are &amp;lt;code&amp;gt;OpenStreetMap&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;OpenTopoMap&amp;lt;/code&amp;gt;,&lt;br /&gt;
# &amp;lt;code&amp;gt;Click sound&amp;lt;/code&amp;gt; – by default, a sound is played when you click on various buttons, you can turn this sound off here.&lt;br /&gt;
# &amp;lt;code&amp;gt;Items per page&amp;lt;/code&amp;gt; – here you can specify how many rows of logs should be displayed in the Logbook view, the default is 10.&lt;br /&gt;
# &amp;lt;code&amp;gt;Optimize database&amp;lt;/code&amp;gt; – this button will defragment the database file (sqlite), which will speed up database operations and reduce its size on the disk. These option is only available for FG &amp;gt;= 2024.1.&lt;br /&gt;
&lt;br /&gt;
== Advance settings ==&lt;br /&gt;
&lt;br /&gt;
When you close the simulator, the settings will save to the &amp;lt;code&amp;gt;autosave_{version}.xml&amp;lt;/code&amp;gt; file in [[$FG_HOME]] location. By editing this file you can configure more options. First, find the &amp;lt;code&amp;gt;org.flightgear.addons.logbook&amp;lt;/code&amp;gt; tag, which will contain the following settings:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;code&amp;gt;tracker-interval-sec&amp;lt;/code&amp;gt; – real number as the number of seconds every which data will be dumped for flight analysis. The smaller the number, the more data you will receive and the flight analysis will be more accurate, therefore the database file will take up more space and the processor may be a little more loaded. If you are making a long flight on an airliner, a value of 20 seconds should be satisfactory. If you want to perform aerobatics and want to analyze your flight in detail, you can set this value even to 1 second. A value less than 1 second (this is the default) means automatic mode, i.e. the add-on will dynamically adjust the time interval seconds. By default, it will be 15 seconds, but during turns, i.e. bank greater than 5 degrees and at an altitude of less than 2000 ft above ground level, the interval will change to 5 seconds. Thanks to this, flight close to the terrain will be recorded more accurately and turns on the map will be more rounded.&lt;br /&gt;
# &amp;lt;code&amp;gt;real-time-duration&amp;lt;/code&amp;gt; – if true then time spent in flight is always real time, i.e. speeding up or slowing down the simulation time will not affect Duration (default true). So if your flight would take 2 hours but you accelerate the time in the simulator 2x, then you will fly in 1 real hour and the log will record 1 hour of flight. On the other hand, if you set this option to false, then in this case 2 hours would be logged, according to the simulator time.&lt;br /&gt;
&lt;br /&gt;
and settings available from GUI:&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;code&amp;gt;dark-style&amp;lt;/code&amp;gt; – if true then dark theme is using (default false).&lt;br /&gt;
# &amp;lt;code&amp;gt;sound-enabled&amp;lt;/code&amp;gt; – if true then click sound will be playing (default true).&lt;br /&gt;
# &amp;lt;code&amp;gt;date-time-display&amp;lt;/code&amp;gt; – which time will be displaying in main Logbook table. Possible values:&lt;br /&gt;
#* &amp;lt;code&amp;gt;real&amp;lt;/code&amp;gt; – your real time, from your OS,&lt;br /&gt;
#* &amp;lt;code&amp;gt;sim-utc&amp;lt;/code&amp;gt; – UTC time in simulator,&lt;br /&gt;
#* &amp;lt;code&amp;gt;sim-local&amp;lt;/code&amp;gt; – local time in simulator.&lt;br /&gt;
# &amp;lt;code&amp;gt;log-items-per-page&amp;lt;/code&amp;gt; – integer number as how many rows of logs should be displayed in the Logbook view (default 20).&lt;br /&gt;
# &amp;lt;code&amp;gt;columns-visible&amp;lt;/code&amp;gt; – which column should be visible in main Logbook view.&lt;br /&gt;
# &amp;lt;code&amp;gt;map-provider&amp;lt;/code&amp;gt; – the default map tile provider used when the Flight Analysis window is opened. Possible values:&lt;br /&gt;
#* &amp;lt;code&amp;gt;OpenStreetMap&amp;lt;/code&amp;gt; – default,&lt;br /&gt;
#* &amp;lt;code&amp;gt;OpenTopoMap&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
NOTE. Before editing the &amp;lt;code&amp;gt;autosave.xml&amp;lt;/code&amp;gt; file, close the simulator.&lt;br /&gt;
&lt;br /&gt;
= Backup (for FG 2020.3 and older) =&lt;br /&gt;
&lt;br /&gt;
If you edit logbook entries via GUI, then before each saving of a single change, the add-on creates a copy of the original CSV file, to which it appends the &amp;lt;code&amp;gt;.bak&amp;lt;/code&amp;gt; extension at the end. So, if something goes wrong while editing the data and the original file is corrupted, you can always recover it by removing the &amp;lt;code&amp;gt;.bak&amp;lt;/code&amp;gt; suffix from the copy name. Remember that you only have a copy of the last one file operation.&lt;br /&gt;
&lt;br /&gt;
For the newer version (2024.1 and later) based on the SQLite database, the backup copy is unnecessary as the database engine takes care of the correctness of the record.&lt;br /&gt;
&lt;br /&gt;
= Recovery mode =&lt;br /&gt;
&lt;br /&gt;
== For FG 2020.3 and older ==&lt;br /&gt;
&lt;br /&gt;
This add-on includes a mechanism to save the current flight status to a separate &amp;lt;code&amp;gt;recovery-v5.csv&amp;lt;/code&amp;gt; file every 30 seconds. If FlightGear unexpectedly closes due to an error, this file will be read on reboot and an entry from this file will be moved to the main log file. In this way, no flight, even aborted, should be lost.&lt;br /&gt;
&lt;br /&gt;
== For FG 2024.1 and later ==&lt;br /&gt;
&lt;br /&gt;
In the newer version based on the SQLite database, the recovery mechanism writes data every 20 seconds directly to the main database, so data will be preserved even if FlightGear crashes and that data will be available for viewing during flight.&lt;br /&gt;
&lt;br /&gt;
= Export database to CSV file (2024.1 and later) =&lt;br /&gt;
&lt;br /&gt;
For FlightGear 2024.1 and newer, the add-on provides an &amp;quot;Export to CSV&amp;quot; menu item that will export all data from the SQLite database to a CSV file, allowing you to process this data in a spreadsheet.&lt;br /&gt;
&lt;br /&gt;
This option will create two CSV files:&lt;br /&gt;
&lt;br /&gt;
# the first in the format &amp;lt;code&amp;gt;export-YYYY-MM-DD-HH-mm-SS-logbook.csv&amp;lt;/code&amp;gt; (suffix &amp;quot;logbook&amp;quot;) for the Logbook table,&lt;br /&gt;
# the second &amp;lt;code&amp;gt;export-YYYY-MM-DD-HH-mm-SS-tracker.csv&amp;lt;/code&amp;gt; (suffix &amp;quot;tracker&amp;quot;) for the flight analysis table.&lt;br /&gt;
&lt;br /&gt;
The relationship between the files is that the &amp;quot;tracker&amp;quot; CSV file contains a &amp;lt;code&amp;gt;Logbook ID&amp;lt;/code&amp;gt; column that contains the &amp;lt;code&amp;gt;ID&amp;lt;/code&amp;gt; column identifiers from the &amp;quot;logbook&amp;quot; CSV file.&lt;br /&gt;
&lt;br /&gt;
These files will be saved in the same directory as the SQLite file. The timestamp is taken from the time the export was made and is identical for both related files.&lt;br /&gt;
&lt;br /&gt;
= Note =&lt;br /&gt;
&lt;br /&gt;
# If you properly close the simulator during the flight (&amp;quot;File&amp;quot; -&amp;gt; &amp;quot;Exit&amp;quot;), the current flight status will be saved to the logbook (without landing information, of course).&lt;br /&gt;
# If the simulator will be closed incorrectly during flight, e.g. via the [X] button on the window bar, or a crash occurs, the logbook data should be saved in the &amp;lt;code&amp;gt;recovery-v5.csv&amp;lt;/code&amp;gt; file. The data in the &amp;lt;code&amp;gt;recovery-v5.csv&amp;lt;/code&amp;gt; file will be automatically transferred to the &amp;lt;code&amp;gt;logbook-v5.csv&amp;lt;/code&amp;gt; file when the simulator is restarted. For version 2024.1 and later, the data is always, cyclically written directly to the SQLite database, so the &amp;lt;code&amp;gt;recovery-v5.csv&amp;lt;/code&amp;gt; file is not used. Data for recovery mode is saved every 30 or 20 seconds.&lt;br /&gt;
# To count as a landing, the aircraft must rest on all wheels and maintain this state for at least 3 seconds. In this way, an ugly bounce off the runway will not be counted as a landing.&lt;br /&gt;
# If you start a simulation in the air, the add-on will recognize this and start logging without waiting for take-off.&lt;br /&gt;
# If you start a simulation in the air, the add-on is unable to recognize the landing gear, so the landing detection pass will extend to 6 seconds (giving an extra 3 seconds to make sure the aircraft is resting on all wheels).&lt;br /&gt;
# Helicopters should also be supported, although I have not tested all of them.&lt;br /&gt;
# The add-on supports JSBSim-based watercraft, although I have not tested all of them.&lt;br /&gt;
# The add-on supports the Space Shuttle.&lt;br /&gt;
# Flights with UFO will not be logged.&lt;br /&gt;
# Pausing the simulation or turning on the replay mode stops the flight statistics from being added to the log.&lt;br /&gt;
# As for fuel burn, the add-on does not take in-flight refueling into account. If you change the amount of fuel during the flight, the result in the &amp;lt;code&amp;gt;Fuel&amp;lt;/code&amp;gt; column will be incorrect. So try to avoid it and refuel the aircraft before the flight.&lt;br /&gt;
# Supported FG versions from 2020.1.&lt;br /&gt;
# The minimum resolution for using the GUI is 1366x768.&lt;br /&gt;
&lt;br /&gt;
= Landing gear hints =&lt;br /&gt;
&lt;br /&gt;
If this add-on has a problem with recognizing the landing gear correctly, then you can put the appropriate properties to indicate which indexes from &amp;lt;code&amp;gt;/gear/gear[index]&amp;lt;/code&amp;gt; are used by the aircraft.&lt;br /&gt;
&lt;br /&gt;
The structure of the property to be passed to FlightGear is as follows:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;PropertyList&amp;gt;&lt;br /&gt;
        &amp;lt;addons&amp;gt;&lt;br /&gt;
            &amp;lt;by-id&amp;gt;&lt;br /&gt;
                &amp;lt;org.flightgear.addons.logbook&amp;gt;&lt;br /&gt;
                    &amp;lt;hints&amp;gt;&lt;br /&gt;
                        &amp;lt;landing-gear-idx type=&amp;quot;int&amp;quot;&amp;gt;12&amp;lt;/landing-gear-idx&amp;gt;&lt;br /&gt;
                        &amp;lt;landing-gear-idx type=&amp;quot;int&amp;quot;&amp;gt;13&amp;lt;/landing-gear-idx&amp;gt;&lt;br /&gt;
                    &amp;lt;/hints&amp;gt;&lt;br /&gt;
                &amp;lt;/org.flightgear.addons.logbook&amp;gt;&lt;br /&gt;
            &amp;lt;/by-id&amp;gt;&lt;br /&gt;
        &amp;lt;/addons&amp;gt;&lt;br /&gt;
    &amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Each &amp;lt;code&amp;gt;&amp;lt;landing-gear-idx&amp;gt;&amp;lt;/code&amp;gt; tag should contain an integer indicating the index of the &amp;lt;code&amp;gt;/gear/gear&amp;lt;/code&amp;gt; property array. Thus, &amp;lt;code&amp;gt;&amp;lt;landing-gear-idx&amp;gt;&amp;lt;/code&amp;gt; with values of 12 and 13, indicate that the aircraft uses &amp;lt;code&amp;gt;/gear/gear[12]&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/gear/gear[13]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
These properties can be added in a number of ways, such as by placing them in the aircraft files, or by using command line options:&lt;br /&gt;
&lt;br /&gt;
    --prop:int:/addons/by-id/org.flightgear.addons.logbook/hints/landing-gear-idx[0]=12&lt;br /&gt;
    --prop:int:/addons/by-id/org.flightgear.addons.logbook/hints/landing-gear-idx[1]=13&lt;br /&gt;
&lt;br /&gt;
Thanks to MariuszXC for this feature.&lt;br /&gt;
&lt;br /&gt;
[[Category:FlightGear addons]]&lt;br /&gt;
[[Category:Nasal software]]&lt;br /&gt;
[[Category:Canvas GUI]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=AI_Traffic&amp;diff=143132</id>
		<title>AI Traffic</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=AI_Traffic&amp;diff=143132"/>
		<updated>2025-12-02T18:49:29Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Runway Usage Configuration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{AI Navbar}}&lt;br /&gt;
&lt;br /&gt;
'''AI Traffic''' (Artificial Intelligence), '''AI-Traffic''', or '''Interactive Traffic''' was introduced with [[FlightGear]] version 0.9.5. The purpose of AI traffic is to automatically populate airports sceneries with animated aircraft models reflecting the actual daily movements at and between airports to enrich the flightgear experience and improve its realism. This page aims at providing the documentation needed to populate airports with AI traffic.&lt;br /&gt;
&lt;br /&gt;
In essence, the AI controlled traffic system is comprised of four elements: &lt;br /&gt;
# AI Aircraft models: Are part of the base package under &amp;lt;tt&amp;gt;$DATA/AI/Aircraft&amp;lt;/tt&amp;gt; and cannot be flown by end user like regular FDM models as they are dedicated and exclusive to AI systems. &lt;br /&gt;
# AI traffic schedules: Are part of the base package under &amp;lt;tt&amp;gt;$DATA/AI/Traffic&amp;lt;/tt&amp;gt; and define where and when AI aircraft should fly. Files are unique per Operator's ICAO and split in sub-folders using the ICAO Initial. For example, traffic for United Airlines is stored under &amp;lt;tt&amp;gt;[[$FG ROOT]]/AI/Traffic/U/UAL.xml&amp;lt;/tt&amp;gt; &lt;br /&gt;
# Groundnets: Are part of the scenery pack under &amp;lt;tt&amp;gt;[[terrasync]]/Airports&amp;lt;/tt&amp;gt; and contain the information required to guide AI aircraft on the ground from gates to runways and vice versa, at each individual airport (one per airport).  The parking stands defined in an airport groundnet can also be used as Starting Positions when flying an FDM aircraft. They are shown and can be selected in Flightgear's &amp;quot;Location&amp;quot; startup tab.&lt;br /&gt;
# Runway Use Configurations (RWYUSE): Are part of the scenery pack under &amp;lt;tt&amp;gt;[[terrasync]]/Airports&amp;lt;/tt&amp;gt; and condition which runway(s) are for AI take off and landings based on the time of the day and wind conditions.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Traffic Schedules =&lt;br /&gt;
Traffic pattern describe the relationship between two separate entities: Aircraft and Flights.&lt;br /&gt;
&lt;br /&gt;
In real life, Flight Scheduling aims at maximizing the number of flights operated with the fleet of aircraft available, taking in account each aircraft’s initial location, the length of each flight, the required turnaround time at each airport and of course the routes operated. In practice an aircraft will fly different routes of different length on different day/time and not all aircraft will return to their home base the same day, especially in the case of long haul routes. The list of flights an aircraft will operate during a set period of time is the Aircraft Schedule.&lt;br /&gt;
AI aircraft provide some extra benefits: They do not need maintenance (or crew replacement) and so can be scheduled for use 24h00 per day; they are also never late nor cancelled hence they will perform 100% of their assigned flights on time.&lt;br /&gt;
&lt;br /&gt;
To minimize the amount of data handled, a frequency is attached to each flight as either daily or weekly; For example an aircraft based at EHAM can fly daily in the morning to EGLL (and back) but then to different destinations in the afternoon depending on the day of the week (LFPG on Monday, EDDF on Tuesday etc). In this scenario the EGLL flights are operated daily and the LFPG and EDDF ones, weekly as it will take another full week before they are operated again.&lt;br /&gt;
&lt;br /&gt;
Like in real life, the flights assigned to an aircraft must follow a logical routing sequence and the arrival city of one flight must be the departure city of the next (AI aircraft do not time travel no teleport). &lt;br /&gt;
Flightgear schedules are set for a full week and repeat indefinitely until the traffic file is updated. As a result, the routing sequence described above must be consistent with the schedule weekly reset: If the last flight in an aircraft schedule (Sunday night) take it to KSFO then the first flight in the schedule (Monday morning) must depart from KSFO.&lt;br /&gt;
From the above. The Home Base of an aircraft is always the departure city of its first flight in the schedule ie the airport it will be departing from for its first flight on Monday morning.&lt;br /&gt;
&lt;br /&gt;
It is recommended the traffic schedules follow as much as possible reality and so they should include turnaround time at each airport even if an AI aircraft does not require cleaning and refuelling.&lt;br /&gt;
 &lt;br /&gt;
To facilitate scheduling, a new database format was introduced in FlightGear 1.9.0 and flights are no longer directly and rigidly assigned to a specific aircraft (Unique registration). Instead, a more generic description of a fleet is given, along with a series of flights that need to be carried out. The routing is then taken care of by FlightGear itself.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== An example of a traffic file ==&lt;br /&gt;
&lt;br /&gt;
Below is a complete and working example of a Traffic Manager II file, as it can be used with FlightGear 1.9.0 and later:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
 &amp;lt;trafficlist&amp;gt;&lt;br /&gt;
    &amp;lt;aircraft&amp;gt;&lt;br /&gt;
        &amp;lt;model&amp;gt;Aircraft/MD11/Models/KLMmd11.xml&amp;lt;/model&amp;gt;&lt;br /&gt;
        &amp;lt;livery&amp;gt;KLM&amp;lt;/livery&amp;gt;&lt;br /&gt;
        &amp;lt;airline&amp;gt;KLM&amp;lt;/airline&amp;gt;&lt;br /&gt;
        &amp;lt;home-port&amp;gt;EHAM&amp;lt;/home-port&amp;gt;&lt;br /&gt;
        &amp;lt;required-aircraft&amp;gt;MD11KLM&amp;lt;/required-aircraft&amp;gt;&lt;br /&gt;
        &amp;lt;actype&amp;gt;MD11/P&amp;lt;/actype&amp;gt;&lt;br /&gt;
        &amp;lt;offset&amp;gt;25&amp;lt;/offset&amp;gt;&lt;br /&gt;
        &amp;lt;radius&amp;gt;39&amp;lt;/radius&amp;gt;&lt;br /&gt;
        &amp;lt;flighttype&amp;gt;gate&amp;lt;/flighttype&amp;gt;&lt;br /&gt;
        &amp;lt;performance-class&amp;gt;jet_transport&amp;lt;/performance-class&amp;gt;&lt;br /&gt;
        &amp;lt;registration&amp;gt;PH-KCA&amp;lt;/registration&amp;gt;&lt;br /&gt;
        &amp;lt;heavy&amp;gt;true&amp;lt;/heavy&amp;gt;&lt;br /&gt;
    &amp;lt;/aircraft&amp;gt;&lt;br /&gt;
    &amp;lt;aircraft&amp;gt;&lt;br /&gt;
        &amp;lt;model&amp;gt;Aircraft/MD11/Models/KLMmd11.xml&amp;lt;/model&amp;gt;&lt;br /&gt;
        &amp;lt;livery&amp;gt;KLM&amp;lt;/livery&amp;gt;&lt;br /&gt;
        &amp;lt;airline&amp;gt;KLM&amp;lt;/airline&amp;gt;&lt;br /&gt;
        &amp;lt;home-port&amp;gt;EHAM&amp;lt;/home-port&amp;gt;&lt;br /&gt;
        &amp;lt;required-aircraft&amp;gt;MD11KLM&amp;lt;/required-aircraft&amp;gt;&lt;br /&gt;
        &amp;lt;actype&amp;gt;MD11/P&amp;lt;/actype&amp;gt;&lt;br /&gt;
        &amp;lt;offset&amp;gt;25&amp;lt;/offset&amp;gt;&lt;br /&gt;
        &amp;lt;radius&amp;gt;39&amp;lt;/radius&amp;gt;&lt;br /&gt;
        &amp;lt;flighttype&amp;gt;gate&amp;lt;/flighttype&amp;gt;&lt;br /&gt;
        &amp;lt;performance-class&amp;gt;jet_transport&amp;lt;/performance-class&amp;gt;&lt;br /&gt;
        &amp;lt;registration&amp;gt;PH-KCB&amp;lt;/registration&amp;gt;&lt;br /&gt;
        &amp;lt;heavy&amp;gt;true&amp;lt;/heavy&amp;gt;&lt;br /&gt;
    &amp;lt;/aircraft&amp;gt;&lt;br /&gt;
  &lt;br /&gt;
    &amp;lt;flight&amp;gt;&lt;br /&gt;
        &amp;lt;callsign&amp;gt;KLM0765&amp;lt;/callsign&amp;gt;&lt;br /&gt;
        &amp;lt;required-aircraft&amp;gt;MD11KLM&amp;lt;/required-aircraft&amp;gt;&lt;br /&gt;
        &amp;lt;fltrules&amp;gt;IFR&amp;lt;/fltrules&amp;gt;&lt;br /&gt;
        &amp;lt;departure&amp;gt;&lt;br /&gt;
            &amp;lt;port&amp;gt;EHAM&amp;lt;/port&amp;gt;&lt;br /&gt;
            &amp;lt;time&amp;gt;0/12:35:00&amp;lt;/time&amp;gt;&lt;br /&gt;
        &amp;lt;/departure&amp;gt;&lt;br /&gt;
        &amp;lt;cruise-alt&amp;gt;330&amp;lt;/cruise-alt&amp;gt;&lt;br /&gt;
        &amp;lt;arrival&amp;gt;&lt;br /&gt;
            &amp;lt;port&amp;gt;TNCM&amp;lt;/port&amp;gt;&lt;br /&gt;
            &amp;lt;time&amp;gt;0/21:15:00&amp;lt;/time&amp;gt;&lt;br /&gt;
        &amp;lt;/arrival&amp;gt;&lt;br /&gt;
        &amp;lt;repeat&amp;gt;WEEK&amp;lt;/repeat&amp;gt;&lt;br /&gt;
    &amp;lt;/flight&amp;gt;&lt;br /&gt;
  &lt;br /&gt;
    &amp;lt;flight&amp;gt;&lt;br /&gt;
        &amp;lt;callsign&amp;gt;KLM0769&amp;lt;/callsign&amp;gt;&lt;br /&gt;
        &amp;lt;required-aircraft&amp;gt;MD11KLM&amp;lt;/required-aircraft&amp;gt;&lt;br /&gt;
        &amp;lt;fltrules&amp;gt;IFR&amp;lt;/fltrules&amp;gt;&lt;br /&gt;
        &amp;lt;departure&amp;gt;&lt;br /&gt;
            &amp;lt;port&amp;gt;TNCM&amp;lt;/port&amp;gt;&lt;br /&gt;
            &amp;lt;time&amp;gt;3/01:25:00&amp;lt;/time&amp;gt;&lt;br /&gt;
        &amp;lt;/departure&amp;gt;&lt;br /&gt;
        &amp;lt;cruise-alt&amp;gt;330&amp;lt;/cruise-alt&amp;gt;&lt;br /&gt;
        &amp;lt;arrival&amp;gt;&lt;br /&gt;
            &amp;lt;port&amp;gt;EHAM&amp;lt;/port&amp;gt;&lt;br /&gt;
            &amp;lt;time&amp;gt;3/10:50:00&amp;lt;/time&amp;gt;&lt;br /&gt;
        &amp;lt;/arrival&amp;gt;&lt;br /&gt;
        &amp;lt;repeat&amp;gt;WEEK&amp;lt;/repeat&amp;gt;&lt;br /&gt;
    &amp;lt;/flight&amp;gt;&lt;br /&gt;
 &amp;lt;/trafficlist&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Dissecting the traffic file ==&lt;br /&gt;
Here I will discuss the general structure of a traffic file in more detail. As discussed, the traffic patterns are centered around aircraft and flights. Therefore, a minimal traffic file will consist of two sections: the aircraft definition and the flights section. In the next two sections, I will discuss each of these sections.&lt;br /&gt;
&lt;br /&gt;
=== General layout ===&lt;br /&gt;
The general layout of each file should look like the above example.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
 &amp;lt;trafficlist&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
 &amp;lt;/trafficlist&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The general layout of each file should look like the above example. The first line contains a generic xml header, which is followed on the second line with a '''&amp;lt;trafficlist&amp;gt;''' statement. Also, the last line in the file should close off the trafficlist section using the '''&amp;lt;/trafficlist&amp;gt;''' statement. As will be illustrated below, between these two statements can be one or more aircraft definitions.&lt;br /&gt;
&lt;br /&gt;
=== Defining the aircraft ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
 &amp;lt;trafficlist&amp;gt;&lt;br /&gt;
     &amp;lt;aircraft&amp;gt;&lt;br /&gt;
         &amp;lt;model&amp;gt;Aircraft/MD11/Models/KLMmd11.xml&amp;lt;/model&amp;gt;&lt;br /&gt;
         &amp;lt;livery&amp;gt;KLM&amp;lt;/livery&amp;gt;&lt;br /&gt;
         &amp;lt;airline&amp;gt;KLM&amp;lt;/airline&amp;gt;&lt;br /&gt;
         &amp;lt;home-port&amp;gt;EHAM&amp;lt;/home-port&amp;gt;&lt;br /&gt;
         &amp;lt;required-aircraft&amp;gt;MD11KLM&amp;lt;/required-aircraft&amp;gt;&lt;br /&gt;
         &amp;lt;actype&amp;gt;MD11/P&amp;lt;/actype&amp;gt;&lt;br /&gt;
         &amp;lt;offset&amp;gt;25&amp;lt;/offset&amp;gt;&lt;br /&gt;
         &amp;lt;radius&amp;gt;39&amp;lt;/radius&amp;gt;&lt;br /&gt;
         &amp;lt;flighttype&amp;gt;gate&amp;lt;/flighttype&amp;gt;&lt;br /&gt;
         &amp;lt;performance-class&amp;gt;jet_transport&amp;lt;/performance-class&amp;gt;&lt;br /&gt;
         &amp;lt;registration&amp;gt;PH-KCA&amp;lt;/registration&amp;gt;&lt;br /&gt;
         &amp;lt;heavy&amp;gt;true&amp;lt;/heavy&amp;gt;&lt;br /&gt;
     &amp;lt;/aircraft&amp;gt;&lt;br /&gt;
     &amp;lt;aircraft&amp;gt;&lt;br /&gt;
     ...&lt;br /&gt;
     &amp;lt;/aircraft&amp;gt;&lt;br /&gt;
 &amp;lt;/trafficlist&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The first lines inside the '''&amp;lt;aircraft&amp;gt;''' definition specify some of the aircraft's performance characteristics.&lt;br /&gt;
&lt;br /&gt;
* '''&amp;lt;model&amp;gt;''' Here is a path specified to the 3D model that should be used in FlightGear.&lt;br /&gt;
* '''&amp;lt;livery&amp;gt;''' This line is currently unused, and will likely remain unused. The original idea was to combine this with the '''&amp;lt;model&amp;gt;''' line (see above) to load a specific combination of aircraft type ('''&amp;lt;actype&amp;gt;''') and paint scheme, but I abandoned that idea, because it didn't turn out to be very compatible with the existing FlightGear model loading code. &lt;br /&gt;
* '''&amp;lt;airline&amp;gt;''' This line refers to the airline operating the aircraft. This information is currently used by FlightGear to handle gate/parking assignments. Use the official 3-letter [[ICAO]] airline code here, in case of commercial traffic. This keyword is unlikely to be used for general aviation and military traffic. &lt;br /&gt;
* '''&amp;lt;home-port&amp;gt;''' Each FlightGear aircraft is assigned to a home airport. Internally, this is used to ensure that routes are setup that will eventually lead back to the home airport. This is done to maintain some sensibility in the routing algorithm. '''[New for Traffic Manager II]'''&lt;br /&gt;
* '''&amp;lt;required-aircraft&amp;gt;''' This value is a key that binds aircraft and flights together. In case of this example, the key MD11KLM. Indicates that this aircraft will only carry out flights containing the same key. Usually, a combination of aircraft type, and airline will suffice for this key. In some cases, in particular, when specific aircraft / airlines are distributed across multiple hubs (i.e. home ports), it may be necessary to specify a key containing the home airport as well. For example, Delta Airlines operates 767's out of Atlanta, as well as out of New York. To separate between these two cases, it would be advisable to use two keys; one for KATL (e.g. 767DALKATL), and one for KJFK (e.g., 767DALKJFK). '''[NEW for Traffic Manager II]'''&lt;br /&gt;
* '''&amp;lt;actype&amp;gt;''' A description of the aircraft type reserved for future use in [[ATC]].&lt;br /&gt;
* '''&amp;lt;offset&amp;gt;''' Ground offset of the 3D model. Not all aircraft 3D models are built using the same convention. Use this parameter to align the wheels with the ground. Notice that this parameter will probably become obsolete in the near future, because model view point references can also be done in the XML file that the '''&amp;lt;model&amp;gt;''' keyword refers to.&lt;br /&gt;
* '''&amp;lt;radius&amp;gt;''' An estimate of the aircraft's size. This is mainly used at airports for gate assignments.&lt;br /&gt;
* '''&amp;lt;flighttype&amp;gt;''' In the near future, this keyword will be used for runway assignments, so that general aviation, commercial, and military traffic will use different runways if that is part of the airport's operational procedures. This line is also used for gate assignments and should be one of the following:&lt;br /&gt;
** '''ga''' (general aviation), &lt;br /&gt;
** '''cargo''' (cargo)&lt;br /&gt;
** '''gate''' (commercial passenger traffic) &lt;br /&gt;
** '''mil-fighter''' (military fighter)&lt;br /&gt;
** '''mil-cargo''' (military transport)&lt;br /&gt;
* '''&amp;lt;performance-class&amp;gt;''' This line is used to determine the performance characteristics of AI aircraft. This should match one of the performance classes that are predefined in FlightGear. Currently, the following performance classes are supported:&lt;br /&gt;
** '''light_aircraft''' (General aviation prop driven single or twin),&lt;br /&gt;
** '''turboprop_transport''' (Commercial Turboprop),&lt;br /&gt;
** '''jet_transport''' (Commercial jet)&lt;br /&gt;
** '''heavy_jet''' (Commercial jet w/ MTOW &amp;gt; 136tons)&lt;br /&gt;
** '''ww2_fighter''' (world war two fighter),&lt;br /&gt;
** '''jet_fighter''' (modern fighter aircraft)&lt;br /&gt;
** '''tanker''' (tanker aircraft), or.&lt;br /&gt;
** '''ufo''' (allows extreme accel/decel capabilities).&lt;br /&gt;
* '''&amp;lt;registration&amp;gt;''' The aircraft's tail number. Future versions of FlightGear will use this registration in ATC for general aviation traffic. For commercial traffic the registration number will likely remain unused.&lt;br /&gt;
* '''&amp;lt;heavy&amp;gt;''' Can be true or false. Reserved for future use by ATC, to determine whether the postfix &amp;quot;Heavy&amp;quot; should be appended to the aircraft's callsign.&lt;br /&gt;
&lt;br /&gt;
=== Defining a flight ===&lt;br /&gt;
The Traffic Manager II file formats contains separate sections for both aircraft and flights information, with the common denominator being a shared key that links aircraft and flight information together. In other words, the general layout of a Traffic Manager II file looks something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;aircraft&amp;gt; &lt;br /&gt;
     ...&lt;br /&gt;
     &amp;lt;required-aircraft&amp;gt;MD11KLM&amp;lt;/required-aircraft&amp;gt;&lt;br /&gt;
     ...&lt;br /&gt;
 &amp;lt;/aircraft&amp;gt;&lt;br /&gt;
  &lt;br /&gt;
 &amp;lt;flight&amp;gt;&lt;br /&gt;
     ...&lt;br /&gt;
     &amp;lt;required-aircraft&amp;gt;MD11KLM&amp;lt;/required-aircraft&amp;gt;&lt;br /&gt;
     ...&lt;br /&gt;
 &amp;lt;/flight&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each flight is defined between the '''&amp;lt;flight&amp;gt;''' and '''&amp;lt;/flight&amp;gt;''' statements. For example, let's have a look at a randomly selected flight from our KLM.xml example. &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;flight&amp;gt;&lt;br /&gt;
     &amp;lt;callsign&amp;gt;KLM0769&amp;lt;/callsign&amp;gt;&lt;br /&gt;
     &amp;lt;required-aircraft&amp;gt;MD11KLM&amp;lt;/required-aircraft&amp;gt;&lt;br /&gt;
     &amp;lt;fltrules&amp;gt;IFR&amp;lt;/fltrules&amp;gt;&lt;br /&gt;
     &amp;lt;departure&amp;gt;&lt;br /&gt;
         &amp;lt;port&amp;gt;EHAM&amp;lt;/port&amp;gt;&lt;br /&gt;
         &amp;lt;time&amp;gt;2/12:35:00&amp;lt;/time&amp;gt;&lt;br /&gt;
     &amp;lt;/departure&amp;gt;&lt;br /&gt;
     &amp;lt;cruise-alt&amp;gt;330&amp;lt;/cruise-alt&amp;gt;&lt;br /&gt;
     &amp;lt;arrival&amp;gt;&lt;br /&gt;
         &amp;lt;port&amp;gt;TNCB&amp;lt;/port&amp;gt;&lt;br /&gt;
         &amp;lt;time&amp;gt;2/22:15:00&amp;lt;/time&amp;gt;&lt;br /&gt;
     &amp;lt;/arrival&amp;gt;&lt;br /&gt;
     &amp;lt;repeat&amp;gt;WEEK&amp;lt;/repeat&amp;gt;&lt;br /&gt;
 &amp;lt;/flight&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The '''&amp;lt;flight&amp;gt;''' section is slightly more complex than the aircraft definition itself, because it has a few nested levels, however, these should mostly be self-explanatory. The following keywords should be specified:&lt;br /&gt;
* '''&amp;lt;flight&amp;gt;''' All the relevant parameter should be specified between the '''&amp;lt;flight&amp;gt;&amp;lt;/flight&amp;gt;''' keywords.&lt;br /&gt;
* '''&amp;lt;callsign&amp;gt;''' The airline callsign used for ATC. If this is an airline flight it should be combination of the airline callsign (e.g. '''KLM''' for KLM, or '''Springbok''' for South African Airways), and the flight number (e.g. '''KLM0605''', or '''Springbok0033''').&lt;br /&gt;
* '''&amp;lt;required-aircraft&amp;gt;''' The key that links this flight to a particular aircraft. The see explanation in the ''aircraft'' section. &lt;br /&gt;
* '''&amp;lt;fltrules&amp;gt;''' Can be ''IFR'' or ''VFR''. This is required for use in ATC, but currently not used. This is likely to change, however.&lt;br /&gt;
* '''&amp;lt;departure&amp;gt;''' Definition of the departure airport. This section should contain the '''&amp;lt;port&amp;gt;''' and '''&amp;lt;nowiki&amp;gt;&amp;lt;time&amp;gt;&amp;lt;/nowiki&amp;gt;''' keywords.&lt;br /&gt;
* '''&amp;lt;cruise-alt&amp;gt;''' Cruising altitude for this flight. This is a bit of a simplification from the real world, where aircraft usually don't stay at the same cruise altitude throughout the flight. This behavior will probably also change in future versions. Values are in [https://en.wikipedia.org/wiki/Flight_level flightlevels]&lt;br /&gt;
* '''&amp;lt;arrival&amp;gt;''' Same as '''&amp;lt;departure&amp;gt;''', but now defining the '''&amp;lt;port&amp;gt;''' and '''&amp;lt;nowiki&amp;gt;&amp;lt;time&amp;gt;&amp;lt;/nowiki&amp;gt;''' of arrival.&lt;br /&gt;
* '''&amp;lt;repeat&amp;gt;''' Repeat period. This can be either the keyword ''WEEK'', or a number followed by ''Hr''. ''WEEK'' means that the whole schedule repeats itself on a weekly basis. ''Hr'' means that the whole schedule repeats after the number of hours specified directly before it (e.g. ''24Hr'' means that the schedule repeats after 24 hours). With Traffic Manager II, it is generally recommended not to mix schedules that rotate on different frequencies. In general, the best results are obtained when using only weekly rotating flights. For flights that are in reality operated on a daily basis, it is recommended to just specify multiple entries, i.e. one separate flight for every weekday.&lt;br /&gt;
* '''&amp;lt;port&amp;gt;''' This should be the international ICAO airport code. This keyword should be specified only within the '''&amp;lt;departure&amp;gt;''' or '''&amp;lt;arrival&amp;gt;''' sections. As far as I know, here it should still be in upper case, although the FlightGear command line currently also supports lower case ICAO codes. &lt;br /&gt;
* '''&amp;lt;nowiki&amp;gt;&amp;lt;time&amp;gt;&amp;lt;/nowiki&amp;gt;''' Used to specify the '''&amp;lt;departure&amp;gt;''' or '''&amp;lt;arrival&amp;gt;''' time. The format of this string is ''hour:minute:second''. Notice that departure day is optional and is specifically intended to be used in combination with a weekly repeating schedule. When used in combination with other schedules, results may be unpredictable. Times should be in UTC. Weekdays start on Sunday (0) and end on Saturday (6).&lt;br /&gt;
&lt;br /&gt;
== Putting it all together: Including a traffic file ==&lt;br /&gt;
Traffic files are found under [[$FG_ROOT]]/AI/Traffic. The actual traffic files should be stored in a subdirectory one level below /AI/Traffic.&lt;br /&gt;
All traffic is organized by Operator ICAO code, and stored in a single letter directory. For example, KLM traffic can be found in [[$FG_ROOT]]/AI/Traffic/K/KLM.xml, and United Airlines traffic is stored in [[$FG_ROOT]]/AI/Traffic/U/UAL.xml.&lt;br /&gt;
&lt;br /&gt;
The name of the file is the ICAO of the operator (aircraft owner) which is normally the same that the one found in the &amp;lt;airline&amp;gt; tag in the file itself but not alway. For example Compass Airlines in Minneapolis, has the ICAO code CPZ but operates flights for both American Airlines (AAL) and Delta Airlines (DAL). In this scenario the traffic file will be stored as [[$FG_ROOT]]/AI/Traffic/C/CPZ.xml and will contain a series of aircraft with airlines tags AAL and another series with tag DAL. Similarly, certain flights in the file will be numbered AAxxxx or DLxxxx.&lt;br /&gt;
&lt;br /&gt;
= Ground networks =&lt;br /&gt;
&lt;br /&gt;
[[File:LFPG T2E ParkPos.jpg|thumb|Using a Groundnet, AI aircraft can park precisely at the Terminal gates]]&lt;br /&gt;
&lt;br /&gt;
Using the traffic files information above, the AI Traffic Manager knows which AI aircraft should land at (and take off from) each airport and when. It will automatically place the relevant aircraft models in the scenery and animate them so they navigate from the runways to the gates and vice versa, according to their individual schedule.&lt;br /&gt;
&lt;br /&gt;
Although the physical layout of each airport is stored in FlightGear's APT.DAT master file, the information is not accurate enough to determine which specific routes AI models can use; Instead, Traffic manager will rely on a dedicated file containing a simple wireframe/network of taxiways and gates AI aircraft can follow whilst on the ground ie a GroundNet. A groundnet is made of 3 different elements: Parking Positions, Nodes and Segments (to join nodes and Parking together). &lt;br /&gt;
&lt;br /&gt;
This routing information is aggregated, per airport, in a XML, stored and distributed by Terrasync as '''/Terrasync/Airports/[I]/[C]/[A]/[ICAO].groundnet.xml''' where ICAO stands for the 4 letter ICAO code of the relevant airport.&lt;br /&gt;
&lt;br /&gt;
Similarly to Terrain and Objects, Terrasync groundnets can be overridden by placing a personalized version in your custom scenery folder, using the same path structure: /Custom Scenery/Airports/[I]/[C]/[A]/[ICAO].groundnet.xml.&lt;br /&gt;
&lt;br /&gt;
Groundnets are not mandatory but, in absence of this routing information, AI Aircraft cannot park anywhere; they will still try to stick to their schedule, appearing magically at the centre of the airport and taxiing directly to the runways’ thresholds, over grass, buildings and static objects, on time.&lt;br /&gt;
&lt;br /&gt;
Groundnets rely on the runway threshold information stored in /Terrasync/Airports/[I]/[C]/[A]/[ICAO].threshold.xml to determine where runways are (the space between each pair of thresholds in the file) and their heading. This data is used to determine an aircraft has reached the runway and can initiate take off. Similarly it is used to select where an arriving AI aircraft can touch down and start braking. &lt;br /&gt;
&lt;br /&gt;
== A technical perspective ==&lt;br /&gt;
A ground network xml file consists of four tables:&lt;br /&gt;
* '''&amp;lt;frequencies&amp;gt;''' The Airport’s radio frequencies (Optional). As of v1.9.0, FlightGear uses these to display some ATC messages like start-up approval requests. You can &amp;quot;hear&amp;quot; them by tuning to the first ground frequency listed in the section.&lt;br /&gt;
* '''&amp;lt;parkingList&amp;gt;''' The details of each parking/gate at the airport and the characteristics of which AI aircraft can use them.&lt;br /&gt;
* '''&amp;lt;TaxiNodes&amp;gt;''' The list of all the nodes in the ground network.&lt;br /&gt;
* '''&amp;lt;TaxiWaySegments&amp;gt;''' A list of all links/segments (or &amp;quot;arcs&amp;quot; as David Luff called them initially) existing between all nodes and Parking Positions.&lt;br /&gt;
&lt;br /&gt;
Each Parking and Node element in the groundnet has a unique index number, allowing routes be formed (from one ID to the next via a segment/arc) between parking positions and runways &lt;br /&gt;
&lt;br /&gt;
''Parking Positions Parameters:''&lt;br /&gt;
* '''index''' Unique ID, internal to the file structure&lt;br /&gt;
* '''lat''' latitude in decimal minutes format, for example &amp;lt;code&amp;gt;N50 56.988&amp;lt;/code&amp;gt;)&lt;br /&gt;
* '''lon''' longitude in decimal minutes format, for example &amp;lt;code&amp;gt;W01 21.756&amp;lt;/code&amp;gt;)&lt;br /&gt;
* '''name &amp;amp; number ''' (gate identification as found on airport charts, for example &amp;lt;code&amp;gt;D23&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;C1&amp;lt;/code&amp;gt;). &lt;br /&gt;
* '''type:''' The type of aircraft which can use this space. Matched to the &amp;lt;flighttype&amp;gt; parameter found in traffic files:&lt;br /&gt;
** '''ga''' (general aviation), &lt;br /&gt;
** '''cargo''' (cargo/freighter)&lt;br /&gt;
** '''gate''' (commercial passenger traffic) &lt;br /&gt;
** '''mil-fighter''' (military fighter)&lt;br /&gt;
** '''mil-cargo''' (military transport)&lt;br /&gt;
* '''heading:''' The heading at which the aircraft parks in this space.&lt;br /&gt;
* '''radius:''' The size of the parking spot. Matched to the aircraft &amp;lt;radius&amp;gt; parameter in traffic files to determine if a given AI Aircraft model will fit the position. See [[aircraft radii]]&lt;br /&gt;
* '''airlineCodes:''' a comma-separated list of ICAO airline codes allowed to park at this gate. Matched to the aircraft  &amp;lt;airline&amp;gt; parameter of traffic files. Leave blank for all airline to be able to use the gate.&lt;br /&gt;
* '''pushBackRoute:''' The ID of the next node in the network along a pushback route. In a correctly configured network, the AI aircraft will taxi to this node in reverse, thus simulating being pushed back. See the documentation for the PushBack hold point type below.&lt;br /&gt;
&lt;br /&gt;
''Nodes parameters:''&lt;br /&gt;
&lt;br /&gt;
* '''isOnRunway'''' A logical value that is 1 when the node is on the runway, 0 otherwise. Aircraft waiting to take off will line up behind the last node marked “not on runway” (“0”) until the runway is clear&lt;br /&gt;
* '''holdPointType''' can have the following values:&lt;br /&gt;
** '''None''' Not a holding point (normal taxi-through node) &lt;br /&gt;
** '''PushBack''' The node marks the end of a pushback route, where the aircraft stops rolling backward and start rolling forward upon clearance. A pushback Holding Point can be part of more than one pushback route. See Pushback below. &lt;br /&gt;
** '''Normal''' (Not yet supported): a regular taxiway holding point.&lt;br /&gt;
** '''CAT II/III''' (Not yet supported): a special holding point for IFR conditions.&lt;br /&gt;
&lt;br /&gt;
''segments parameters:''&lt;br /&gt;
&lt;br /&gt;
* '''begin''' The id of the parking space or AINode where this segment starts&lt;br /&gt;
* '''end''' The id of the parking space or AINode where this segment ends&lt;br /&gt;
* '''IsPushBackRoute''' a logical value. Should be 1 if the current segment is part of a route connecting a gate to a push back hold point, or 0 otherwise.&lt;br /&gt;
* '''name''' Name of the taxiway.&lt;br /&gt;
&lt;br /&gt;
== Tools &amp;amp; Source Material ==&lt;br /&gt;
Groundnets can be built using either FG Airports (v0.032 or later) or Taxidraw (legacy) &lt;br /&gt;
&lt;br /&gt;
[[FGAirports]] is the current tool of choice. Its philosophy is airport centered rather than file specific and allows you to visualize at a glance all of the existing groundnets as well as editing them and submitting them to Terrasync. It leverages Open Street Map data, APT.DAT and scenery xmls to provide a visual representation of the airport layout as well as thresholds and tower positions. &lt;br /&gt;
&lt;br /&gt;
FGA automatically checks the validity of groundnet files before submission and assess their adequacy by comparing the number of gates set against the volume of AI traffic at a particular airport.&lt;br /&gt;
&lt;br /&gt;
[[TaxiDraw]] pre dates WED and allowed the creation of airport layouts from basic geometrical shapes. Its ground network module is somewhat separate from the rest of the airport project code but allows the creation and local export of simple groundnets.&lt;br /&gt;
&lt;br /&gt;
See the [[TaxiDraw]] and [[FGAirports]] articles for instructions on how to obtain the tools and operating instructions.&lt;br /&gt;
&lt;br /&gt;
Most Civil Aviation authorities make electronic versions of their Aeronautical Information Publication available on the web (Lookup 'eAIP' or go to https://www.eurocontrol.int/articles/ais-online). AIPs contain precise Airport Charts but also lists of parking stands with their exact Latitude/Longitude as well as usage (Cargo, Gate) and the category (radii) of aircraft they can accommodate.&lt;br /&gt;
 &lt;br /&gt;
Gates Numbers and Airlines operating them can often be found on Airports website. Most flight tracking sites/apps will also allow you to monitor a flight all to the way to its gate to identify who parks where.&lt;br /&gt;
&lt;br /&gt;
In essence, all the information you need can be compiled from multiple sources, including Wikipedia, airport diagrams published on the net, in flight airline magazines, etc. etc. In other words, be creative!&lt;br /&gt;
&lt;br /&gt;
== Ground traffic rendering ==&lt;br /&gt;
''19th November 2021.''&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
&lt;br /&gt;
* CPU: Currently having a lot of nodes in the OSG scene-graph will rapidly make FlightGear CPU bound. A lot of separately animated ground traffic objects mean a lot of nodes. Static objects (i.e. non-animated objects) can be put into one mesh - meaning one scenegraph node with the current system.&lt;br /&gt;
* GPU: Having fewer draw calls is fast. Having fewer state changes is fast. &lt;br /&gt;
* The fast path for both CPU and GPU is creating a way to combine multiple animated objects in one mesh - this is how modern 3d applications do lots of objects with animations fast.&lt;br /&gt;
&lt;br /&gt;
=== The fast way to do moving objects is to let the vertex shader look at information in buffers and position each object ===&lt;br /&gt;
&lt;br /&gt;
* Have all the different path segments stored in one buffer that can be read by the vertex shader. This buffer can be generated at runtime for each airport, maybe using NASAL scripts, and not updated.It's also possible to store the 1st buffer in TerraSync. &lt;br /&gt;
* Have a second buffer store all per-object data in an array. The array is indexed by the object ID. This includes things like vehicle animation state, which path segment each object is on, the starting time, and a speed scaling factor. The second buffer is updated regularly by the CPU.&lt;br /&gt;
* Two approaches to packing different types of objects in a 1 or few meshes &lt;br /&gt;
**'''a).''' Every type of object is combined in one vertex buffer and rendered with the same shaders, texture atlas or array. Every vehicle is packed one after the other in the vertex buffers. Multiple types of vehicles are contained in the same mesh. Multiple instances of the same vehicle take up extra space. &lt;br /&gt;
*** All material parameters or uniform values that change between objects, or within objects, like specularity are added as vertex attributes or textures. A vertex shader that can handle all object animations is used. &lt;br /&gt;
*** The entire set of objects takes up one scene graph node, and is rendered in one draw call. It's possible to do ground vehicles and AI aircraft as two meshes.&lt;br /&gt;
*** The object ID (an integer) can be added to each vertex as an vertex attribute. This is used to lookup per-object info in the 2nd buffer, including the paths from the first buffer.&lt;br /&gt;
** '''b).''' Each different type of object (different ground vehicle) is instanced and takes up one scene-graph node. This can allow different shaders and textures per object type. There is less vertex data taking up RAM and VRAM. There are more scenegraph nodes, and draw calls.&lt;br /&gt;
*** The object ID (an integer) can be added as a per-instance vertex attribute.&lt;br /&gt;
* The vertex shader can trivially look at the object ID, then find the path segment, the starting time, speed scaling factor, and current time to position each object. If the current time is past the end of the current path, the object will stay at the end position.&lt;br /&gt;
* The buffers can be a uniform array (minimum of points in each path segment and more smoothing), Uniform Buffer Object (UBO - not available until Vulkan due to Macs not supporting it), a Texture Buffer Object (TBO - Mac support unknown), or a texture looked up in a vertex shader (maybe slower).&lt;br /&gt;
* Data format: Each path segment is x,y,z position at regular times - a time-series of positions - e.g. [10m, 20m, 0m elevation] at t = 1s. [20m, 25m, 0m] at t=2s . [30m, 30m, 0m] at t= 3s. 16 bit integers are enough, but 32 but floats can also be used at the cost of 2x the occupancy. 16 bit implementation: If a texture is used it can be 16bit RGB - R=x, G=y, B=z.  2 consecutive 8 bit values can also be read from a texture.16 bit integers can cover an ground traffic area of ~65km with a spacing of 1m. 16bits can give a ~13km ground traffic area with an accuracy of 20cm. The accuracy of the positions doesn't matter too much - the vertex shader can look at 2-3+ path positions and create a smooth interpolation (a smooth curve joining the points). The time steps can also be large. &lt;br /&gt;
* If a path has a slowly moving segment, it will take up more points. If a path segment takes a short amount of time, it can have a special xyz numner to indicate path has ended - otherwise the path start/end time information can be stored as uniforms or in a UBO. Vehicles that move faster can have a higher speed scaling. There can be different path segment variations, for example if vehicles have a different acceleration pattern - e.g. speeding up and braking hard, or sharper turns, in an emergency.&lt;br /&gt;
* Both ground vehicles, and taxiing aircraft can be done this way. It's possible to replace a conventional AI aircraft model, with a fast rendering ground traffic model once an aircraft starts taxiing.&lt;br /&gt;
* If models use animations, they should ideally use a vertex shader that can handle all these animations - so having multiple draw calls per model and CPU-side animation is avoided.&lt;br /&gt;
* It's possible to split of different parts of craft into separate meshes for parts that are very dissimilar - at the cost of more scenegraph nodes and draw calls. For example, a separate mesh for lights of various types - these lights will follow the same paths as the vechiles and position themselves correctly.&lt;br /&gt;
* &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The way animation is handled (animation meaning moving objects, and parts of moving objects) in modern 3d application is to store the animation data in arrays/buffers on the GPU side and render multiple objects in one draw call.&lt;br /&gt;
&lt;br /&gt;
From a quick google search - a 2010 blog about whether UBOs or TBOs are more suited for different tasks:&lt;br /&gt;
&lt;br /&gt;
''&amp;quot;Personally I use them [UBOs] for instanced rendering by storing the model-view matrix and related information of each and every instance in a common uniform buffer and use the instance id as an index to this combined data structure. This usage performs very well on my system.''&lt;br /&gt;
&lt;br /&gt;
''Also uniform buffers can be used to store the matrices of bones and use them for implementing skeletal animation, however, I personally prefer using normal 2D textures for this purpose to take advantage of the free interpolation thanks to the dedicated texture fetching units but that’s another story.''&lt;br /&gt;
&lt;br /&gt;
''[..] Personally I use texture buffers for different geometry deformation techniques, to resolve batching issues when the size limitation of uniform buffers is a blocking factor, and for some inverse kinematics effects.&amp;quot; - Rastergrid blog, 2010 [https://www.rastergrid.com/blog/2010/01/uniform-buffers-vs-texture-buffers/][https://web.archive.org/web/20211119134448/https://www.rastergrid.com/blog/2010/01/uniform-buffers-vs-texture-buffers/]''&lt;br /&gt;
&lt;br /&gt;
This is from a 2010 blog - this general approach has been the way to do lots of animated objects for a long time. &lt;br /&gt;
&lt;br /&gt;
Even really complicated animations like moving bodies and cloth physics are handled by moving animation data into buffers - this approach would be needed if FlightGear ever did a simulation of crowds at an airport - not just for boarding one plane which probably won't be too cripplingly slow. But for crowds boarding lots of planes, and moving about large airports. For animating humans, there are will be standard animation formats compatible with output of tools in blender or make human[http://www.makehumancommunity.org/]. This approach may also be worth while for rendering lots of seated passengers with really simple movements - and a few walking about with a simple walk cycle. It may also be justified for large amounts of ground personnel at big airports (not just a few relevant for the users plane).&lt;br /&gt;
&lt;br /&gt;
== Warnings and Limitations ==&lt;br /&gt;
The complexity of building a fully functional groundnet (and the time spent on it) grows exponentially with the size of the airport but very small airports, on Pacific Islands for example, pose even larger challenges. An ideal project to start with is a Metropolitan airport with one or two runways, and two dozen of parking positions. &lt;br /&gt;
&lt;br /&gt;
FG Scenery and Traffic manager have their limitations and dependencies which create specific challenges of their own. For example:&lt;br /&gt;
[[File:AI Traffic at KJFK former Terminal 3.jpg|300px|thumb|AI Traffic sitting on top of KJFK former Terminal 3]]&lt;br /&gt;
* TM does not space landing aircraft. You will most likely see packs of landing aircraft hitting your runways at once, sometimes from both ends. Departures are spaced properly though &lt;br /&gt;
* TM does not yet use Regular/Cat III Holding points data and so you cannot force an AI aircraft to pause/hold on a route. &lt;br /&gt;
* TM does not support the conditional use of gates (Do not use A if B is occupied); Always assume all the Parking position you set will be occupied. &lt;br /&gt;
* AI Aircraft cannot &amp;quot;pivot&amp;quot; on their parking positions. The last segment on a route leading to or exiting a parking position must have the same heading than the parking position itself (Park Straight) &lt;br /&gt;
&lt;br /&gt;
* Groundnets, terrain and scenery objects are independent. If you see your AI aircraft rolling on grass or sitting on buildings and for as long as your groundnet lat.lon data is correct, consider that the airport terrain/layout may be outdated or misplaced. Similarly, Building/Object may have been placed at certain Lat/Lon but since demolished (example of JFK Terminal 3 on the right) to make space for a new taxiway as airports expand and change layout regularly.&lt;br /&gt;
&lt;br /&gt;
Make sure you have verified the data in your ICAO.threshold.xml before starting building your groundnet. If the data is incorrect, locate the correct one on the web and post a request for adjustment in the FlightGear AI forum with a link to the correct data source.&lt;br /&gt;
&lt;br /&gt;
Your candidate groundnet will need to be tested thoroughly by running it in FG, at different time of the day as wind conditions and traffic patterns will impact which runways are used and how many AI aircraft are handled. &lt;br /&gt;
You should have log/debug enabled as traffic manager will create :ai entries, allowing easier troubleshooting.&lt;br /&gt;
&lt;br /&gt;
On the bright side, both Taxidraw and FG Airports contain a validation tool which will allow you to detect any structural problem with your groundnet. You will also find a lot of resources and tips in the AI section of the Flightgear forums.&lt;br /&gt;
&lt;br /&gt;
Finally, the approach to creating a groudnet is described below as three separate phases, each one including its own specific testing which will hopefully simplify your journey.&lt;br /&gt;
[[File:Gate Definition in FG Airports.png|300px|thumb|FG Airports UI for Parking Position]]&lt;br /&gt;
== Creating the base network == &lt;br /&gt;
&lt;br /&gt;
Objective:&lt;br /&gt;
* You have enough gates for all AI aircraft using the airport&lt;br /&gt;
* Each gate has a valid incoming and outgoing link (route) to the rest of the groundnet&lt;br /&gt;
* Each Runway(s) threshold has a unique link to the rest of the groundnet&lt;br /&gt;
* Departing Aircraft can reach any threshold, from any parking position &lt;br /&gt;
* Arriving Aircraft can reach any suitable parking position from any threshold&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Step 1 : Place and configure the Parking Positions'''&lt;br /&gt;
&lt;br /&gt;
Place a parking position at each location an aircraft is allowed to park. Use your AIP Lat/Lon data and/or the FGA OSM background for extra precision.&lt;br /&gt;
[[File:Extend Route passed Displaced THR.png|thumb|Example of route ended after a displaced Threshold]] &lt;br /&gt;
AI Aircraft will be positioned so their centre of rotation (main gear and/or X=0 in .ac model file) sits at the Lat/Lon defined for the parking position.&lt;br /&gt;
&lt;br /&gt;
Set a type for each off your gates: GATE if the stand is used by commercial traffic or CARGO if used by freighters. &lt;br /&gt;
Military and General Aviation can also be used if you are running personalized traffic files on your machine.&lt;br /&gt;
&lt;br /&gt;
Adjust the heading and radius for each of your gates according to the maximum wingspan the stand can accommodate. See [[aircraft radii]]. &lt;br /&gt;
Set a unique name for the gate.&lt;br /&gt;
Leave the AIRLINE field empty at this point.&lt;br /&gt;
&lt;br /&gt;
'''Step 2 : Place and configure the Runway accesses'''&lt;br /&gt;
&lt;br /&gt;
AI aircraft need an access route to each threshold of each runway you want them to use for take off or landing. Your groundnet will need at least one route to one threshold for validation.&lt;br /&gt;
&lt;br /&gt;
At this stage, place only two access route per runway, one at each end, do not create routes starting/ending hallway (to vacate the runway at midpoint).&lt;br /&gt;
Your access routes will be used to ‘guide’ the aircraft all the way to a final node at which point it will be ‘handed over’ to the tower, start accelerating and take off.&lt;br /&gt;
This final node of each access route (Take Off Point) must sit within the runway thresholds. If not, departing aircraft will simply stop and queue at the entrance of the runway. This is crucial when dealing with displaced thresholds. As a rule of thumb, your access route should end up on the white marking indicating the runway number/identification.&lt;br /&gt;
&lt;br /&gt;
Backtracking: Certain airports do not have taxiways along the runway and aircraft will 'backtrack' on the runway itself to reach the threshold (often circling on a turnaround area to align for take off). In this scenario, you still need a route to guide your aircraft all the way to the take off node by placing nodes and segments on the runway and the the turnaround loop area. Make sure your loop starts (exits the runway) ahead of passing the final take off node: see an example [[:File:Using_the_runway_to_backtrack_to_the_threshold.jpg|HERE]].&lt;br /&gt;
&lt;br /&gt;
Your access routes (all routes at this stage in fact) must be bi directional so they can also be used for aircraft to vacate the runway if they taxi all the way to the threshold.&lt;br /&gt;
[[File:Multiple Route leading to a single Threshold.png|thumb|Runway access routes must be unique]]&lt;br /&gt;
Your access route must be unique: Traffic manager does not handle intersections (nodes with 4 routes) very well. Your take off points should not be connected to more than one route (case of taxiways on both sides of the runway feeding a single take off point from both sides as shown on the right.&lt;br /&gt;
&lt;br /&gt;
Select each of the nodes placed on the physical runway (not the ones leading to it) and mark them &amp;quot;On Runway&amp;quot; with the toggle in their properties panel &lt;br /&gt;
&lt;br /&gt;
'''Step 3 : Connect ParPos and Runway Access routes to form the groundnet.''' &lt;br /&gt;
&lt;br /&gt;
Link each of your parking positions and runway access routes by marking the taxiways with segments, avoiding sharp 90 degrees angles by breaking curves into 2 or 3 segments.&lt;br /&gt;
Keep the network as unconstrained as possible; Make all segments bi-directional, do not include any holding points (Pushback, Regular or Cat III) and do not mark any segment as pushback; At this stage, the idea is to give AI aircraft as many routing options as possible.&lt;br /&gt;
&lt;br /&gt;
Nodes should be placed only at points where the aircraft will change heading so they should be none in the middle of straight routes and definitely none at taxiway crossings.&lt;br /&gt;
&lt;br /&gt;
Segments starting (or ending) at a parking position must have the same heading than the parking stand. AI aircraft park straight and leave their parking straight (rolling forward or pushing back).&lt;br /&gt;
&lt;br /&gt;
Your groundnet does not need to cover 100% of the airport taxiways; certain areas (like De Icing stations or Engine Test Areas) will not be used by AI aircraft. In essence draw just enough routes so that all gates and runways are connected.&lt;br /&gt;
&lt;br /&gt;
'''Step 4 : Visual Check'''&lt;br /&gt;
Once your base groundnet complete, Check visually that your parking positions do not overlap, that nodes placed on physical runways are marked as such and that intersections are formed properly (no node unless the aircraft can turn here)&lt;br /&gt;
&lt;br /&gt;
'''Step 5 : Structural Check and Test'''&lt;br /&gt;
&lt;br /&gt;
Run the verification tool (available in both Taxidraw and FGAirports). Both programs will ensure at this stage that routes are valid and that nodes on runway are marked as such. &lt;br /&gt;
Taxidraw does not check the routes structures individually but rather that a route solution exist: If an aircraft can use different path/routes to go from its ParkPos to a given runway threshold and even if one of these routes is &amp;quot;broken&amp;quot; (unconnected segments) no error will be reported, because the aircraft could use another (unbroken) route to get to the threshold and hence still has a routing solution.&lt;br /&gt;
&lt;br /&gt;
FGAirports will do the same routing check but will also signal any unconnected node (broken routes). &lt;br /&gt;
Correct the errors found and re run the verification tools until no more problems are found.&lt;br /&gt;
&lt;br /&gt;
Time to test with 'real' traffic. Save your groundnet and export it to a custom scenery folder (NOT to the Terrasync Folder), usually something like F:\My Sceneries\Airports\S\B\SBGR.groundnet.xml.&lt;br /&gt;
Startup Flightgear and add your custom scenery folder path (the parent of \Airports) to your extra sceneries list in the Add On screen. Select your Airport as Startup Location. Run &lt;br /&gt;
&lt;br /&gt;
What to expect (normal behaviour with base groundnet, if FG has AI traffic at the airport):&lt;br /&gt;
[[File:Base GRoundnet Visual Check.jpg|border|right|950px|Base Taxidraw Groundnet for SBGR]] &lt;br /&gt;
* Parked aircraft will start appearing on the parking stands you have set. Departing aircraft will light up (red/green nav lights on wing tips, flashing beacon on top)&lt;br /&gt;
* Upon ATC clearance (automatic) Departing aircraft will roll forward into the terminal buildings then dance around a bit then join a taxiway and head for a threshold. The route they will use may not be the shortest nor the one you expected but it will be a route you have set.&lt;br /&gt;
* Departing Aircraft will queue up at the threshold. The first in line will enter the runway, accelerate (strobes lights activate) and take off. Aircraft will climb in a straight line and after +/- 10 seconds, change heading. At this point, the next aircraft in line will enter the runway and initiate its take off&lt;br /&gt;
* Arriving aircraft will start landing. The touchdown position will depend on the type of aircraft. Arrivals are not spaced out and you can see more than one aircraft landing at once on a single runway. After slowing down, they will taxi all the way to the end of the runway, exit via the access route you have set then head for an available parking position. Again the route they will use may not be the shortest or the one you expected and they may not park where you wanted. Once parked, Nav Lights will turn off&lt;br /&gt;
* At any given intersection (4 or more segments sharing a node) and at any time during the test, one aircraft will get stuck. Aircraft following it will start queueing. The intersection will remain clogged and AI taxiing stuck until you decide to shut down Flightgear.&lt;br /&gt;
* Aircraft will taxi from both directions on single taxiways, eventually passing through each other&lt;br /&gt;
* On larger airports where AI aircraft can use more than one route to go from one point to another and/or can park at multiple aprons they will show a geographical preference (normally North West) so parking and routes in this area will be busier. &lt;br /&gt;
What to look for (abnormal behaviour with base groundnet, if FG has AI traffic at the airport):&lt;br /&gt;
* Aircraft appearing in the middle of the airport and not following taxiways (not enough Parking Positions or not suitable in size or type CARGO/GATE)&lt;br /&gt;
* No aircraft (no AI traffic at this airport or not activated in FG options)&lt;br /&gt;
* Aircraft queue up at runway but do not take off (Misplaced Threshold in ICAO.threshold.xml)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Refining the network: Routing Flow &amp;amp; Baggage Carousels Belts === &lt;br /&gt;
&lt;br /&gt;
Objective:&lt;br /&gt;
* All Objectives in the &amp;quot;Creating the base Network&amp;quot; section&lt;br /&gt;
* No clogging at intersections&lt;br /&gt;
* No Head on crashes of aircraft sharing a single taxiway&lt;br /&gt;
&lt;br /&gt;
The traffic manager code is not fully documented nor maintained so only repeated testing (and discussions on the FG AI Forum) will allow you to form and understanding of the way AI aircraft will behave based on the type of groundnet data you feed them. This is an interesting but potentially frustrating exercise.&lt;br /&gt;
&lt;br /&gt;
You will eventually realize that AI traffic has a “mind of its own” which fluctuates between two extremes: &lt;br /&gt;
* AI traffic cannot make decisions: Typically, where presented with multiple routing options (an X intersection with 4 or more valid routes), AI traffic will consistently freeze/clog after some time if not immediately.&lt;br /&gt;
* AI traffic does not stand bullying: If presented with no alternative at all (too many unidirectional routes) AI traffic will move away from your groundnet taxiways and start mowing the lawns. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Groundnet Routing Flow Example.png|border|right|950px|Groundnet Routing Flow Example]]&lt;br /&gt;
Keeping these two constraints in mind, the clogging and head on collisions noted in your previous tests can be addressed by setting up a ‘Routing Flow’. The purpose of said routing is to guide each aircraft to their final destination (Parking or Threshold), by ensuring it uses a single route and never crosses any other aircraft path.&lt;br /&gt;
To achieve this we only have and need two tools: The ability to force the direction of traffic on any given segment (by making it “unidirectional”) and the ability of Traffic manager to handle priorities and queues formation at routes merging points (Y shaped intersections).&lt;br /&gt;
&lt;br /&gt;
The complexity of Routing Flow will increase exponentially with the number of gates, thresholds and traffic files handled: A single groundnet flow must accommodate indifferently a cargo flight arriving on RWY 1 in the evening or a commuter departing from RWY 2 in the morning.&lt;br /&gt;
&lt;br /&gt;
To better understand how efficient routing is achieved, draw on your experience at an airport Baggage carousel (or at a ‘Sushi Train' restaurant) where all bags arrive from one or two tunnels onto a moving belt and are then distributed to passengers waiting around the carousel. &lt;br /&gt;
That is all bags (Aircraft) arrive from a limited number of tunnels (Thresholds) and can reach any standing passenger (ParkPos) using a single belt (routing flow). It does not matter where the passenger stands nor what order the bags arrives in; if you wait long enough all bags will meet their owner, (unless you are at Heathrow).&lt;br /&gt;
&lt;br /&gt;
An advantage AI aircraft have over bags is that they can ‘transfer’ from one belt to another if a segment belongs to more than one belt/route. Putting it visually, a typical routing flow will resemble something like the image on the right : ‘Belts’ in green rotate clockwise, red ones rotate counter clockwise, segments in blue provide access in and out of the belts. The main belt along the northern runway includes bypasses allowing an aircraft to quickly reach the other side of the belt without having to travel its full length.&lt;br /&gt;
&lt;br /&gt;
Using the diagram, you can pick any combination of one runway access (threshold or intermediate vacating point) and one parking position and realize you can always find a unique route from A to B and another unique route from B to A without ever coming across an intersection, always using &amp;quot;Y&amp;quot; shaped merging lanes.&lt;br /&gt;
&lt;br /&gt;
[[File:Apron Belt at Bahrein Intl.png|left|thumb|Using the Belt technique to feed the OBBI apron]] &lt;br /&gt;
An additional benefit of the technique is visible when comparing the routing flow diagram and the base network image in the previous section: A groundnet with proper routing uses less nodes and segments than a full network, saving you time during the building phase. In fact, as you get more familiar with the technique you will realize it is a good idea to map your routing flow before building your groundnet so you create just enough nodes and segments. It is also important to know that you do NOT need to mark each and every segment as &amp;quot;unidirectional&amp;quot; but only the ones forming your Y shaped intersections.&lt;br /&gt;
&lt;br /&gt;
The belt technique can easily be adjusted to the specific shape of different airports: All of KJFK’s traffic is routed with only 2 belts set as concentric rings running in opposite directions. The inner ring connects all the aprons in an infinite loop; the external ring connects all the runways in a similar loop. A small number of “transfer belts” allow aircraft to move from one belt to the other.&lt;br /&gt;
&lt;br /&gt;
You can have a look at existing groundnets’ routing to better understand how this technique can be applied to your project. VVNB has the most basic version; LEMD and LFPG have very elaborate ones.&lt;br /&gt;
&lt;br /&gt;
Certain smaller airports do not have enough taxiways to allow the formation of a proper global belt but the technique can be used on individual aprons to ensure proper flow in and out of parking areas and simplify the design of pushback routes (next section) as shown on the image on the left, at Bahrein Intl.&lt;br /&gt;
&lt;br /&gt;
Testing : Once your routing flows in place you should re-test you groundnet. The expected behaviour is the same then the one described in the “Base Network, Step 5” section, with the exception of head on collisions and clogged intersections which should no longer exist&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Refining the network: Pushback routes ===&lt;br /&gt;
&lt;br /&gt;
'''THIS SECTION IS WIP'''&lt;br /&gt;
With the above-mentioned refinements, the ground network should be fully working with one notable exception. Aircraft will be driving forward when leaving the gate, making a sharp turn (while probably destroying themselves and the terminal building in the process). To prevent this, a ''push back'' route should be created. A push back route consists of at least one or more taxiway segments that have the &amp;quot;PushBack Route&amp;quot; property set to true. The last of these segments should be terminated by a PushBack HoldPoint network node. Pushback routes are optional (if you like the terminal crashing scenario described above).&lt;br /&gt;
&lt;br /&gt;
'''Examples of simple valid pushback routes''' (Left: If the taxiway is bidirectional | Centre: If the taxiway is not bidirectional | Right: With shared Pushback Holding point)&lt;br /&gt;
&lt;br /&gt;
[[File:MultiDirectionnal Pushback Route in Taxidraw.jpg|400px|Valid AI Groudnet pushback for  multi directional taxiway]] &lt;br /&gt;
[[File:UniDirectionnal Pushback Route in Taxidraw.jpg|400px|Valid Pushback Route on Un directional taxiway]]&lt;br /&gt;
[[File:Shared Pushback Holding Point in Taxidraw.jpg|400px|Shared Pushback Holding Point]] &lt;br /&gt;
&lt;br /&gt;
[[File:Roll Forward Gate in Taxidraw.jpg|thumb|Invalid Park Pos Exist route : at and angle with Parking Position]]&lt;br /&gt;
===== Pushback Holding Points must be unique per Parking Position =====&lt;br /&gt;
&lt;br /&gt;
Each Parking space (ParkPos) can't have more than one push back route and one pushback holding point at the end of the route. Nevertheless, multiple Parkpos can share part of their pushback routes and a single Pushback Holding Point.&lt;br /&gt;
 &lt;br /&gt;
The formal criteria for a valid push back route is that each gate should have a maximum of one push back holding point associated with it, which can be reached using one route only.&lt;br /&gt;
From an editing point of view, mark all segments between your parking position and its final holding point as &amp;quot;push back&amp;quot;, do not forget to mark the ending node as Pushback Holding Point.&lt;br /&gt;
&lt;br /&gt;
===== The AI code does not handle sharp angles =====&lt;br /&gt;
&lt;br /&gt;
Add nodes to smoothen your routes so that no consecutive segments form an angle of less than 90 degrees. &lt;br /&gt;
This rule applies to both pushback and roll forward / taxiways routes &lt;br /&gt;
&lt;br /&gt;
===== The Inbound route and the outbound routes are parallel ===== &lt;br /&gt;
[[File:On Taxiway Pushback Holding Point in Taxidraw.jpg|thumb|Invalid Groudnet Pushback : Holding Point on Taxiway]]&lt;br /&gt;
&lt;br /&gt;
The last segment of the pushback route will condition the aircraft heading:&lt;br /&gt;
&lt;br /&gt;
1. During its travel along the pushback route &lt;br /&gt;
&lt;br /&gt;
2. At the Pushback Holding point (whilst waiting for clearance)&lt;br /&gt;
&lt;br /&gt;
3. On departing the pushback holding point, rolling forward to the runway.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
You can not control/force an AI aircraft to leave a Pushback Holding point at a different angle/heading than the one it came onto its final holding node at, whatever the number of routes you add.&lt;br /&gt;
&lt;br /&gt;
As a result, the last segment of the pushback route must align with the segment you expect the aircraft to use when starting to roll forward after clearance. The simpler set up is to have this final pushback segment overlapping the first segment of the roll forward route.&lt;br /&gt;
&lt;br /&gt;
Alternatively, the last segment of the pushback route can -itself- be the first segment of the roll forward route but this means Traffic Manager will consider this segment 'reserved' whilst the aircraft is pushing back and no other aircraft will be able to use the taxiway.&lt;br /&gt;
&lt;br /&gt;
For the same reason the Pushback Holding point should NOT be directly placed on the taxiway as shown in the image on the right 'On Taxiway Pushback Holding Point in Taxidraw'.&lt;br /&gt;
Using this particular configuration, once the aircraft 'cleared for taxi', it will ignore the taxiway segments on its left and right and keep its heading, rolling forward towards the original parking position, then 'get lost' and start spinning around, looking for a node to re-anchor to. &lt;br /&gt;
&lt;br /&gt;
The &amp;quot;Roll Forward on my current heading&amp;quot; rules also applies to Parking position with no pushback route. These are often used for smaller propeller aircraft which &amp;quot;pivot&amp;quot; on their parking position before rolling forward. You cannot currently replicate this pivot behaviour in Flightgear AI &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
It is important to note that the Taxidraw &amp;quot;Verify Ground network&amp;quot; process ''should be run'' in order to get a correctly working push back system, because this function runs some internal consistency checks. Push back routes can be very simple, from just one route segment, to fairly complex, as illustrated below with all aircraft from one side of the E terminal are being linked to one shared push back point. &lt;br /&gt;
&lt;br /&gt;
Given the current push back system allows for fairly complicated behavior it is advisable to test extensively your groundnet and play with various configurations before sharing it to the community. &lt;br /&gt;
&lt;br /&gt;
Taxidraw note: TaxiDraw versions prior to February 5, 2009 did export the pushBackRoute attribute correctly. &lt;br /&gt;
&lt;br /&gt;
[[File:TaxiDraw2.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
==== Verifying the network ====&lt;br /&gt;
Finally, with a network complete, it is important to verify it! The verification function not only detects obvious problems with the network, it also updates some internal states that FlightGear relies on. Presumably, an automatic verification process will be added to the export function, but until that is the case, make sure to do this manually. The verify network function can be found in the &amp;quot;Tools&amp;quot; menu. &lt;br /&gt;
&lt;br /&gt;
Notice that TaxiDraw does not automatically fix problems. It is left to the user to fix the problems manually. TaxiDraw does select the offending node(s) / segments(s) for easier identification. Also note that TaxiDraw stops verifying at the first problem encountered, so it is worthwhile to continue checking until no further errors are found. Currently, the following checks are performed.&lt;br /&gt;
&lt;br /&gt;
* '''On runway points''' Added on January 24, 2009, this check is most likely not yet available in any distributed version. This is currently just a very lame check to see if any point in the network has been marked as such. This check is not exhaustive, but simply meant as a reminder to the editor that the OnRunway points should still be marked. Ultimately, this check should be replaced by the aforementioned automatic geometry function.&lt;br /&gt;
* '''Duplicate Taxiway Segments''' It's easy to connect two network nodes twice. While this doesn't really hurt, it does add dead weight, so checking for duplicates is not a bad thing. &lt;br /&gt;
* '''Routing''' One of the most persistent headache causing problems is that of a disconnected parking space in the network. FlightGear will happily place an aircraft there, but bail out when that aircraft cannot reach the runway. The routing check attempts to prevent this. Notice that it is of utmost importance that the OnRunway points are set correctly, because TaxiDraw relies on these points for it's route finding algorithm. Because the route finding algorithm is rather computationally intensive, some progress information is currently written to the console (a proper progress bar would be nice). When a disconnected parking space is found, TaxiDraw selects both the parking and the runway node. It is still left to the user to trace a route between these two points and find where the two pieces are disconnected. &lt;br /&gt;
* '''Check and set pushback nodes''' this function verifies whether any specified pushback nodes adhere to the above specifications and updates some internal consistency.&lt;br /&gt;
&lt;br /&gt;
=== Copying the ground network into FlightGear's scenery directory ===&lt;br /&gt;
Finally, once you have finished creating a groundnet project, you can test it in FlightGear. Create a directory in your &amp;lt;tt&amp;gt;[[$FG_SCENERY]]/Airports/[I]/[C]/[A]/&amp;lt;/tt&amp;gt; directory, with the three first letters of the ICAO code of your airport. For example &amp;lt;tt&amp;gt;[[$FG_SCENERY]]/Airports/E/H/A/&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In Taxidraw menu select File, Export AI Network and save the file in the above folde as ICAO.groundnet.xml. Please note that the File / Save project menu applies only to the airport layout and does NOT save your groundnet.&lt;br /&gt;
&lt;br /&gt;
=== Testing the network ===&lt;br /&gt;
Startup FlightGear at the airport for which you have just created the network, and make sure you have traffic for that aircraft. FlightGear will check the network and report errors on the console. Aircraft that can't be placed at one of the parking locations will be placed at a default location. &lt;br /&gt;
&lt;br /&gt;
If the FlightGear AI system can't find a valid route between startup location and runway, it will list which nodes are not connected and exit.&lt;br /&gt;
&lt;br /&gt;
=== Airports with ground networks ===&lt;br /&gt;
&lt;br /&gt;
== Collision Detection == &lt;br /&gt;
&lt;br /&gt;
Version 2024.1.X and all version prior use an avoidance strategy based on the ground network. This means there are lots of Dijkstra calls to determine the possible route to the runway. Another implication is that the designer of the ground network must ensure the separation. This is of course not possible in pushback or even with the user aircraft. &lt;br /&gt;
The next version will get real detection of nearness and integration with ATC. This is based on a Quadtree and just reacts on the current snapshot and not on &amp;quot;future&amp;quot; plans of an aircraft. &lt;br /&gt;
&lt;br /&gt;
=== Nearness rose ===&lt;br /&gt;
[[File:Ai-windrose.png|thumb|A diagram showing an aircraft and different szenarios based on heading|none]]&lt;br /&gt;
&lt;br /&gt;
This diagram shows some possible angles of approach with the aircraft pointing away. &lt;br /&gt;
&lt;br /&gt;
* In this case all aircraft within the arc from -89° to +89° are considered blocking aircraft.  &lt;br /&gt;
* Speed must be adjusted to the speed of the other aircraft if it's positive&lt;br /&gt;
&lt;br /&gt;
[[File:Ai-windrose-approach.png|thumb|A diagram showing an aircraft and different szenarios based on heading|none]]&lt;br /&gt;
&lt;br /&gt;
====== Moving forward. ======&lt;br /&gt;
&lt;br /&gt;
* In this case all aircraft within the arc from -89° to +89° are considered blocking aircraft. &lt;br /&gt;
* Aircraft must stop&lt;br /&gt;
&lt;br /&gt;
====== Moving in reverse ======&lt;br /&gt;
&lt;br /&gt;
* Speed must be adjusted to the speed of the other aircraft if it's negative&lt;br /&gt;
&lt;br /&gt;
=== Braking ===&lt;br /&gt;
[[File:Following slowdown.png|thumb|A diagram showing the braking of a following aircraft|none]]&lt;br /&gt;
&lt;br /&gt;
When following aircraft must brake in a plausible way.&lt;br /&gt;
&lt;br /&gt;
= Runway Usage Configuration = &lt;br /&gt;
&lt;br /&gt;
ICAO.rwyuse.xml dictates which runway(s) should be used for AI take off and which ones accept AI landings. These instructions are organised in multiple sets (or configurations) and ordered by preference of use.&lt;br /&gt;
&lt;br /&gt;
Schedules can be defined so different runway configurations can be used at different time of the day. Different Sets can also be applied to the General Aviation and Commercial traffic.&lt;br /&gt;
&lt;br /&gt;
Each set of runway configuration is tested against the wind conditions and applies only if the crosswind and tailwind are within tolerance. If not, the next set in the order of preference, will be picked.&lt;br /&gt;
&lt;br /&gt;
Examples below taken from the Amsterdam Schiphol EHAM.rwyuse.xml file.&lt;br /&gt;
&lt;br /&gt;
Wind conditions are stored at the beginning of the file and indicate the maximum values of Crosswind and Tailwind acceptable to authorize take off and landing at the airport.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;wind tail=&amp;quot;7&amp;quot;&lt;br /&gt;
          cross=&amp;quot;20&amp;quot; /&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{quote|As well as aircraft, airports have operational crosswind and tailwind limits. [] The values vary but crosswind limits are normally 15-25 kts and tailwinds are normally no more than 10 kts.|ICAO|AMOFSG/10-SN No. 14}}&lt;br /&gt;
&lt;br /&gt;
The &amp;lt; time &amp;gt; table defines the hours of activation for a particular Schedule. Each schedule contains a 'Pattern' ie a fixed number of runways allocated to either take off or landing).&lt;br /&gt;
&lt;br /&gt;
For instance, at EHAM, the commercial &amp;quot;inbound&amp;quot; schedule is active from 06:20 to 08:30 UTC, at a time where there is more inbound than outbound traffic and the airport needs to allocate more runways to landing than to take off.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;time start = &amp;quot;06:20&amp;quot;&lt;br /&gt;
          end   = &amp;quot;08:30&amp;quot;&lt;br /&gt;
          schedule = &amp;quot;inbound&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In most cases, a single schedule can apply 24h every day.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;time start = &amp;quot;00:00&amp;quot;&lt;br /&gt;
          end   = &amp;quot;24:00&amp;quot;&lt;br /&gt;
          schedule = &amp;quot;Day&amp;quot;/&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The different sets of runways allocation are listed in each &amp;lt; schedule &amp;gt; table by order of preference and can be read/deciphered vertically as ”columns” delimited by commas.&lt;br /&gt;
&lt;br /&gt;
When wind conditions do not allow the use of a set, the next one will apply (next “column”).&lt;br /&gt;
&lt;br /&gt;
Still using the EHAM.rwyuse.xml file as example, from 06:20 to 08:30 UTC, when the Inbound schedule applies; AI traffic will preferably use runway 36L for take-offs, and both 06 and 36R for landings (first “column” of data).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;schedule name=&amp;quot;inbound&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;takeoff&amp;gt;36L, 24,  18L, 36L, 24,  24,  18L, 09&amp;lt;/takeoff&amp;gt;&lt;br /&gt;
    &amp;lt;landing&amp;gt;06,  18R, 18R, 36R, 27,  18R, 18R, 06&amp;lt;/landing&amp;gt;&lt;br /&gt;
    &amp;lt;landing&amp;gt;36R, 18C, 18C, 36C, 18R, 22,  22,  09&amp;lt;/landing&amp;gt;&lt;br /&gt;
&amp;lt;/schedule&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Shall wind conditions prevent the use of this configuration (Tailwind or Crosswind values exceeding the ones defined in &amp;lt;wind&amp;gt; above), the next set (or “column”) will apply ie 24 for take-offs, and 18R + 18C for landings.&lt;br /&gt;
&lt;br /&gt;
Again, if wind conditions prevent this configuration use, the next set will be used (18L for take-offs, 18R and 18C for landing).&lt;br /&gt;
&lt;br /&gt;
This test of runway sets against wind values will continue with the rest of the sets in the schedule until a valid one is found and applied.&lt;br /&gt;
&lt;br /&gt;
It is necessary for each of the &amp;lt;takeoff&amp;gt; and &amp;lt;landing&amp;gt; entries to have an equal number of runway values, per schedule, so each set contains the same number of runways.&lt;br /&gt;
&lt;br /&gt;
[https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/CAMbEsE46ontK4G43zmrEqLEk%3D8ZK5sQ-vxZup%3DNjpaUDgb-ASw%40mail.gmail.com/#msg37166996 Source from Durk post in Devel]&lt;br /&gt;
&lt;br /&gt;
= Model Animation =&lt;br /&gt;
&lt;br /&gt;
To assist with creating realistic aircraft behaviour, some properties are set by the Traffic Manager during particular flight phases as follows&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Property !! Description !! Type !! At Gate !! Taxiing !! Take-off !! Cruise !! Approach and Landing&lt;br /&gt;
|-&lt;br /&gt;
| gear/gear[0..5]/position-norm || Gear Position || double (interpolated over 10 seconds || 1.0 || 1.0 || 0.0 (above 400ft) || 0.0 || 1.0 (below 2000ft)&lt;br /&gt;
|-&lt;br /&gt;
| surface-positions/flap-pos-norm || Flaps position || double (interpolated over 20 seconds || 0.0 || 0.0 || 0.5 (below 2000ft) || 0.0 || 1.0 ( below 2000ft)&lt;br /&gt;
|-&lt;br /&gt;
| surface-positions/spoiler-pos-norm || Spoiler position || double || 0.0 || 0.0 || 0.0 || 0.0 || 1.0 (on runway)&lt;br /&gt;
|-&lt;br /&gt;
| surface-positions/speedbrake-pos-norm || Speedbrake position || double || 0.0 || 0.0 || 0.0 || 0.0 || 1.0 (below 2000ft)&lt;br /&gt;
|-&lt;br /&gt;
| controls/lighting/beacon || Beacon || bool || false || true || true || true || true&lt;br /&gt;
|-&lt;br /&gt;
| controls/lighting/cabin-lights || Cabin lights || bool || true || true || true || false || false&lt;br /&gt;
|-&lt;br /&gt;
| controls/lighting/landing-lights || Landing lights || bool || false || false || false || true || true&lt;br /&gt;
|-&lt;br /&gt;
| controls/lighting/nav-lights || Navigation lights || bool || false || true || true || true || true&lt;br /&gt;
|-&lt;br /&gt;
| controls/lighting/strobe || Strobes || bool || false || false || true || true || true&lt;br /&gt;
|-&lt;br /&gt;
| controls/lighting/taxi-lights || Taxi lights || bool || false || true || false || false || false&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= SIDs / STARs =&lt;br /&gt;
SID is an acronym for [[Standard Instrument Departure]]. Likewise, STAR is an acronym for [[Standard Terminal Arrival Route]]. Directly after takeoff, in particular at busy airports, aircraft follow a standard flight path, that will keep them safely separated from arriving traffic, avoid ground obstructions, and also keeps traffic away from populated areas as much as possible. Currently, steps are in progress to allow FlightGear traffic to follow SIDs. Ultimately, the plan is to provide SID and STAR data in a format that can also be used by the user controlled Aircraft's Flight Management Computer. Currently, some sample data exist in the form of a PropertyList formatted XML file that contains a list of way points. &lt;br /&gt;
&lt;br /&gt;
This section of the AI Traffic documentation is meant as a stub that keeps track of the current development. At the moment of writing, some proof-of-concept data for EHAM SIDs will be committed to the World Scenery Repository, along with some experimental code in FlightGear to make use of this data. Note that this data is meant to be just that; a proof-of-concept. User contributions will be accepted as soon as the format has stabilized.&lt;br /&gt;
&lt;br /&gt;
= Appendix: Special Notes =&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Performance ==&lt;br /&gt;
With recent versions of FlightGear, we have much better performance than before so realistic density of traffic is currently possible even for slower computers. For those who are still afraid, there is an, as of yet, unpublished feature: set a new property &amp;quot;/sim/traffic-manager/proportion&amp;quot; to any value between 0 and 1 in your preferences.xml. During program start up, the traffic manager draws a random number (between 0 and 1) for each aircraft, and if that random number is smaller than the value specified in /sim/traffic-manager/proportion, the aircraft is added, otherwise it is discarded. In essence, only the specified proportion of aircraft will be loaded. &lt;br /&gt;
&lt;br /&gt;
Unfortunately you can't change this at runtime yet, so you need a little bit trial and error! However, it should allow the combination of slower computers and dense traffic files.&lt;br /&gt;
&lt;br /&gt;
= Related content =&lt;br /&gt;
* [[Airports with ground networks]]&lt;br /&gt;
* [[FGAirports]] - A groundnet editor&lt;br /&gt;
* [[Flightplan XML formats]]&lt;br /&gt;
* [[Status of AI in FlightGear]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[de:Interaktiver Verkehr]]&lt;br /&gt;
&lt;br /&gt;
[[Category:AI Traffic]]&lt;br /&gt;
[[Category:FlightGear feature]]&lt;br /&gt;
[[Category:Scenery enhancement]]&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143073</id>
		<title>Canvas snippets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143073"/>
		<updated>2025-11-26T16:44:16Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Customizing MapStructure Styling */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{-}}&lt;br /&gt;
This article is meant to be a collection of self-contained code snippets for doing Nasal/Canvas coding without having to spend too much time working through APIs, documentation and existing code - the idea is to provide snippets that contain comments and explicit assumptions, so that these snippets can be easily adapted by people without necessarily having to be very familiar with Nasal/Canvas coding. &lt;br /&gt;
&lt;br /&gt;
In the long term, we're hoping to grow a library of useful code snippets to cover most use-cases, while also covering more complex scenarios, e.g. using Canvas-specific helper frameworks like [[Canvas MapStructure]], the [[NavDisplay]] or the recent CDU work.&lt;br /&gt;
&lt;br /&gt;
Canvas itself is designed to be front-end agnostic, meaning that it doesn't matter where a Canvas is used/displayed - this is accomplished by so called &amp;quot;placements&amp;quot;, which determine where a Canvas texture is shown. Thus, the back-end logic can remain the same usually, so that GUI dialogs may show avionics, but also so that avionics may show GUI widgets, which also applies to HUDs, liveries and even placements within the FlightGear scenery.&lt;br /&gt;
&lt;br /&gt;
We encourage people to use the snippets listed below to get started with Nasal/Canvas coding, but also to provide feedback on extending/improving this article to make it even more accessible and useful. Equally, if you're aware of any Canvas-related efforts that may contain useful code snippets, please do feel free to add those snippets here. Even people who don't have any interest in coding itself are encouraged to get involved by helping test the snippets listed below, for which you only need to the [[Nasal Console]], and while you're at it, please also help provide/update screen shots for each code snippet.&lt;br /&gt;
&lt;br /&gt;
Contributions added to this article should ideally satisfy some requirements:&lt;br /&gt;
* be entirely self-contained&lt;br /&gt;
* contain good/clear comments&lt;br /&gt;
* have pointers/references to related code/use-cases&lt;br /&gt;
* make assumptions explicit&lt;br /&gt;
* contain screen shots for each example&lt;br /&gt;
* avoid overloaded symbol names to ensure that examples can be merged and adapted easily&lt;br /&gt;
* dependencies (svg files, images/textures etc) should always be chosen such that snippets always work using $FG_ROOT&lt;br /&gt;
* hard-coded assumptions (e.g. texture dimensions in terms of width/height etc) should be encapsulated using variables&lt;br /&gt;
 &lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
== Creating a standalone Canvas ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (512, 512);&lt;br /&gt;
# Create a standalone Canvas (not attached to any GUI dialog/aircraft etc) &lt;br /&gt;
var myCanvas = canvas.new({&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Livery Test&amp;quot;,   # The name is optional but allow for easier identification&lt;br /&gt;
    &amp;quot;size&amp;quot;: [width, height], # Size of the underlying texture (should be a power of 2, required) [Resolution]&lt;br /&gt;
    &amp;quot;view&amp;quot;: [width, height], # Virtual resolution (Defines the coordinate system of the canvas [Dimensions]&lt;br /&gt;
                             # which will be stretched the size of the texture, required)&lt;br /&gt;
    &amp;quot;mipmapping&amp;quot;: 1,         # Enable mipmapping (optional)&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# set background color&lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# OPTIONAL: Create a Canvas dialog window to hold the canvas and show that it's working&lt;br /&gt;
# the Canvas is now standalone, i.e. continues to live once the dialog is closed!&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(myCanvas);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Accessing the Canvas Desktop ==&lt;br /&gt;
Sometimes you may want to render to the main screen, without creating a separate Canvas window - this can be accomplished by using the Canvas desktop, note that the following example is fully self-contained, i.e. does not require any code to be added to work:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myNode = canvas.getDesktop().createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello Canvas Desktop&amp;quot;)&lt;br /&gt;
    .setFontSize(25, 1.0)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Creating Tooltips ==&lt;br /&gt;
== Creating Popups ==&lt;br /&gt;
== Creating a Canvas GUI Window ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-dialog.png|left|thumb|This is what the [[Nasal]]/[[Canvas]] snippet will look like once you pasted it into the [[Nasal Console]] and click &amp;quot;Execute&amp;quot;.]] || {{Note|This example uses so called method chaining, if you're not familiar with the concept, please see: [[Object_Oriented_Programming_with_Nasal#More_on_methods:_Chaining|Method Chaining]].}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Snippets Boilerplate}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas Snippets-raster image.png|thumb|Canvas snippet demonstrating how to load a raster image]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# path is relative to $FG_ROOT (base package)&lt;br /&gt;
var path = &amp;quot;Textures/Splash1.png&amp;quot;;&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(path)&lt;br /&gt;
    .setTranslation(100, 10)&lt;br /&gt;
    .setSize(130, 130);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images downloaded on demand ==&lt;br /&gt;
&lt;br /&gt;
The '''path''' could also just as well be a URL, i.e. a raster image retrieved via http - for example, the following snippet is entirely self-contained and can be pasted into the [[Nasal Console]] and directly executed &amp;quot;as is&amp;quot;: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-raster-images-via-url.png|thumb|screen shot demonstrating how Nasal and Canvas can be used to display raster images downloaded on demand]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400, 200], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# path now a URL&lt;br /&gt;
var url = &amp;quot;http://www.worldwidetelescope.org/docs/Images/MapOfEarth.jpg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(url) &lt;br /&gt;
    .setTranslation(45, 22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310, 155);     # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Clipping ==&lt;br /&gt;
{{Note|{{FGCquote&lt;br /&gt;
|1= Scaling or any other type of transformation or changing the coordinates of individual points is definitely more efficient than clipping (which requires to change the OpenGL clip planes for every rendered object with a different clipping rectangle).&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=277062#p277062&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;TheTom&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
}}&lt;br /&gt;
I would suggest to refer to api.nas and look for &amp;quot;clip&amp;quot; and/or &amp;quot;rect&amp;quot; - IIRC, you need to set up a clipping rectangle by setting some kind of &amp;quot;clip&amp;quot; property and setting it to a rect value in the form of rect(...)&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= For details, see the clipping example at: [[Canvas Nasal API#set 2]] and  [[Canvas Element#clip.28.29]]&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=276965#p276965&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:MapStructure_dialog_with_clipping_and_event_handling_applied.png|thumb]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;.........&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding OpenVG Paths ==&lt;br /&gt;
{{Main article|How to manipulate Canvas elements}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Openvg-via-canvas.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var graph = root.createChild(&amp;quot;group&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var x_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;x-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .lineTo(width - 10, height / 2)&lt;br /&gt;
    .setColor(1, 0, 0)&lt;br /&gt;
    .setStrokeLineWidth(3);&lt;br /&gt;
&lt;br /&gt;
var y_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;y-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, 10)&lt;br /&gt;
    .lineTo(10, height - 10)&lt;br /&gt;
    .setColor(0, 0, 1)&lt;br /&gt;
    .setStrokeLineWidth(2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-quadTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG quadTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .quadTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-cubicTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG cubicTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    120, height - 120,&lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .cubicTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Vector Images ==&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas SVG]]&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-svg-support.png|thumb|screen shot demonstrating how the scripted Nasal-based SVG parser can be used to dynamically turn SVG files into OpenVG instructions understood by Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# change the background color &lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var filename = &amp;quot;/Nasal/canvas/map/Images/boeingAirplane.svg&amp;quot;;&lt;br /&gt;
var svg_symbol = root.createChild('group');&lt;br /&gt;
canvas.parsesvg(svg_symbol, filename);&lt;br /&gt;
&lt;br /&gt;
svg_symbol.setTranslation(width / 2, height / 2);&lt;br /&gt;
&lt;br /&gt;
# svg_symbol.setScale(0.2);&lt;br /&gt;
# svg_symbol.setRotation(radians)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using the SymbolCache ==&lt;br /&gt;
Whenever a symbol may need to be shown/instanced multiple times (possibly using different styling), it makes sense to use caching - otherwise, identical symbols would be treated as separate OpenVG groups, all of which would need to be rasterized/rendedered separately (i.e. 100 identical symbols would be updated/rendered one by one). &lt;br /&gt;
&lt;br /&gt;
Typically, a map may display multiple instances of an otherwise identical symbol (think VOR, NDB, DME etc) - equally, a multiplayer map may showing multiple aircraft symbols at the same time. In these cases, it makes sense to use the SymbolCache framework, which will render symbols into a separate Canvas texture and provide a texture map that can be treated as a lookup map, which even supports styling for otherwise identical symbols. To learn more, please refer to [[Canvas MapStructure#The_SymbolCache|SymbolCache]]&lt;br /&gt;
... &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-SymbolCache-Instancing.png|thumb|Screen shot showing a Canvas based GUI dialog that is using the SymbolCache for instancing multiple symbols (including support for styling)]] &lt;br /&gt;
| &amp;lt;!--{{Caution|This is currently untested code}}--&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the function that draws a symbol using OpenVG paths&lt;br /&gt;
# it accepts a group to draw to and returns the rendered group &lt;br /&gt;
# to the caller&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5, 12.5)&lt;br /&gt;
        .lineTo(7.5, 12.5)&lt;br /&gt;
        .lineTo(15, 0)&lt;br /&gt;
        .lineTo(7.5, -12.5)&lt;br /&gt;
        .lineTo(-7.5, -12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width) # style-able&lt;br /&gt;
        .setColor(color); # style-able&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var style = { # styling related attributes (as per the draw* function above)&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    color: [1, 0, 0],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# create a new cache entry for the styled symbol&lt;br /&gt;
var myCachedSymbol = canvas.StyleableCacheable.new(&lt;br /&gt;
    name: 'myVOR',&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: canvas.SymbolCache32x32, # the cache to be used&lt;br /&gt;
    draw_mode: canvas.SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: ['line_width', 'color'], # styling related attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var target = root.createChild('group');&lt;br /&gt;
&lt;br /&gt;
var x = 0;&lt;br /&gt;
var y = height / 2;&lt;br /&gt;
&lt;br /&gt;
var xoffset = 50;&lt;br /&gt;
&lt;br /&gt;
# render 5 instanced symbols using the style specified above&lt;br /&gt;
for (var i = 0; i &amp;lt; 5; i += 1) {&lt;br /&gt;
    # look up the raster image for the symbol&lt;br /&gt;
    # render it using the passed style and adjust scaling&lt;br /&gt;
    var instanced = myCachedSymbol.render(target, style)&lt;br /&gt;
        .setScale(style.scale_factor)&lt;br /&gt;
        .setTranslation(x += xoffset, y);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The example shown above uses a fixed symbol/icon cache that is set up while booting Flightgear - sometimes, we may need cache for different purposes. So, let's assume, we need a new/custom cache with a different resolution for each entry in the cache (e.g. 256x256), we can easily accomplish that by setting up a new cache like this: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var mySymbolCache256x256 = canvas.SymbolCache.new(1024, 256);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adding Text Elements ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:CanvasText-Hello-World.png|thumb|Screen shot showing the CanvasText example contributed by Necolatis]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myText = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello world!&amp;quot;)&lt;br /&gt;
    .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Labels ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-Layout-Label-example-by-Necolatis.png|thumb|Canvas demo: Layouts and Labels (by Necolatis)]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label.setText(&amp;quot;Hello World!&amp;quot;);&lt;br /&gt;
myLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
var label2 = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label2.setText(&amp;quot;Hello FlightGear&amp;quot;);&lt;br /&gt;
myLayout.addItem(label2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Buttons (Layouts)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-demo-layouts-and-buttons-by-Necolatis.png|thumb|Canvas snippet: buttons and layouts (by Necolatis)]] &lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
{{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
# click button&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
	.setText(&amp;quot;Click on me&amp;quot;)&lt;br /&gt;
	.setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;clicked&amp;quot;, func {&lt;br /&gt;
    # add code here to react on click on button.&lt;br /&gt;
    print(&amp;quot;Button clicked !&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-toggle-button-snippet-by-Necolatis.png|thumb|Canvas toggle button demo by Necolatis]]&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;Toggle me&amp;quot;)&lt;br /&gt;
    .setCheckable(1) # this indicates that is should be a toggle button&lt;br /&gt;
    .setChecked(0) # depressed by default&lt;br /&gt;
    .setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;toggled&amp;quot;, func (e) {&lt;br /&gt;
    if (e.detail.checked) {&lt;br /&gt;
        # add code here to react on button being depressed.&lt;br /&gt;
    } else {&lt;br /&gt;
        # add code here to react on button not being depressed.&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas Input Dialog ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Snippets-canvas-input-dialog.png|thumb|Canvas input dialog]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new InputDialog with a title, label, and a callback&lt;br /&gt;
canvas.InputDialog.getText(&amp;quot;Input Dialog Title&amp;quot;, &amp;quot;Please enter some text&amp;quot;, func(btn, value) {&lt;br /&gt;
    if (value) {&lt;br /&gt;
        gui.popupTip(&amp;quot;You entered: &amp;quot; ~ value);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas ScrollArea ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas-snippets-scrollArea-demo.png|thumb|Screen shot showing a Canvas ScrollArea populated with different splash screens, loaded from $FG_ROOT/Textures]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, { size: [96, 128] }).move(20, 100);&lt;br /&gt;
vbox.addItem(scroll, 1);&lt;br /&gt;
&lt;br /&gt;
var scrollContent = scroll.getContent()&lt;br /&gt;
    .set(&amp;quot;font&amp;quot;, &amp;quot;LiberationFonts/LiberationSans-Bold.ttf&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;character-size&amp;quot;, 16)&lt;br /&gt;
    .set(&amp;quot;alignment&amp;quot;, &amp;quot;left-center&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var list = canvas.VBoxLayout.new();&lt;br /&gt;
scroll.setLayout(list);&lt;br /&gt;
&lt;br /&gt;
for (var i = 1; i &amp;lt;= 5; i += 1) {&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(scrollContent, canvas.style, { wordWrap: 0 });&lt;br /&gt;
    label.setImage(&amp;quot;Textures/Splash&amp;quot; ~ i ~ &amp;quot;.png&amp;quot;);&lt;br /&gt;
    label.setFixedSize(256, 256);&lt;br /&gt;
    list.addItem(label);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using TabWidgets ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Screenshot&lt;br /&gt;
!Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas TabWidget example.png|alt=Canvas TabWIdget example|thumb|Canvas TabWidget example showing the three default splash screens]]&lt;br /&gt;
|&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
var window = canvas.Window.new([300, 300], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {});&lt;br /&gt;
var tabsContent = tabs.getContent();&lt;br /&gt;
&lt;br /&gt;
var createTabContent = func(imgPath, text) {&lt;br /&gt;
    var image = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setImage(imgPath)&lt;br /&gt;
        .setFixedSize(128, 128);&lt;br /&gt;
&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setText(text);&lt;br /&gt;
&lt;br /&gt;
    var tabLayout = canvas.VBoxLayout.new();&lt;br /&gt;
    tabLayout.addItem(image);&lt;br /&gt;
    tabLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
    return tabLayout;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
tabs.addTab(&amp;quot;tab1&amp;quot;, &amp;quot;Texture 1&amp;quot;, createTabContent(&amp;quot;Textures/Splash1.png&amp;quot;, &amp;quot;Texture 1&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab2&amp;quot;, &amp;quot;Texture 2&amp;quot;, createTabContent(&amp;quot;Textures/Splash2.png&amp;quot;, &amp;quot;Texture 2&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab3&amp;quot;, &amp;quot;Texture 3&amp;quot;, createTabContent(&amp;quot;Textures/Splash3.png&amp;quot;, &amp;quot;Texture 3&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
vbox.addItem(tabs);&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using Layouts ==&lt;br /&gt;
&lt;br /&gt;
== Using Styling ==&lt;br /&gt;
== Adding a HUD ==&lt;br /&gt;
== Adding a 2D Instrument ==&lt;br /&gt;
== Adding a 2D Panel ==&lt;br /&gt;
== Adding a PFD ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Failure Mgmt Widget ==&lt;br /&gt;
{{Note|This kind of widget will typically be useful for dialogs requiring a method for managing aircraft specific system failures (e.g. an instructor console).}}&lt;br /&gt;
&lt;br /&gt;
== Adding a MapStructure map to a Canvas  ==&lt;br /&gt;
{{Main article|Canvas MapStructure Layers}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-mapstructure-dialog.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var testMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
testMap.setRange(25);&lt;br /&gt;
 &lt;br /&gt;
testMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
    return caller(0)[0];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# APT and VOR are the layer names&lt;br /&gt;
foreach (var type; [r('APT'), r('VOR')]) {&lt;br /&gt;
    testMap.addLayer(&lt;br /&gt;
        factory: canvas.SymbolLayer,&lt;br /&gt;
        type_arg: type.name, &lt;br /&gt;
        visible: type.vis,&lt;br /&gt;
        priority: type.zindex,&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Customizing MapStructure Styling  ==&lt;br /&gt;
{{See also|Canvas MapStructure#Styling}}&lt;br /&gt;
&lt;br /&gt;
In general MapStructure symbols contain their own styles using hard-coded defaults, however these can be overridden by providing a hash with keys (fields) to customize these hard-coded defaults. &lt;br /&gt;
&lt;br /&gt;
This means that anything that may be specific to a single style (colors, fonts, images etc) should be encoded in the form of variables that are looked up using the styles hash - this provides a great deal of freedom to customize an existing symbol. In addition, MapStructure layers can be set up to even customize/override the default drawing routines, at which point you are free to do whatever you want basically, because the existing draw routine in .symbol file is ignored &lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Layers style change.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog. Active VOR and radial in red color, inactive VOR in green color]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var testMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
testMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
testMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
var r = func(name, vis = true, zindex = nil) return caller(0)[0];&lt;br /&gt;
var type = r('APT');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in APT.symbol&lt;br /&gt;
var style_apt = {&lt;br /&gt;
    scale_factor: 0.5,             # 50 %&lt;br /&gt;
    color_default: [0, 1, 0.9],    # rgb&lt;br /&gt;
    line_width: 4,                 # thickness&lt;br /&gt;
    label_font_color: [0, 1, 0.9], # rgb&lt;br /&gt;
    label_font_size: 30,           # font size&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
testMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_apt,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var type = r('VOR');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in VOR.symbol&lt;br /&gt;
var style_vor = {&lt;br /&gt;
    scale_factor: 0.6,&lt;br /&gt;
    active_color: [1, 0, 0],&lt;br /&gt;
    inactive_color: [0, 1, 0],&lt;br /&gt;
    line_width: 4,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
testMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_vor,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
### See $FG_ROOT/Nasal/canvas/map/APT and $FG_ROOT/Nasal/canvas/map/VOR.symbol for the style variables that can be configured.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using MapStructure and Overlays ==&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Support for overlays using geo-referenced raster images isn't too well-developed currently, so would need to work with a few hard-coded assumptions (e.g. being specific to a certain raster image and map layout) - but maybe TheTom can provide a few more ideas on how to proceed from here - otherwise, the Map is normally not aware of any non-Map items, i.e. as long as the overlay is added &amp;quot;outside&amp;quot; the Map, it isn't even aware of the raster image - and when it is added as a map element, it would probably be rotated. So there's still some work needed here. But generally, this should work well enough even in its current form (the screen shot is purely based on the Canvas Snippets article, i.e. it just displays a Canvas GUI dialog, adds the downloaded raster image and then adds the MapStructure APS layer - without the Map being aware of the overlay/geo-referencing that is needed currently).&lt;br /&gt;
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=238316#p238316&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Using Canvas for visualizing orbital flights (cont'd PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri Apr 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-overlay-with-mapstructure.png|thumb|Using the [[MapStructure]] framework in conjunction with raster images as overlays]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== A simple tile map ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas - Tile map demo.png|thumb|A simple, canvas based tile map which is centered around the aircraft.]]&lt;br /&gt;
| &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (768, 512);&lt;br /&gt;
var tile_size = 256;&lt;br /&gt;
&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;).set('title', &amp;quot;Tile map demo&amp;quot;);&lt;br /&gt;
var group = window.getCanvas(1).createGroup();&lt;br /&gt;
&lt;br /&gt;
# Simple user interface (Buttons for zoom and label for displaying it)&lt;br /&gt;
var zoom = 10;&lt;br /&gt;
var type = &amp;quot;intl&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
var ui_root = window.getCanvas().createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
window.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var button_in = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;+&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(1));&lt;br /&gt;
&lt;br /&gt;
var button_out = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;-&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(-1));&lt;br /&gt;
&lt;br /&gt;
button_in.setSizeHint([32, 32]);&lt;br /&gt;
button_out.setSizeHint([32, 32]);&lt;br /&gt;
&lt;br /&gt;
var label_zoom = canvas.gui.widgets.Label.new(ui_root, canvas.style, {});&lt;br /&gt;
&lt;br /&gt;
var button_box = canvas.HBoxLayout.new();&lt;br /&gt;
button_box.addItem(button_in);&lt;br /&gt;
button_box.addItem(label_zoom);&lt;br /&gt;
button_box.addItem(button_out);&lt;br /&gt;
button_box.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
vbox.addItem(button_box);&lt;br /&gt;
vbox.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
var changeZoom = func(d) {&lt;br /&gt;
    zoom = math.max(2, math.min(19, zoom + d));&lt;br /&gt;
    label_zoom.setText(&amp;quot;Zoom &amp;quot; ~ zoom);&lt;br /&gt;
    updateTiles();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# http://polymaps.org/docs/&lt;br /&gt;
# https://github.com/simplegeo/polymaps&lt;br /&gt;
# https://github.com/Leaflet/Leaflet&lt;br /&gt;
&lt;br /&gt;
var maps_base = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/cache/maps';&lt;br /&gt;
&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/map&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/sat&lt;br /&gt;
# (also see http://wiki.openstreetmap.org/wiki/Tile_usage_policy)&lt;br /&gt;
var makeUrl = string.compileTemplate('https://maps.wikimedia.org/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
# https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png&lt;br /&gt;
var makePath = string.compileTemplate(maps_base ~ '/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
var num_tiles = [4, 3];&lt;br /&gt;
&lt;br /&gt;
var center_tile_offset = [&lt;br /&gt;
    (num_tiles[0] - 1) / 2,&lt;br /&gt;
    (num_tiles[1] - 1) / 2,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
# simple aircraft icon at current position/center of the map&lt;br /&gt;
group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
    .moveTo(&lt;br /&gt;
        tile_size * center_tile_offset[0] - 10,&lt;br /&gt;
        tile_size * center_tile_offset[1],&lt;br /&gt;
    )&lt;br /&gt;
    .horiz(20)&lt;br /&gt;
    .move(-10, -10)&lt;br /&gt;
    .vert(20)&lt;br /&gt;
    .set(&amp;quot;stroke&amp;quot;, &amp;quot;red&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;stroke-width&amp;quot;, 2)&lt;br /&gt;
    .set(&amp;quot;z-index&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# initialize the map by setting up&lt;br /&gt;
# a grid of raster images  &lt;br /&gt;
&lt;br /&gt;
var tiles = setsize([], num_tiles[0]);&lt;br /&gt;
for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
    tiles[x] = setsize([], num_tiles[1]);&lt;br /&gt;
    for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
        tiles[x][y] = group.createChild(&amp;quot;image&amp;quot;, &amp;quot;map-tile&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var last_tile = [-1, -1];&lt;br /&gt;
var last_type = type;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the callback that will be regularly called by the timer&lt;br /&gt;
# to update the map&lt;br /&gt;
var updateTiles = func {&lt;br /&gt;
    # get current position&lt;br /&gt;
    var lat = getprop('/position/latitude-deg');&lt;br /&gt;
    var lon = getprop('/position/longitude-deg');&lt;br /&gt;
&lt;br /&gt;
    var n = math.pow(2, zoom);&lt;br /&gt;
&lt;br /&gt;
    var latRad = lat * D2R;&lt;br /&gt;
    var mercatorY = math.ln(math.tan(latRad) + 1 / math.cos(latRad));&lt;br /&gt;
    var offset = [&lt;br /&gt;
        n * ((lon + 180) / 360) - center_tile_offset[0],&lt;br /&gt;
        (1 - mercatorY / math.pi) / 2 * n - center_tile_offset[1],&lt;br /&gt;
    ];&lt;br /&gt;
    var tile_index = [&lt;br /&gt;
        int(offset[0]),&lt;br /&gt;
        int(offset[1]),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var ox = tile_index[0] - offset[0];&lt;br /&gt;
    var oy = tile_index[1] - offset[1];&lt;br /&gt;
&lt;br /&gt;
    for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
        for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
            tiles[x][y].setTranslation(&lt;br /&gt;
                int((ox + x) * tile_size + 0.5),&lt;br /&gt;
                int((oy + y) * tile_size + 0.5),&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tile_index[0] != last_tile[0]&lt;br /&gt;
        or tile_index[1] != last_tile[1]&lt;br /&gt;
        or type != last_type&lt;br /&gt;
    ) {&lt;br /&gt;
        for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
            for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
                var pos = {&lt;br /&gt;
                    z: zoom,&lt;br /&gt;
                    x: int(offset[0] + x),&lt;br /&gt;
                    y: int(offset[1] + y),&lt;br /&gt;
                    type: type,&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                (func {&lt;br /&gt;
                    var img_path = makePath(pos);&lt;br /&gt;
                    var tile = tiles[x][y];&lt;br /&gt;
&lt;br /&gt;
                    if (io.stat(img_path) == nil) { &lt;br /&gt;
                        # image not found, save in $FG_HOME&lt;br /&gt;
                        var img_url = makeUrl(pos);&lt;br /&gt;
                        print('requesting ' ~ img_url);&lt;br /&gt;
                        http.save(img_url, img_path)&lt;br /&gt;
                            .done(func {&lt;br /&gt;
                                print('received image ' ~ img_path);&lt;br /&gt;
                                tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                             })&lt;br /&gt;
                            .fail(func(r) {&lt;br /&gt;
                                print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason);&lt;br /&gt;
                            });&lt;br /&gt;
                    } else {&lt;br /&gt;
                        # cached image found, reusing&lt;br /&gt;
                        print('loading ' ~ img_path);&lt;br /&gt;
                        tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                    }&lt;br /&gt;
                })();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        last_tile = tile_index;&lt;br /&gt;
        last_type = type;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up a timer that will invoke updateTiles at 2-second intervals&lt;br /&gt;
var update_timer = maketimer(2, updateTiles);&lt;br /&gt;
# actually start the timer&lt;br /&gt;
update_timer.start();&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up default zoom level&lt;br /&gt;
changeZoom(0);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###&lt;br /&gt;
# The following lines were recently added and have not yet been tested&lt;br /&gt;
# (if in doubt, remove them)&lt;br /&gt;
window.del = func {&lt;br /&gt;
    print(&amp;quot;Cleaning up window:&amp;quot;, ,&amp;quot;\n&amp;quot;);&lt;br /&gt;
    update_timer.stop();&lt;br /&gt;
    # explanation for the call() technique at:&lt;br /&gt;
    # http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls&lt;br /&gt;
    call(canvas.Window.del, [], me);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding a NavDisplay ==&lt;br /&gt;
&lt;br /&gt;
== Adding a CDU ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Multi-Function Display (MFD) ==&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143072</id>
		<title>Canvas snippets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143072"/>
		<updated>2025-11-26T16:42:52Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Adding a MapStructure map to a Canvas */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{-}}&lt;br /&gt;
This article is meant to be a collection of self-contained code snippets for doing Nasal/Canvas coding without having to spend too much time working through APIs, documentation and existing code - the idea is to provide snippets that contain comments and explicit assumptions, so that these snippets can be easily adapted by people without necessarily having to be very familiar with Nasal/Canvas coding. &lt;br /&gt;
&lt;br /&gt;
In the long term, we're hoping to grow a library of useful code snippets to cover most use-cases, while also covering more complex scenarios, e.g. using Canvas-specific helper frameworks like [[Canvas MapStructure]], the [[NavDisplay]] or the recent CDU work.&lt;br /&gt;
&lt;br /&gt;
Canvas itself is designed to be front-end agnostic, meaning that it doesn't matter where a Canvas is used/displayed - this is accomplished by so called &amp;quot;placements&amp;quot;, which determine where a Canvas texture is shown. Thus, the back-end logic can remain the same usually, so that GUI dialogs may show avionics, but also so that avionics may show GUI widgets, which also applies to HUDs, liveries and even placements within the FlightGear scenery.&lt;br /&gt;
&lt;br /&gt;
We encourage people to use the snippets listed below to get started with Nasal/Canvas coding, but also to provide feedback on extending/improving this article to make it even more accessible and useful. Equally, if you're aware of any Canvas-related efforts that may contain useful code snippets, please do feel free to add those snippets here. Even people who don't have any interest in coding itself are encouraged to get involved by helping test the snippets listed below, for which you only need to the [[Nasal Console]], and while you're at it, please also help provide/update screen shots for each code snippet.&lt;br /&gt;
&lt;br /&gt;
Contributions added to this article should ideally satisfy some requirements:&lt;br /&gt;
* be entirely self-contained&lt;br /&gt;
* contain good/clear comments&lt;br /&gt;
* have pointers/references to related code/use-cases&lt;br /&gt;
* make assumptions explicit&lt;br /&gt;
* contain screen shots for each example&lt;br /&gt;
* avoid overloaded symbol names to ensure that examples can be merged and adapted easily&lt;br /&gt;
* dependencies (svg files, images/textures etc) should always be chosen such that snippets always work using $FG_ROOT&lt;br /&gt;
* hard-coded assumptions (e.g. texture dimensions in terms of width/height etc) should be encapsulated using variables&lt;br /&gt;
 &lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
== Creating a standalone Canvas ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (512, 512);&lt;br /&gt;
# Create a standalone Canvas (not attached to any GUI dialog/aircraft etc) &lt;br /&gt;
var myCanvas = canvas.new({&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Livery Test&amp;quot;,   # The name is optional but allow for easier identification&lt;br /&gt;
    &amp;quot;size&amp;quot;: [width, height], # Size of the underlying texture (should be a power of 2, required) [Resolution]&lt;br /&gt;
    &amp;quot;view&amp;quot;: [width, height], # Virtual resolution (Defines the coordinate system of the canvas [Dimensions]&lt;br /&gt;
                             # which will be stretched the size of the texture, required)&lt;br /&gt;
    &amp;quot;mipmapping&amp;quot;: 1,         # Enable mipmapping (optional)&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# set background color&lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# OPTIONAL: Create a Canvas dialog window to hold the canvas and show that it's working&lt;br /&gt;
# the Canvas is now standalone, i.e. continues to live once the dialog is closed!&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(myCanvas);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Accessing the Canvas Desktop ==&lt;br /&gt;
Sometimes you may want to render to the main screen, without creating a separate Canvas window - this can be accomplished by using the Canvas desktop, note that the following example is fully self-contained, i.e. does not require any code to be added to work:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myNode = canvas.getDesktop().createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello Canvas Desktop&amp;quot;)&lt;br /&gt;
    .setFontSize(25, 1.0)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Creating Tooltips ==&lt;br /&gt;
== Creating Popups ==&lt;br /&gt;
== Creating a Canvas GUI Window ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-dialog.png|left|thumb|This is what the [[Nasal]]/[[Canvas]] snippet will look like once you pasted it into the [[Nasal Console]] and click &amp;quot;Execute&amp;quot;.]] || {{Note|This example uses so called method chaining, if you're not familiar with the concept, please see: [[Object_Oriented_Programming_with_Nasal#More_on_methods:_Chaining|Method Chaining]].}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Snippets Boilerplate}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas Snippets-raster image.png|thumb|Canvas snippet demonstrating how to load a raster image]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# path is relative to $FG_ROOT (base package)&lt;br /&gt;
var path = &amp;quot;Textures/Splash1.png&amp;quot;;&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(path)&lt;br /&gt;
    .setTranslation(100, 10)&lt;br /&gt;
    .setSize(130, 130);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images downloaded on demand ==&lt;br /&gt;
&lt;br /&gt;
The '''path''' could also just as well be a URL, i.e. a raster image retrieved via http - for example, the following snippet is entirely self-contained and can be pasted into the [[Nasal Console]] and directly executed &amp;quot;as is&amp;quot;: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-raster-images-via-url.png|thumb|screen shot demonstrating how Nasal and Canvas can be used to display raster images downloaded on demand]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400, 200], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# path now a URL&lt;br /&gt;
var url = &amp;quot;http://www.worldwidetelescope.org/docs/Images/MapOfEarth.jpg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(url) &lt;br /&gt;
    .setTranslation(45, 22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310, 155);     # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Clipping ==&lt;br /&gt;
{{Note|{{FGCquote&lt;br /&gt;
|1= Scaling or any other type of transformation or changing the coordinates of individual points is definitely more efficient than clipping (which requires to change the OpenGL clip planes for every rendered object with a different clipping rectangle).&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=277062#p277062&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;TheTom&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
}}&lt;br /&gt;
I would suggest to refer to api.nas and look for &amp;quot;clip&amp;quot; and/or &amp;quot;rect&amp;quot; - IIRC, you need to set up a clipping rectangle by setting some kind of &amp;quot;clip&amp;quot; property and setting it to a rect value in the form of rect(...)&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= For details, see the clipping example at: [[Canvas Nasal API#set 2]] and  [[Canvas Element#clip.28.29]]&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=276965#p276965&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:MapStructure_dialog_with_clipping_and_event_handling_applied.png|thumb]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;.........&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding OpenVG Paths ==&lt;br /&gt;
{{Main article|How to manipulate Canvas elements}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Openvg-via-canvas.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var graph = root.createChild(&amp;quot;group&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var x_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;x-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .lineTo(width - 10, height / 2)&lt;br /&gt;
    .setColor(1, 0, 0)&lt;br /&gt;
    .setStrokeLineWidth(3);&lt;br /&gt;
&lt;br /&gt;
var y_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;y-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, 10)&lt;br /&gt;
    .lineTo(10, height - 10)&lt;br /&gt;
    .setColor(0, 0, 1)&lt;br /&gt;
    .setStrokeLineWidth(2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-quadTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG quadTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .quadTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-cubicTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG cubicTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    120, height - 120,&lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .cubicTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Vector Images ==&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas SVG]]&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-svg-support.png|thumb|screen shot demonstrating how the scripted Nasal-based SVG parser can be used to dynamically turn SVG files into OpenVG instructions understood by Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# change the background color &lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var filename = &amp;quot;/Nasal/canvas/map/Images/boeingAirplane.svg&amp;quot;;&lt;br /&gt;
var svg_symbol = root.createChild('group');&lt;br /&gt;
canvas.parsesvg(svg_symbol, filename);&lt;br /&gt;
&lt;br /&gt;
svg_symbol.setTranslation(width / 2, height / 2);&lt;br /&gt;
&lt;br /&gt;
# svg_symbol.setScale(0.2);&lt;br /&gt;
# svg_symbol.setRotation(radians)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using the SymbolCache ==&lt;br /&gt;
Whenever a symbol may need to be shown/instanced multiple times (possibly using different styling), it makes sense to use caching - otherwise, identical symbols would be treated as separate OpenVG groups, all of which would need to be rasterized/rendedered separately (i.e. 100 identical symbols would be updated/rendered one by one). &lt;br /&gt;
&lt;br /&gt;
Typically, a map may display multiple instances of an otherwise identical symbol (think VOR, NDB, DME etc) - equally, a multiplayer map may showing multiple aircraft symbols at the same time. In these cases, it makes sense to use the SymbolCache framework, which will render symbols into a separate Canvas texture and provide a texture map that can be treated as a lookup map, which even supports styling for otherwise identical symbols. To learn more, please refer to [[Canvas MapStructure#The_SymbolCache|SymbolCache]]&lt;br /&gt;
... &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-SymbolCache-Instancing.png|thumb|Screen shot showing a Canvas based GUI dialog that is using the SymbolCache for instancing multiple symbols (including support for styling)]] &lt;br /&gt;
| &amp;lt;!--{{Caution|This is currently untested code}}--&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the function that draws a symbol using OpenVG paths&lt;br /&gt;
# it accepts a group to draw to and returns the rendered group &lt;br /&gt;
# to the caller&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5, 12.5)&lt;br /&gt;
        .lineTo(7.5, 12.5)&lt;br /&gt;
        .lineTo(15, 0)&lt;br /&gt;
        .lineTo(7.5, -12.5)&lt;br /&gt;
        .lineTo(-7.5, -12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width) # style-able&lt;br /&gt;
        .setColor(color); # style-able&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var style = { # styling related attributes (as per the draw* function above)&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    color: [1, 0, 0],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# create a new cache entry for the styled symbol&lt;br /&gt;
var myCachedSymbol = canvas.StyleableCacheable.new(&lt;br /&gt;
    name: 'myVOR',&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: canvas.SymbolCache32x32, # the cache to be used&lt;br /&gt;
    draw_mode: canvas.SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: ['line_width', 'color'], # styling related attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var target = root.createChild('group');&lt;br /&gt;
&lt;br /&gt;
var x = 0;&lt;br /&gt;
var y = height / 2;&lt;br /&gt;
&lt;br /&gt;
var xoffset = 50;&lt;br /&gt;
&lt;br /&gt;
# render 5 instanced symbols using the style specified above&lt;br /&gt;
for (var i = 0; i &amp;lt; 5; i += 1) {&lt;br /&gt;
    # look up the raster image for the symbol&lt;br /&gt;
    # render it using the passed style and adjust scaling&lt;br /&gt;
    var instanced = myCachedSymbol.render(target, style)&lt;br /&gt;
        .setScale(style.scale_factor)&lt;br /&gt;
        .setTranslation(x += xoffset, y);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The example shown above uses a fixed symbol/icon cache that is set up while booting Flightgear - sometimes, we may need cache for different purposes. So, let's assume, we need a new/custom cache with a different resolution for each entry in the cache (e.g. 256x256), we can easily accomplish that by setting up a new cache like this: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var mySymbolCache256x256 = canvas.SymbolCache.new(1024, 256);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adding Text Elements ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:CanvasText-Hello-World.png|thumb|Screen shot showing the CanvasText example contributed by Necolatis]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myText = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello world!&amp;quot;)&lt;br /&gt;
    .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Labels ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-Layout-Label-example-by-Necolatis.png|thumb|Canvas demo: Layouts and Labels (by Necolatis)]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label.setText(&amp;quot;Hello World!&amp;quot;);&lt;br /&gt;
myLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
var label2 = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label2.setText(&amp;quot;Hello FlightGear&amp;quot;);&lt;br /&gt;
myLayout.addItem(label2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Buttons (Layouts)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-demo-layouts-and-buttons-by-Necolatis.png|thumb|Canvas snippet: buttons and layouts (by Necolatis)]] &lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
{{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
# click button&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
	.setText(&amp;quot;Click on me&amp;quot;)&lt;br /&gt;
	.setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;clicked&amp;quot;, func {&lt;br /&gt;
    # add code here to react on click on button.&lt;br /&gt;
    print(&amp;quot;Button clicked !&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-toggle-button-snippet-by-Necolatis.png|thumb|Canvas toggle button demo by Necolatis]]&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;Toggle me&amp;quot;)&lt;br /&gt;
    .setCheckable(1) # this indicates that is should be a toggle button&lt;br /&gt;
    .setChecked(0) # depressed by default&lt;br /&gt;
    .setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;toggled&amp;quot;, func (e) {&lt;br /&gt;
    if (e.detail.checked) {&lt;br /&gt;
        # add code here to react on button being depressed.&lt;br /&gt;
    } else {&lt;br /&gt;
        # add code here to react on button not being depressed.&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas Input Dialog ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Snippets-canvas-input-dialog.png|thumb|Canvas input dialog]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new InputDialog with a title, label, and a callback&lt;br /&gt;
canvas.InputDialog.getText(&amp;quot;Input Dialog Title&amp;quot;, &amp;quot;Please enter some text&amp;quot;, func(btn, value) {&lt;br /&gt;
    if (value) {&lt;br /&gt;
        gui.popupTip(&amp;quot;You entered: &amp;quot; ~ value);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas ScrollArea ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas-snippets-scrollArea-demo.png|thumb|Screen shot showing a Canvas ScrollArea populated with different splash screens, loaded from $FG_ROOT/Textures]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, { size: [96, 128] }).move(20, 100);&lt;br /&gt;
vbox.addItem(scroll, 1);&lt;br /&gt;
&lt;br /&gt;
var scrollContent = scroll.getContent()&lt;br /&gt;
    .set(&amp;quot;font&amp;quot;, &amp;quot;LiberationFonts/LiberationSans-Bold.ttf&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;character-size&amp;quot;, 16)&lt;br /&gt;
    .set(&amp;quot;alignment&amp;quot;, &amp;quot;left-center&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var list = canvas.VBoxLayout.new();&lt;br /&gt;
scroll.setLayout(list);&lt;br /&gt;
&lt;br /&gt;
for (var i = 1; i &amp;lt;= 5; i += 1) {&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(scrollContent, canvas.style, { wordWrap: 0 });&lt;br /&gt;
    label.setImage(&amp;quot;Textures/Splash&amp;quot; ~ i ~ &amp;quot;.png&amp;quot;);&lt;br /&gt;
    label.setFixedSize(256, 256);&lt;br /&gt;
    list.addItem(label);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using TabWidgets ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Screenshot&lt;br /&gt;
!Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas TabWidget example.png|alt=Canvas TabWIdget example|thumb|Canvas TabWidget example showing the three default splash screens]]&lt;br /&gt;
|&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
var window = canvas.Window.new([300, 300], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {});&lt;br /&gt;
var tabsContent = tabs.getContent();&lt;br /&gt;
&lt;br /&gt;
var createTabContent = func(imgPath, text) {&lt;br /&gt;
    var image = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setImage(imgPath)&lt;br /&gt;
        .setFixedSize(128, 128);&lt;br /&gt;
&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setText(text);&lt;br /&gt;
&lt;br /&gt;
    var tabLayout = canvas.VBoxLayout.new();&lt;br /&gt;
    tabLayout.addItem(image);&lt;br /&gt;
    tabLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
    return tabLayout;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
tabs.addTab(&amp;quot;tab1&amp;quot;, &amp;quot;Texture 1&amp;quot;, createTabContent(&amp;quot;Textures/Splash1.png&amp;quot;, &amp;quot;Texture 1&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab2&amp;quot;, &amp;quot;Texture 2&amp;quot;, createTabContent(&amp;quot;Textures/Splash2.png&amp;quot;, &amp;quot;Texture 2&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab3&amp;quot;, &amp;quot;Texture 3&amp;quot;, createTabContent(&amp;quot;Textures/Splash3.png&amp;quot;, &amp;quot;Texture 3&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
vbox.addItem(tabs);&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using Layouts ==&lt;br /&gt;
&lt;br /&gt;
== Using Styling ==&lt;br /&gt;
== Adding a HUD ==&lt;br /&gt;
== Adding a 2D Instrument ==&lt;br /&gt;
== Adding a 2D Panel ==&lt;br /&gt;
== Adding a PFD ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Failure Mgmt Widget ==&lt;br /&gt;
{{Note|This kind of widget will typically be useful for dialogs requiring a method for managing aircraft specific system failures (e.g. an instructor console).}}&lt;br /&gt;
&lt;br /&gt;
== Adding a MapStructure map to a Canvas  ==&lt;br /&gt;
{{Main article|Canvas MapStructure Layers}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-mapstructure-dialog.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var testMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
testMap.setRange(25);&lt;br /&gt;
 &lt;br /&gt;
testMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
    return caller(0)[0];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# APT and VOR are the layer names&lt;br /&gt;
foreach (var type; [r('APT'), r('VOR')]) {&lt;br /&gt;
    testMap.addLayer(&lt;br /&gt;
        factory: canvas.SymbolLayer,&lt;br /&gt;
        type_arg: type.name, &lt;br /&gt;
        visible: type.vis,&lt;br /&gt;
        priority: type.zindex,&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Customizing MapStructure Styling  ==&lt;br /&gt;
{{See also|Canvas MapStructure#Styling}}&lt;br /&gt;
&lt;br /&gt;
In general MapStructure symbols contain their own styles using hard-coded defaults, however these can be overridden by providing a hash with keys (fields) to customize these hard-coded defaults. &lt;br /&gt;
&lt;br /&gt;
This means that anything that may be specific to a single style (colors, fonts, images etc) should be encoded in the form of variables that are looked up using the styles hash - this provides a great deal of freedom to customize an existing symbol. In addition, MapStructure layers can be set up to even customize/override the default drawing routines, at which point you are free to do whatever you want basically, because the existing draw routine in .symbol file is ignored &lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Layers style change.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog. Active VOR and radial in red color, inactive VOR in green color]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
var r = func(name, vis = 1, zindex = nil) return caller(0)[0];&lt;br /&gt;
var type = r('APT');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in APT.symbol&lt;br /&gt;
var style_apt = {&lt;br /&gt;
    scale_factor: 0.5,             # 50 %&lt;br /&gt;
    color_default: [0, 1, 0.9],    # rgb&lt;br /&gt;
    line_width: 4,                 # thickness&lt;br /&gt;
    label_font_color: [0, 1, 0.9], # rgb&lt;br /&gt;
    label_font_size: 30,           # font size&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
TestMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_apt,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var type = r('VOR');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in VOR.symbol&lt;br /&gt;
var style_vor = {&lt;br /&gt;
    scale_factor: 0.6,&lt;br /&gt;
    active_color: [1, 0, 0],&lt;br /&gt;
    inactive_color: [0, 1, 0],&lt;br /&gt;
    line_width: 4,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
TestMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_vor,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
### See $FG_ROOT/Nasal/canvas/map/APT and $FG_ROOT/Nasal/canvas/map/VOR.symbol for the style variables that can be configured.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using MapStructure and Overlays ==&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Support for overlays using geo-referenced raster images isn't too well-developed currently, so would need to work with a few hard-coded assumptions (e.g. being specific to a certain raster image and map layout) - but maybe TheTom can provide a few more ideas on how to proceed from here - otherwise, the Map is normally not aware of any non-Map items, i.e. as long as the overlay is added &amp;quot;outside&amp;quot; the Map, it isn't even aware of the raster image - and when it is added as a map element, it would probably be rotated. So there's still some work needed here. But generally, this should work well enough even in its current form (the screen shot is purely based on the Canvas Snippets article, i.e. it just displays a Canvas GUI dialog, adds the downloaded raster image and then adds the MapStructure APS layer - without the Map being aware of the overlay/geo-referencing that is needed currently).&lt;br /&gt;
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=238316#p238316&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Using Canvas for visualizing orbital flights (cont'd PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri Apr 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-overlay-with-mapstructure.png|thumb|Using the [[MapStructure]] framework in conjunction with raster images as overlays]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== A simple tile map ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas - Tile map demo.png|thumb|A simple, canvas based tile map which is centered around the aircraft.]]&lt;br /&gt;
| &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (768, 512);&lt;br /&gt;
var tile_size = 256;&lt;br /&gt;
&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;).set('title', &amp;quot;Tile map demo&amp;quot;);&lt;br /&gt;
var group = window.getCanvas(1).createGroup();&lt;br /&gt;
&lt;br /&gt;
# Simple user interface (Buttons for zoom and label for displaying it)&lt;br /&gt;
var zoom = 10;&lt;br /&gt;
var type = &amp;quot;intl&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
var ui_root = window.getCanvas().createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
window.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var button_in = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;+&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(1));&lt;br /&gt;
&lt;br /&gt;
var button_out = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;-&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(-1));&lt;br /&gt;
&lt;br /&gt;
button_in.setSizeHint([32, 32]);&lt;br /&gt;
button_out.setSizeHint([32, 32]);&lt;br /&gt;
&lt;br /&gt;
var label_zoom = canvas.gui.widgets.Label.new(ui_root, canvas.style, {});&lt;br /&gt;
&lt;br /&gt;
var button_box = canvas.HBoxLayout.new();&lt;br /&gt;
button_box.addItem(button_in);&lt;br /&gt;
button_box.addItem(label_zoom);&lt;br /&gt;
button_box.addItem(button_out);&lt;br /&gt;
button_box.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
vbox.addItem(button_box);&lt;br /&gt;
vbox.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
var changeZoom = func(d) {&lt;br /&gt;
    zoom = math.max(2, math.min(19, zoom + d));&lt;br /&gt;
    label_zoom.setText(&amp;quot;Zoom &amp;quot; ~ zoom);&lt;br /&gt;
    updateTiles();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# http://polymaps.org/docs/&lt;br /&gt;
# https://github.com/simplegeo/polymaps&lt;br /&gt;
# https://github.com/Leaflet/Leaflet&lt;br /&gt;
&lt;br /&gt;
var maps_base = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/cache/maps';&lt;br /&gt;
&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/map&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/sat&lt;br /&gt;
# (also see http://wiki.openstreetmap.org/wiki/Tile_usage_policy)&lt;br /&gt;
var makeUrl = string.compileTemplate('https://maps.wikimedia.org/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
# https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png&lt;br /&gt;
var makePath = string.compileTemplate(maps_base ~ '/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
var num_tiles = [4, 3];&lt;br /&gt;
&lt;br /&gt;
var center_tile_offset = [&lt;br /&gt;
    (num_tiles[0] - 1) / 2,&lt;br /&gt;
    (num_tiles[1] - 1) / 2,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
# simple aircraft icon at current position/center of the map&lt;br /&gt;
group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
    .moveTo(&lt;br /&gt;
        tile_size * center_tile_offset[0] - 10,&lt;br /&gt;
        tile_size * center_tile_offset[1],&lt;br /&gt;
    )&lt;br /&gt;
    .horiz(20)&lt;br /&gt;
    .move(-10, -10)&lt;br /&gt;
    .vert(20)&lt;br /&gt;
    .set(&amp;quot;stroke&amp;quot;, &amp;quot;red&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;stroke-width&amp;quot;, 2)&lt;br /&gt;
    .set(&amp;quot;z-index&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# initialize the map by setting up&lt;br /&gt;
# a grid of raster images  &lt;br /&gt;
&lt;br /&gt;
var tiles = setsize([], num_tiles[0]);&lt;br /&gt;
for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
    tiles[x] = setsize([], num_tiles[1]);&lt;br /&gt;
    for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
        tiles[x][y] = group.createChild(&amp;quot;image&amp;quot;, &amp;quot;map-tile&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var last_tile = [-1, -1];&lt;br /&gt;
var last_type = type;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the callback that will be regularly called by the timer&lt;br /&gt;
# to update the map&lt;br /&gt;
var updateTiles = func {&lt;br /&gt;
    # get current position&lt;br /&gt;
    var lat = getprop('/position/latitude-deg');&lt;br /&gt;
    var lon = getprop('/position/longitude-deg');&lt;br /&gt;
&lt;br /&gt;
    var n = math.pow(2, zoom);&lt;br /&gt;
&lt;br /&gt;
    var latRad = lat * D2R;&lt;br /&gt;
    var mercatorY = math.ln(math.tan(latRad) + 1 / math.cos(latRad));&lt;br /&gt;
    var offset = [&lt;br /&gt;
        n * ((lon + 180) / 360) - center_tile_offset[0],&lt;br /&gt;
        (1 - mercatorY / math.pi) / 2 * n - center_tile_offset[1],&lt;br /&gt;
    ];&lt;br /&gt;
    var tile_index = [&lt;br /&gt;
        int(offset[0]),&lt;br /&gt;
        int(offset[1]),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var ox = tile_index[0] - offset[0];&lt;br /&gt;
    var oy = tile_index[1] - offset[1];&lt;br /&gt;
&lt;br /&gt;
    for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
        for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
            tiles[x][y].setTranslation(&lt;br /&gt;
                int((ox + x) * tile_size + 0.5),&lt;br /&gt;
                int((oy + y) * tile_size + 0.5),&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tile_index[0] != last_tile[0]&lt;br /&gt;
        or tile_index[1] != last_tile[1]&lt;br /&gt;
        or type != last_type&lt;br /&gt;
    ) {&lt;br /&gt;
        for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
            for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
                var pos = {&lt;br /&gt;
                    z: zoom,&lt;br /&gt;
                    x: int(offset[0] + x),&lt;br /&gt;
                    y: int(offset[1] + y),&lt;br /&gt;
                    type: type,&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                (func {&lt;br /&gt;
                    var img_path = makePath(pos);&lt;br /&gt;
                    var tile = tiles[x][y];&lt;br /&gt;
&lt;br /&gt;
                    if (io.stat(img_path) == nil) { &lt;br /&gt;
                        # image not found, save in $FG_HOME&lt;br /&gt;
                        var img_url = makeUrl(pos);&lt;br /&gt;
                        print('requesting ' ~ img_url);&lt;br /&gt;
                        http.save(img_url, img_path)&lt;br /&gt;
                            .done(func {&lt;br /&gt;
                                print('received image ' ~ img_path);&lt;br /&gt;
                                tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                             })&lt;br /&gt;
                            .fail(func(r) {&lt;br /&gt;
                                print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason);&lt;br /&gt;
                            });&lt;br /&gt;
                    } else {&lt;br /&gt;
                        # cached image found, reusing&lt;br /&gt;
                        print('loading ' ~ img_path);&lt;br /&gt;
                        tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                    }&lt;br /&gt;
                })();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        last_tile = tile_index;&lt;br /&gt;
        last_type = type;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up a timer that will invoke updateTiles at 2-second intervals&lt;br /&gt;
var update_timer = maketimer(2, updateTiles);&lt;br /&gt;
# actually start the timer&lt;br /&gt;
update_timer.start();&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up default zoom level&lt;br /&gt;
changeZoom(0);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###&lt;br /&gt;
# The following lines were recently added and have not yet been tested&lt;br /&gt;
# (if in doubt, remove them)&lt;br /&gt;
window.del = func {&lt;br /&gt;
    print(&amp;quot;Cleaning up window:&amp;quot;, ,&amp;quot;\n&amp;quot;);&lt;br /&gt;
    update_timer.stop();&lt;br /&gt;
    # explanation for the call() technique at:&lt;br /&gt;
    # http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls&lt;br /&gt;
    call(canvas.Window.del, [], me);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding a NavDisplay ==&lt;br /&gt;
&lt;br /&gt;
== Adding a CDU ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Multi-Function Display (MFD) ==&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143071</id>
		<title>Canvas snippets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143071"/>
		<updated>2025-11-26T16:42:30Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* Adding a MapStructure map to a Canvas */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{-}}&lt;br /&gt;
This article is meant to be a collection of self-contained code snippets for doing Nasal/Canvas coding without having to spend too much time working through APIs, documentation and existing code - the idea is to provide snippets that contain comments and explicit assumptions, so that these snippets can be easily adapted by people without necessarily having to be very familiar with Nasal/Canvas coding. &lt;br /&gt;
&lt;br /&gt;
In the long term, we're hoping to grow a library of useful code snippets to cover most use-cases, while also covering more complex scenarios, e.g. using Canvas-specific helper frameworks like [[Canvas MapStructure]], the [[NavDisplay]] or the recent CDU work.&lt;br /&gt;
&lt;br /&gt;
Canvas itself is designed to be front-end agnostic, meaning that it doesn't matter where a Canvas is used/displayed - this is accomplished by so called &amp;quot;placements&amp;quot;, which determine where a Canvas texture is shown. Thus, the back-end logic can remain the same usually, so that GUI dialogs may show avionics, but also so that avionics may show GUI widgets, which also applies to HUDs, liveries and even placements within the FlightGear scenery.&lt;br /&gt;
&lt;br /&gt;
We encourage people to use the snippets listed below to get started with Nasal/Canvas coding, but also to provide feedback on extending/improving this article to make it even more accessible and useful. Equally, if you're aware of any Canvas-related efforts that may contain useful code snippets, please do feel free to add those snippets here. Even people who don't have any interest in coding itself are encouraged to get involved by helping test the snippets listed below, for which you only need to the [[Nasal Console]], and while you're at it, please also help provide/update screen shots for each code snippet.&lt;br /&gt;
&lt;br /&gt;
Contributions added to this article should ideally satisfy some requirements:&lt;br /&gt;
* be entirely self-contained&lt;br /&gt;
* contain good/clear comments&lt;br /&gt;
* have pointers/references to related code/use-cases&lt;br /&gt;
* make assumptions explicit&lt;br /&gt;
* contain screen shots for each example&lt;br /&gt;
* avoid overloaded symbol names to ensure that examples can be merged and adapted easily&lt;br /&gt;
* dependencies (svg files, images/textures etc) should always be chosen such that snippets always work using $FG_ROOT&lt;br /&gt;
* hard-coded assumptions (e.g. texture dimensions in terms of width/height etc) should be encapsulated using variables&lt;br /&gt;
 &lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
== Creating a standalone Canvas ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (512, 512);&lt;br /&gt;
# Create a standalone Canvas (not attached to any GUI dialog/aircraft etc) &lt;br /&gt;
var myCanvas = canvas.new({&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Livery Test&amp;quot;,   # The name is optional but allow for easier identification&lt;br /&gt;
    &amp;quot;size&amp;quot;: [width, height], # Size of the underlying texture (should be a power of 2, required) [Resolution]&lt;br /&gt;
    &amp;quot;view&amp;quot;: [width, height], # Virtual resolution (Defines the coordinate system of the canvas [Dimensions]&lt;br /&gt;
                             # which will be stretched the size of the texture, required)&lt;br /&gt;
    &amp;quot;mipmapping&amp;quot;: 1,         # Enable mipmapping (optional)&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# set background color&lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# OPTIONAL: Create a Canvas dialog window to hold the canvas and show that it's working&lt;br /&gt;
# the Canvas is now standalone, i.e. continues to live once the dialog is closed!&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(myCanvas);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Accessing the Canvas Desktop ==&lt;br /&gt;
Sometimes you may want to render to the main screen, without creating a separate Canvas window - this can be accomplished by using the Canvas desktop, note that the following example is fully self-contained, i.e. does not require any code to be added to work:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myNode = canvas.getDesktop().createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello Canvas Desktop&amp;quot;)&lt;br /&gt;
    .setFontSize(25, 1.0)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Creating Tooltips ==&lt;br /&gt;
== Creating Popups ==&lt;br /&gt;
== Creating a Canvas GUI Window ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-dialog.png|left|thumb|This is what the [[Nasal]]/[[Canvas]] snippet will look like once you pasted it into the [[Nasal Console]] and click &amp;quot;Execute&amp;quot;.]] || {{Note|This example uses so called method chaining, if you're not familiar with the concept, please see: [[Object_Oriented_Programming_with_Nasal#More_on_methods:_Chaining|Method Chaining]].}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Snippets Boilerplate}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas Snippets-raster image.png|thumb|Canvas snippet demonstrating how to load a raster image]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# path is relative to $FG_ROOT (base package)&lt;br /&gt;
var path = &amp;quot;Textures/Splash1.png&amp;quot;;&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(path)&lt;br /&gt;
    .setTranslation(100, 10)&lt;br /&gt;
    .setSize(130, 130);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images downloaded on demand ==&lt;br /&gt;
&lt;br /&gt;
The '''path''' could also just as well be a URL, i.e. a raster image retrieved via http - for example, the following snippet is entirely self-contained and can be pasted into the [[Nasal Console]] and directly executed &amp;quot;as is&amp;quot;: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-raster-images-via-url.png|thumb|screen shot demonstrating how Nasal and Canvas can be used to display raster images downloaded on demand]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400, 200], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# path now a URL&lt;br /&gt;
var url = &amp;quot;http://www.worldwidetelescope.org/docs/Images/MapOfEarth.jpg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(url) &lt;br /&gt;
    .setTranslation(45, 22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310, 155);     # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Clipping ==&lt;br /&gt;
{{Note|{{FGCquote&lt;br /&gt;
|1= Scaling or any other type of transformation or changing the coordinates of individual points is definitely more efficient than clipping (which requires to change the OpenGL clip planes for every rendered object with a different clipping rectangle).&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=277062#p277062&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;TheTom&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
}}&lt;br /&gt;
I would suggest to refer to api.nas and look for &amp;quot;clip&amp;quot; and/or &amp;quot;rect&amp;quot; - IIRC, you need to set up a clipping rectangle by setting some kind of &amp;quot;clip&amp;quot; property and setting it to a rect value in the form of rect(...)&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= For details, see the clipping example at: [[Canvas Nasal API#set 2]] and  [[Canvas Element#clip.28.29]]&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=276965#p276965&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:MapStructure_dialog_with_clipping_and_event_handling_applied.png|thumb]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;.........&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding OpenVG Paths ==&lt;br /&gt;
{{Main article|How to manipulate Canvas elements}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Openvg-via-canvas.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var graph = root.createChild(&amp;quot;group&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var x_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;x-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .lineTo(width - 10, height / 2)&lt;br /&gt;
    .setColor(1, 0, 0)&lt;br /&gt;
    .setStrokeLineWidth(3);&lt;br /&gt;
&lt;br /&gt;
var y_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;y-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, 10)&lt;br /&gt;
    .lineTo(10, height - 10)&lt;br /&gt;
    .setColor(0, 0, 1)&lt;br /&gt;
    .setStrokeLineWidth(2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-quadTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG quadTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .quadTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-cubicTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG cubicTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    120, height - 120,&lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .cubicTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Vector Images ==&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas SVG]]&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-svg-support.png|thumb|screen shot demonstrating how the scripted Nasal-based SVG parser can be used to dynamically turn SVG files into OpenVG instructions understood by Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# change the background color &lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var filename = &amp;quot;/Nasal/canvas/map/Images/boeingAirplane.svg&amp;quot;;&lt;br /&gt;
var svg_symbol = root.createChild('group');&lt;br /&gt;
canvas.parsesvg(svg_symbol, filename);&lt;br /&gt;
&lt;br /&gt;
svg_symbol.setTranslation(width / 2, height / 2);&lt;br /&gt;
&lt;br /&gt;
# svg_symbol.setScale(0.2);&lt;br /&gt;
# svg_symbol.setRotation(radians)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using the SymbolCache ==&lt;br /&gt;
Whenever a symbol may need to be shown/instanced multiple times (possibly using different styling), it makes sense to use caching - otherwise, identical symbols would be treated as separate OpenVG groups, all of which would need to be rasterized/rendedered separately (i.e. 100 identical symbols would be updated/rendered one by one). &lt;br /&gt;
&lt;br /&gt;
Typically, a map may display multiple instances of an otherwise identical symbol (think VOR, NDB, DME etc) - equally, a multiplayer map may showing multiple aircraft symbols at the same time. In these cases, it makes sense to use the SymbolCache framework, which will render symbols into a separate Canvas texture and provide a texture map that can be treated as a lookup map, which even supports styling for otherwise identical symbols. To learn more, please refer to [[Canvas MapStructure#The_SymbolCache|SymbolCache]]&lt;br /&gt;
... &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-SymbolCache-Instancing.png|thumb|Screen shot showing a Canvas based GUI dialog that is using the SymbolCache for instancing multiple symbols (including support for styling)]] &lt;br /&gt;
| &amp;lt;!--{{Caution|This is currently untested code}}--&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the function that draws a symbol using OpenVG paths&lt;br /&gt;
# it accepts a group to draw to and returns the rendered group &lt;br /&gt;
# to the caller&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5, 12.5)&lt;br /&gt;
        .lineTo(7.5, 12.5)&lt;br /&gt;
        .lineTo(15, 0)&lt;br /&gt;
        .lineTo(7.5, -12.5)&lt;br /&gt;
        .lineTo(-7.5, -12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width) # style-able&lt;br /&gt;
        .setColor(color); # style-able&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var style = { # styling related attributes (as per the draw* function above)&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    color: [1, 0, 0],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# create a new cache entry for the styled symbol&lt;br /&gt;
var myCachedSymbol = canvas.StyleableCacheable.new(&lt;br /&gt;
    name: 'myVOR',&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: canvas.SymbolCache32x32, # the cache to be used&lt;br /&gt;
    draw_mode: canvas.SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: ['line_width', 'color'], # styling related attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var target = root.createChild('group');&lt;br /&gt;
&lt;br /&gt;
var x = 0;&lt;br /&gt;
var y = height / 2;&lt;br /&gt;
&lt;br /&gt;
var xoffset = 50;&lt;br /&gt;
&lt;br /&gt;
# render 5 instanced symbols using the style specified above&lt;br /&gt;
for (var i = 0; i &amp;lt; 5; i += 1) {&lt;br /&gt;
    # look up the raster image for the symbol&lt;br /&gt;
    # render it using the passed style and adjust scaling&lt;br /&gt;
    var instanced = myCachedSymbol.render(target, style)&lt;br /&gt;
        .setScale(style.scale_factor)&lt;br /&gt;
        .setTranslation(x += xoffset, y);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The example shown above uses a fixed symbol/icon cache that is set up while booting Flightgear - sometimes, we may need cache for different purposes. So, let's assume, we need a new/custom cache with a different resolution for each entry in the cache (e.g. 256x256), we can easily accomplish that by setting up a new cache like this: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var mySymbolCache256x256 = canvas.SymbolCache.new(1024, 256);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adding Text Elements ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:CanvasText-Hello-World.png|thumb|Screen shot showing the CanvasText example contributed by Necolatis]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myText = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello world!&amp;quot;)&lt;br /&gt;
    .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Labels ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-Layout-Label-example-by-Necolatis.png|thumb|Canvas demo: Layouts and Labels (by Necolatis)]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label.setText(&amp;quot;Hello World!&amp;quot;);&lt;br /&gt;
myLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
var label2 = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label2.setText(&amp;quot;Hello FlightGear&amp;quot;);&lt;br /&gt;
myLayout.addItem(label2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Buttons (Layouts)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-demo-layouts-and-buttons-by-Necolatis.png|thumb|Canvas snippet: buttons and layouts (by Necolatis)]] &lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
{{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
# click button&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
	.setText(&amp;quot;Click on me&amp;quot;)&lt;br /&gt;
	.setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;clicked&amp;quot;, func {&lt;br /&gt;
    # add code here to react on click on button.&lt;br /&gt;
    print(&amp;quot;Button clicked !&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-toggle-button-snippet-by-Necolatis.png|thumb|Canvas toggle button demo by Necolatis]]&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;Toggle me&amp;quot;)&lt;br /&gt;
    .setCheckable(1) # this indicates that is should be a toggle button&lt;br /&gt;
    .setChecked(0) # depressed by default&lt;br /&gt;
    .setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;toggled&amp;quot;, func (e) {&lt;br /&gt;
    if (e.detail.checked) {&lt;br /&gt;
        # add code here to react on button being depressed.&lt;br /&gt;
    } else {&lt;br /&gt;
        # add code here to react on button not being depressed.&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas Input Dialog ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Snippets-canvas-input-dialog.png|thumb|Canvas input dialog]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new InputDialog with a title, label, and a callback&lt;br /&gt;
canvas.InputDialog.getText(&amp;quot;Input Dialog Title&amp;quot;, &amp;quot;Please enter some text&amp;quot;, func(btn, value) {&lt;br /&gt;
    if (value) {&lt;br /&gt;
        gui.popupTip(&amp;quot;You entered: &amp;quot; ~ value);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas ScrollArea ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas-snippets-scrollArea-demo.png|thumb|Screen shot showing a Canvas ScrollArea populated with different splash screens, loaded from $FG_ROOT/Textures]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, { size: [96, 128] }).move(20, 100);&lt;br /&gt;
vbox.addItem(scroll, 1);&lt;br /&gt;
&lt;br /&gt;
var scrollContent = scroll.getContent()&lt;br /&gt;
    .set(&amp;quot;font&amp;quot;, &amp;quot;LiberationFonts/LiberationSans-Bold.ttf&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;character-size&amp;quot;, 16)&lt;br /&gt;
    .set(&amp;quot;alignment&amp;quot;, &amp;quot;left-center&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var list = canvas.VBoxLayout.new();&lt;br /&gt;
scroll.setLayout(list);&lt;br /&gt;
&lt;br /&gt;
for (var i = 1; i &amp;lt;= 5; i += 1) {&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(scrollContent, canvas.style, { wordWrap: 0 });&lt;br /&gt;
    label.setImage(&amp;quot;Textures/Splash&amp;quot; ~ i ~ &amp;quot;.png&amp;quot;);&lt;br /&gt;
    label.setFixedSize(256, 256);&lt;br /&gt;
    list.addItem(label);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using TabWidgets ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Screenshot&lt;br /&gt;
!Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas TabWidget example.png|alt=Canvas TabWIdget example|thumb|Canvas TabWidget example showing the three default splash screens]]&lt;br /&gt;
|&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
var window = canvas.Window.new([300, 300], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {});&lt;br /&gt;
var tabsContent = tabs.getContent();&lt;br /&gt;
&lt;br /&gt;
var createTabContent = func(imgPath, text) {&lt;br /&gt;
    var image = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setImage(imgPath)&lt;br /&gt;
        .setFixedSize(128, 128);&lt;br /&gt;
&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setText(text);&lt;br /&gt;
&lt;br /&gt;
    var tabLayout = canvas.VBoxLayout.new();&lt;br /&gt;
    tabLayout.addItem(image);&lt;br /&gt;
    tabLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
    return tabLayout;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
tabs.addTab(&amp;quot;tab1&amp;quot;, &amp;quot;Texture 1&amp;quot;, createTabContent(&amp;quot;Textures/Splash1.png&amp;quot;, &amp;quot;Texture 1&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab2&amp;quot;, &amp;quot;Texture 2&amp;quot;, createTabContent(&amp;quot;Textures/Splash2.png&amp;quot;, &amp;quot;Texture 2&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab3&amp;quot;, &amp;quot;Texture 3&amp;quot;, createTabContent(&amp;quot;Textures/Splash3.png&amp;quot;, &amp;quot;Texture 3&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
vbox.addItem(tabs);&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using Layouts ==&lt;br /&gt;
&lt;br /&gt;
== Using Styling ==&lt;br /&gt;
== Adding a HUD ==&lt;br /&gt;
== Adding a 2D Instrument ==&lt;br /&gt;
== Adding a 2D Panel ==&lt;br /&gt;
== Adding a PFD ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Failure Mgmt Widget ==&lt;br /&gt;
{{Note|This kind of widget will typically be useful for dialogs requiring a method for managing aircraft specific system failures (e.g. an instructor console).}}&lt;br /&gt;
&lt;br /&gt;
== Adding a MapStructure map to a Canvas  ==&lt;br /&gt;
{{Main article|Canvas MapStructure Layers}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-mapstructure-dialog.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var testMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
testMap.setRange(25);&lt;br /&gt;
 &lt;br /&gt;
testMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var r = func(name, vis = 1, zindex = nil) return caller(0)[0];&lt;br /&gt;
&lt;br /&gt;
# APT and VOR are the layer names&lt;br /&gt;
foreach (var type; [r('APT'), r('VOR')]) {&lt;br /&gt;
    testMap.addLayer(&lt;br /&gt;
        factory: canvas.SymbolLayer,&lt;br /&gt;
        type_arg: type.name, &lt;br /&gt;
        visible: type.vis,&lt;br /&gt;
        priority: type.zindex,&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Customizing MapStructure Styling  ==&lt;br /&gt;
{{See also|Canvas MapStructure#Styling}}&lt;br /&gt;
&lt;br /&gt;
In general MapStructure symbols contain their own styles using hard-coded defaults, however these can be overridden by providing a hash with keys (fields) to customize these hard-coded defaults. &lt;br /&gt;
&lt;br /&gt;
This means that anything that may be specific to a single style (colors, fonts, images etc) should be encoded in the form of variables that are looked up using the styles hash - this provides a great deal of freedom to customize an existing symbol. In addition, MapStructure layers can be set up to even customize/override the default drawing routines, at which point you are free to do whatever you want basically, because the existing draw routine in .symbol file is ignored &lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Layers style change.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog. Active VOR and radial in red color, inactive VOR in green color]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
var r = func(name, vis = 1, zindex = nil) return caller(0)[0];&lt;br /&gt;
var type = r('APT');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in APT.symbol&lt;br /&gt;
var style_apt = {&lt;br /&gt;
    scale_factor: 0.5,             # 50 %&lt;br /&gt;
    color_default: [0, 1, 0.9],    # rgb&lt;br /&gt;
    line_width: 4,                 # thickness&lt;br /&gt;
    label_font_color: [0, 1, 0.9], # rgb&lt;br /&gt;
    label_font_size: 30,           # font size&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
TestMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_apt,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var type = r('VOR');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in VOR.symbol&lt;br /&gt;
var style_vor = {&lt;br /&gt;
    scale_factor: 0.6,&lt;br /&gt;
    active_color: [1, 0, 0],&lt;br /&gt;
    inactive_color: [0, 1, 0],&lt;br /&gt;
    line_width: 4,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
TestMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_vor,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
### See $FG_ROOT/Nasal/canvas/map/APT and $FG_ROOT/Nasal/canvas/map/VOR.symbol for the style variables that can be configured.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using MapStructure and Overlays ==&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Support for overlays using geo-referenced raster images isn't too well-developed currently, so would need to work with a few hard-coded assumptions (e.g. being specific to a certain raster image and map layout) - but maybe TheTom can provide a few more ideas on how to proceed from here - otherwise, the Map is normally not aware of any non-Map items, i.e. as long as the overlay is added &amp;quot;outside&amp;quot; the Map, it isn't even aware of the raster image - and when it is added as a map element, it would probably be rotated. So there's still some work needed here. But generally, this should work well enough even in its current form (the screen shot is purely based on the Canvas Snippets article, i.e. it just displays a Canvas GUI dialog, adds the downloaded raster image and then adds the MapStructure APS layer - without the Map being aware of the overlay/geo-referencing that is needed currently).&lt;br /&gt;
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=238316#p238316&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Using Canvas for visualizing orbital flights (cont'd PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri Apr 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-overlay-with-mapstructure.png|thumb|Using the [[MapStructure]] framework in conjunction with raster images as overlays]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== A simple tile map ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas - Tile map demo.png|thumb|A simple, canvas based tile map which is centered around the aircraft.]]&lt;br /&gt;
| &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (768, 512);&lt;br /&gt;
var tile_size = 256;&lt;br /&gt;
&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;).set('title', &amp;quot;Tile map demo&amp;quot;);&lt;br /&gt;
var group = window.getCanvas(1).createGroup();&lt;br /&gt;
&lt;br /&gt;
# Simple user interface (Buttons for zoom and label for displaying it)&lt;br /&gt;
var zoom = 10;&lt;br /&gt;
var type = &amp;quot;intl&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
var ui_root = window.getCanvas().createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
window.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var button_in = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;+&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(1));&lt;br /&gt;
&lt;br /&gt;
var button_out = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;-&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(-1));&lt;br /&gt;
&lt;br /&gt;
button_in.setSizeHint([32, 32]);&lt;br /&gt;
button_out.setSizeHint([32, 32]);&lt;br /&gt;
&lt;br /&gt;
var label_zoom = canvas.gui.widgets.Label.new(ui_root, canvas.style, {});&lt;br /&gt;
&lt;br /&gt;
var button_box = canvas.HBoxLayout.new();&lt;br /&gt;
button_box.addItem(button_in);&lt;br /&gt;
button_box.addItem(label_zoom);&lt;br /&gt;
button_box.addItem(button_out);&lt;br /&gt;
button_box.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
vbox.addItem(button_box);&lt;br /&gt;
vbox.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
var changeZoom = func(d) {&lt;br /&gt;
    zoom = math.max(2, math.min(19, zoom + d));&lt;br /&gt;
    label_zoom.setText(&amp;quot;Zoom &amp;quot; ~ zoom);&lt;br /&gt;
    updateTiles();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# http://polymaps.org/docs/&lt;br /&gt;
# https://github.com/simplegeo/polymaps&lt;br /&gt;
# https://github.com/Leaflet/Leaflet&lt;br /&gt;
&lt;br /&gt;
var maps_base = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/cache/maps';&lt;br /&gt;
&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/map&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/sat&lt;br /&gt;
# (also see http://wiki.openstreetmap.org/wiki/Tile_usage_policy)&lt;br /&gt;
var makeUrl = string.compileTemplate('https://maps.wikimedia.org/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
# https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png&lt;br /&gt;
var makePath = string.compileTemplate(maps_base ~ '/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
var num_tiles = [4, 3];&lt;br /&gt;
&lt;br /&gt;
var center_tile_offset = [&lt;br /&gt;
    (num_tiles[0] - 1) / 2,&lt;br /&gt;
    (num_tiles[1] - 1) / 2,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
# simple aircraft icon at current position/center of the map&lt;br /&gt;
group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
    .moveTo(&lt;br /&gt;
        tile_size * center_tile_offset[0] - 10,&lt;br /&gt;
        tile_size * center_tile_offset[1],&lt;br /&gt;
    )&lt;br /&gt;
    .horiz(20)&lt;br /&gt;
    .move(-10, -10)&lt;br /&gt;
    .vert(20)&lt;br /&gt;
    .set(&amp;quot;stroke&amp;quot;, &amp;quot;red&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;stroke-width&amp;quot;, 2)&lt;br /&gt;
    .set(&amp;quot;z-index&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# initialize the map by setting up&lt;br /&gt;
# a grid of raster images  &lt;br /&gt;
&lt;br /&gt;
var tiles = setsize([], num_tiles[0]);&lt;br /&gt;
for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
    tiles[x] = setsize([], num_tiles[1]);&lt;br /&gt;
    for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
        tiles[x][y] = group.createChild(&amp;quot;image&amp;quot;, &amp;quot;map-tile&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var last_tile = [-1, -1];&lt;br /&gt;
var last_type = type;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the callback that will be regularly called by the timer&lt;br /&gt;
# to update the map&lt;br /&gt;
var updateTiles = func {&lt;br /&gt;
    # get current position&lt;br /&gt;
    var lat = getprop('/position/latitude-deg');&lt;br /&gt;
    var lon = getprop('/position/longitude-deg');&lt;br /&gt;
&lt;br /&gt;
    var n = math.pow(2, zoom);&lt;br /&gt;
&lt;br /&gt;
    var latRad = lat * D2R;&lt;br /&gt;
    var mercatorY = math.ln(math.tan(latRad) + 1 / math.cos(latRad));&lt;br /&gt;
    var offset = [&lt;br /&gt;
        n * ((lon + 180) / 360) - center_tile_offset[0],&lt;br /&gt;
        (1 - mercatorY / math.pi) / 2 * n - center_tile_offset[1],&lt;br /&gt;
    ];&lt;br /&gt;
    var tile_index = [&lt;br /&gt;
        int(offset[0]),&lt;br /&gt;
        int(offset[1]),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var ox = tile_index[0] - offset[0];&lt;br /&gt;
    var oy = tile_index[1] - offset[1];&lt;br /&gt;
&lt;br /&gt;
    for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
        for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
            tiles[x][y].setTranslation(&lt;br /&gt;
                int((ox + x) * tile_size + 0.5),&lt;br /&gt;
                int((oy + y) * tile_size + 0.5),&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tile_index[0] != last_tile[0]&lt;br /&gt;
        or tile_index[1] != last_tile[1]&lt;br /&gt;
        or type != last_type&lt;br /&gt;
    ) {&lt;br /&gt;
        for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
            for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
                var pos = {&lt;br /&gt;
                    z: zoom,&lt;br /&gt;
                    x: int(offset[0] + x),&lt;br /&gt;
                    y: int(offset[1] + y),&lt;br /&gt;
                    type: type,&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                (func {&lt;br /&gt;
                    var img_path = makePath(pos);&lt;br /&gt;
                    var tile = tiles[x][y];&lt;br /&gt;
&lt;br /&gt;
                    if (io.stat(img_path) == nil) { &lt;br /&gt;
                        # image not found, save in $FG_HOME&lt;br /&gt;
                        var img_url = makeUrl(pos);&lt;br /&gt;
                        print('requesting ' ~ img_url);&lt;br /&gt;
                        http.save(img_url, img_path)&lt;br /&gt;
                            .done(func {&lt;br /&gt;
                                print('received image ' ~ img_path);&lt;br /&gt;
                                tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                             })&lt;br /&gt;
                            .fail(func(r) {&lt;br /&gt;
                                print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason);&lt;br /&gt;
                            });&lt;br /&gt;
                    } else {&lt;br /&gt;
                        # cached image found, reusing&lt;br /&gt;
                        print('loading ' ~ img_path);&lt;br /&gt;
                        tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                    }&lt;br /&gt;
                })();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        last_tile = tile_index;&lt;br /&gt;
        last_type = type;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up a timer that will invoke updateTiles at 2-second intervals&lt;br /&gt;
var update_timer = maketimer(2, updateTiles);&lt;br /&gt;
# actually start the timer&lt;br /&gt;
update_timer.start();&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up default zoom level&lt;br /&gt;
changeZoom(0);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###&lt;br /&gt;
# The following lines were recently added and have not yet been tested&lt;br /&gt;
# (if in doubt, remove them)&lt;br /&gt;
window.del = func {&lt;br /&gt;
    print(&amp;quot;Cleaning up window:&amp;quot;, ,&amp;quot;\n&amp;quot;);&lt;br /&gt;
    update_timer.stop();&lt;br /&gt;
    # explanation for the call() technique at:&lt;br /&gt;
    # http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls&lt;br /&gt;
    call(canvas.Window.del, [], me);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding a NavDisplay ==&lt;br /&gt;
&lt;br /&gt;
== Adding a CDU ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Multi-Function Display (MFD) ==&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143069</id>
		<title>Canvas snippets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143069"/>
		<updated>2025-11-26T01:23:12Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* A simple tile map */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{-}}&lt;br /&gt;
This article is meant to be a collection of self-contained code snippets for doing Nasal/Canvas coding without having to spend too much time working through APIs, documentation and existing code - the idea is to provide snippets that contain comments and explicit assumptions, so that these snippets can be easily adapted by people without necessarily having to be very familiar with Nasal/Canvas coding. &lt;br /&gt;
&lt;br /&gt;
In the long term, we're hoping to grow a library of useful code snippets to cover most use-cases, while also covering more complex scenarios, e.g. using Canvas-specific helper frameworks like [[Canvas MapStructure]], the [[NavDisplay]] or the recent CDU work.&lt;br /&gt;
&lt;br /&gt;
Canvas itself is designed to be front-end agnostic, meaning that it doesn't matter where a Canvas is used/displayed - this is accomplished by so called &amp;quot;placements&amp;quot;, which determine where a Canvas texture is shown. Thus, the back-end logic can remain the same usually, so that GUI dialogs may show avionics, but also so that avionics may show GUI widgets, which also applies to HUDs, liveries and even placements within the FlightGear scenery.&lt;br /&gt;
&lt;br /&gt;
We encourage people to use the snippets listed below to get started with Nasal/Canvas coding, but also to provide feedback on extending/improving this article to make it even more accessible and useful. Equally, if you're aware of any Canvas-related efforts that may contain useful code snippets, please do feel free to add those snippets here. Even people who don't have any interest in coding itself are encouraged to get involved by helping test the snippets listed below, for which you only need to the [[Nasal Console]], and while you're at it, please also help provide/update screen shots for each code snippet.&lt;br /&gt;
&lt;br /&gt;
Contributions added to this article should ideally satisfy some requirements:&lt;br /&gt;
* be entirely self-contained&lt;br /&gt;
* contain good/clear comments&lt;br /&gt;
* have pointers/references to related code/use-cases&lt;br /&gt;
* make assumptions explicit&lt;br /&gt;
* contain screen shots for each example&lt;br /&gt;
* avoid overloaded symbol names to ensure that examples can be merged and adapted easily&lt;br /&gt;
* dependencies (svg files, images/textures etc) should always be chosen such that snippets always work using $FG_ROOT&lt;br /&gt;
* hard-coded assumptions (e.g. texture dimensions in terms of width/height etc) should be encapsulated using variables&lt;br /&gt;
 &lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
== Creating a standalone Canvas ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (512, 512);&lt;br /&gt;
# Create a standalone Canvas (not attached to any GUI dialog/aircraft etc) &lt;br /&gt;
var myCanvas = canvas.new({&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Livery Test&amp;quot;,   # The name is optional but allow for easier identification&lt;br /&gt;
    &amp;quot;size&amp;quot;: [width, height], # Size of the underlying texture (should be a power of 2, required) [Resolution]&lt;br /&gt;
    &amp;quot;view&amp;quot;: [width, height], # Virtual resolution (Defines the coordinate system of the canvas [Dimensions]&lt;br /&gt;
                             # which will be stretched the size of the texture, required)&lt;br /&gt;
    &amp;quot;mipmapping&amp;quot;: 1,         # Enable mipmapping (optional)&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# set background color&lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# OPTIONAL: Create a Canvas dialog window to hold the canvas and show that it's working&lt;br /&gt;
# the Canvas is now standalone, i.e. continues to live once the dialog is closed!&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(myCanvas);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Accessing the Canvas Desktop ==&lt;br /&gt;
Sometimes you may want to render to the main screen, without creating a separate Canvas window - this can be accomplished by using the Canvas desktop, note that the following example is fully self-contained, i.e. does not require any code to be added to work:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myNode = canvas.getDesktop().createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello Canvas Desktop&amp;quot;)&lt;br /&gt;
    .setFontSize(25, 1.0)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Creating Tooltips ==&lt;br /&gt;
== Creating Popups ==&lt;br /&gt;
== Creating a Canvas GUI Window ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-dialog.png|left|thumb|This is what the [[Nasal]]/[[Canvas]] snippet will look like once you pasted it into the [[Nasal Console]] and click &amp;quot;Execute&amp;quot;.]] || {{Note|This example uses so called method chaining, if you're not familiar with the concept, please see: [[Object_Oriented_Programming_with_Nasal#More_on_methods:_Chaining|Method Chaining]].}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Snippets Boilerplate}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas Snippets-raster image.png|thumb|Canvas snippet demonstrating how to load a raster image]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# path is relative to $FG_ROOT (base package)&lt;br /&gt;
var path = &amp;quot;Textures/Splash1.png&amp;quot;;&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(path)&lt;br /&gt;
    .setTranslation(100, 10)&lt;br /&gt;
    .setSize(130, 130);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images downloaded on demand ==&lt;br /&gt;
&lt;br /&gt;
The '''path''' could also just as well be a URL, i.e. a raster image retrieved via http - for example, the following snippet is entirely self-contained and can be pasted into the [[Nasal Console]] and directly executed &amp;quot;as is&amp;quot;: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-raster-images-via-url.png|thumb|screen shot demonstrating how Nasal and Canvas can be used to display raster images downloaded on demand]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400, 200], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# path now a URL&lt;br /&gt;
var url = &amp;quot;http://www.worldwidetelescope.org/docs/Images/MapOfEarth.jpg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(url) &lt;br /&gt;
    .setTranslation(45, 22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310, 155);     # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Clipping ==&lt;br /&gt;
{{Note|{{FGCquote&lt;br /&gt;
|1= Scaling or any other type of transformation or changing the coordinates of individual points is definitely more efficient than clipping (which requires to change the OpenGL clip planes for every rendered object with a different clipping rectangle).&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=277062#p277062&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;TheTom&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
}}&lt;br /&gt;
I would suggest to refer to api.nas and look for &amp;quot;clip&amp;quot; and/or &amp;quot;rect&amp;quot; - IIRC, you need to set up a clipping rectangle by setting some kind of &amp;quot;clip&amp;quot; property and setting it to a rect value in the form of rect(...)&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= For details, see the clipping example at: [[Canvas Nasal API#set 2]] and  [[Canvas Element#clip.28.29]]&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=276965#p276965&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:MapStructure_dialog_with_clipping_and_event_handling_applied.png|thumb]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;.........&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding OpenVG Paths ==&lt;br /&gt;
{{Main article|How to manipulate Canvas elements}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Openvg-via-canvas.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var graph = root.createChild(&amp;quot;group&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var x_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;x-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .lineTo(width - 10, height / 2)&lt;br /&gt;
    .setColor(1, 0, 0)&lt;br /&gt;
    .setStrokeLineWidth(3);&lt;br /&gt;
&lt;br /&gt;
var y_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;y-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, 10)&lt;br /&gt;
    .lineTo(10, height - 10)&lt;br /&gt;
    .setColor(0, 0, 1)&lt;br /&gt;
    .setStrokeLineWidth(2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-quadTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG quadTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .quadTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-cubicTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG cubicTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    120, height - 120,&lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .cubicTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Vector Images ==&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas SVG]]&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-svg-support.png|thumb|screen shot demonstrating how the scripted Nasal-based SVG parser can be used to dynamically turn SVG files into OpenVG instructions understood by Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# change the background color &lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var filename = &amp;quot;/Nasal/canvas/map/Images/boeingAirplane.svg&amp;quot;;&lt;br /&gt;
var svg_symbol = root.createChild('group');&lt;br /&gt;
canvas.parsesvg(svg_symbol, filename);&lt;br /&gt;
&lt;br /&gt;
svg_symbol.setTranslation(width / 2, height / 2);&lt;br /&gt;
&lt;br /&gt;
# svg_symbol.setScale(0.2);&lt;br /&gt;
# svg_symbol.setRotation(radians)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using the SymbolCache ==&lt;br /&gt;
Whenever a symbol may need to be shown/instanced multiple times (possibly using different styling), it makes sense to use caching - otherwise, identical symbols would be treated as separate OpenVG groups, all of which would need to be rasterized/rendedered separately (i.e. 100 identical symbols would be updated/rendered one by one). &lt;br /&gt;
&lt;br /&gt;
Typically, a map may display multiple instances of an otherwise identical symbol (think VOR, NDB, DME etc) - equally, a multiplayer map may showing multiple aircraft symbols at the same time. In these cases, it makes sense to use the SymbolCache framework, which will render symbols into a separate Canvas texture and provide a texture map that can be treated as a lookup map, which even supports styling for otherwise identical symbols. To learn more, please refer to [[Canvas MapStructure#The_SymbolCache|SymbolCache]]&lt;br /&gt;
... &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-SymbolCache-Instancing.png|thumb|Screen shot showing a Canvas based GUI dialog that is using the SymbolCache for instancing multiple symbols (including support for styling)]] &lt;br /&gt;
| &amp;lt;!--{{Caution|This is currently untested code}}--&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the function that draws a symbol using OpenVG paths&lt;br /&gt;
# it accepts a group to draw to and returns the rendered group &lt;br /&gt;
# to the caller&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5, 12.5)&lt;br /&gt;
        .lineTo(7.5, 12.5)&lt;br /&gt;
        .lineTo(15, 0)&lt;br /&gt;
        .lineTo(7.5, -12.5)&lt;br /&gt;
        .lineTo(-7.5, -12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width) # style-able&lt;br /&gt;
        .setColor(color); # style-able&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var style = { # styling related attributes (as per the draw* function above)&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    color: [1, 0, 0],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# create a new cache entry for the styled symbol&lt;br /&gt;
var myCachedSymbol = canvas.StyleableCacheable.new(&lt;br /&gt;
    name: 'myVOR',&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: canvas.SymbolCache32x32, # the cache to be used&lt;br /&gt;
    draw_mode: canvas.SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: ['line_width', 'color'], # styling related attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var target = root.createChild('group');&lt;br /&gt;
&lt;br /&gt;
var x = 0;&lt;br /&gt;
var y = height / 2;&lt;br /&gt;
&lt;br /&gt;
var xoffset = 50;&lt;br /&gt;
&lt;br /&gt;
# render 5 instanced symbols using the style specified above&lt;br /&gt;
for (var i = 0; i &amp;lt; 5; i += 1) {&lt;br /&gt;
    # look up the raster image for the symbol&lt;br /&gt;
    # render it using the passed style and adjust scaling&lt;br /&gt;
    var instanced = myCachedSymbol.render(target, style)&lt;br /&gt;
        .setScale(style.scale_factor)&lt;br /&gt;
        .setTranslation(x += xoffset, y);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The example shown above uses a fixed symbol/icon cache that is set up while booting Flightgear - sometimes, we may need cache for different purposes. So, let's assume, we need a new/custom cache with a different resolution for each entry in the cache (e.g. 256x256), we can easily accomplish that by setting up a new cache like this: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var mySymbolCache256x256 = canvas.SymbolCache.new(1024, 256);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adding Text Elements ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:CanvasText-Hello-World.png|thumb|Screen shot showing the CanvasText example contributed by Necolatis]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myText = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello world!&amp;quot;)&lt;br /&gt;
    .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Labels ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-Layout-Label-example-by-Necolatis.png|thumb|Canvas demo: Layouts and Labels (by Necolatis)]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label.setText(&amp;quot;Hello World!&amp;quot;);&lt;br /&gt;
myLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
var label2 = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label2.setText(&amp;quot;Hello FlightGear&amp;quot;);&lt;br /&gt;
myLayout.addItem(label2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Buttons (Layouts)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-demo-layouts-and-buttons-by-Necolatis.png|thumb|Canvas snippet: buttons and layouts (by Necolatis)]] &lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
{{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
# click button&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
	.setText(&amp;quot;Click on me&amp;quot;)&lt;br /&gt;
	.setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;clicked&amp;quot;, func {&lt;br /&gt;
    # add code here to react on click on button.&lt;br /&gt;
    print(&amp;quot;Button clicked !&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-toggle-button-snippet-by-Necolatis.png|thumb|Canvas toggle button demo by Necolatis]]&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;Toggle me&amp;quot;)&lt;br /&gt;
    .setCheckable(1) # this indicates that is should be a toggle button&lt;br /&gt;
    .setChecked(0) # depressed by default&lt;br /&gt;
    .setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;toggled&amp;quot;, func (e) {&lt;br /&gt;
    if (e.detail.checked) {&lt;br /&gt;
        # add code here to react on button being depressed.&lt;br /&gt;
    } else {&lt;br /&gt;
        # add code here to react on button not being depressed.&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas Input Dialog ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Snippets-canvas-input-dialog.png|thumb|Canvas input dialog]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new InputDialog with a title, label, and a callback&lt;br /&gt;
canvas.InputDialog.getText(&amp;quot;Input Dialog Title&amp;quot;, &amp;quot;Please enter some text&amp;quot;, func(btn, value) {&lt;br /&gt;
    if (value) {&lt;br /&gt;
        gui.popupTip(&amp;quot;You entered: &amp;quot; ~ value);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas ScrollArea ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas-snippets-scrollArea-demo.png|thumb|Screen shot showing a Canvas ScrollArea populated with different splash screens, loaded from $FG_ROOT/Textures]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, { size: [96, 128] }).move(20, 100);&lt;br /&gt;
vbox.addItem(scroll, 1);&lt;br /&gt;
&lt;br /&gt;
var scrollContent = scroll.getContent()&lt;br /&gt;
    .set(&amp;quot;font&amp;quot;, &amp;quot;LiberationFonts/LiberationSans-Bold.ttf&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;character-size&amp;quot;, 16)&lt;br /&gt;
    .set(&amp;quot;alignment&amp;quot;, &amp;quot;left-center&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var list = canvas.VBoxLayout.new();&lt;br /&gt;
scroll.setLayout(list);&lt;br /&gt;
&lt;br /&gt;
for (var i = 1; i &amp;lt;= 5; i += 1) {&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(scrollContent, canvas.style, { wordWrap: 0 });&lt;br /&gt;
    label.setImage(&amp;quot;Textures/Splash&amp;quot; ~ i ~ &amp;quot;.png&amp;quot;);&lt;br /&gt;
    label.setFixedSize(256, 256);&lt;br /&gt;
    list.addItem(label);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using TabWidgets ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Screenshot&lt;br /&gt;
!Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas TabWidget example.png|alt=Canvas TabWIdget example|thumb|Canvas TabWidget example showing the three default splash screens]]&lt;br /&gt;
|&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
var window = canvas.Window.new([300, 300], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {});&lt;br /&gt;
var tabsContent = tabs.getContent();&lt;br /&gt;
&lt;br /&gt;
var createTabContent = func(imgPath, text) {&lt;br /&gt;
    var image = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setImage(imgPath)&lt;br /&gt;
        .setFixedSize(128, 128);&lt;br /&gt;
&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setText(text);&lt;br /&gt;
&lt;br /&gt;
    var tabLayout = canvas.VBoxLayout.new();&lt;br /&gt;
    tabLayout.addItem(image);&lt;br /&gt;
    tabLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
    return tabLayout;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
tabs.addTab(&amp;quot;tab1&amp;quot;, &amp;quot;Texture 1&amp;quot;, createTabContent(&amp;quot;Textures/Splash1.png&amp;quot;, &amp;quot;Texture 1&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab2&amp;quot;, &amp;quot;Texture 2&amp;quot;, createTabContent(&amp;quot;Textures/Splash2.png&amp;quot;, &amp;quot;Texture 2&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab3&amp;quot;, &amp;quot;Texture 3&amp;quot;, createTabContent(&amp;quot;Textures/Splash3.png&amp;quot;, &amp;quot;Texture 3&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
vbox.addItem(tabs);&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using Layouts ==&lt;br /&gt;
&lt;br /&gt;
== Using Styling ==&lt;br /&gt;
== Adding a HUD ==&lt;br /&gt;
== Adding a 2D Instrument ==&lt;br /&gt;
== Adding a 2D Panel ==&lt;br /&gt;
== Adding a PFD ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Failure Mgmt Widget ==&lt;br /&gt;
{{Note|This kind of widget will typically be useful for dialogs requiring a method for managing aircraft specific system failures (e.g. an instructor console).}}&lt;br /&gt;
&lt;br /&gt;
== Adding a MapStructure map to a Canvas  ==&lt;br /&gt;
{{Main article|Canvas MapStructure Layers}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-mapstructure-dialog.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25);&lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var r = func(name, vis = 1, zindex = nil) return caller(0)[0];&lt;br /&gt;
&lt;br /&gt;
# APT and VOR are the layer names&lt;br /&gt;
foreach (var type; [r('APT'), r('VOR')]) {&lt;br /&gt;
    TestMap.addLayer(&lt;br /&gt;
        factory: canvas.SymbolLayer,&lt;br /&gt;
        type_arg: type.name, &lt;br /&gt;
        visible: type.vis,&lt;br /&gt;
        priority: type.zindex,&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Customizing MapStructure Styling  ==&lt;br /&gt;
{{See also|Canvas MapStructure#Styling}}&lt;br /&gt;
&lt;br /&gt;
In general MapStructure symbols contain their own styles using hard-coded defaults, however these can be overridden by providing a hash with keys (fields) to customize these hard-coded defaults. &lt;br /&gt;
&lt;br /&gt;
This means that anything that may be specific to a single style (colors, fonts, images etc) should be encoded in the form of variables that are looked up using the styles hash - this provides a great deal of freedom to customize an existing symbol. In addition, MapStructure layers can be set up to even customize/override the default drawing routines, at which point you are free to do whatever you want basically, because the existing draw routine in .symbol file is ignored &lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Layers style change.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog. Active VOR and radial in red color, inactive VOR in green color]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
var r = func(name, vis = 1, zindex = nil) return caller(0)[0];&lt;br /&gt;
var type = r('APT');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in APT.symbol&lt;br /&gt;
var style_apt = {&lt;br /&gt;
    scale_factor: 0.5,             # 50 %&lt;br /&gt;
    color_default: [0, 1, 0.9],    # rgb&lt;br /&gt;
    line_width: 4,                 # thickness&lt;br /&gt;
    label_font_color: [0, 1, 0.9], # rgb&lt;br /&gt;
    label_font_size: 30,           # font size&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
TestMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_apt,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var type = r('VOR');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in VOR.symbol&lt;br /&gt;
var style_vor = {&lt;br /&gt;
    scale_factor: 0.6,&lt;br /&gt;
    active_color: [1, 0, 0],&lt;br /&gt;
    inactive_color: [0, 1, 0],&lt;br /&gt;
    line_width: 4,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
TestMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_vor,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
### See $FG_ROOT/Nasal/canvas/map/APT and $FG_ROOT/Nasal/canvas/map/VOR.symbol for the style variables that can be configured.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using MapStructure and Overlays ==&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Support for overlays using geo-referenced raster images isn't too well-developed currently, so would need to work with a few hard-coded assumptions (e.g. being specific to a certain raster image and map layout) - but maybe TheTom can provide a few more ideas on how to proceed from here - otherwise, the Map is normally not aware of any non-Map items, i.e. as long as the overlay is added &amp;quot;outside&amp;quot; the Map, it isn't even aware of the raster image - and when it is added as a map element, it would probably be rotated. So there's still some work needed here. But generally, this should work well enough even in its current form (the screen shot is purely based on the Canvas Snippets article, i.e. it just displays a Canvas GUI dialog, adds the downloaded raster image and then adds the MapStructure APS layer - without the Map being aware of the overlay/geo-referencing that is needed currently).&lt;br /&gt;
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=238316#p238316&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Using Canvas for visualizing orbital flights (cont'd PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri Apr 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-overlay-with-mapstructure.png|thumb|Using the [[MapStructure]] framework in conjunction with raster images as overlays]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== A simple tile map ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas - Tile map demo.png|thumb|A simple, canvas based tile map which is centered around the aircraft.]]&lt;br /&gt;
| &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (768, 512);&lt;br /&gt;
var tile_size = 256;&lt;br /&gt;
&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;).set('title', &amp;quot;Tile map demo&amp;quot;);&lt;br /&gt;
var group = window.getCanvas(1).createGroup();&lt;br /&gt;
&lt;br /&gt;
# Simple user interface (Buttons for zoom and label for displaying it)&lt;br /&gt;
var zoom = 10;&lt;br /&gt;
var type = &amp;quot;intl&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
var ui_root = window.getCanvas().createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
window.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var button_in = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;+&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(1));&lt;br /&gt;
&lt;br /&gt;
var button_out = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;-&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(-1));&lt;br /&gt;
&lt;br /&gt;
button_in.setSizeHint([32, 32]);&lt;br /&gt;
button_out.setSizeHint([32, 32]);&lt;br /&gt;
&lt;br /&gt;
var label_zoom = canvas.gui.widgets.Label.new(ui_root, canvas.style, {});&lt;br /&gt;
&lt;br /&gt;
var button_box = canvas.HBoxLayout.new();&lt;br /&gt;
button_box.addItem(button_in);&lt;br /&gt;
button_box.addItem(label_zoom);&lt;br /&gt;
button_box.addItem(button_out);&lt;br /&gt;
button_box.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
vbox.addItem(button_box);&lt;br /&gt;
vbox.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
var changeZoom = func(d) {&lt;br /&gt;
    zoom = math.max(2, math.min(19, zoom + d));&lt;br /&gt;
    label_zoom.setText(&amp;quot;Zoom &amp;quot; ~ zoom);&lt;br /&gt;
    updateTiles();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# http://polymaps.org/docs/&lt;br /&gt;
# https://github.com/simplegeo/polymaps&lt;br /&gt;
# https://github.com/Leaflet/Leaflet&lt;br /&gt;
&lt;br /&gt;
var maps_base = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/cache/maps';&lt;br /&gt;
&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/map&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/sat&lt;br /&gt;
# (also see http://wiki.openstreetmap.org/wiki/Tile_usage_policy)&lt;br /&gt;
var makeUrl = string.compileTemplate('https://maps.wikimedia.org/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
# https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png&lt;br /&gt;
var makePath = string.compileTemplate(maps_base ~ '/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
var num_tiles = [4, 3];&lt;br /&gt;
&lt;br /&gt;
var center_tile_offset = [&lt;br /&gt;
    (num_tiles[0] - 1) / 2,&lt;br /&gt;
    (num_tiles[1] - 1) / 2,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
# simple aircraft icon at current position/center of the map&lt;br /&gt;
group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
    .moveTo(&lt;br /&gt;
        tile_size * center_tile_offset[0] - 10,&lt;br /&gt;
        tile_size * center_tile_offset[1],&lt;br /&gt;
    )&lt;br /&gt;
    .horiz(20)&lt;br /&gt;
    .move(-10, -10)&lt;br /&gt;
    .vert(20)&lt;br /&gt;
    .set(&amp;quot;stroke&amp;quot;, &amp;quot;red&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;stroke-width&amp;quot;, 2)&lt;br /&gt;
    .set(&amp;quot;z-index&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# initialize the map by setting up&lt;br /&gt;
# a grid of raster images  &lt;br /&gt;
&lt;br /&gt;
var tiles = setsize([], num_tiles[0]);&lt;br /&gt;
for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
    tiles[x] = setsize([], num_tiles[1]);&lt;br /&gt;
    for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
        tiles[x][y] = group.createChild(&amp;quot;image&amp;quot;, &amp;quot;map-tile&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var last_tile = [-1, -1];&lt;br /&gt;
var last_type = type;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the callback that will be regularly called by the timer&lt;br /&gt;
# to update the map&lt;br /&gt;
var updateTiles = func {&lt;br /&gt;
    # get current position&lt;br /&gt;
    var lat = getprop('/position/latitude-deg');&lt;br /&gt;
    var lon = getprop('/position/longitude-deg');&lt;br /&gt;
&lt;br /&gt;
    var n = math.pow(2, zoom);&lt;br /&gt;
&lt;br /&gt;
    var latRad = lat * D2R;&lt;br /&gt;
    var mercatorY = math.ln(math.tan(latRad) + 1 / math.cos(latRad));&lt;br /&gt;
    var offset = [&lt;br /&gt;
        n * ((lon + 180) / 360) - center_tile_offset[0],&lt;br /&gt;
        (1 - mercatorY / math.pi) / 2 * n - center_tile_offset[1],&lt;br /&gt;
    ];&lt;br /&gt;
    var tile_index = [&lt;br /&gt;
        int(offset[0]),&lt;br /&gt;
        int(offset[1]),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var ox = tile_index[0] - offset[0];&lt;br /&gt;
    var oy = tile_index[1] - offset[1];&lt;br /&gt;
&lt;br /&gt;
    for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
        for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
            tiles[x][y].setTranslation(&lt;br /&gt;
                int((ox + x) * tile_size + 0.5),&lt;br /&gt;
                int((oy + y) * tile_size + 0.5),&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tile_index[0] != last_tile[0]&lt;br /&gt;
        or tile_index[1] != last_tile[1]&lt;br /&gt;
        or type != last_type&lt;br /&gt;
    ) {&lt;br /&gt;
        for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
            for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
                var pos = {&lt;br /&gt;
                    z: zoom,&lt;br /&gt;
                    x: int(offset[0] + x),&lt;br /&gt;
                    y: int(offset[1] + y),&lt;br /&gt;
                    type: type,&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                (func {&lt;br /&gt;
                    var img_path = makePath(pos);&lt;br /&gt;
                    var tile = tiles[x][y];&lt;br /&gt;
&lt;br /&gt;
                    if (io.stat(img_path) == nil) { &lt;br /&gt;
                        # image not found, save in $FG_HOME&lt;br /&gt;
                        var img_url = makeUrl(pos);&lt;br /&gt;
                        print('requesting ' ~ img_url);&lt;br /&gt;
                        http.save(img_url, img_path)&lt;br /&gt;
                            .done(func {&lt;br /&gt;
                                print('received image ' ~ img_path);&lt;br /&gt;
                                tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                             })&lt;br /&gt;
                            .fail(func(r) {&lt;br /&gt;
                                print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason);&lt;br /&gt;
                            });&lt;br /&gt;
                    } else {&lt;br /&gt;
                        # cached image found, reusing&lt;br /&gt;
                        print('loading ' ~ img_path);&lt;br /&gt;
                        tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                    }&lt;br /&gt;
                })();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        last_tile = tile_index;&lt;br /&gt;
        last_type = type;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up a timer that will invoke updateTiles at 2-second intervals&lt;br /&gt;
var update_timer = maketimer(2, updateTiles);&lt;br /&gt;
# actually start the timer&lt;br /&gt;
update_timer.start();&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up default zoom level&lt;br /&gt;
changeZoom(0);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###&lt;br /&gt;
# The following lines were recently added and have not yet been tested&lt;br /&gt;
# (if in doubt, remove them)&lt;br /&gt;
window.del = func {&lt;br /&gt;
    print(&amp;quot;Cleaning up window:&amp;quot;, ,&amp;quot;\n&amp;quot;);&lt;br /&gt;
    update_timer.stop();&lt;br /&gt;
    # explanation for the call() technique at:&lt;br /&gt;
    # http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls&lt;br /&gt;
    call(canvas.Window.del, [], me);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding a NavDisplay ==&lt;br /&gt;
&lt;br /&gt;
== Adding a CDU ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Multi-Function Display (MFD) ==&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143068</id>
		<title>Canvas snippets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=143068"/>
		<updated>2025-11-26T01:18:49Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* A simple tile map */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{-}}&lt;br /&gt;
This article is meant to be a collection of self-contained code snippets for doing Nasal/Canvas coding without having to spend too much time working through APIs, documentation and existing code - the idea is to provide snippets that contain comments and explicit assumptions, so that these snippets can be easily adapted by people without necessarily having to be very familiar with Nasal/Canvas coding. &lt;br /&gt;
&lt;br /&gt;
In the long term, we're hoping to grow a library of useful code snippets to cover most use-cases, while also covering more complex scenarios, e.g. using Canvas-specific helper frameworks like [[Canvas MapStructure]], the [[NavDisplay]] or the recent CDU work.&lt;br /&gt;
&lt;br /&gt;
Canvas itself is designed to be front-end agnostic, meaning that it doesn't matter where a Canvas is used/displayed - this is accomplished by so called &amp;quot;placements&amp;quot;, which determine where a Canvas texture is shown. Thus, the back-end logic can remain the same usually, so that GUI dialogs may show avionics, but also so that avionics may show GUI widgets, which also applies to HUDs, liveries and even placements within the FlightGear scenery.&lt;br /&gt;
&lt;br /&gt;
We encourage people to use the snippets listed below to get started with Nasal/Canvas coding, but also to provide feedback on extending/improving this article to make it even more accessible and useful. Equally, if you're aware of any Canvas-related efforts that may contain useful code snippets, please do feel free to add those snippets here. Even people who don't have any interest in coding itself are encouraged to get involved by helping test the snippets listed below, for which you only need to the [[Nasal Console]], and while you're at it, please also help provide/update screen shots for each code snippet.&lt;br /&gt;
&lt;br /&gt;
Contributions added to this article should ideally satisfy some requirements:&lt;br /&gt;
* be entirely self-contained&lt;br /&gt;
* contain good/clear comments&lt;br /&gt;
* have pointers/references to related code/use-cases&lt;br /&gt;
* make assumptions explicit&lt;br /&gt;
* contain screen shots for each example&lt;br /&gt;
* avoid overloaded symbol names to ensure that examples can be merged and adapted easily&lt;br /&gt;
* dependencies (svg files, images/textures etc) should always be chosen such that snippets always work using $FG_ROOT&lt;br /&gt;
* hard-coded assumptions (e.g. texture dimensions in terms of width/height etc) should be encapsulated using variables&lt;br /&gt;
 &lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
== Creating a standalone Canvas ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (512, 512);&lt;br /&gt;
# Create a standalone Canvas (not attached to any GUI dialog/aircraft etc) &lt;br /&gt;
var myCanvas = canvas.new({&lt;br /&gt;
    &amp;quot;name&amp;quot;: &amp;quot;Livery Test&amp;quot;,   # The name is optional but allow for easier identification&lt;br /&gt;
    &amp;quot;size&amp;quot;: [width, height], # Size of the underlying texture (should be a power of 2, required) [Resolution]&lt;br /&gt;
    &amp;quot;view&amp;quot;: [width, height], # Virtual resolution (Defines the coordinate system of the canvas [Dimensions]&lt;br /&gt;
                             # which will be stretched the size of the texture, required)&lt;br /&gt;
    &amp;quot;mipmapping&amp;quot;: 1,         # Enable mipmapping (optional)&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# set background color&lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# OPTIONAL: Create a Canvas dialog window to hold the canvas and show that it's working&lt;br /&gt;
# the Canvas is now standalone, i.e. continues to live once the dialog is closed!&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(myCanvas);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Accessing the Canvas Desktop ==&lt;br /&gt;
Sometimes you may want to render to the main screen, without creating a separate Canvas window - this can be accomplished by using the Canvas desktop, note that the following example is fully self-contained, i.e. does not require any code to be added to work:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myNode = canvas.getDesktop().createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello Canvas Desktop&amp;quot;)&lt;br /&gt;
    .setFontSize(25, 1.0)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Creating Tooltips ==&lt;br /&gt;
== Creating Popups ==&lt;br /&gt;
== Creating a Canvas GUI Window ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-dialog.png|left|thumb|This is what the [[Nasal]]/[[Canvas]] snippet will look like once you pasted it into the [[Nasal Console]] and click &amp;quot;Execute&amp;quot;.]] || {{Note|This example uses so called method chaining, if you're not familiar with the concept, please see: [[Object_Oriented_Programming_with_Nasal#More_on_methods:_Chaining|Method Chaining]].}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Snippets Boilerplate}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas Snippets-raster image.png|thumb|Canvas snippet demonstrating how to load a raster image]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# path is relative to $FG_ROOT (base package)&lt;br /&gt;
var path = &amp;quot;Textures/Splash1.png&amp;quot;;&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(path)&lt;br /&gt;
    .setTranslation(100, 10)&lt;br /&gt;
    .setSize(130, 130);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images downloaded on demand ==&lt;br /&gt;
&lt;br /&gt;
The '''path''' could also just as well be a URL, i.e. a raster image retrieved via http - for example, the following snippet is entirely self-contained and can be pasted into the [[Nasal Console]] and directly executed &amp;quot;as is&amp;quot;: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-raster-images-via-url.png|thumb|screen shot demonstrating how Nasal and Canvas can be used to display raster images downloaded on demand]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400, 200], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# path now a URL&lt;br /&gt;
var url = &amp;quot;http://www.worldwidetelescope.org/docs/Images/MapOfEarth.jpg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(url) &lt;br /&gt;
    .setTranslation(45, 22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310, 155);     # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Clipping ==&lt;br /&gt;
{{Note|{{FGCquote&lt;br /&gt;
|1= Scaling or any other type of transformation or changing the coordinates of individual points is definitely more efficient than clipping (which requires to change the OpenGL clip planes for every rendered object with a different clipping rectangle).&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=277062#p277062&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;TheTom&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
}}&lt;br /&gt;
I would suggest to refer to api.nas and look for &amp;quot;clip&amp;quot; and/or &amp;quot;rect&amp;quot; - IIRC, you need to set up a clipping rectangle by setting some kind of &amp;quot;clip&amp;quot; property and setting it to a rect value in the form of rect(...)&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= For details, see the clipping example at: [[Canvas Nasal API#set 2]] and  [[Canvas Element#clip.28.29]]&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=276965#p276965&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:MapStructure_dialog_with_clipping_and_event_handling_applied.png|thumb]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;.........&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding OpenVG Paths ==&lt;br /&gt;
{{Main article|How to manipulate Canvas elements}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Openvg-via-canvas.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var graph = root.createChild(&amp;quot;group&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var x_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;x-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .lineTo(width - 10, height / 2)&lt;br /&gt;
    .setColor(1, 0, 0)&lt;br /&gt;
    .setStrokeLineWidth(3);&lt;br /&gt;
&lt;br /&gt;
var y_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;y-axis&amp;quot;)&lt;br /&gt;
    .moveTo(10, 10)&lt;br /&gt;
    .lineTo(10, height - 10)&lt;br /&gt;
    .setColor(0, 0, 1)&lt;br /&gt;
    .setStrokeLineWidth(2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-quadTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG quadTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .quadTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-cubicTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG cubicTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [&lt;br /&gt;
    60,  height - 20, &lt;br /&gt;
    120, height - 120,&lt;br /&gt;
    230, height - 100,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
    .moveTo(10, height / 2)&lt;br /&gt;
    .cubicTo(points)&lt;br /&gt;
    .setColor(0, 1, 0)&lt;br /&gt;
    .setStrokeLineWidth(4);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Vector Images ==&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas SVG]]&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-svg-support.png|thumb|screen shot demonstrating how the scripted Nasal-based SVG parser can be used to dynamically turn SVG files into OpenVG instructions understood by Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# change the background color &lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var filename = &amp;quot;/Nasal/canvas/map/Images/boeingAirplane.svg&amp;quot;;&lt;br /&gt;
var svg_symbol = root.createChild('group');&lt;br /&gt;
canvas.parsesvg(svg_symbol, filename);&lt;br /&gt;
&lt;br /&gt;
svg_symbol.setTranslation(width / 2, height / 2);&lt;br /&gt;
&lt;br /&gt;
# svg_symbol.setScale(0.2);&lt;br /&gt;
# svg_symbol.setRotation(radians)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using the SymbolCache ==&lt;br /&gt;
Whenever a symbol may need to be shown/instanced multiple times (possibly using different styling), it makes sense to use caching - otherwise, identical symbols would be treated as separate OpenVG groups, all of which would need to be rasterized/rendedered separately (i.e. 100 identical symbols would be updated/rendered one by one). &lt;br /&gt;
&lt;br /&gt;
Typically, a map may display multiple instances of an otherwise identical symbol (think VOR, NDB, DME etc) - equally, a multiplayer map may showing multiple aircraft symbols at the same time. In these cases, it makes sense to use the SymbolCache framework, which will render symbols into a separate Canvas texture and provide a texture map that can be treated as a lookup map, which even supports styling for otherwise identical symbols. To learn more, please refer to [[Canvas MapStructure#The_SymbolCache|SymbolCache]]&lt;br /&gt;
... &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-SymbolCache-Instancing.png|thumb|Screen shot showing a Canvas based GUI dialog that is using the SymbolCache for instancing multiple symbols (including support for styling)]] &lt;br /&gt;
| &amp;lt;!--{{Caution|This is currently untested code}}--&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the function that draws a symbol using OpenVG paths&lt;br /&gt;
# it accepts a group to draw to and returns the rendered group &lt;br /&gt;
# to the caller&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5, 12.5)&lt;br /&gt;
        .lineTo(7.5, 12.5)&lt;br /&gt;
        .lineTo(15, 0)&lt;br /&gt;
        .lineTo(7.5, -12.5)&lt;br /&gt;
        .lineTo(-7.5, -12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width) # style-able&lt;br /&gt;
        .setColor(color); # style-able&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var style = { # styling related attributes (as per the draw* function above)&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    color: [1, 0, 0],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# create a new cache entry for the styled symbol&lt;br /&gt;
var myCachedSymbol = canvas.StyleableCacheable.new(&lt;br /&gt;
    name: 'myVOR',&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: canvas.SymbolCache32x32, # the cache to be used&lt;br /&gt;
    draw_mode: canvas.SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: ['line_width', 'color'], # styling related attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var target = root.createChild('group');&lt;br /&gt;
&lt;br /&gt;
var x = 0;&lt;br /&gt;
var y = height / 2;&lt;br /&gt;
&lt;br /&gt;
var xoffset = 50;&lt;br /&gt;
&lt;br /&gt;
# render 5 instanced symbols using the style specified above&lt;br /&gt;
for (var i = 0; i &amp;lt; 5; i += 1) {&lt;br /&gt;
    # look up the raster image for the symbol&lt;br /&gt;
    # render it using the passed style and adjust scaling&lt;br /&gt;
    var instanced = myCachedSymbol.render(target, style)&lt;br /&gt;
        .setScale(style.scale_factor)&lt;br /&gt;
        .setTranslation(x += xoffset, y);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The example shown above uses a fixed symbol/icon cache that is set up while booting Flightgear - sometimes, we may need cache for different purposes. So, let's assume, we need a new/custom cache with a different resolution for each entry in the cache (e.g. 256x256), we can easily accomplish that by setting up a new cache like this: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var mySymbolCache256x256 = canvas.SymbolCache.new(1024, 256);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adding Text Elements ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:CanvasText-Hello-World.png|thumb|Screen shot showing the CanvasText example contributed by Necolatis]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myText = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(&amp;quot;Hello world!&amp;quot;)&lt;br /&gt;
    .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio&lt;br /&gt;
    .setColor(1, 0, 0, 1)          # red, fully opaque&lt;br /&gt;
    .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
    .setTranslation(160, 80);      # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Labels ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-Layout-Label-example-by-Necolatis.png|thumb|Canvas demo: Layouts and Labels (by Necolatis)]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label.setText(&amp;quot;Hello World!&amp;quot;);&lt;br /&gt;
myLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
var label2 = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label2.setText(&amp;quot;Hello FlightGear&amp;quot;);&lt;br /&gt;
myLayout.addItem(label2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Buttons (Layouts)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-demo-layouts-and-buttons-by-Necolatis.png|thumb|Canvas snippet: buttons and layouts (by Necolatis)]] &lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
{{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
# click button&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
	.setText(&amp;quot;Click on me&amp;quot;)&lt;br /&gt;
	.setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;clicked&amp;quot;, func {&lt;br /&gt;
    # add code here to react on click on button.&lt;br /&gt;
    print(&amp;quot;Button clicked !&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-toggle-button-snippet-by-Necolatis.png|thumb|Canvas toggle button demo by Necolatis]]&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;Toggle me&amp;quot;)&lt;br /&gt;
    .setCheckable(1) # this indicates that is should be a toggle button&lt;br /&gt;
    .setChecked(0) # depressed by default&lt;br /&gt;
    .setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;toggled&amp;quot;, func (e) {&lt;br /&gt;
    if (e.detail.checked) {&lt;br /&gt;
        # add code here to react on button being depressed.&lt;br /&gt;
    } else {&lt;br /&gt;
        # add code here to react on button not being depressed.&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas Input Dialog ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Snippets-canvas-input-dialog.png|thumb|Canvas input dialog]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new InputDialog with a title, label, and a callback&lt;br /&gt;
canvas.InputDialog.getText(&amp;quot;Input Dialog Title&amp;quot;, &amp;quot;Please enter some text&amp;quot;, func(btn, value) {&lt;br /&gt;
    if (value) {&lt;br /&gt;
        gui.popupTip(&amp;quot;You entered: &amp;quot; ~ value);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas ScrollArea ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas-snippets-scrollArea-demo.png|thumb|Screen shot showing a Canvas ScrollArea populated with different splash screens, loaded from $FG_ROOT/Textures]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, { size: [96, 128] }).move(20, 100);&lt;br /&gt;
vbox.addItem(scroll, 1);&lt;br /&gt;
&lt;br /&gt;
var scrollContent = scroll.getContent()&lt;br /&gt;
    .set(&amp;quot;font&amp;quot;, &amp;quot;LiberationFonts/LiberationSans-Bold.ttf&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;character-size&amp;quot;, 16)&lt;br /&gt;
    .set(&amp;quot;alignment&amp;quot;, &amp;quot;left-center&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var list = canvas.VBoxLayout.new();&lt;br /&gt;
scroll.setLayout(list);&lt;br /&gt;
&lt;br /&gt;
for (var i = 1; i &amp;lt;= 5; i += 1) {&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(scrollContent, canvas.style, { wordWrap: 0 });&lt;br /&gt;
    label.setImage(&amp;quot;Textures/Splash&amp;quot; ~ i ~ &amp;quot;.png&amp;quot;);&lt;br /&gt;
    label.setFixedSize(256, 256);&lt;br /&gt;
    list.addItem(label);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using TabWidgets ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Screenshot&lt;br /&gt;
!Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas TabWidget example.png|alt=Canvas TabWIdget example|thumb|Canvas TabWidget example showing the three default splash screens]]&lt;br /&gt;
|&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
var window = canvas.Window.new([300, 300], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {});&lt;br /&gt;
var tabsContent = tabs.getContent();&lt;br /&gt;
&lt;br /&gt;
var createTabContent = func(imgPath, text) {&lt;br /&gt;
    var image = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setImage(imgPath)&lt;br /&gt;
        .setFixedSize(128, 128);&lt;br /&gt;
&lt;br /&gt;
    var label = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
        .setText(text);&lt;br /&gt;
&lt;br /&gt;
    var tabLayout = canvas.VBoxLayout.new();&lt;br /&gt;
    tabLayout.addItem(image);&lt;br /&gt;
    tabLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
    return tabLayout;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
tabs.addTab(&amp;quot;tab1&amp;quot;, &amp;quot;Texture 1&amp;quot;, createTabContent(&amp;quot;Textures/Splash1.png&amp;quot;, &amp;quot;Texture 1&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab2&amp;quot;, &amp;quot;Texture 2&amp;quot;, createTabContent(&amp;quot;Textures/Splash2.png&amp;quot;, &amp;quot;Texture 2&amp;quot;));&lt;br /&gt;
tabs.addTab(&amp;quot;tab3&amp;quot;, &amp;quot;Texture 3&amp;quot;, createTabContent(&amp;quot;Textures/Splash3.png&amp;quot;, &amp;quot;Texture 3&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
vbox.addItem(tabs);&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using Layouts ==&lt;br /&gt;
&lt;br /&gt;
== Using Styling ==&lt;br /&gt;
== Adding a HUD ==&lt;br /&gt;
== Adding a 2D Instrument ==&lt;br /&gt;
== Adding a 2D Panel ==&lt;br /&gt;
== Adding a PFD ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Failure Mgmt Widget ==&lt;br /&gt;
{{Note|This kind of widget will typically be useful for dialogs requiring a method for managing aircraft specific system failures (e.g. an instructor console).}}&lt;br /&gt;
&lt;br /&gt;
== Adding a MapStructure map to a Canvas  ==&lt;br /&gt;
{{Main article|Canvas MapStructure Layers}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-mapstructure-dialog.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25);&lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var r = func(name, vis = 1, zindex = nil) return caller(0)[0];&lt;br /&gt;
&lt;br /&gt;
# APT and VOR are the layer names&lt;br /&gt;
foreach (var type; [r('APT'), r('VOR')]) {&lt;br /&gt;
    TestMap.addLayer(&lt;br /&gt;
        factory: canvas.SymbolLayer,&lt;br /&gt;
        type_arg: type.name, &lt;br /&gt;
        visible: type.vis,&lt;br /&gt;
        priority: type.zindex,&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Customizing MapStructure Styling  ==&lt;br /&gt;
{{See also|Canvas MapStructure#Styling}}&lt;br /&gt;
&lt;br /&gt;
In general MapStructure symbols contain their own styles using hard-coded defaults, however these can be overridden by providing a hash with keys (fields) to customize these hard-coded defaults. &lt;br /&gt;
&lt;br /&gt;
This means that anything that may be specific to a single style (colors, fonts, images etc) should be encoded in the form of variables that are looked up using the styles hash - this provides a great deal of freedom to customize an existing symbol. In addition, MapStructure layers can be set up to even customize/override the default drawing routines, at which point you are free to do whatever you want basically, because the existing draw routine in .symbol file is ignored &lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Layers style change.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog. Active VOR and radial in red color, inactive VOR in green color]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(&lt;br /&gt;
    myCanvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    myCanvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
var r = func(name, vis = 1, zindex = nil) return caller(0)[0];&lt;br /&gt;
var type = r('APT');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in APT.symbol&lt;br /&gt;
var style_apt = {&lt;br /&gt;
    scale_factor: 0.5,             # 50 %&lt;br /&gt;
    color_default: [0, 1, 0.9],    # rgb&lt;br /&gt;
    line_width: 4,                 # thickness&lt;br /&gt;
    label_font_color: [0, 1, 0.9], # rgb&lt;br /&gt;
    label_font_size: 30,           # font size&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
TestMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_apt,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var type = r('VOR');&lt;br /&gt;
&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in VOR.symbol&lt;br /&gt;
var style_vor = {&lt;br /&gt;
    scale_factor: 0.6,&lt;br /&gt;
    active_color: [1, 0, 0],&lt;br /&gt;
    inactive_color: [0, 1, 0],&lt;br /&gt;
    line_width: 4,&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
TestMap.addLayer(&lt;br /&gt;
    factory: canvas.SymbolLayer,&lt;br /&gt;
    type_arg: type.name,&lt;br /&gt;
    visible: type.vis,&lt;br /&gt;
    priority: type.zindex,&lt;br /&gt;
    style: style_vor,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
### See $FG_ROOT/Nasal/canvas/map/APT and $FG_ROOT/Nasal/canvas/map/VOR.symbol for the style variables that can be configured.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using MapStructure and Overlays ==&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Support for overlays using geo-referenced raster images isn't too well-developed currently, so would need to work with a few hard-coded assumptions (e.g. being specific to a certain raster image and map layout) - but maybe TheTom can provide a few more ideas on how to proceed from here - otherwise, the Map is normally not aware of any non-Map items, i.e. as long as the overlay is added &amp;quot;outside&amp;quot; the Map, it isn't even aware of the raster image - and when it is added as a map element, it would probably be rotated. So there's still some work needed here. But generally, this should work well enough even in its current form (the screen shot is purely based on the Canvas Snippets article, i.e. it just displays a Canvas GUI dialog, adds the downloaded raster image and then adds the MapStructure APS layer - without the Map being aware of the overlay/geo-referencing that is needed currently).&lt;br /&gt;
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=238316#p238316&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Using Canvas for visualizing orbital flights (cont'd PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri Apr 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-overlay-with-mapstructure.png|thumb|Using the [[MapStructure]] framework in conjunction with raster images as overlays]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== A simple tile map ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas - Tile map demo.png|thumb|A simple, canvas based tile map which is centered around the aircraft.]]&lt;br /&gt;
| &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (768, 512);&lt;br /&gt;
var tile_size = 256;&lt;br /&gt;
&lt;br /&gt;
var window = canvas.Window.new([width, height], &amp;quot;dialog&amp;quot;).set('title', &amp;quot;Tile map demo&amp;quot;);&lt;br /&gt;
var group = window.getCanvas(1).createGroup();&lt;br /&gt;
&lt;br /&gt;
# Simple user interface (Buttons for zoom and label for displaying it)&lt;br /&gt;
var zoom = 10;&lt;br /&gt;
var type = &amp;quot;intl&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
var ui_root = window.getCanvas().createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
window.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var button_in = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;+&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(1));&lt;br /&gt;
&lt;br /&gt;
var button_out = canvas.gui.widgets.Button.new(ui_root, canvas.style, {})&lt;br /&gt;
    .setText(&amp;quot;-&amp;quot;)&lt;br /&gt;
    .listen(&amp;quot;clicked&amp;quot;, func changeZoom(-1));&lt;br /&gt;
&lt;br /&gt;
button_in.setSizeHint([32, 32]);&lt;br /&gt;
button_out.setSizeHint([32, 32]);&lt;br /&gt;
&lt;br /&gt;
var label_zoom = canvas.gui.widgets.Label.new(ui_root, canvas.style, {});&lt;br /&gt;
&lt;br /&gt;
var button_box = canvas.HBoxLayout.new();&lt;br /&gt;
button_box.addItem(button_in);&lt;br /&gt;
button_box.addItem(label_zoom);&lt;br /&gt;
button_box.addItem(button_out);&lt;br /&gt;
button_box.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
vbox.addItem(button_box);&lt;br /&gt;
vbox.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
var changeZoom = func(d) {&lt;br /&gt;
    zoom = math.max(2, math.min(19, zoom + d));&lt;br /&gt;
    label_zoom.setText(&amp;quot;Zoom &amp;quot; ~ zoom);&lt;br /&gt;
    updateTiles();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# http://polymaps.org/docs/&lt;br /&gt;
# https://github.com/simplegeo/polymaps&lt;br /&gt;
# https://github.com/Leaflet/Leaflet&lt;br /&gt;
&lt;br /&gt;
var maps_base = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/cache/maps';&lt;br /&gt;
&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/map&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/sat&lt;br /&gt;
# (also see http://wiki.openstreetmap.org/wiki/Tile_usage_policy)&lt;br /&gt;
var makeUrl = string.compileTemplate('https://maps.wikimedia.org/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
# https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png&lt;br /&gt;
var makePath = string.compileTemplate(maps_base ~ '/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
var num_tiles = [4, 3];&lt;br /&gt;
&lt;br /&gt;
var center_tile_offset = [&lt;br /&gt;
    (num_tiles[0] - 1) / 2,&lt;br /&gt;
    (num_tiles[1] - 1) / 2,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
# simple aircraft icon at current position/center of the map&lt;br /&gt;
group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
    .moveTo(&lt;br /&gt;
        tile_size * center_tile_offset[0] - 10,&lt;br /&gt;
        tile_size * center_tile_offset[1],&lt;br /&gt;
    )&lt;br /&gt;
    .horiz(20)&lt;br /&gt;
    .move(-10, -10)&lt;br /&gt;
    .vert(20)&lt;br /&gt;
    .set(&amp;quot;stroke&amp;quot;, &amp;quot;red&amp;quot;)&lt;br /&gt;
    .set(&amp;quot;stroke-width&amp;quot;, 2)&lt;br /&gt;
    .set(&amp;quot;z-index&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# initialize the map by setting up&lt;br /&gt;
# a grid of raster images  &lt;br /&gt;
&lt;br /&gt;
var tiles = setsize([], num_tiles[0]);&lt;br /&gt;
for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
    tiles[x] = setsize([], num_tiles[1]);&lt;br /&gt;
    for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
        tiles[x][y] = group.createChild(&amp;quot;image&amp;quot;, &amp;quot;map-tile&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var last_tile = [-1, -1];&lt;br /&gt;
var last_type = type;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the callback that will be regularly called by the timer&lt;br /&gt;
# to update the map&lt;br /&gt;
var updateTiles = func {&lt;br /&gt;
    # get current position&lt;br /&gt;
    var lat = getprop('/position/latitude-deg');&lt;br /&gt;
    var lon = getprop('/position/longitude-deg');&lt;br /&gt;
&lt;br /&gt;
    var n = math.pow(2, zoom);&lt;br /&gt;
&lt;br /&gt;
    var latRad = lat * math.pi / 180;&lt;br /&gt;
    var mercatorY = math.ln(math.tan(latRad) + 1 / math.cos(latRad));&lt;br /&gt;
    var offset = [&lt;br /&gt;
        n * ((lon + 180) / 360) - center_tile_offset[0],&lt;br /&gt;
        (1 - mercatorY / math.pi) / 2 * n - center_tile_offset[1],&lt;br /&gt;
    ];&lt;br /&gt;
    var tile_index = [&lt;br /&gt;
        int(offset[0]),&lt;br /&gt;
        int(offset[1]),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var ox = tile_index[0] - offset[0];&lt;br /&gt;
    var oy = tile_index[1] - offset[1];&lt;br /&gt;
&lt;br /&gt;
    for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
        for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
            tiles[x][y].setTranslation(&lt;br /&gt;
                int((ox + x) * tile_size + 0.5),&lt;br /&gt;
                int((oy + y) * tile_size + 0.5),&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tile_index[0] != last_tile[0]&lt;br /&gt;
        or tile_index[1] != last_tile[1]&lt;br /&gt;
        or type != last_type&lt;br /&gt;
    ) {&lt;br /&gt;
        for (var x = 0; x &amp;lt; num_tiles[0]; x += 1) {&lt;br /&gt;
            for (var y = 0; y &amp;lt; num_tiles[1]; y += 1) {&lt;br /&gt;
                var pos = {&lt;br /&gt;
                    z: zoom,&lt;br /&gt;
                    x: int(offset[0] + x),&lt;br /&gt;
                    y: int(offset[1] + y),&lt;br /&gt;
                    type: type,&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                (func {&lt;br /&gt;
                    var img_path = makePath(pos);&lt;br /&gt;
                    var tile = tiles[x][y];&lt;br /&gt;
&lt;br /&gt;
                    if (io.stat(img_path) == nil) { &lt;br /&gt;
                        # image not found, save in $FG_HOME&lt;br /&gt;
                        var img_url = makeUrl(pos);&lt;br /&gt;
                        print('requesting ' ~ img_url);&lt;br /&gt;
                        http.save(img_url, img_path)&lt;br /&gt;
                            .done(func {&lt;br /&gt;
                                print('received image ' ~ img_path);&lt;br /&gt;
                                tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                             })&lt;br /&gt;
                            .fail(func(r) {&lt;br /&gt;
                                print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason);&lt;br /&gt;
                            });&lt;br /&gt;
                    } else {&lt;br /&gt;
                        # cached image found, reusing&lt;br /&gt;
                        print('loading ' ~ img_path);&lt;br /&gt;
                        tile.set(&amp;quot;src&amp;quot;, img_path);&lt;br /&gt;
                    }&lt;br /&gt;
                })();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        last_tile = tile_index;&lt;br /&gt;
        last_type = type;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up a timer that will invoke updateTiles at 2-second intervals&lt;br /&gt;
var update_timer = maketimer(2, updateTiles);&lt;br /&gt;
# actually start the timer&lt;br /&gt;
update_timer.start();&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up default zoom level&lt;br /&gt;
changeZoom(0);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###&lt;br /&gt;
# The following lines were recently added and have not yet been tested&lt;br /&gt;
# (if in doubt, remove them)&lt;br /&gt;
window.del = func {&lt;br /&gt;
    print(&amp;quot;Cleaning up window:&amp;quot;, ,&amp;quot;\n&amp;quot;);&lt;br /&gt;
    update_timer.stop();&lt;br /&gt;
    # explanation for the call() technique at:&lt;br /&gt;
    # http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls&lt;br /&gt;
    call(canvas.Window.del, [], me);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding a NavDisplay ==&lt;br /&gt;
&lt;br /&gt;
== Adding a CDU ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Multi-Function Display (MFD) ==&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_MapStructure&amp;diff=143067</id>
		<title>Canvas MapStructure</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_MapStructure&amp;diff=143067"/>
		<updated>2025-11-25T21:09:48Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* The SymbolCache */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{infobox subsystem&lt;br /&gt;
|image =MapStructure-GRID-layer.png&lt;br /&gt;
|name =MapStructure&lt;br /&gt;
|started= 11/2013 &lt;br /&gt;
|description = Charting abstraction layer for [[Canvas]]/[[Nasal]] maps&lt;br /&gt;
|status = Under active development as of 11/2013&lt;br /&gt;
|maintainers  = Philosopher, Hooray, {{Usr|Stuart}}&lt;br /&gt;
|developers = [[User:Philosopher]] (since 11/2013),&lt;br /&gt;
|topic-fgdata= main fgdata repo&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
[[File:Gpsmap196-MapStructure.png|thumb|F-JJTH's GPSMap196 in the process of being ported to [[Canvas]] using Philosopher's MapStructure framework for mapping (LOD/scaling not applied here, but available) - see {{forum link|t=23047}}]]&lt;br /&gt;
&lt;br /&gt;
[[File:Gpsmap196-MapStructure-with-styling-applied.png|thumb|MapStructure layers integrated in GPSMap196 with some custom styling applied]]&lt;br /&gt;
&lt;br /&gt;
[[File:VSD-prototype-by-omega95.png|thumb|Canvas-based VSD prototype (Vertical Situation Display) developed by Omega95]]&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-WXR-layer-by-omega95.png|thumb|MapStructure WXR (weather) layer created by omega95's using a web service API to fetch live online imagery {{forum link|t=23753}}]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Targeted FlightGear versions: 3.20+'''&lt;br /&gt;
&lt;br /&gt;
[[File:Canvasready.png]]&lt;br /&gt;
&lt;br /&gt;
{{Note|This article is intended for people wanting to add custom scripted mapping/charting displays to FlightGear (aircraft, GUI dialogs, HUDs etc) using layered maps- it assumes familiarity with [[Nasal]] scripting, [[Object Oriented Programming with Nasal]] and [[Canvas]] - while an existing chart layer (e.g. to show VORs/NDBs or DMEs) can be easily added and used/customied by non-programmers within a few minutes, creating new MapStructure layers requires additional knowledge which is detailed below. Usually, new layers can be easily created by adapting existing layers. In its simplest form, MapStructure is just a library of a handful of existing layers (navaids, traffic, weather etc) that can be easily -and quickly- reused for all kinds of different purposes. &lt;br /&gt;
For people familiar with Nasal coding, MapStructure is also a framework to create new fully reusable layers with integrated support for caching, resource management (listener/timers) and efficient spatial searching/updating.&lt;br /&gt;
For FlightGear versions &amp;lt;nowiki&amp;gt;&amp;gt;=&amp;lt;/nowiki&amp;gt; 3.3+, there will also be a [[Canvas Widgets|Canvas Widget]] to easily add MapStructure-based maps to any Canvas dialog and/or to Canvas MFDs that internally use the Canvas GUI framework. Another novelty that we're currently exploring is extending the framework such that GUI-based creation/editing/customization of layers becomes a first-class concept, e.g. to create a weather or tutorial/missions GUI editor: see [[#Porting the map dialog]].|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
Basically, the ND/MapStructure frameworks came to be out of a certain degree of disagreement with other MFD/glass cockpit efforts using the Canvas system.&lt;br /&gt;
That was primarily because many of those lacked support for:&lt;br /&gt;
&lt;br /&gt;
*  truly independent instances&lt;br /&gt;
*  reuse (other aircraft/use-cases, e.g. sharing common logic between aircraft displays and GUI dialogs)&lt;br /&gt;
*  customiation (think styling)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Especially, we found it hugely frustrating to see awesome avionics like the Avidyne Entegra R9 being developed for aircraft like the extra500, that were impossible to reuse for other aircraft cockpits, but that also contained features/code that would have been useful elsewhere (think mapping/charting displays).&lt;br /&gt;
So that more often than not, copy &amp;amp;amp; paste was the only option to &amp;quot;reuse&amp;quot;  useful code elsewhere.&lt;br /&gt;
Thus, the idea was to grow a library of generic building blocks that live in $FG_ROOT and that are sufficiently generic and customizable to basically enable people to collaborate more properly by providing a strong incentive via compelling functionality that can be easily maintained/updated in the future. The thinking was that all aircraft/GUI dialogs using a single back-end would automatically benefit from significant updates to the back-end that way (think performance, features etc).&lt;br /&gt;
Our strategy was to adopt a MVC (model/view/controller) approach, where the model would represent what is to be rendered, the view would map the Canvas system and its primitives (elements) and underlying APIs, whereas the controller would usually be use-case specific (think cockpit vs. GUI dialog).&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=317039}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; my &amp;quot;2c&amp;quot; ;-) &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Aug 21st, 2017 &lt;br /&gt;
  |added  =  Aug 21st, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Today, just the MapStructure framework is under 1k lines of code, and is making roughly 9k-15k lines of C++ code obsolete in FlightGear (agradar,groundradar,wxradar,map dialog), unifying the whole shebang in the process, which also ensures that more modern OSG/OpenGL can be used, i.e. better performance in the long-term.&lt;br /&gt;
&lt;br /&gt;
This all has taken place in under 24 months actually (MapStructure just being 6 months old now actually) - whereas hard-coded instruments (wxradar, agradar, navdisplay,kln89 etc) are usually 5+ years old, and things like the Map dialog even older - in comparison, these were all &amp;quot;easier&amp;quot; to come up with in the first place, but obviously don't scale as well as a Canvas-based solution. Gijs NavDisplay framework is already better accessible and more feature-rich than the original ND code.&lt;br /&gt;
&lt;br /&gt;
But this kind of work isn't exactly fun, it comprises lots of refactoring and is more about talking and coordinating things than actually coding stuff - because the implementation may very well just be a fraction the size of all the manifestations that are hopefully replaced over time.&lt;br /&gt;
&lt;br /&gt;
We've seen some extremeley skilled contributors making sizable contributions without them ever documenting the internals, so that getting up to scratch with things later on may be next to impossible without spending a huge amount of time, that could be equally spent on re-designing certain features/systems.&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
This is something that we have actively worked to address in the context of Nasal/Canvas and efforts like the ND/MapStructure frameworks, i.e. those are now extensively documented, not just including &amp;quot;roadmaps&amp;quot; and &amp;quot;milestones&amp;quot;, but also internal design stuff, including even step-by-step tutorials and coding examples - admittedly, this has taken up quite a bit of spare time, that could have just as well been spent &amp;quot;coding&amp;quot; - but given the wiki stats, we seem to be on the right track here, i.e. most of these articles have seen between 2k-8k views within just ~10-12 weeks, which is kinda impressive (some of our most popular articles &amp;quot;only&amp;quot; have seen 40k views in years!), but this also ensures at the same time that even if some of us were to disappear for a few months, people would still be able to pick up where we (TheTom, Philosopher, Gijs, myself) left off, no matter if this means &amp;quot;maintaining&amp;quot; our existing code - or modernizing/replacing it completely.&lt;br /&gt;
&lt;br /&gt;
In professional software development circles, writing documentation is a necessary evil, as is writing unit tests - in FlightGear, people prefer to spend their time doing &amp;quot;fun&amp;quot; stuff instead for understandable reasons. Then again, some of the main building blocks and key technologies in FlightGear were developed by people who obviously understood that having sufficient docs is at least as important for a feature to survive than the actual code, just look at architectural pillars contributed by people like David Megginson (property tree) or Andy Ross (Nasal) - those are typically the same guys who were responsible for much of the original documentation targeted at core developers, no matter if it's through extensive use of doxygen comments or through dedicated design articles. &lt;br /&gt;
&lt;br /&gt;
Some of our most active contributors spent little to no time ensuring that future developers will be able to continue their work - and that's a problem that will only really become obvious once someone is too busy with other aspects of their life to contribute (or even just back to answer questions).&lt;br /&gt;
&lt;br /&gt;
== What is it ? ==&lt;br /&gt;
&lt;br /&gt;
MapStructure is a scripting-space Nasal framework (designed and maintained by Philosopher) for managing layers of symbols in Nasal/Canvas-based mapping displays, which can be used in both aircraft MFDs/instruments and GUI dialogs, like the airport selection or [[Map]] dialogs. MapStructure is all about separating the visualization of the map from the visualized data itself, and the way it is shown to, and controlled by, the user. MapStructure is designed as a Model/View/Controller (MVC) framework.&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructureMVC.png|thumb|MapStructure-MVC-Framework]]&lt;br /&gt;
&lt;br /&gt;
The primary challenge here is, that these different front-ends (e.g. a GUI dialog or cockpit) will typically all have very different means to interact with the map and its rendered layers. &lt;br /&gt;
&lt;br /&gt;
[[File:MapStructureOverview.png|thumb|MapStructure MVC separation]]&lt;br /&gt;
&lt;br /&gt;
A GUI dialog may respond to mouse events (e.g. panning/zooming etc), while a cockpit display would typically respond to cockpit hot-spots (bindings), such as clicking virtual CDU/MCP buttons. Therefore, user input handling must be well encapsulated and handled by so called &amp;quot;delegates&amp;quot;, so that the back-end doesn't need to know anything about its front-end. Otherwise, back-ends designed for aircraft would no longer work when used in GUI dialogs and vice versa.&lt;br /&gt;
&lt;br /&gt;
For instance, imagine a piece of code showing navaids within a range that can be set in the cockpit: This code would stop working when used in a different cockpit, or when used in a GUI dialog. Likewise, referencing properties that are specific to a certain GUI dialog, would stop working once the layer is used in a different GUI dialog or in an aircraft.&lt;br /&gt;
&lt;br /&gt;
Conventionally, a simple navaid layer showing NDBs would be implemented as a single piece of code that's running through these steps:&lt;br /&gt;
* run a query to find navaids within 50 nm&lt;br /&gt;
* get the positions of each navaid&lt;br /&gt;
* for each navaid, render a symbol onto a canvas map&lt;br /&gt;
&lt;br /&gt;
Now, once such a piece of code needs to do something else, it would be typically copied/pasted and adapted, e.g. to change the range of the query, the type of symbol, the type of map or even just the type of query (VORs vs NDBs). Now, MapStructure encourages a more modular design, so that each stage is implemented via separate files that can be easily reused, and that can be augmented by adding new files to accomplish a related task. &lt;br /&gt;
&lt;br /&gt;
The next complication is that different front-ends may require different data to be shown - such as taxiways, runways, fixes, waypoints or routing (waypoints). Therefore, the framework works in terms of &amp;quot;layers&amp;quot;, where each layer manages its own set of symbols - symbols typically represent geographic positions (latitude/longitude), as well as a '''drawable''' (symbol, SVG file name or a callback handling the implementation).&lt;br /&gt;
&lt;br /&gt;
Control of individual layers is delegated to callbacks that can be overridden by the front-end, i.e. to hide/show a layer. &lt;br /&gt;
&lt;br /&gt;
[[File:MapStructureController.png|thumb|MapStructure Controllers]]&lt;br /&gt;
&lt;br /&gt;
There's basically several '''drivers''' at work here:&lt;br /&gt;
* one driver determines what is to be drawn (e.g. VORs)&lt;br /&gt;
* one driver determines where it is to be drawn (e.g. lat/lon)&lt;br /&gt;
* one driver determines how it is to be drawn (e.g. scaled SVG/PNG images) &lt;br /&gt;
* another driver determines how the layer responds to events (such as e.g. recurring updates or mouse clicks)&lt;br /&gt;
&lt;br /&gt;
The primary concern here is to ensure that the DRY-principle (don't-repeat-yourself) is not violated, so that maps, layers and symbols can be easily reused without requring any copy&amp;amp;paste- and so that new use-cases can be easily supported by adding custom controllers that interact with maps/layers/symbols as needed (these are typically '''boilerplate''' files that are roughly ~30-50 lines of code, of which 5-10 lines may need customizing). &lt;br /&gt;
&lt;br /&gt;
By establishing this separation of concerns, the design is heavily focused on gathering new contributions in a single place ($FG_ROOT/Nasal/canvas/map), rather than having custom-coded solutions in various places - such as GUI dialogs or aircraft, which used to be the predominant practice prior to this framework, and which often meant that useful code was only available for certain purposes.&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-UseCases.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Thus, to the framework itself it doesn't matter if you need a layered map to be shown on an instrument or in a GUI dialog, or even as part of the scenery (VGDS) or  maybe as a livery. By following this approach, we can use a shared back-end to implement layered maps for different needs, without having to duplicate any code, where people usually would use &amp;quot;copy &amp;amp; paste&amp;quot; and customize things afterwards, a very error-prone and tedious process, that doesn't lend itself to long-term maintenance.&lt;br /&gt;
&lt;br /&gt;
As of 05/2015, all this still is work in progress and not yet completely finished, but we're hoping to completely replace the old map.nas code by FG 3.2 and provide a pure Canvas-based re-implementation of the Map dialog in the 4.0 release.&lt;br /&gt;
&lt;br /&gt;
Aircraft developers working on airliners or modern biz jets (and the corresponding MFD avionics) will probably want to get in touch with Philosopher and Hooray via the forum/wiki to coordinate things a little. We also appreciate any related feature requests and other constructive feedback. Flexibility is the ultimate design goal of this effort.&lt;br /&gt;
&lt;br /&gt;
At the moment, the primary users of the framework are the 747-400 and the 777-200ER - both make use of the new [[NavDisplay|ND framework]], which internally uses the MapStructure framework.&lt;br /&gt;
&lt;br /&gt;
If you just need an ND, you won't need to deal with MapStructure directly, it is all done transparently by the [[NavDisplay]] code. &lt;br /&gt;
&lt;br /&gt;
However, if you'd like  to create custom charting displays, or GUI dialogs with embedded charts (map dialog, instructor console, ATC or RADAR displays etc), you'll probably want to use the MapStructure framework, because it reduces the amount of specialized Nasal code significantly - typically to ~10-15 lines of configuration code per layer.&lt;br /&gt;
&lt;br /&gt;
To learn more about the motivation of using a MVC design, see [[Canvas Map API]].&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-Internals.png|thumb|MapStructure Internals]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
== Performance Considerations ==&lt;br /&gt;
&lt;br /&gt;
[[File:Map-canvas-mapstructure-performance.png|thumb|stress-testing [[MapStructure]] using the ufo @ KSFO circling at 2000 kts in a climb, see {{forum link|p=211611}}]]&lt;br /&gt;
&lt;br /&gt;
Recently, it has been two years since [[Canvas]], our 2D rendering API, got integrated. And even today, there are people claiming that aircraft using Nasal and Canvas are generally slow and don't provide sufficient performance, suggesting not to adopt Canvas yet, so here are some comments about that, to help put some context around such statements:&lt;br /&gt;
&lt;br /&gt;
Canvas is a relatively new technology in FlightGear, and it not just a single technology, it's built on top of other technologies, like the [[Property Tree]], ShivaVG/OpenVG, OSG and OpenGL. As you may have noticed, we didn't even mention [[Nasal]] yet - that's because Nasal scripting, strictly speaking, isn't even required to use [[Canvas]] - the whole thing can be used without touching any Nasal - just by using the property tree, the built-in httpd or even the [[Telnet usage|telnet]] interface.&lt;br /&gt;
&lt;br /&gt;
In theory, you could create a canvas just by setting properties, and even add placements (GUI/scenery/aircraft) by doing just that.&lt;br /&gt;
&lt;br /&gt;
Canvas being a relatively new technology, we obviously still have to learn what works and what doesn't, and often that also means finding things that are too slow for some reason. And these days, there are easily a handful of abstraction layers involved, all of which may contribute to things being too slow. And debugging/profiling isn't always straightforward, especially when several boundaries are crossed (Nasal, property tree, C++, OSG/OpenGL).&lt;br /&gt;
&lt;br /&gt;
But whenever we've seen dead-slow Nasal/Canvas code it was usually not the problem of Nasal or Canvas per se, but due the original code/implementation in the first place, including our own code by the way  :D &lt;br /&gt;
&lt;br /&gt;
Just because there are means available to add scripted graphics to FlightGear, doesn't mean that people automatically know how to use this technology properly.&lt;br /&gt;
&lt;br /&gt;
For example, the very first version of the airport selection dialog was much slower than necessary, simply because it would redraw stuff unnecessarily, and not even use separate layers (canvas groups) for different features like airports, taxiways, runways etc.&lt;br /&gt;
&lt;br /&gt;
Since then, we've learned a lot about using Canvas properly, but also about the combination of using Nasal and Canvas, including other subsystems.&lt;br /&gt;
&lt;br /&gt;
So, as Canvas is a fairly recent feature, there's sometimes some really naive stuff done in various areas, that makes people think that Nasal and/or Canvas are generally slow. &lt;br /&gt;
&lt;br /&gt;
However, that is not generally true. Aircraft like the m2000-5 or the extra500 are indeed fairly slow - not primarily because of Nasal/Canvas, but due a combination of factors, including several other FG technologies. And the 747 or 777 are slow even without any Nasal/Canvas code running; we can actually measure now how little impact our Nasal/Canvas code has, and how significant the impact is due to complex cockpit modeling (many polygons and high resolution textures). Understandably, under such circumstances, improper use of Nasal and/or Canvas may cause additional performance issues. And people may easily draw false conclusions under these circumstances.&lt;br /&gt;
&lt;br /&gt;
Anyway, nobody should simply &amp;quot;port/convert&amp;quot; stuff like -for example- the Garmin196 to use Canvas, because that's exactly where all those problems come from: Canvas and Nasal themselves are simply too low-level for these purposes, people still need to be very experienced to come up with fast code here, very familiar with FG and certain Nasal technologies like timers and listeners (or the whole process inevitably involves lots of trial &amp;amp; error). Typically, that really only applies to core developers still, or fgdata committers who have extensively worked with both Nasal and Canvas over the years.&lt;br /&gt;
&lt;br /&gt;
To use Nasal and Canvas properly, there are several layers that need to be understood, such as:&lt;br /&gt;
# Nasal&lt;br /&gt;
# Canvas scripting bindings&lt;br /&gt;
# Canvas elements (OpenVG paths, raster images, maps)&lt;br /&gt;
# FlightGear internals (navdb, timers, listeners etc)&lt;br /&gt;
# OpenSceneGraph&lt;br /&gt;
# OpenGL&lt;br /&gt;
&lt;br /&gt;
Most people only understand #1 (well, partially), even though writing really fast code may very well involve looking at several layers in combination to see how they affect each other.&lt;br /&gt;
&lt;br /&gt;
So writing &amp;quot;fast&amp;quot; Nasal/Canvas code still is quite an undertaking despite things being accessible from scripting space these days. And even our most experienced Nasal/Canvas contributors are still learning new things each day. &lt;br /&gt;
&lt;br /&gt;
Which is exactly the point of having [[MapStructure]] and the [[NavDisplay]] frameworks: those are designed to handle things efficiently, e.g. by using smarter queries, smarter updates, and techniques like caching or selective/delta searches. &lt;br /&gt;
&lt;br /&gt;
Imagine it like &amp;quot;Ruby on Rails&amp;quot; for aviation charts - that's what the whole MapStructure framework is all about: maps and charts. &lt;br /&gt;
&lt;br /&gt;
The NavDisplay framework also used to be fairly inefficient in its early days - since then, we've significantly reworked it to use MapStructure. That first introduced some ugly hacks because we didn't want to rewrite it completely, but now its layers are mostly using MapStructure. And most of the original ND stuff has been refactored, generalized and integrated with the MapStructure framework, so that this very code can be used in other places, not just other aircraft, but also GUI dialogs.  &lt;br /&gt;
&lt;br /&gt;
Very fast Nasal/Canvas code will typically do very little at runtime/frame rate, but instead use pre-created data structures and dynamically toggle canvas layers on/off (show/hide groups) and update the underlying data selectively, while using cached images instead of having lots of identical OpenVG paths. &lt;br /&gt;
&lt;br /&gt;
We've arrived at these conclusions based on testing and lots of profiling.&lt;br /&gt;
&lt;br /&gt;
Frame rates &amp;gt;= 60 fps are not impossible to achieve like this - but coding such a design is obviously much more involved than simply drawing/updating once per frame, which is what people will typically do. This is very similar to people running timer callbacks at frame rate instead of using using split-frame loops or listeners to do certain computations and processing selectively. A simple design can get you only so far, but an efficient design requires much more thinking, and time to get right.&lt;br /&gt;
&lt;br /&gt;
For instance, D-LEON has done quite some Nasal/Canvas benchmarking and confirmed that MapStructure/Canvas are sufficiently fast already (and we're still exploring additional options for making it even faster). Also, we're looking into augmenting/re-implementing certain features through native code additions, such as having dedicated extension functions, Nasal library functions or new Canvas extensions to make things even faster.&lt;br /&gt;
&lt;br /&gt;
The key point here is that having a set of generic abstraction layers allows us to easily generalize, but also optimize, things - without having to rewrite all the places where Nasal/Canvas are used for mapping purposes. Things like the [[Avidyne Entegra R9]] don't use the [[MapStructure]] framework at all currently - which also means that the code doesn't benefit from recent MapStructure optimizations unfortunately.&lt;br /&gt;
&lt;br /&gt;
Imagine we were to optimize positioned queries (NavDB stuff like VORs, NDBs, airports etc) to become 50% faster - all instruments and dialogs using the MapStructure framework would get this speedup for free, while custom solutions would need to separately implement the optimized query. And that applies to pretty much any effort that doesn't favor a framework-centric development approach.&lt;br /&gt;
&lt;br /&gt;
By the way, this is exactly the reason why the MapStructure framework lives under $FG_ROOT/Nasal, to help grow a shared library of '''fast''' components that can be easily reused, but also maintained - freeing aircraft and GUI developers from such chores and obligations, so that they don't have to track development and update all their own work in order to avoid breakage, but still benefit from recent optimizations. This is a lesson that we've learned from all the feedback that some of the most active airliner developers, like omega95 and redneck, have provided over time.&lt;br /&gt;
&lt;br /&gt;
Unless someone is a really experienced programmer, working with &amp;quot;low-level&amp;quot; Nasal &amp;amp; Canvas for mapping purposes is probably not a good idea, because we already have a handful of people working on a single framework that is dedicated to just that, and we actually do regularly profile things and we're talking to the Canvas guys to sneak out more performance, better speed, less lag etc. &lt;br /&gt;
&lt;br /&gt;
But whenever someone comes up with a custom mapping solution -solving the same problem that MapStructure and Gijs' ND frameworks solve- that doesn't use those frameworks, they need to do all the hard work from scratch, and code sharing/reuse and maintenance is made increasingly difficult, too. Basically, we end up &amp;quot;competing&amp;quot; without meaning to, by working on fairly similar projects, without coordinating our work to generalize and reuse existing solutions.  &lt;br /&gt;
&lt;br /&gt;
Which is why we talked with the [[Avidyne Entegra R9]] developers to team up with us: their code is much more elegant than the original ND/MapStructure code, but the latter is much more generic (aircraft/instrument agnostic), and also fairly optimized already.&lt;br /&gt;
&lt;br /&gt;
Admittedly, we already spent a while examining all the [[Avidyne Entegra R9]] code there - we were kinda surprised seeing OOP code using design patterns solving a problem that we had been working on for several months. Actually, had we seen that code earlier, it would have been a much more mature foundation for a generic ND/MapStructure framework, because of its OO nature. But in the meantime, we've come up with something fairly generic - mostly thanks to Philosopher's MapStructure framework.&lt;br /&gt;
&lt;br /&gt;
As mentioned elsewhere, if we could have had a look at the Avidyne code it would have been a great/better foundation for MapStructure and the ND framework - but we had to work off a completely different code base (several actually),  so things are a little inconsistent and less elegant - but those frameworks are '''really''' designed to be generic, not just intended to be used with different aircraft, but also different GUI dialogs. And whenever we add a new optimized feature, all people/aircraft/dialogs using those frameworks will benefit automatically (backward compatibility is handled by US).&lt;br /&gt;
&lt;br /&gt;
We literally depend on having many different use cases and front-end scenarios, i.e. for these two frameworks to evolve properly, we need many different aircraft developers to adopt them, do regular testing/profiling and provide feedback - and GUI use-cases are just as important. In fact, just for the sake of generalizing things, both frameworks contain support for being not just driven by the main/FDM aircraft, but even by AI aircraft.&lt;br /&gt;
&lt;br /&gt;
Technically, MapStructure layers are a bit more sophisticated than having pre-created SVG/OpenVG groups, because symbols can be cached in a separate canvas via a texture map, so that there's true &amp;quot;instancing&amp;quot; support for each cached symbol. This is something that we've been working on in the last two weeks. Also, positioned queries are handled by an abstraction layer to ensure that things are sufficiently fast using selective delta-updating. Besides, the ND/MapStructure code was intended to be reusable right from the beginning.&lt;br /&gt;
&lt;br /&gt;
Quite a few of the problems people, interested in mapping, are likely to encounter are already solved via MapStructure, i.e. identical paths for different symbols are simply instanced by using a raster image (canvas) as a cache.&lt;br /&gt;
&lt;br /&gt;
The bottom line is: if there's someone who actually IS experienced enough to know how to write good and fast Nasal/Canvas code, that person should better team up with us, to help improve the generic framework, rather than develop some niche instrument or dialog that will only ever be used by a few aircraft, work that may never make it into fgdata.&lt;br /&gt;
&lt;br /&gt;
Nasal &amp;amp; Canvas are really just tools, and tools can be misused obviously. Using them properly still requires certain knowledge, or slow code may result from it. We've been trying to make this easier by providing a few wrappers on top of Nasal/Canvas, that are intended to help with the creation of mapping displays, so that unnecessarily slow code can be avoided, while also allowing front-end code to directly benefit from optimized features.&lt;br /&gt;
&lt;br /&gt;
Badly written code will remain slow no matter how optimized underlying technologies like Nasal or Canvas are, even if they should be completely replaced at some point. Those are algorithmic issues, and MapStructure/NavDisplay are intended to help people focus on non-algorithmic stuff.&lt;br /&gt;
&lt;br /&gt;
{{cquote&lt;br /&gt;
  |&amp;lt;nowiki&amp;gt;Claiming that Nasal/Canvas would be &amp;quot;a failure as a tool&amp;quot; just because people can still implement slow code, is far too short-sighted - just because you are allowed to drive a car (or fly an airplane) doesn't make you an expert in car engines or airplane turbines - things like Nasal and Canvas are really just enablers, that are truly powerful in the hands of people who know how to use them, but that can still be misused by less-experienced contributors.&lt;br /&gt;
&lt;br /&gt;
That is exactly why people are working towards more targeted frameworks on top of Nasal/Canvas - but it's a process that is very much still in progress, and probably will be for at least another 2-3 release cycles. Pointing out obvious shortcomings isn't going to help anyone though - certainly not as much as getting involved in one way or another&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  |{{cite web |url={{forum url|p=211169}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Does FlightGear has Multiplayer Combat mode?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri May 30&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Demonstration ==&lt;br /&gt;
To easily show a moving map layer with all AI/MP traffic within a range of 25 nm, only 10-15 lines of code are needed. Paste this into the [[Nasal Console]]:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var MapStructure_demo = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); &lt;br /&gt;
  &lt;br /&gt;
    # this will center the map&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    # TFC, APT and APS are the layer names as per $FG_ROOT/Nasal/canvas/map and the names used in each .lcontroller file&lt;br /&gt;
    # in this case, it will load the traffic layer (TFC), airports (APT) and render an airplane symbol (APS)&lt;br /&gt;
    foreach (var type; [r('TFC'), r('APT'), r('APS')]) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_demo&lt;br /&gt;
&lt;br /&gt;
MapStructure_demo();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As can be seen, using just ~10-15 lines of copied Nasal code, creates a fully working map that shows AI/MP traffic and airports. Next,  you can add/replace additional layers, such as: '''FIX''', '''VOR''', '''NDB''' etc. For a complete overview, check out [[Canvas MapStructure Layers]].&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructureWaypoints.png|thumb|Initial Waypoint support for Philosopher's MapStructure framework]]&lt;br /&gt;
&lt;br /&gt;
== Layers ==&lt;br /&gt;
&lt;br /&gt;
See [[Canvas MapStructure Layers]]&lt;br /&gt;
&lt;br /&gt;
== Issues ==&lt;br /&gt;
(link to canvas-navdisplay label in issue tracker)&lt;br /&gt;
&lt;br /&gt;
We currently have quite a few old files doing basically identical stuff, just with different draw routines - such as e.g. &amp;quot;runway-nd&amp;quot;, &amp;quot;airports&amp;quot;, &amp;quot;airports-nd&amp;quot;. It would probably be a good idea to generalize this by implementing LOD and styling support - so that we can use a single APT layer that supports all necessary customizations.&lt;br /&gt;
&lt;br /&gt;
Basically, we could unify things a bit by using LOD support to show APT in different modes, so that taxiways etc would be only shown if necessary. It would still make sense to maintain separate draw/symbol  files for these, so that things can be easily reused.&lt;br /&gt;
&lt;br /&gt;
=== Scale/Ratio Handling ===&lt;br /&gt;
Need to take original canvas texture dimensions into account, and also keep in mind that layers may be shown not in &amp;quot;fullscreen&amp;quot; mode (using the whole texture), but just a sub-area, i.e. a clipped canvas-region, and setTranslation/view/size calls must be adjusted accordingly. The GPSMap196 and Avidyne are instruments that support partial-screen modes.&lt;br /&gt;
&lt;br /&gt;
[[File:Gpsmap196-mapstructure-layers-ratio.png|thumb|GPSMap196 with [[MapStructure]] layers (currently exhibiting some scaling/LOD issues due to hard-coded assumptions in some of the layers)]]&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The extra500 developers just posted a few screen shots of the Avidyne Entegra R9 in &amp;quot;moving map&amp;quot; mode, which demonstrates a few use-cases that we do not currently support in MapStructure (the ND being a different matter for now):&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
[[Extra_EA-500#Map_:_Moving_Map]]&amp;lt;br/&amp;gt;&lt;br /&gt;
[[File:IFD_FMS-Map_FPL.png|250px]]&amp;lt;br/&amp;gt;&lt;br /&gt;
[[File:IFD_MAP-Map%2B.png|250px]]&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
Specifically, this probably means that we may need to revamp/resurrect symbol controllers and maybe accept an optional callback to pre-/post-transform individual symbols (they're rotated apparently according to runway orientation), and allow them to be overridden when instantiating the layer (i.e. via the ctor if SVG-based). And we may also want to explore SVG styling by patching svg.nas to run a &amp;quot;transform&amp;quot; callback to customize/colorize certain elements of the SVG. The instrument itself is meanwhile making fairly aggressive use of texture-map based caching for basically ALL symbols.&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
The other thing is that translation (centered/off center mode) should always be based on the parent group's dimensions (bounding box) and that we really shouldn't be using the top-level canvas, because a MFD very well be split into several screen areas (which also applies to the GPSMap196 and most other modern avionics).&lt;br /&gt;
  |{{cite web |url={{forum url|p=213354}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Evolving the MapStructure &amp;amp; NavDisplay Frameworks ...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Jun 24&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-ND-Profiling.png|thumb|MapStructure/NavDisplay testbed with integrated profiling support for each layer]]&lt;br /&gt;
&lt;br /&gt;
the performance issue related to the map-canvas.xml dialog pointed out by another user (whenooming) has nothing to do with Canvas or Nasal - it is C++ code that is causing this, and this is no surprise - it's a known issue actually. As a matter of fact, the hard-coded NavDisplay, as well as the hard-coded Map dialog both suffered from the exact same issue, until Gijs solved it by rewriting the projection handling code. &lt;br /&gt;
However, the original Canvas projection code is still using the old implementation, i.e. the update got never back-ported: [[Canvas MapStructure#Performance]]&lt;br /&gt;
We're talking here about roughly 50 lines of C++ code, which exist already and which would need to be turned into a Canvas::Map::Projection&lt;br /&gt;
Note that this would benefit any, and all, Canvas-based NavDisplay/Map applications, too - also note that this information is readily available in various places, so there is no need to draw any wrong conclusions or spread any misinformation here.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=300979}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: Any plans for a new GUI? &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Dec 10th, 2016 &lt;br /&gt;
  |added  =  Dec 10th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |We've already fixed that in the (old) map dialog, by using an azimuthal equidistant projection (see [[images/7/71/Map_north_pole_route.jpg|screenshot]]). Porting the projection to Canvas is on my todo list. Such a projection is much much better for navigational use.&lt;br /&gt;
Curves in routes are not calculated by Canvas, nor by the ND though. It's the route manager that splits up a route in segments in order to get smooth transitions.&lt;br /&gt;
  |{{cite web |url={{forum url|p=227838}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Canvas ND performance issues with route-manager&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Gijs&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Dec 23&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The new ND uses the actual route-manager paths, which allows it to draw holdings, flyby waypoints (thanks to James recent work) etc. But we'll need the azimuthal projection anyway, so I'll bump my todo list&lt;br /&gt;
  |{{cite web |url={{forum url|p=227845}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Canvas ND performance issues with route-manager&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Gijs&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Dec 23&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |I do agree that it would make sense to sub-class the Canvas projection class and implement Gijs' changes there, like we originally discussed in the merge request: {{gitorious url|fg|flightgear|commit=3f433e2c35ef533a847138e6ae10a5cb398323d7}}&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
{{gitorious url|fg|flightgear|commit=96a2673dd8a08b70396e2be1e567c0e89d8cf6e3|path=src/GUI/MapWidget.cxx|line=1573}}&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
Ideally, we would expose the projection as a property for each Map so that it can be changed dynamically.&lt;br /&gt;
  |{{cite web |url={{forum url|p=227932}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Canvas ND performance issues with route-manager&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Wed Dec 24&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
These are the  most likely jobs ahead to improve MapStructure performance:&lt;br /&gt;
* adopt our caching scheme (i.e. referencing precreated raster images rather than having hundreds of openVG paths) {{Progressbar|80}}&lt;br /&gt;
** port existing MapStructure layers to make use of the cache (VOR, NDB, DME etc)&lt;br /&gt;
* optimize our use of navcache queries as per {{Issue|1320}}&lt;br /&gt;
* use cppbind to turn props.nas APIs into native code ?&lt;br /&gt;
* investigate exposing systime() stats for each loop/callback per layer ?&lt;br /&gt;
* expose canvas-specific stats via SGTimeStamp to each canvas element ?&lt;br /&gt;
&lt;br /&gt;
=== Porting the map dialog === &lt;br /&gt;
{{Progressbar|80}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |is anyone working on replacing the map dialog based upon this code? Because that seems like the most important use case to demonstrate that MVC pattern can support, aside from the NavDisplays.&lt;br /&gt;
  |{{cite web |url={{forum url|p=193275}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: How to display Airport Chart?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;zakalawe&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Mon Nov 04&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |I was just reviewing some UI changes, and realised we have three map options, which seems a little excessive. I’d like to deprecate my ‘manual' OpenGL map in favour of the canvas variant, but the Canvas variant is missing some features:&lt;br /&gt;
* can’t be panned with the mouse&amp;lt;br/&amp;gt;&lt;br /&gt;
* dialog doesn’t resize nicely&amp;lt;br/&amp;gt;&lt;br /&gt;
* no course + distance computation (although this feature also seems to be broken in the OpenGL version)&lt;br /&gt;
Is anyone actively working on the Canvas map? I’d be happy to collaborate to address remaining pieces so we can drop the old map (probably after 3.4 I guess, given the timeframe)&lt;br /&gt;
  |{{cite web |url=http://sourceforge.net/p/flightgear/mailman/message/33119678/&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;[Flightgear-devel] Canvas map dialog status&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;James Turner&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;2014-12-06&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  | I noticed a couple of bugs in the Canvas-in-PUI map in 3.3 git:&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;ol style{{=}}&amp;quot;list-style-type: decimal&amp;quot;&amp;gt;&amp;lt;li&amp;gt; The VOR radial is always true. It doesn't listen to the &amp;quot;Magnetic headings&amp;quot; checkbutton. In northern part of Canada true heading is used (because of the extreme magnetic variation), but anywhere else magnetic.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; When I click &amp;quot;Aircraft heading up&amp;quot;, the radial of the VOR doesn't get rotated.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; I cannot click and drag the map.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; The blue &amp;quot;flight history&amp;quot; line is very thick.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;When I zoom in, the map moves and display certain symbols to the edges for a split second, but then hides them. It should instead hide them before doing the translation of the symbol.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;The map hides too much symbols at the edges in any case. It should be more like the PUI map IMHO, which displays a symbol if it could be visible in the GUI widget.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;When I zoom in or out, the relative location of various symbols changes. For example, go to CYRB and zoom in on the map. The VOR seems to be between the 2 NDB's.&amp;lt;/li&amp;gt; &amp;lt;li&amp;gt;If you zoom all the way out, the VOR moves to the left of the 2 NDB's. The old PUI map doesn't have this problem.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Sometimes labels of waypoints are difficult to read.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ol&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  |{{cite web |url={{forum url|p=227124}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Phasing out MapWidget post 3.2&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;onox&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Mon Dec 15&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
this is now committed to fgdata&lt;br /&gt;
{{Git Topic Branch}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|The dialog as shown below will be part of FlightGear 3.2, future changes (mostly additional/refined layers and controllers) are planned for the subsequent release cycle, i.e. post 3.2}}&lt;br /&gt;
&amp;lt;gallery mode=packed widths=230px heights=230px&amp;gt;&lt;br /&gt;
Map-canvas-dialog.png|The upcoming Canvas/MapStructure based dialog (purely scripted) &lt;br /&gt;
Map-canvas-dialog-flightpath.png|Philosopher's new FLT (flight path/history) layer for the MapStructure framework (flight path exposed to Nasal by TheTom)&lt;br /&gt;
Map-canvas-dialog-route-mgr.png|The new Canvas Map dialog showing the waypoint/route layers originally developed by Gijs, and now ported to MapStructure by Philosopher&lt;br /&gt;
Map-canvas-dialog-storms-overlay.png|Gijs' original wxr (storms) layer ported to Philosopher's MapStructure framework (still experimental)&lt;br /&gt;
Map-canvas-chain-home-editor.png|Experimental interactive MapStructure layer for visually placing 3D objects&lt;br /&gt;
Map-canvas-dialog-native.png|native [[Canvas]] map widget windows without any PUI (legacy GUI) involved, supporting multiple independent instances - good for quick regression testing, but also profiling and benchmarking&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |the recent GUI work committed by TheTom means that we can phase out the PUI map-canvas.xml dialog and create the whole dialog procedurally using a native canvas window, because we really only need two types of widgets: buttons and checkboxes, which are both supported now, thanks to the new &amp;quot;Aircraft Center&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
[[File:Native Canvas MapWidget Dialog.png|450px|thumb|Native Canvas MapWidget dialog using just Canvas Widgets, the Map has been turned into a Canvas Widget itself, so that it can be easily embedded in other dialogs (obviously the whole thing still needs to be cleaned up, layouting isn't currently used yet). See {{forum link|p=213435}}]]&lt;br /&gt;
&lt;br /&gt;
[[File:Native Canvas MapWidget Dialog Prototyping.png|400px|thumb|More Canvas/MapWidget prototyping. See {{forum link|p=213435}}]]&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure dialog with clipping and event handling applied.png|400px|thumb|Screen shot showing TheTom's modified demo dialog with a [[MapStructure]] based map that has clipping and event handling applied, i.e. can respond to common mouse events in order to either display tooltips or support drag&amp;amp;drop-style GUI dialogs for editing map-like displays, e.g. for creating an [[Advanced weather]] GUI {{forum link|t=17642}}. Particular care must be taken to formalize z-index handling (rendering priority) for each MapStructure layer, which is something we still need to work out, especially for possibly overlapping symbols...]]&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-based Weather Editor Widget for Canvas using interactive and editable lcontroller files.png|500px|thumb|This screen shot shows an experimental MapStructure-based Weather Editor Widget for [[Canvas]] using interactive and editable lcontroller files that respond to GUI events so that map symbols can be interactively placed on a map.]]&lt;br /&gt;
&lt;br /&gt;
  |{{cite web |url={{forum url|p=213083}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: NavDisplay &amp;amp; MapStructure discussion (previously via PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Sat Jun 21&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Porting the airports.xml dialog ===&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The rationale being that we're in the process of turning our map-canvas.xml dialog into a generic, and reusable, widget for $FG_ROOT/Nasal/canvas/gui/widgets - the widget is using the new Layout engine to align other widgets (checkboxes &amp;amp;amp; buttons for now). And one of our goals is to also modernize airports.xml (the airport selection dialog) such that it can use MapStructure. The simplest way would be to simply turn the PUI CanvasWidget into a Canvas.Window() so that layouting etc works as expected, and so that we can simply show the new scripted MapWidget there.&lt;br /&gt;
  |{{cite web |url={{forum url|p=213493}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Using a PUI CanvasWidget as a Canvas.Window ?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Thu Jun 26&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[File:The legacy PUI-airport selection dialog using the new Canvas MapWidget.png|450px|thumb|This is a screen shot showing Stuart's PUI/XML based airport selection dialog with an embedded Canvas MapWidget that is driven by Philosopher's [[MapStructure]] framework using TheTom's latest GUI/Layouting changes (e.g. checkbox widgets).]]&lt;br /&gt;
&lt;br /&gt;
For runways.draw it would make sense to split up the function to have a helper function that draws a single runway.&lt;br /&gt;
Runway-drawing is fairly modular already, so we should be able to populate a SymbolCache with a handful of building blocks for '''any''' runway, and then scale/transform it as needed, while custom stuff would be rendered on top. That should help speed up things rather significantly. Also, we could then have a shared AirportCache that keeps a FIFO of 4x4 airports by using a single 1024x1024 texture. Multiple ND/dialog instances could then access the same cache to speed up things. Taxiways are a different beast, would need to be rendered and cached in full, because they're too custom drawing-wise.&lt;br /&gt;
&lt;br /&gt;
Regarding generic-canvas-map.xml: it would be awesome to remove this, but doing so will break Stuart's airport selection dialog ...&lt;br /&gt;
We need to get rid of map.nas first of all, i.e. port airport-selection to use MapStructure - because airport-select includes and parameterizes the corresponding file.&lt;br /&gt;
&lt;br /&gt;
I suppose we should continue this discussion in public, either on the forum or via the wiki - where we have a section on porting this dialog, and getting rid of map.nas&lt;br /&gt;
&lt;br /&gt;
I'd love to see all this stuff removed obviously - but there's still some work ahead AFAIU&lt;br /&gt;
But it should be perfectly doable within the upcoming release cycle. It's not exactly a lot of work either. Mostly refactoring and generalization.&lt;br /&gt;
The whole workaround is documented at: http://wiki.flightgear.org/Canvas_Map_API#Full_Example:_Creating_dialogs_with_embedded_Canvas_Maps&lt;br /&gt;
&lt;br /&gt;
The first step would be porting the missing layers to MapStructure *.symbol files, i.e. stuff like towers, airports (parking, runways, taxiways).&lt;br /&gt;
Then, we'll need to add another map controller for panning support.&lt;br /&gt;
The XML file itself will also procedurally add &amp;quot;toggle&amp;quot; checkboxes for each requested layer, so that's why there's so much code in it.&lt;br /&gt;
map-canvas.xml can probably become 100% Canvas based pretty soon. But the airport-selection dialog contains a ton of PUI widgets for which do not yet have Canvas equivalents. So I am more inclined to &amp;quot;just&amp;quot; port the 3-4 missing layers, add a map controller for panning and then adopt MapStructure there, at which point the original map.nas stuff can be deleted, it will have served its purpose by then, i.e. establishing the MVC separation.&lt;br /&gt;
&lt;br /&gt;
Also, some missing layers may benefit from caching to some extent, and styling should probably be supported by all of them.&lt;br /&gt;
&lt;br /&gt;
=== TFC ===&lt;br /&gt;
* generalize aircraft position controller to support AI/MP traffic&lt;br /&gt;
* investigate adopting our caching scheme to make the layer faster&lt;br /&gt;
&lt;br /&gt;
=== Hooray's Todo List ===&lt;br /&gt;
* 06/2014 MapStructure.nas:&lt;br /&gt;
** CENTERED/HDG/MAG TRK view modes (navdisplay.mfd) ?&lt;br /&gt;
** ILS &amp;amp; WIND layers are missing (see Map dialog, see navdisplay.mfd for WIND code)&lt;br /&gt;
** add StationaryObjectLayerController helper where update semantics are wrapper properly (i.e. used by the new NavaidSymbolLayer)&lt;br /&gt;
** introduce a LegSymbolLayer for LineSymbols (RTE/FLT)  ?&lt;br /&gt;
** instead of directly accessing properties like AP/RM/RADIO, encapsulate in driver hash (conceptually, we could read/display an AI flight plan for example)?&lt;br /&gt;
** encapsulate '''main'''-only layers (RTE,WPT) and depending features (AP/RM/RADIO stuff in VOR/DME etc) that won't work for AI/MP traffic (driver hash)&lt;br /&gt;
** LayerController: provide a method to enable/disable layers when paused (/sim/freeze), e.g. FLT doesn't make much sense to constantly redraw here ?&lt;br /&gt;
** the SAT (fetched image overlay) stuff is too hacky, need to introduce an '''OverlayLayer''' helper that wraps remotely fetched raster images&lt;br /&gt;
** displaying TCAS/AIS feeds can now be done via Nasal http bindings and running JSON queries, better sub-class MultiSymbolLayer to support feeds where the fetching method is the equivalent of searchCmd, but can be re-implemented for different URL schemes using Philosoper's/TheTom's demo code &lt;br /&gt;
** interactive stuff is not yet mature enough to work, unless it's just tooltips for each symbol, but could help us implement DATA ?&lt;br /&gt;
&lt;br /&gt;
* layers should probably encode information about being &amp;quot;safe&amp;quot; for non-main aircraft, i.e. a few layers don't make much sense for AI/MP traffic due to references to properties/APIs that are n/a, such as flight plan/routing, autopilot, instrumentation properties - those lookups should be disabled by the &amp;quot;driver&amp;quot; hash (or position controller).  &lt;br /&gt;
* SymbolLayer should probably be sub-classed for &amp;quot;static&amp;quot; layers (navaids) and &amp;quot;volatile&amp;quot; layers with movign objects as per Philosopher's comments&lt;br /&gt;
* LOD handling needs to be refined, cannot be just setScale() in complex cases like taxiways/runways - we better add an interface that receives notifications once the range is changed (MapController) and use different callbacks for certain ranges ? {{Progressbar|60}}&lt;br /&gt;
* work out a good way to integrate caching and styling, where each style variation would be a separate cache entry {{Progressbar|40}}&lt;br /&gt;
* once that works, we can easily implement animations on top of cached entries with different styles and use a timer to change/hide/show groups&lt;br /&gt;
* extend the SymbolCache class to integrate the dialog for inspection purposes, i.e. via a method so that each cache can be quickly visualized/inspected&lt;br /&gt;
** maybe maintain a refcount for each referenced cache entry ?&lt;br /&gt;
** provide a method freeSpots() ?&lt;br /&gt;
** introduce a CacheManager class as the super class managing different SymbolCache objects ?&lt;br /&gt;
* need to extend the svg parser to provide hooks for registering callbacks so that we can also support styling for SVG images, based on looking up elements by ID and override colors &lt;br /&gt;
* Symbol.Controller.getpos() is doing two things at once 1) checking if the obj is supported, 2) extracting lat/lon - it would be better to split this, so that we can reuse the ''is_supported'' check in other places, such as validating searchCmd() results&lt;br /&gt;
* MapController: need to expose some flags to determine if a map is used inside a GUI dialog or aircraft: This is  because we do not typically need to update most map layers shown in an aircraft when paused - but we may very well want to update layers when used inside a GUI dialog, i.e. check maketimer() use here.  &lt;br /&gt;
* make logging a part of the framework, overload print/printlog &lt;br /&gt;
* make profiling via systime() a part of the framework, for each controller/model/view (optional)&lt;br /&gt;
* investigate hardening, i.e. &lt;br /&gt;
** type-checking (typeof) &lt;br /&gt;
** e.g. searchCmd should always return a vector, and isa(type) should be supported by getpos {{Progressbar|60}}&lt;br /&gt;
** checking available symbols (and typeof) for scontroller/lcontroller and symbol files &lt;br /&gt;
* clean up existing files and add more comments {{Progressbar|20}}&lt;br /&gt;
** including a MapStructure URL (wiki) to each file so that people know where to look for further info {{Done}}&lt;br /&gt;
* add a new section: Porting Layers (i.e. from the old format to the new one)&lt;br /&gt;
* provide an option to suspend/restart MapStructure&lt;br /&gt;
* provide an option to reload MapStructure (layers) from disk (RAD)&lt;br /&gt;
* styling - will involve: {{Progressbar|30}} (currently being prototyped inside the DME layer)&lt;br /&gt;
** allow colors/fonts to be overridden, i.e. any draw .symbol callback would use a lookup hash that can be customized (instead of hard-coding things)&lt;br /&gt;
** allow size to be overridden (overlapping with LOD support)&lt;br /&gt;
** allow custom file names or callbacks to be provided for symbols (draw routines)&lt;br /&gt;
** i.e. come up with a &amp;quot;Styleable&amp;quot; class that exposes an interfaces that is implemented by StyleableColor, StyleableFont, StyleableSymbol&lt;br /&gt;
** this means that style-specific things should never be directly part of the draw routine itself, but need to be encapsulated, i.e. setColor/setColorFill/setSize/setText/setFont/setFontSize etc - so that these methods can be afterwards called on the canvas group - the styleable class should probably just sub-class Symbol.Controller to make this work&lt;br /&gt;
&lt;br /&gt;
== Roadmap post 3.2 ==&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Well, we've been talking about the lack of current navdata in FG recently. On the MapStructure side of things we really only need to encapsulate any NasalPositioned calls, to support arbitrary data - including even fetched (XML), or manually entered navaids/fixes. Such data could then  be centrally served or based on NaviGraph. So we wouldn't have to touch any of the existing NavDB stuff to work around its limitations.&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
So I am thinking about moving all NasalPositioned queries in MapStructure into a &amp;quot;driver&amp;quot; hash, but maybe more in line with the aircraftpos.controller stuff that Philosopher developed, just specific to some kind of &amp;quot;NavaidSource&amp;quot;. &amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
That would allow us to easily work around such limitations, so that I-NEMO, Hyde, Gijs etc can easily use the latest data on their EFB/ND. The other motivation  here is that I experimented with interactive layers, i.e. where objects could be visually placed on a map-these could be scenery objects, but also navaids. And once we move such assumptions out of the lcontroller files, we can trivially support other cool use-cases, not just GUI-based editing, but even simulated radio navigation or other &amp;quot;learning&amp;quot; tools:&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
(see the [http://www.sportys.com/morepics/17155r.jpg linked image]) (see the [http://a4.mzstatic.com/us/r30/Purple/v4/4f/00/fe/4f00fe9c-5ff7-142c-3e92-f71a23304471/screen480x480.jpeg linked image])&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; [http://www.visi.com/~mim/nav/ http://www.visi.com/~mim/nav/]&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; [http://www.pyrochta.ch/english/PageNavigationTrainer/PageVOR/vor.html http://www.pyrochta.ch/english/PageNavi ... R/vor.html]&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; [http://www.luizmonteiro.com/Learning_VOR_Sim_1.aspx http://www.luizmonteiro.com/Learning_VOR_Sim_1.aspx]&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  |{{cite web |url={{forum url|p=213215}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: 777 EFB: initial feedback&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Mon Jun 23&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* port missing layers {{Progressbar|70}}&lt;br /&gt;
* the granularity of the maketimer()-created update timers should be configurable/exposed when creating a layer, i.e. to allow aircraft developers to adjust timing as required&lt;br /&gt;
* handle signals to trigger controller updates for radio, autopilot, route manager or AI/MP and weather changes {{Progressbar|60}} &lt;br /&gt;
* adapt [[NavDisplay]] framework to use MapStructure internally {{Progressbar|80}}&lt;br /&gt;
* look into extending the controller framework for different use cases (ND/airport selection dialog) {{Progressbar|30}}&lt;br /&gt;
* look into porting the [[Map]] dialog {{Progressbar|20}}&lt;br /&gt;
* look into updating the airport-selection dialog {{Not done}}&lt;br /&gt;
* add a MapStructure-based chart to the route manager dialog {{Not done}}&lt;br /&gt;
* integrate the ND into other airliner cockpits (757,767 and Airbus series) to ensure that things remain generic {{Progressbar|20}}&lt;br /&gt;
&lt;br /&gt;
=== Framework ===&lt;br /&gt;
* we should add some helper functions for people to more easily do RAD when developing new layers/controllers, this would include supporting reloading from disk, and creating a simple canvas window to show the corresponding layers, maybe with a &amp;quot;reload&amp;quot; button that suspends and restarts everything after reloading - this would probably even help us {{Not done}}&lt;br /&gt;
* investigate adding a dedicated &amp;quot;Filter&amp;quot; class for positionedSearches, so that things like custom object filters (think ATC/RADAR etc) can be more easily implemented by implementing a corresponding interface (mirage2000) {{forum link|p=199555}} {{Not done}}&lt;br /&gt;
* logging should probably be handled by the framework directly, so that messages can be redirected or stored {{Not done}}&lt;br /&gt;
* support benchmarking/profiling of layers via systime() - as mentioned by Philosopher on the forum {{Not done}}&lt;br /&gt;
* a simple form of unit testing (sanity checks) would make sense for regression testing purposes, i.e. to ensure that the number of drawables never grows beyond the total number of elements (ditto for layers) {{Not done}}&lt;br /&gt;
* properly implement (separate) init/update at the framework level {{Not done}}&lt;br /&gt;
* consider supporting loops that are split across frames {{Not done}}&lt;br /&gt;
* add LOD support {{Progressbar|20}}&lt;br /&gt;
* add styling support (colors, different SVG images or draw callbacks for symbols) {{Not done}} &lt;br /&gt;
* many features would greatly benefit from having a simple animation framework, i.e. for changing/scaling symbols etc (not specific to positioned objects) {{Not done}}&lt;br /&gt;
* implement a caching scheme {{Progressbar|70}}&lt;br /&gt;
&lt;br /&gt;
== Using Layers ==&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-Layers.png|500px|Using MapStructure Layers]]&lt;br /&gt;
&lt;br /&gt;
the kind of object that is typically needed by MapStructure is just an geo-referenced position, which is just fancy lingo for anything that has a 3D position (lat,lon,altitude)&lt;br /&gt;
MapStructure will internally handle all the details to implement each layer efficiently.&lt;br /&gt;
&lt;br /&gt;
The framework works in terms of &amp;quot;symbols&amp;quot; and &amp;quot;layers&amp;quot; where each layer would have symbols, along with controllers for each symbol (i.e. to animate things, to change the color/style etc) - also, each layer can be controlled using a layer-controller, e.g. to change the range for example.&lt;br /&gt;
&lt;br /&gt;
I suggest to look at some of the simpler examples, e.g. the NDB symbol to see how everything is implemented.&lt;br /&gt;
You can basically load a SVG file or draw your own symbols using OpenVG instructions.&lt;br /&gt;
&lt;br /&gt;
Then, the layer controller will determine where symbols are to be drawn, and do any filtering (range, altitude, mountains, azimuth).&lt;br /&gt;
&lt;br /&gt;
So the result will just return a vector (resizable array) to the layer controller with drawables (symbols).&lt;br /&gt;
&lt;br /&gt;
You will probably want to experiment first with a really simple example to see how everything works.&lt;br /&gt;
&lt;br /&gt;
I suggest to try out the demo/example mentioned in the article.&lt;br /&gt;
&lt;br /&gt;
Ultimately, you can them combine many layers to form a single map, and each layer can have &amp;quot;many&amp;quot; different symbols&lt;br /&gt;
&lt;br /&gt;
== Creating new Layers ==&lt;br /&gt;
As you may have noticed, we have significantly grown the MapStructure docs meanwhile - the short-term goal here is that people should be able to come up with their own layers for MapStructure, or at least be able to help port the old files used by map.nas (*.model/*.layer/*.draw) to MapStructure (*.scontroller/*.lcontroller and *.symbol).&lt;br /&gt;
&lt;br /&gt;
Technically, there's not very much involved meanwhile. &lt;br /&gt;
&lt;br /&gt;
{{Note|&lt;br /&gt;
It makes sense to use existing layers as templates, i.e. copy a set of files that is close to what you want to implement - e.g.  a RADAR or ATC layer will typically involve processing AI/MP traffic (changing positions) - which is what the TCAS (TFC) layer is already doing - so it's a good idea to use that as a template for your new layer.&lt;br /&gt;
&lt;br /&gt;
Using other layers like VOR, NDB or DME would also be possible - but think about what these represent: navaids, with fixed geographic coordinates, while an ATC/RADAR layer would be all about showing live traffic, so it would be less work to reuse and customize a similar layer instead.&lt;br /&gt;
&lt;br /&gt;
Then again, using a complex layer to represent a simple thing would also be more customizing work than necessary obviously. You can also borrow things from different layers-for example, the VOR/DME layers contain support for animating symbols and range-selection based display modes.&lt;br /&gt;
Thus, it's a good idea to spend 5-10 minutes looking through existing files and playing with them, to see where you can borrow code from.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
So, porting a simple layer can now be done within a few minutes. It mostly boils down to:&lt;br /&gt;
&lt;br /&gt;
* finding a similar layer (i.e. for static objects like navaids - just take an existing navaid layer, for dynamic non-static objects (AI/MP traffic), look at the TFC layer instead - e.g. this could be used for porting WXR/storms or to come up with a radar/ATC layer)&lt;br /&gt;
* copying a set of similar *.lcontroller/*.scontroller and *.symbol files (you can also combine things from different files, e.g. to reuse animated symbols (altitude arc)&lt;br /&gt;
* add either custom drawing routines (Nasal/Canvas) or Inkscape svg files&lt;br /&gt;
* write (or port) the corresponding draw() routine (inside the symbol) file&lt;br /&gt;
* registering the whole thing in MapStructure.nas to load the new layer files&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Adding-new-MapStructure-Layers.png]]&lt;br /&gt;
&lt;br /&gt;
Copy three files (all in Nasal/canvas/map/): &lt;br /&gt;
* *.symbol (drawing/update routine(s))&lt;br /&gt;
* *.scontroller (symbol controller)&lt;br /&gt;
* *.lcontroller (layer controller)&lt;br /&gt;
&lt;br /&gt;
People interested in developing/maintaining MapStructure layers, will probably want to start with a simple example first.&lt;br /&gt;
&lt;br /&gt;
The most straightforward starting points should be:&lt;br /&gt;
* airplaneSymbol&lt;br /&gt;
* tower&lt;br /&gt;
* parking&lt;br /&gt;
* wxr/storms&lt;br /&gt;
&lt;br /&gt;
You may also want to check out [[MapStructure Debugger]].&lt;br /&gt;
&lt;br /&gt;
=== Internals ===&lt;br /&gt;
&lt;br /&gt;
Each file represents a virtual class (no explicit hash needed - just use caller(0)[0]).&lt;br /&gt;
&lt;br /&gt;
First, we need to set up class things, so each file &amp;quot;bootstraps&amp;quot; itself.&lt;br /&gt;
&lt;br /&gt;
Class members/local variables:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| name&lt;br /&gt;
! scope=&amp;quot;col&amp;quot; width=&amp;quot;340&amp;quot; | Description&lt;br /&gt;
! *.symbol&lt;br /&gt;
! *.scontroller&lt;br /&gt;
! *.lcontroller&lt;br /&gt;
! *.controller&lt;br /&gt;
! Links&lt;br /&gt;
|-&lt;br /&gt;
! parents = &lt;br /&gt;
| ''Set up class inheritance.''&lt;br /&gt;
|align=&amp;quot;center&amp;quot;| &amp;lt;tt&amp;gt;[DotSym]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;| &amp;lt;tt&amp;gt;[Symbol.Controller]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;| &amp;lt;tt&amp;gt;[SymbolLayer.Controller]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;| &amp;lt;tt&amp;gt;[Map.Controller]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! __self__ = &lt;br /&gt;
| ''Current class being generated.''&lt;br /&gt;
|align=&amp;quot;center&amp;quot; colspan=&amp;quot;4&amp;quot;| &amp;lt;tt&amp;gt;caller(0)[0]&amp;lt;/tt&amp;gt;&lt;br /&gt;
| [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.symbol|line=4}}] [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.scontroller|line=3}}] [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.lcontroller|line=3}}]&lt;br /&gt;
|-&lt;br /&gt;
! name = &lt;br /&gt;
| ''Name to use for referencing (via &amp;lt;tt&amp;gt;Symbol&amp;lt;wbr/&amp;gt;[SymbolLayer]&amp;lt;wbr/&amp;gt;[.Controller]&amp;lt;wbr/&amp;gt;.get(name)&amp;lt;/tt&amp;gt;, etc.) and console messages.''&lt;br /&gt;
|align=&amp;quot;center&amp;quot; colspan=&amp;quot;4&amp;quot;| name of file before the extension, or otherwise as appropriate; e.g. &amp;lt;tt&amp;gt;&amp;quot;VOR&amp;quot;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! element_type = &lt;br /&gt;
| ''Type of [[Canvas Element]] to use; becomes &amp;lt;tt&amp;gt;me.element&amp;lt;/tt&amp;gt;.''&lt;br /&gt;
| &amp;lt;tt&amp;gt;&amp;quot;group&amp;quot;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;&amp;quot;path&amp;quot;&amp;lt;/tt&amp;gt; (usually)&lt;br /&gt;
| {{N/a}}&lt;br /&gt;
| {{N/a}}&lt;br /&gt;
| {{N/a}}&lt;br /&gt;
| {{gitorious source|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.symbol|line=7|text=VOR}} {{gitorious source|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/FIX.symbol|line=7|text=FIX}}&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Adding this instance to MapStructure's dictionaries and make it be the default controller (where &amp;quot;name&amp;quot; is an arbitrary-but unique-handle used to reference it inside of OOP abstraction):&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! *.symbol&lt;br /&gt;
! *.scontroller&lt;br /&gt;
! *.lcontroller&lt;br /&gt;
! *.controller&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;DotSym.makeinstance( name, __self__ );&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;Symbol.registry[ name ].df_controller = __self__;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;SymbolLayer.Controller.add( name, __self__);&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;Map.Controller.add( name, __self__);&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| {{N/a}}&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;Symbol.registry[ name ].df_controller = __self__;&amp;lt;/tt&amp;gt;&lt;br /&gt;
| {{N/a}}, see below regarding the SymbolLayer&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;Map.df_controller = __self__;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Also in *.lcontroller, we need to set up the actual MultiSymbolLayer, whose methods are already handled by MapStructure, so we only need to specify a few items:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
SymbolLayer.add(name, {&lt;br /&gt;
    parents: [MultiSymbolLayer],&lt;br /&gt;
    type: name, # Symbol type, i.e. for Symbol.get( ... )&lt;br /&gt;
    df_controller: __self__, # controller to use by default -- this one&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|There's also a SingleSymbolLayer which has a slightly different API -- no searchCmd(), only a getModel(), see below}}&lt;br /&gt;
&lt;br /&gt;
=== Symbols ===&lt;br /&gt;
[[File:MapStructure-Symbol-UML.png]]&lt;br /&gt;
&lt;br /&gt;
Symbols are pretty simple - just stick to the members/methods outlined above: &amp;lt;tt&amp;gt;.init()&amp;lt;/tt&amp;gt; is called when the symbol is created, and may optionally call &amp;lt;tt&amp;gt;me.update()&amp;lt;/tt&amp;gt; to ensure the symbol is immediately ready to go; and &amp;lt;tt&amp;gt;.draw()&amp;lt;/tt&amp;gt; is called each time the symbol is updated. Do '''not''' overwrite .update()! It is already handled by MapStructure, so use .draw() instead.&lt;br /&gt;
&lt;br /&gt;
{{Note|Should we add a runtime/sanity check to ensure that symbol.update is a func and points to MapStructure, i.e. is not overridden ?}}&lt;br /&gt;
&lt;br /&gt;
For each object, one is provided with a handle to the model and controller of the symbol:&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;model&amp;quot; object (me.model) is ultimately specified by the one creating the Canvas.Symbol - aka the SymbolLayer.Controller. It is usually a [[List_of_Nasal_extension_functions#Positioned_Object_Queries|positioned object]], though it could be anything, as long as the methods required by MapStructure are provided by the programmer - specifically the latlon()/getPos() helpers.&lt;br /&gt;
&lt;br /&gt;
(In the case of the TFC layer (handling AI/MP traffic), it is a hash wrapping props.Node and geo.Coord).&lt;br /&gt;
&lt;br /&gt;
The position of the symbol is automatically handled by MapStructure by extracting position information from the object via the corresponding .getPos() call and applying it via .setGeoPosition() on me.element, so the programmer only has to worry about drawing it. The model, however, often provides other information relevant to drawing the symbol, like the frequency of a VOR, or how fast it is going.&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;controller&amp;quot; object (me.controller) is typically obtained from the corresponding *.scontroller file (as a default, see Symbol.df_controller), but can be overridden by the creator of the symbol (hopefully it supports the same API to be compatible!). This will handle the rest of information ''not'' handled by the model, typically via &amp;quot;query&amp;quot; methods - stuff like whether this symbol is selected in some way (e.g., by radio setting), settings of the parent map (like range), etc.&lt;br /&gt;
&lt;br /&gt;
In both the .init() and .draw() methods, one can use the automatically created canvas element (me.element) to draw the actual symbol. These are usually static images that are procedurally created via the draw() callback, so it's best to set up lazily-rendered-yet-persistent elements that are simply hidden or shown as needed. &lt;br /&gt;
&lt;br /&gt;
If the symbol is really simple, such that it only needs to be drawn once and never updated, then draw it in .init() and don't define .draw() at all.&lt;br /&gt;
&lt;br /&gt;
Once we adopt the built-in caching scheme, some things will change here - specifically such that each variation in styling of a certain symbol is pre-created so that it will later on only need to be referenced as a canvas raster image (via a texture map lookup).&lt;br /&gt;
&lt;br /&gt;
{{Note|Stlying is currently being prototyped, see the DME.symbol file in the topics/canvas-radar branch for examples.}}&lt;br /&gt;
&lt;br /&gt;
To support LOD-handling and symbol-specific styling, we will also need to expose a handful of methods to encourage separation of scale/style-specific things into symbol files, such as the following methods which should ideally not be hard-coded in symbol files, but use hash lookups for customization purposes: &lt;br /&gt;
* setColor()&lt;br /&gt;
* setColorFill()&lt;br /&gt;
* setLineWidth()&lt;br /&gt;
* setScale()&lt;br /&gt;
* setSize()&lt;br /&gt;
* setFont()&lt;br /&gt;
* setFontSize()&lt;br /&gt;
* setText()&lt;br /&gt;
* setImage()&lt;br /&gt;
&lt;br /&gt;
Currently, we have a bunch of symbol files calling directly the corresponding canvas equivalents via method chaining - however, to allow LOD-handling and styling, we must encourage separating these things, so that things can be customized as needed, i.e. by calling the symbol's me.apply_styling() method after the draw/update routine has finished-which will allow aircraft developers to easily use custom fonts/size/colors or symbols - without having to edit -or worse- duplicate the .symbol-file itself&lt;br /&gt;
&lt;br /&gt;
{{Note|I am wondering if we could use some fancy metaprogramming to compile a draw() callback and ensure that it does NOT use certain Canvas APIs directly ? That would be kinda cool and useful, i.e. we could do a dry test-run to make sure that methods only use certain allowed APIs. One possible method would be overloading the canvas namespace to provide sstubs for each allowed/disallowed API and use call() to call each method and keep track via any disallowed APIs were called by using a counter or even just doing die()-so that people would get an error message along the lines of &amp;quot;please do not use setColor/setColorFill etc inside the draw() callback. Given that MapStructure itself handles positioning via setGeoPosition(me.element), we could even watch out for such mistakes that way}}&lt;br /&gt;
&lt;br /&gt;
=== The SymbolCache ===&lt;br /&gt;
&lt;br /&gt;
For the time being, the main optimization that helps speed up rendering Canvas/MapStructure-based displays like the [[NavDisplay]], is using caching. &lt;br /&gt;
&lt;br /&gt;
Caching is accomplished by a little helper framework called '''SymbolCache''' which sets up an empty Canvas texture for storing required symbols there. &lt;br /&gt;
&lt;br /&gt;
[[File:PropertyBrowser with Canvas Preview.png|thumb|Slightly extended Property Browser to show a preview for each Canvas for troubleshooting/debugging purposes, as per {{forum link|p=195232}}.]]&lt;br /&gt;
&lt;br /&gt;
This is where symbols for VORs, DMEs, FIXes and waypoints will be kept. &lt;br /&gt;
&lt;br /&gt;
Internally, each *.symbol file will still contain all the logic required to actually render the corresponding symbol, which may include  hard-coded OpenVG drawing commands, but also SVG or raster images. &lt;br /&gt;
&lt;br /&gt;
[[File:Early MapStructure Caching Experiments.png|thumb|MapStructure/SymbolCache experiments for caching symbols and symbol instancing {{forum link|p=193672}}]]&lt;br /&gt;
&lt;br /&gt;
However, the cache will be dynamically populated according to all the symbols that are required for each layer. This approach proved quite efficient and straightforward so that even the extra500 developers adopted this method on their [[Avidyne Entegra R9]] instrument. &lt;br /&gt;
&lt;br /&gt;
In the meantime, the SymbolCache has been significantly extended to also support styling: in other words, the SymbolCache now even works for *.symbol files supporting styling by being aware of styling-relevant attributes (think width, symbols, colors etc) and will create distinct cache entries for each symbol variant. &lt;br /&gt;
&lt;br /&gt;
Under the hood, this is using some fancy meta-programming that Philosopher came up with last year - but all you need to know as a MapStructure/ND contributor is that styling and caching work in conjunction as long as you follow a few simple rules - which can easily translate into significant frame rate gains when compared to the old method. This section is intended to describe the basic method, as well as provide a few examples/pointers - we're hoping to grow this over time.&lt;br /&gt;
&lt;br /&gt;
First of all, let's consider an existing layer/example already using caching properly: VOR.symbol:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5,12.5)&lt;br /&gt;
        .lineTo(7.5,12.5)&lt;br /&gt;
        .lineTo(15,0)&lt;br /&gt;
        .lineTo(7.5,-12.5)&lt;br /&gt;
        .lineTo(-7.5,-12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width)&lt;br /&gt;
        .setColor(color);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a function named '''drawVOR''' (note, that the name is arbitrary) which accepts a single argument: a canvas group.&lt;br /&gt;
The function body itself then uses the passed group to create the relevant geometry for the corresponding symbol (in this case a VOR symbol).&lt;br /&gt;
There are two configurable (&amp;quot;styling&amp;quot;) settings: &lt;br /&gt;
* line_width&lt;br /&gt;
* color&lt;br /&gt;
&lt;br /&gt;
Styling defaults for things like line_width and color are set up in each symbol file - for example, refer to VOR.symbol:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
SymbolLayer.get(name).df_style = { # style to use by default&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    range_line_width: 3,&lt;br /&gt;
    radial_line_width: 3,&lt;br /&gt;
    range_dash_array: [5, 15, 5, 15, 5],&lt;br /&gt;
    radial_dash_array: [15, 5, 15, 5, 15],&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    active_color: [0, 1, 0],&lt;br /&gt;
    inactive_color: [0, 0.6, 0.85],&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''df_style''' is a new hash that contains styling defaults for this particular layer.&lt;br /&gt;
&lt;br /&gt;
Otherwise, everything else in the drawVOR() function can be considered to be &amp;quot;hard-coded&amp;quot;. When looking at other examples, the only other thing worth keeping in mind here is that  Nasal will implicitly return the last expression to the caller absent any explicit return statements - i.e. the group will be returned to the caller (for the sake of clarity, you could also add an explicit return statement, as is done in the snippet above).&lt;br /&gt;
&lt;br /&gt;
However, the code snippet above is just a drawing routine that is still unknown to the system, so that needs to be done is to set up a corresponding cache entry, which can be seen below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var cache = StyleableCacheable.new(&lt;br /&gt;
    name: name,&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: SymbolCache32x32,&lt;br /&gt;
    draw_mode: SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: [&amp;quot;line_width&amp;quot;, &amp;quot;color&amp;quot;],&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What is happening here is that a new cache entry supporting styling is set up (refer to MapStructure.nas for details), specifically:&lt;br /&gt;
* a new cache is set up using the name specified at the top of the file: '''VOR'''&lt;br /&gt;
* drawVOR is passed as the drawing function &lt;br /&gt;
* the cache texture to be used is SymbolCache32x32 (which is always available, provided by MapStructure itself)&lt;br /&gt;
* next, a draw_mode is set up (refer to  MapStructure.nas for etails)&lt;br /&gt;
&lt;br /&gt;
And finally, the really cool stuff is happening in the last line: This is where we meet again the two styling-related variables we saw earlier in the actual drawVOR() implementation: there's an argument called '''relevant_keys''' which should be a vector of symbols that are styling-related. This can be used by the SymbolCache/MapStructure framework to tell if a styled symbol is already cached or not.&lt;br /&gt;
&lt;br /&gt;
All of these steps may look complicated at first, but the difficult stuff is happening behind the scenes - now, when it comes to actually using/accessing our &amp;quot;style-able cache&amp;quot;, the only thing you need to know is how to get a certain pre-cached symbol out of the cache, which is why we'll take another look at VOR.symbol and its draw() implementation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# determine the color to be used (remember, this is relevant for styling!) &lt;br /&gt;
me.style.color = active ? me.style.active_color : me.style.inactive_color; &lt;br /&gt;
&lt;br /&gt;
# look up the correct symbol from the cache and render it into the group as a raster image, applying custom scaling&lt;br /&gt;
me.icon_vor = cache.render(me.element, me.style).setScale(me.style.scale_factor);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What is happening here is that the cache entry is looked up for the selected style using '''me.style''', the textured quad (sub-texture) is dynamically retrieved from the SymbolCache and rendered into the canvas group specified via '''me.element''' - finally, scaling is applied to the raster image.&lt;br /&gt;
&lt;br /&gt;
Another example can be seen in FIX.symbol&lt;br /&gt;
&lt;br /&gt;
There are still several layers where caching+styling isn't widely used yet - however, over time this is the correct method to lighten the canvas workload quite a bit, and doesn't require any C++ level modifications. If you're seeing heavy impact on performance with complex layers, we suggest you explore adding styling and caching support as described above.&lt;br /&gt;
&lt;br /&gt;
Originally, the whole point of the cache was to simplify symbol management - meanwhile, it also supports styling. Under the hood, all it is dealing with is Canvas elements rendered to a single canvas, with each element having look-up coordinates conveniently stored in a hash. Which means that the SymbolCache could also be easily adapted to become a '''LayerCache''' at some point.&lt;br /&gt;
&lt;br /&gt;
This may be very useful in order not to re-draw redundant layers: for instance, the compass rose on the ND can be considered &amp;quot;redundant&amp;quot;: no matter how many NDs you are displaying - it makes sense to treat the compass rose as a dedicated layer on its own (see APS.* for examples) and then render that into its own texture map - we could easily set up a LayerCache analogous to the existing SymbolCache - transformations (rotating) would then merely be applied to the referenced raster image - all of a sudden, there will be less work for shivaVG (the OpenVG rendering back-end) to do, because the compass rose will rarely -if ever- need to be updated at all.&lt;br /&gt;
&lt;br /&gt;
All variations will be rendered into a layer cache and the COMPASS.symbol file would merely reference the correct sub-texture (e.g. plan/arc), and merely update/rotate the raster image child referenced by the actual ND. &lt;br /&gt;
&lt;br /&gt;
We kinda discussed the technique a few times already - i.e. introducing the concept of an &amp;quot;Overlay&amp;quot;-layer would make sense at some point- typically, this could be shared among multiple instances of a MFD (think ND/PFD) - this would even be more efficient than the existing hard-coded od_gauge based instruments are currently.&lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
Basically, you should look at Canvas-based MFDs and ask yourself which elements/layers are likely to be identical (or close enough), so that it would make sense to share certain elements (imagine background images, symbols, overlays and so on). Usually, this will reduce the workload for the Canvas system quite significantly, because an otherwise &amp;quot;complex&amp;quot; layer containing -for instance- OpenVG primitives will only ever be drawn/updated once during initialization and then merely referenced by instances of the actual instrument using it. You'll quickly see the merits of using this approach once you imagine rendering many (think 10+) instances of an ND or PFD.&lt;br /&gt;
&lt;br /&gt;
The basics on turning our existing SymbolCache framework into a LayerCache will be covered below:&lt;br /&gt;
&lt;br /&gt;
{{Caution|This code is still experimental and hasn't been tested yet ...}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# this sets up a new Canvas texture 1024x1024 with 4 locations for cached layers (each 512x512) &lt;br /&gt;
var compassLayerCache = canvas.SymbolCache.new(1024, 512); &lt;br /&gt;
&lt;br /&gt;
# FIXME: check what else is required to cache SVG files loaded into a texture map  ...&lt;br /&gt;
compassLayerCache.add(name: 'compass', callback: func(group) {&lt;br /&gt;
    canvas.parsesvg(group, &amp;quot;Nasal/canvas/map/Images/boeingND.svg&amp;quot;, { 'font-mapper': me.nd_style.font_mapper });&lt;br /&gt;
    group.getElementById('compass').updateCenter();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# TODO: at some point, this could be a foreach loop moving all compass variants&lt;br /&gt;
# into the LayerCache as per navdisplay.styles (compass, compassApp, compassMapCtr)&lt;br /&gt;
&lt;br /&gt;
# troubleshooting code to inspect the contents of the LayerCache ...&lt;br /&gt;
var window = canvas.Window.new([1024, 1024], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(compassLayerCache.canvas_texture);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|We should explore what's required in order to support &amp;quot;animated-style-able layers&amp;quot; - for instance, we could register a single callback to animate a texture and avoid redundant updates this way. Equally, this would allow us to register an update callback along with a layer - e.g. for transforming/rotating the compass rose).}}&lt;br /&gt;
&lt;br /&gt;
=== Controllers ===&lt;br /&gt;
[[File:MapStructure-Controller-UML.png]]&lt;br /&gt;
&lt;br /&gt;
With &amp;quot;models&amp;quot; (=what is to be drawn) and &amp;quot;views&amp;quot; (=how it is to be drawn) being typically generic -and thus- shared, '''controllers''' are meant to handle all the specific implementation details and behaviors of the corresponding object itself (Symbol, SymbolLayer, Canvas Map, etc.). The idea being that people will normally only need to parametrize an existing controller, or at worst, copy and customize an existing controller file to be able to use existing layers.&lt;br /&gt;
&lt;br /&gt;
For most of the API, the .new() should be optional and return nil, but if resource management is required (listeners or timers), set up listeners/timers during .new(), store them in a member list, and remove them in .del() using removelistener(). &lt;br /&gt;
&lt;br /&gt;
Timers should be added using the maketimer API.&lt;br /&gt;
&lt;br /&gt;
All model objects or canvas map objects should be passed as the first argument to controllers' methods (FIXME: need to make sure this holds, see comment: [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/MapStructure.nas|line=218}}]).&lt;br /&gt;
&lt;br /&gt;
==== Symbol Controller ====&lt;br /&gt;
[[File:MapStructure-SymbolController-UML.png]]&lt;br /&gt;
&lt;br /&gt;
{{Note|In FlightGear 3.1+ there's one additional step between init/draw which creates cache entries for symbols during initialization and may only use a cache lookup in draw/update. Obviously, caching makes only sense for symbols that are fairly static - i.e. text labels or animated elements won't typically use caching at all. You could implement a simple animation by toggling between different cache elements though.}}&lt;br /&gt;
&lt;br /&gt;
This is very simple: default symbol controllers can just be wrappers for the corresponding SymbolLayer controller, e.g. [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.scontroller}}].&lt;br /&gt;
&lt;br /&gt;
There's a simple API, which currently just passes data on to the layer controller.&lt;br /&gt;
&lt;br /&gt;
In general, you will want to make sure that your classes implement the '''geo.Coord''' interface (easy to do by inheriting from geo.coord) - otherwise, you will typically need to add special code to handle custom classes, to extract the required values, such as latitude-deg and longitude-deg.&lt;br /&gt;
&lt;br /&gt;
For this, you will want to refer to the getpos method in Symbol.Controller (see MapStructure.nas).&lt;br /&gt;
This is also where Nasal ghosts are handled (see for example: positioned/Navaid or Fix).&lt;br /&gt;
&lt;br /&gt;
Another example, [{{gitorious url|fg|hoorays-fgdata|path=Nasal/canvas/map/TFC.lcontroller}}], uses the controller as a wrapper for the model, but this should really be moved to the model object itself.&lt;br /&gt;
&lt;br /&gt;
FIXME: we need wrapper objects for positioned, so that a class can handle higher-level operations  (e.g. like .isActive()). Something like [http://docs.python.org/3/library/collections.html#collections.UserDict collections.UserDict] in Python.&lt;br /&gt;
&lt;br /&gt;
Yet another use would be to have the controller manage listeners for updating its symbol, like the SymbolLayer Controller does for the whole layer. This would be useful for, e.g. keeping track of if a certain symbol changes place, or such. Make sure to implement .new() and .del() functions!&lt;br /&gt;
&lt;br /&gt;
FIXME: I don't think that updating a single symbol can be handled currently.&lt;br /&gt;
&lt;br /&gt;
==== searchCmd: Filtering ====&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
{{WIP}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Canvas-MapStructure-Demo-Layer-with-custom-searchCmd.png|550px|thumb|A simple MapStructure layer called DEMO that renders a circle of NDB symbols (SVGs) in the vicinity of the aircraft using geo.nas and its  helpers like geo.aircraft_position() and the geo.Coord.apply_course_distance(course, dist) method as per {{forum link|p=213177}}. See [[Canvas Radar]] to learn more. ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The '''searchCmd()''' method is responsible for populating a vector with results that are to be drawn by the '''draw()''' method in the *.symbol file. Each object will typically be either a NasalPositioned ghost (i.e. a C++ FGPositioned object returned from a positioned query) or a Nasal hash with latitude/longitude fields/methods, or just a geo.Coord object create via geo.Coord.new().&lt;br /&gt;
&lt;br /&gt;
For example, for navaid-based layers, this will normally serve as the back-end for a '''positionedSearch''' using a delta of the current result, compared to the previous result. This is done to enable a layer to tell how many results are new/missing, to selectively update those - rather than having to always delete all previous results and add all new ones.&lt;br /&gt;
&lt;br /&gt;
Whenever a new element (object) is added to the vector, the '''.onAdded()''' method will be invoked to add a new symbol to list of managed symbols - once an element is removed, the '''.onRemoved()''' method will be called to call the symbol's destructor.&lt;br /&gt;
&lt;br /&gt;
Some of the more straightforward examples are to be found in the implementation of navaid layers:&lt;br /&gt;
* NDB&lt;br /&gt;
* VOR&lt;br /&gt;
* DME&lt;br /&gt;
* FIX&lt;br /&gt;
&lt;br /&gt;
Here, searchCmd is typically just a one-liner calling an existing positionedSearch API, such as '''findNavaidsWithinRange()''', which always returns a vector of positioned ghosts (which are automatically supported by MapStructure). &lt;br /&gt;
&lt;br /&gt;
{{Note|For the sake of simplicity, the MapStructure framework exposes a generator function that returns a proper callback for the most common needs as long as the lcontroller itself inherits from NavaidSymbolLayer. Which is why most navaid lcontroller files will typically just invoke '''make(TYPE_OF_NAVAID)'''  to obtain a proper searchCmd callback. In the future, we'll probably further generalize lcontroller files accordingly, i.e. by coming up with generators and/or base classes for the more common purposes and needs (post 3.2).}}&lt;br /&gt;
&lt;br /&gt;
The only thing that's typically done there is to get the query range (1st argument of the API) via a delegate-callback out of the layer controller, so that navaid range can be easily provided by the MapStructure front-end - such as a GUI dialog or a cockpit display like the ND. in a custom, non-navaid layer, the whole query-type thing can be ignored - it's not even used anywhere, it's really only used to &amp;quot;make&amp;quot; a searchCmd() for navaids like vor, ndb, dme etc - but for that to work, you would have to inherit from &amp;quot;NavaidSymbolLayer&amp;quot;, whereas you're probably using &amp;quot;MultiSymbolLayer&amp;quot; now - which is why it's not having any effect. It's really only used in a single place. So &amp;quot;query_type&amp;quot; is really just for navaids.&lt;br /&gt;
&lt;br /&gt;
A more sophisticated example is to be found in the traffic layer (TFC) which handles AI/MP traffic and processes the corresponding properties. This is also where you can see a bunch of helper functions used to &amp;quot;filter&amp;quot; results, e.g. based on range. So you could add other filtering heuristics there.&lt;br /&gt;
&lt;br /&gt;
For other more involved examples, see the implementation of the ROUTE (RTE) and WAYPOINT (WPT) layers.&lt;br /&gt;
&lt;br /&gt;
Support for simple animations can be provided by changing size/color of a symbol if required, i.e. to use a different style for &amp;quot;active/close&amp;quot; symbols (e.g. waypoints). Such simple animations can be pre-created by populating a custom SymbolCache with instances of each required variation in style, more complex animations are better manually implemented by customizing the draw() routine inside the .symbol file.&lt;br /&gt;
&lt;br /&gt;
==== SymbolLayer Controller ====&lt;br /&gt;
For now, VOR.lcontroller simply handles the individual controller operations on a per-layer scale, e.g. looking up if the VOR is a selected frequency via a list of current frequencies copied from property tree.&lt;br /&gt;
&lt;br /&gt;
For MultiSymbolLayers: this is also responsible for the searchCmd (mandatory!), which handles searching for the model objects used by symbols, and returns a current list of models. The models are compared to the previous list, and models are added/removed to match the new list. If custom equality comparison is needed (i.e. outside of &amp;lt;tt&amp;gt;id(a) == id(b)&amp;lt;/tt&amp;gt;), set layer.searcher._equals(a,b) to an appropriate function during construction.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For SingleSymbolLayers: this is responsible for the mandatory getModel() to provide a model object. This is called once and used for the lifetime of the layer, so the object should be dynamic (this works particularly well with a simple property node that supplies position/latitude-deg + position/longitude-deg or latitude-deg + longitude-deg -- this can of course be extended, see [{{gitorious url|fg|fgdata|commit=4732a3b620cd0df36ebffdba76a3872c43539bf8|path=Nasal/canvas/MapStructure.nas#L243-246}}]). If the returned object has an update() method, this is called before the position is retrieved.&lt;br /&gt;
&lt;br /&gt;
{{Note|the _equals line adds a new function to the layer's searcher hash - we need to provide a way to check for &amp;quot;equality&amp;quot;, i.e. for navaids that could be position and/or the ID (name). For custom/new layers, we need to provide a custom equality check function. What you are doing there is just adding a custom equality check function that always returns &amp;quot;false&amp;quot; (not equal). This is used by MapStructure to &amp;quot;smartly&amp;quot; identify and differentiate between old and new objects, i.e. to reduce workload and improve performance - imagine the &amp;quot;FIX&amp;quot; layer, which may have hundreds of fixes - while flying, a few dozen will be &amp;quot;new&amp;quot; ones, while most others will be &amp;quot;old&amp;quot; - we'll only remove the old ones, and only add new ones. If the custom definition is not provided, you should get an error suggesting that you add a corresponding method so that the underlying logic can check all objects for equality - see the bottom of the MapStructure article for details, or search for &amp;quot;_equals&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |the real reason behind the _equals is because it's a design hack: the geo difference search function (whatever it's called) originally supported almost any object through Nasal, but since Tom extended it in C++ space it doesn't really work with non-positioned objects (he uses cppbind typecasting into positioned objects), so I retained the old capability by allowing the user to specify a custom equals function to use the old Nasal-space difference engine. (For non-positioned layers this allows us to use roughly the same mechanism with Nasal objects/classes.)&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
Setting _equals to a &amp;quot;return 0&amp;quot; function simply means that each object will be taken off and recreated on each search. But, when there's a better id to use, MapStructure can remove only the old ones and add only the new ones. How that is done depends on both the model object and the drawing – for instance, if a *.symbol file says to completely redraw the Canvas group based on the model, that means it potentially could be used with different models during its lifetime, whereas if the symbol is only set up once on initialization, it would not reflect changes in it's model object and thus should be removed when it is gone.&lt;br /&gt;
  |{{cite web |url={{forum url|p=213308}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Get objects to show up on Map/Radar&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Philosopher&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Jun 24&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The other ugly thing about _equals is that it's essentially duplicated by MapStructure {{gitorious source|fg|fgdata|commit=fcf18d70e20ef629d96367cd0907dae6178e1997|path=Nasal/canvas/MapStructure.nas|line=516|text=here}}, and is used {{gitorious source|fg|fgdata|commit=fcf18d70e20ef629d96367cd0907dae6178e1997|path=Nasal/canvas/MapStructure.nas|line=894|text=here}} to find models inside of the Layer's internal list, while the geo search obviously uses a competing approach. I might get around to fixing that ;). Basically MapStructure's whole flow is: layer searchCmd -&amp;amp;gt; models -&amp;amp;gt; symbols -&amp;amp;gt; canvas drawings.&lt;br /&gt;
  |{{cite web |url={{forum url|p=213308}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Get objects to show up on Map/Radar&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Philosopher&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Jun 24&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The main thing to keep in mind here is that many MapStructure layers deal with positioned objects, i.e. objects that have lat/lon/altitude - internally, the system uses a &amp;quot;diff&amp;quot; (delta) method to compare the current result set against the previous result set to tell how many new/removed items are there - so that things can be selectived updated (added/removed), i.e. to only partially redraw/update things for the sake of efficiency.&lt;br /&gt;
  |{{cite web |url={{forum url|p=216407}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Live WXRadar MapStructure Layer Development&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Sun Aug 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
TFC (TCAS) is a bit problematic to test. Assuming that the map's range is set to ~ 50 nm, we could simply use the .apply_course_distance() method in geo.nas to create 10 objects in a range of 20 nm with a 36 degree spacing using something along the lines of.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var searchCmdTest = func {&lt;br /&gt;
var mainPos = geo.aircraft_position();&lt;br /&gt;
var results = [];&lt;br /&gt;
&lt;br /&gt;
for(var course=0; course&amp;lt;360;course+=36) {&lt;br /&gt;
 var newPos = mainPos&lt;br /&gt;
 # TODO: add offset altitude here, i.e. +/-500 ft&lt;br /&gt;
 newPos.apply_course_distance(course, 25*NM2M);&lt;br /&gt;
 append(results,newPos);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
return results;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This should result in an evenly-spaced circle of objects around the aircraft, no matter where you're going.&lt;br /&gt;
&lt;br /&gt;
To make this a bit more fancy, we could just use TrafficModel objects instead of plain geo.Coord instances here&lt;br /&gt;
&lt;br /&gt;
Thinking about it, we could make such a testing facility a part of MapStructure itself, such as that lcontrollers need to provide a &amp;quot;testSearch&amp;quot; implementation, so that MapStructure developers do not need to be familiar with each and every FG system...--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Canvas Map Controller ====&lt;br /&gt;
&amp;lt;!--{{WIP}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This has the most interesting jobs: it manages how the whole map is positioned and re-rendered. Example: [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/aircraftpos.controller}}]. It will have to manage its own updating routines, i.e. keeping track of timers/listeners that are hooked to update it. For example, like in the above, one can make a timer object which calls an &amp;quot;update_pos&amp;quot; method, which will reposition the map (via me.map.setPos) and call update on all layers if necessary (via me.map.update()), which will often call positioned searches and thus should be spaced out, e.g. ~4 or more seconds apart. Obviously one should also check other conditions other than time, like difference in position since last query.&lt;br /&gt;
&lt;br /&gt;
Some things to keep in mind when working on Map Controllers:&lt;br /&gt;
* we may also want to support optional mouse-panning (see airport-select dialog)&lt;br /&gt;
* we may also want to support optional tooltips for layer elements&lt;br /&gt;
&lt;br /&gt;
== Styling ==&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
{{WIP}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a list of MapStructure files (meanwhile incomplete), see: [[Canvas MapStructure Layers]]&lt;br /&gt;
Once you have found a layer that is not yet style-able, you need to encode style-able attributes using the relevant_keys vector.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=297586}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [SOLVED] How to modify symbols in MapStructure layers &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 27th, 2016 &lt;br /&gt;
  |added  =  Oct 27th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
some layers are still missing styling-support - which basically means identifying everything that is currently hard-coded but styling related (think colors, fonts, sie, images/artwork) and replacing that with a variable, the same variable should be added to the df_style (default style) of the symbol, and then you need to set up a cache entry specifiying the symbol specific variables (e.g. color and fontsize), so that the styleable-cache can tell if it has a certain variant of a symbol or not&lt;br /&gt;
map-canvas.xml is a simple example, artix's Airbus style is more sophisticated.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=297587}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [SOLVED] How to modify symbols in MapStructure layers &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 27th, 2016 &lt;br /&gt;
  |added  =  Oct 27th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example, open the VOR.symbol file and see how it sets up a default style hash (df_style) here: https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/Nasal/canvas/map/VOR.symbol&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=297586}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [SOLVED] How to modify symbols in MapStructure layers &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 27th, 2016 &lt;br /&gt;
  |added  =  Oct 27th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In the drawVOR callback it is then using those variables (as per the hash).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=297586}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [SOLVED] How to modify symbols in MapStructure layers &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 27th, 2016 &lt;br /&gt;
  |added  =  Oct 27th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var cache = StyleableCacheable.new(&lt;br /&gt;
    name:name,&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: SymbolCache32x32,&lt;br /&gt;
    draw_mode: SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: [&amp;quot;line_width&amp;quot;, &amp;quot;color&amp;quot;], # supported style-able attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
You can find more sophisticated examples in the Airbus style created by  Artix, he also had to touch a bunch of MapStructure files to make that work.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example: Tutorial Layer TUT ==&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
{{WIP}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This section will cover the main steps for implementing a new layer whose purpose is showing all targets of a selectable [[Tutorials|tutorial]] on a Canvas/MapStructure map. &lt;br /&gt;
&lt;br /&gt;
First of all, we need to find an aircraft that comes with a tutorial that contains targets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;cd $FG_ROOT/Aircraft/c172p/Tutorials &amp;amp;&amp;amp; grep -nr targets&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
taxiing.xml:29:    &amp;lt;targets&amp;gt;&lt;br /&gt;
taxiing.xml:55:    &amp;lt;/targets&amp;gt;&lt;br /&gt;
taxiing.xml:221:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:225:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:245:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:249:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:267:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:271:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:290:                    &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/distance-m&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:306:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j3/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:310:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j3/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:329:                    &amp;lt;property&amp;gt;/sim/tutorials/targets/j3/distance-m&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:342:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a1/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:346:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a1/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:356:                    &amp;lt;property&amp;gt;/sim/tutorials/targets/a1/distance-m&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:370:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:374:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:385:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:389:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:407:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:411:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:421:                    &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/distance-m&amp;lt;/property&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, for testing/development purposes, we will be using the default FlightGear aircraft, i.e. the c172p, because it comes with well-maintained tutorials, and because its '''Taxiing''' tutorials contains a number of &amp;lt;code&amp;gt;targets&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:PropertyBrowser-TutorialsNode.png|thumb|Screenshot showing the tutorials node in the property browser]]&lt;br /&gt;
&lt;br /&gt;
[[File:C172p-taxiing-tutorial-selection.png|thumb|Screen shot showing the tutorials dialog with the c172p taxiing tutorial selected]]&lt;br /&gt;
&lt;br /&gt;
For starters, we need to take a look at $FG_ROOT/Nasal/tutorial/tutorial.nas to see how to get a list of tutorials, so we open the corresponding file and search for '''tutorials''' and see this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
tutorialN = nil;&lt;br /&gt;
foreach (var c; props.globals.getNode(&amp;quot;/sim/tutorials&amp;quot;).getChildren(&amp;quot;tutorial&amp;quot;)) {&lt;br /&gt;
    if (c.getNode(&amp;quot;name&amp;quot;).getValue() == name) {&lt;br /&gt;
        tutorialN = c;&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (tutorialN == nil) {&lt;br /&gt;
    screen.log.write('Unable to find tutorial &amp;quot;' ~ name ~ '&amp;quot;');&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As can be seen, this requires only a single variable: '''name''' - so we can easily turn this into a helper function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var getTutorialNode = func(name) { &lt;br /&gt;
    var tutorialN = nil;&lt;br /&gt;
    foreach (var c; props.globals.getNode(&amp;quot;/sim/tutorials&amp;quot;).getChildren(&amp;quot;tutorial&amp;quot;)) {&lt;br /&gt;
        if (c.getNode(&amp;quot;name&amp;quot;).getValue() == name) {&lt;br /&gt;
            tutorialN = c;&lt;br /&gt;
            return tutorialN;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tutorialN == nil) {&lt;br /&gt;
        screen.log.write('Unable to find tutorial &amp;quot;' ~ name ~ '&amp;quot;');&lt;br /&gt;
        return nil;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, we need to check tutorials.nas to see how it gets a list of targets for the corresponding tutorial, which can be seen below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
set_targets(tutorialN.getNode(&amp;quot;targets&amp;quot;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, basically, all we need to do is calling &amp;lt;code&amp;gt;tutorialN.getNode(&amp;quot;targets&amp;quot;);&amp;lt;/code&amp;gt; to get a list of targets for the corresponding tutorial.&lt;br /&gt;
&lt;br /&gt;
Now, let's look up the definition of the set_targets() function, which is processing all targets, to see what we need to do to extract the latitude/longitude for each target:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
##&lt;br /&gt;
# For each &amp;lt;target&amp;gt;&amp;lt;*&amp;gt;&amp;lt;longitude-deg|latitude-deg&amp;gt; calculate and update&lt;br /&gt;
# /sim/tutorials/targets/*/...&lt;br /&gt;
#   heading-deg   ... absolute heading to target  (0 -&amp;gt; North)&lt;br /&gt;
#   direction-deg ... relative angle to target    (0 -&amp;gt; ahead, 90 -&amp;gt; to the right)&lt;br /&gt;
#   distance-m    ... distance in meters&lt;br /&gt;
#   eta-min       ... estimated time of arrival (assuming aircraft flies in&lt;br /&gt;
#                     in current speed towards target)&lt;br /&gt;
#&lt;br /&gt;
var set_targets = func(node) {&lt;br /&gt;
    if (node == nil) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var time = time_elapsedN.getValue();&lt;br /&gt;
    var dest = props.globals.getNode(&amp;quot;/sim/tutorials/targets&amp;quot;, 1);&lt;br /&gt;
    var aircraft = geo.aircraft_position();&lt;br /&gt;
    var hdg = headingN.getValue() + slipN.getValue();&lt;br /&gt;
&lt;br /&gt;
    foreach (var t; node.getChildren()) {&lt;br /&gt;
        var lon = t.getNode(&amp;quot;longitude-deg&amp;quot;);&lt;br /&gt;
        var lat = t.getNode(&amp;quot;latitude-deg&amp;quot;);&lt;br /&gt;
        if (lon == nil or lat == nil) {&lt;br /&gt;
            die(&amp;quot;target coords undefined&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var target = geo.Coord.new().set_latlon(lat.getValue(), lon.getValue());&lt;br /&gt;
        var dist = aircraft.distance_to(target);&lt;br /&gt;
        var course = aircraft.course_to(target);&lt;br /&gt;
        var angle = geo.normdeg(course - hdg);&lt;br /&gt;
        if (angle &amp;gt;= 180) {&lt;br /&gt;
            angle -= 360;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var d = dest.getChild(t.getName(), t.getIndex(), 1);&lt;br /&gt;
        d.getNode(&amp;quot;heading-deg&amp;quot;, 1).setDoubleValue(course);&lt;br /&gt;
        d.getNode(&amp;quot;direction-deg&amp;quot;, 1).setDoubleValue(angle);&lt;br /&gt;
        var distN = d.getNode(&amp;quot;distance-m&amp;quot;, 1);&lt;br /&gt;
        var lastdist = distN.getValue();&lt;br /&gt;
        distN.setDoubleValue(dist);&lt;br /&gt;
        if (lastdist != nil) {&lt;br /&gt;
            var speed = (lastdist - dist) / (time - last_step_time) + 0.00001;  # m/s&lt;br /&gt;
            d.getNode(&amp;quot;eta-min&amp;quot;, 1).setDoubleValue(dist / (speed * 60));&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    last_step_time = time;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most relevant/interesting part being the foreach loop where all targets are processed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
foreach (var t; node.getChildren()) {&lt;br /&gt;
    var lon = t.getNode(&amp;quot;longitude-deg&amp;quot;);&lt;br /&gt;
    var lat = t.getNode(&amp;quot;latitude-deg&amp;quot;);&lt;br /&gt;
    if (lon == nil or lat == nil) {&lt;br /&gt;
        die(&amp;quot;target coords undefined&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var target = geo.Coord.new().set_latlon(lat.getValue(), lon.getValue());&lt;br /&gt;
    # ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can easily turn this into a new helper function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var getTargets = func(node) {&lt;br /&gt;
    var results = [];&lt;br /&gt;
    foreach (var t; node.getChildren()) {&lt;br /&gt;
        var lon = t.getNode(&amp;quot;longitude-deg&amp;quot;);&lt;br /&gt;
        var lat = t.getNode(&amp;quot;latitude-deg&amp;quot;);&lt;br /&gt;
        if (lon == nil or lat == nil) {&lt;br /&gt;
            die(&amp;quot;target coords undefined&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    &lt;br /&gt;
        var target = geo.Coord.new().set_latlon(lat.getValue(), lon.getValue());&lt;br /&gt;
        append(results,target);&lt;br /&gt;
    }&lt;br /&gt;
    return results;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now, let's use the c172p and the [[Nasal Console]] to see if our code works as expected:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var getTutorialNode = func(name) { &lt;br /&gt;
    var tutorialN = nil;&lt;br /&gt;
    foreach (var c; props.globals.getNode(&amp;quot;/sim/tutorials&amp;quot;).getChildren(&amp;quot;tutorial&amp;quot;)) {&lt;br /&gt;
        if (c.getNode(&amp;quot;name&amp;quot;).getValue() == name) {&lt;br /&gt;
            tutorialN = c;&lt;br /&gt;
            return tutorialN;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tutorialN == nil) {&lt;br /&gt;
        screen.log.write('Unable to find tutorial &amp;quot;' ~ name ~ '&amp;quot;');&lt;br /&gt;
        return nil;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var tutorial = getTutorialNode(&amp;quot;Taxiing&amp;quot;);&lt;br /&gt;
var allTargets = nil;&lt;br /&gt;
&lt;br /&gt;
if (tutorial != nil) {&lt;br /&gt;
    allTargets = tutorial.getNode(&amp;quot;targets&amp;quot;);&lt;br /&gt;
    props.dump(allTargets);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once you start fgfs with the c172p, this snippet of code should load the &amp;quot;Taxiing&amp;quot; tutorial (it having a number of targets) and dump all info to the console:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
targets {NONE} = nil&lt;br /&gt;
targets/j1 {NONE} = nil&lt;br /&gt;
targets/j1/longitude-deg {UNSPECIFIED} = -121.81664&lt;br /&gt;
targets/j1/latitude-deg {UNSPECIFIED} = 37.6949&lt;br /&gt;
targets/j2 {NONE} = nil&lt;br /&gt;
targets/j2/longitude-deg {UNSPECIFIED} = -121.82258&lt;br /&gt;
targets/j2/latitude-deg {UNSPECIFIED} = 37.6949&lt;br /&gt;
targets/j3 {NONE} = nil&lt;br /&gt;
targets/j3/longitude-deg {UNSPECIFIED} = -121.8250&lt;br /&gt;
targets/j3/latitude-deg {UNSPECIFIED} = 37.69498&lt;br /&gt;
targets/a1 {NONE} = nil&lt;br /&gt;
targets/a1/longitude-deg {UNSPECIFIED} = -121.8251&lt;br /&gt;
targets/a1/latitude-deg {UNSPECIFIED} = 37.694616&lt;br /&gt;
targets/a2 {NONE} = nil&lt;br /&gt;
targets/a2/longitude-deg {UNSPECIFIED} = -121.8294&lt;br /&gt;
targets/a2/latitude-deg {UNSPECIFIED} = 37.69459&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now, let's turn the whole thing into a searchCmd() function that we can us in our MapStructure layer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var getTutorialNode = func(name) { &lt;br /&gt;
    var tutorialN = nil;&lt;br /&gt;
    foreach (var c; props.globals.getNode(&amp;quot;/sim/tutorials&amp;quot;).getChildren(&amp;quot;tutorial&amp;quot;)) {&lt;br /&gt;
        if (c.getNode(&amp;quot;name&amp;quot;).getValue() == name) {&lt;br /&gt;
            tutorialN = c;&lt;br /&gt;
            return tutorialN;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    if (tutorialN == nil) {&lt;br /&gt;
        screen.log.write('Unable to find tutorial &amp;quot;' ~ name ~ '&amp;quot;');&lt;br /&gt;
        return nil;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var getTargets = func(node) {&lt;br /&gt;
    var results = [];&lt;br /&gt;
    foreach (var t; node.getChildren()) {&lt;br /&gt;
        var lon = t.getNode(&amp;quot;longitude-deg&amp;quot;);&lt;br /&gt;
        var lat = t.getNode(&amp;quot;latitude-deg&amp;quot;);&lt;br /&gt;
        if (lon == nil or lat == nil) {&lt;br /&gt;
            die(&amp;quot;target coords undefined&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
 &lt;br /&gt;
        var target = geo.Coord.new().set_latlon(lat.getValue(), lon.getValue());&lt;br /&gt;
        append(results,target);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return results;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var searchCmd = func() {&lt;br /&gt;
    var tutorial = getTutorialNode(&amp;quot;Taxiing&amp;quot;);&lt;br /&gt;
    var allTargets = nil;&lt;br /&gt;
&lt;br /&gt;
    if (tutorial != nil) {&lt;br /&gt;
        allTargets = tutorial.getNode(&amp;quot;targets&amp;quot;);&lt;br /&gt;
        return getTargets(allTargets);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
debug.dump(searchCmd());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, we need to come up with some boilerplate code for creating a new MapStructure layer, and then populate the searchCmd() method using our two helper fnctions&lt;br /&gt;
&lt;br /&gt;
{{MapStructure lcontroller|layername=TUT}}&lt;br /&gt;
&lt;br /&gt;
Now, here's some code to actually test the new layer (contributed by ludomotico {{forum link|p=259306}}):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var temp = {};&lt;br /&gt;
temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
testMap.setRange(25);&lt;br /&gt;
&lt;br /&gt;
testMap.setTranslation(&lt;br /&gt;
    temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
    return caller(0)[0];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach (var type; [r('TUT'), r('APT'), r('APS')]) {&lt;br /&gt;
    TestMap.addLayer(&lt;br /&gt;
        factory: canvas.SymbolLayer, &lt;br /&gt;
        type_arg: type.name, &lt;br /&gt;
        visible: type.vis, &lt;br /&gt;
        priority: type.zindex,&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To test the new layer, we need to review the taxiing tutorial to check where all those targets are located, specifically see the presets section below:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;presets&amp;gt;&lt;br /&gt;
    &amp;lt;airport-id&amp;gt;KLVK&amp;lt;/airport-id&amp;gt;&lt;br /&gt;
    &amp;lt;on-ground&amp;gt;1&amp;lt;/on-ground&amp;gt;&lt;br /&gt;
    &amp;lt;runway&amp;gt;12&amp;lt;/runway&amp;gt;&lt;br /&gt;
    &amp;lt;altitude-ft&amp;gt;-9999&amp;lt;/altitude-ft&amp;gt;&lt;br /&gt;
    &amp;lt;latitude-deg&amp;gt;37.6952&amp;lt;/latitude-deg&amp;gt;&lt;br /&gt;
    &amp;lt;longitude-deg&amp;gt;-121.8167&amp;lt;/longitude-deg&amp;gt;&lt;br /&gt;
    &amp;lt;heading-deg&amp;gt;175.0&amp;lt;/heading-deg&amp;gt;&lt;br /&gt;
    &amp;lt;airspeed-kt&amp;gt;0&amp;lt;/airspeed-kt&amp;gt;&lt;br /&gt;
    &amp;lt;glideslope-deg&amp;gt;0&amp;lt;/glideslope-deg&amp;gt;&lt;br /&gt;
    &amp;lt;offset-azimuth-deg&amp;gt;0&amp;lt;/offset-azimuth-deg&amp;gt;&lt;br /&gt;
    &amp;lt;offset-distance-nm&amp;gt;0&amp;lt;/offset-distance-nm&amp;gt;&lt;br /&gt;
&amp;lt;/presets&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Fgfs-MapStructure-tutorial-layer.png|thumb|Custom MapStructure layer for displaying [[Tutorials]] targets - showing the c172p taxiing tutorial at KLVK]]&lt;br /&gt;
This tells us that we need to start fgfs using &amp;lt;code&amp;gt;--airport=KLVK --runway=12&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_MapStructure&amp;diff=143066</id>
		<title>Canvas MapStructure</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_MapStructure&amp;diff=143066"/>
		<updated>2025-11-25T20:26:01Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* The SymbolCache */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{infobox subsystem&lt;br /&gt;
|image =MapStructure-GRID-layer.png&lt;br /&gt;
|name =MapStructure&lt;br /&gt;
|started= 11/2013 &lt;br /&gt;
|description = Charting abstraction layer for [[Canvas]]/[[Nasal]] maps&lt;br /&gt;
|status = Under active development as of 11/2013&lt;br /&gt;
|maintainers  = Philosopher, Hooray, {{Usr|Stuart}}&lt;br /&gt;
|developers = [[User:Philosopher]] (since 11/2013),&lt;br /&gt;
|topic-fgdata= main fgdata repo&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
[[File:Gpsmap196-MapStructure.png|thumb|F-JJTH's GPSMap196 in the process of being ported to [[Canvas]] using Philosopher's MapStructure framework for mapping (LOD/scaling not applied here, but available) - see {{forum link|t=23047}}]]&lt;br /&gt;
&lt;br /&gt;
[[File:Gpsmap196-MapStructure-with-styling-applied.png|thumb|MapStructure layers integrated in GPSMap196 with some custom styling applied]]&lt;br /&gt;
&lt;br /&gt;
[[File:VSD-prototype-by-omega95.png|thumb|Canvas-based VSD prototype (Vertical Situation Display) developed by Omega95]]&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-WXR-layer-by-omega95.png|thumb|MapStructure WXR (weather) layer created by omega95's using a web service API to fetch live online imagery {{forum link|t=23753}}]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Targeted FlightGear versions: 3.20+'''&lt;br /&gt;
&lt;br /&gt;
[[File:Canvasready.png]]&lt;br /&gt;
&lt;br /&gt;
{{Note|This article is intended for people wanting to add custom scripted mapping/charting displays to FlightGear (aircraft, GUI dialogs, HUDs etc) using layered maps- it assumes familiarity with [[Nasal]] scripting, [[Object Oriented Programming with Nasal]] and [[Canvas]] - while an existing chart layer (e.g. to show VORs/NDBs or DMEs) can be easily added and used/customied by non-programmers within a few minutes, creating new MapStructure layers requires additional knowledge which is detailed below. Usually, new layers can be easily created by adapting existing layers. In its simplest form, MapStructure is just a library of a handful of existing layers (navaids, traffic, weather etc) that can be easily -and quickly- reused for all kinds of different purposes. &lt;br /&gt;
For people familiar with Nasal coding, MapStructure is also a framework to create new fully reusable layers with integrated support for caching, resource management (listener/timers) and efficient spatial searching/updating.&lt;br /&gt;
For FlightGear versions &amp;lt;nowiki&amp;gt;&amp;gt;=&amp;lt;/nowiki&amp;gt; 3.3+, there will also be a [[Canvas Widgets|Canvas Widget]] to easily add MapStructure-based maps to any Canvas dialog and/or to Canvas MFDs that internally use the Canvas GUI framework. Another novelty that we're currently exploring is extending the framework such that GUI-based creation/editing/customization of layers becomes a first-class concept, e.g. to create a weather or tutorial/missions GUI editor: see [[#Porting the map dialog]].|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
Basically, the ND/MapStructure frameworks came to be out of a certain degree of disagreement with other MFD/glass cockpit efforts using the Canvas system.&lt;br /&gt;
That was primarily because many of those lacked support for:&lt;br /&gt;
&lt;br /&gt;
*  truly independent instances&lt;br /&gt;
*  reuse (other aircraft/use-cases, e.g. sharing common logic between aircraft displays and GUI dialogs)&lt;br /&gt;
*  customiation (think styling)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Especially, we found it hugely frustrating to see awesome avionics like the Avidyne Entegra R9 being developed for aircraft like the extra500, that were impossible to reuse for other aircraft cockpits, but that also contained features/code that would have been useful elsewhere (think mapping/charting displays).&lt;br /&gt;
So that more often than not, copy &amp;amp;amp; paste was the only option to &amp;quot;reuse&amp;quot;  useful code elsewhere.&lt;br /&gt;
Thus, the idea was to grow a library of generic building blocks that live in $FG_ROOT and that are sufficiently generic and customizable to basically enable people to collaborate more properly by providing a strong incentive via compelling functionality that can be easily maintained/updated in the future. The thinking was that all aircraft/GUI dialogs using a single back-end would automatically benefit from significant updates to the back-end that way (think performance, features etc).&lt;br /&gt;
Our strategy was to adopt a MVC (model/view/controller) approach, where the model would represent what is to be rendered, the view would map the Canvas system and its primitives (elements) and underlying APIs, whereas the controller would usually be use-case specific (think cockpit vs. GUI dialog).&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=317039}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; my &amp;quot;2c&amp;quot; ;-) &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Aug 21st, 2017 &lt;br /&gt;
  |added  =  Aug 21st, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Today, just the MapStructure framework is under 1k lines of code, and is making roughly 9k-15k lines of C++ code obsolete in FlightGear (agradar,groundradar,wxradar,map dialog), unifying the whole shebang in the process, which also ensures that more modern OSG/OpenGL can be used, i.e. better performance in the long-term.&lt;br /&gt;
&lt;br /&gt;
This all has taken place in under 24 months actually (MapStructure just being 6 months old now actually) - whereas hard-coded instruments (wxradar, agradar, navdisplay,kln89 etc) are usually 5+ years old, and things like the Map dialog even older - in comparison, these were all &amp;quot;easier&amp;quot; to come up with in the first place, but obviously don't scale as well as a Canvas-based solution. Gijs NavDisplay framework is already better accessible and more feature-rich than the original ND code.&lt;br /&gt;
&lt;br /&gt;
But this kind of work isn't exactly fun, it comprises lots of refactoring and is more about talking and coordinating things than actually coding stuff - because the implementation may very well just be a fraction the size of all the manifestations that are hopefully replaced over time.&lt;br /&gt;
&lt;br /&gt;
We've seen some extremeley skilled contributors making sizable contributions without them ever documenting the internals, so that getting up to scratch with things later on may be next to impossible without spending a huge amount of time, that could be equally spent on re-designing certain features/systems.&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
This is something that we have actively worked to address in the context of Nasal/Canvas and efforts like the ND/MapStructure frameworks, i.e. those are now extensively documented, not just including &amp;quot;roadmaps&amp;quot; and &amp;quot;milestones&amp;quot;, but also internal design stuff, including even step-by-step tutorials and coding examples - admittedly, this has taken up quite a bit of spare time, that could have just as well been spent &amp;quot;coding&amp;quot; - but given the wiki stats, we seem to be on the right track here, i.e. most of these articles have seen between 2k-8k views within just ~10-12 weeks, which is kinda impressive (some of our most popular articles &amp;quot;only&amp;quot; have seen 40k views in years!), but this also ensures at the same time that even if some of us were to disappear for a few months, people would still be able to pick up where we (TheTom, Philosopher, Gijs, myself) left off, no matter if this means &amp;quot;maintaining&amp;quot; our existing code - or modernizing/replacing it completely.&lt;br /&gt;
&lt;br /&gt;
In professional software development circles, writing documentation is a necessary evil, as is writing unit tests - in FlightGear, people prefer to spend their time doing &amp;quot;fun&amp;quot; stuff instead for understandable reasons. Then again, some of the main building blocks and key technologies in FlightGear were developed by people who obviously understood that having sufficient docs is at least as important for a feature to survive than the actual code, just look at architectural pillars contributed by people like David Megginson (property tree) or Andy Ross (Nasal) - those are typically the same guys who were responsible for much of the original documentation targeted at core developers, no matter if it's through extensive use of doxygen comments or through dedicated design articles. &lt;br /&gt;
&lt;br /&gt;
Some of our most active contributors spent little to no time ensuring that future developers will be able to continue their work - and that's a problem that will only really become obvious once someone is too busy with other aspects of their life to contribute (or even just back to answer questions).&lt;br /&gt;
&lt;br /&gt;
== What is it ? ==&lt;br /&gt;
&lt;br /&gt;
MapStructure is a scripting-space Nasal framework (designed and maintained by Philosopher) for managing layers of symbols in Nasal/Canvas-based mapping displays, which can be used in both aircraft MFDs/instruments and GUI dialogs, like the airport selection or [[Map]] dialogs. MapStructure is all about separating the visualization of the map from the visualized data itself, and the way it is shown to, and controlled by, the user. MapStructure is designed as a Model/View/Controller (MVC) framework.&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructureMVC.png|thumb|MapStructure-MVC-Framework]]&lt;br /&gt;
&lt;br /&gt;
The primary challenge here is, that these different front-ends (e.g. a GUI dialog or cockpit) will typically all have very different means to interact with the map and its rendered layers. &lt;br /&gt;
&lt;br /&gt;
[[File:MapStructureOverview.png|thumb|MapStructure MVC separation]]&lt;br /&gt;
&lt;br /&gt;
A GUI dialog may respond to mouse events (e.g. panning/zooming etc), while a cockpit display would typically respond to cockpit hot-spots (bindings), such as clicking virtual CDU/MCP buttons. Therefore, user input handling must be well encapsulated and handled by so called &amp;quot;delegates&amp;quot;, so that the back-end doesn't need to know anything about its front-end. Otherwise, back-ends designed for aircraft would no longer work when used in GUI dialogs and vice versa.&lt;br /&gt;
&lt;br /&gt;
For instance, imagine a piece of code showing navaids within a range that can be set in the cockpit: This code would stop working when used in a different cockpit, or when used in a GUI dialog. Likewise, referencing properties that are specific to a certain GUI dialog, would stop working once the layer is used in a different GUI dialog or in an aircraft.&lt;br /&gt;
&lt;br /&gt;
Conventionally, a simple navaid layer showing NDBs would be implemented as a single piece of code that's running through these steps:&lt;br /&gt;
* run a query to find navaids within 50 nm&lt;br /&gt;
* get the positions of each navaid&lt;br /&gt;
* for each navaid, render a symbol onto a canvas map&lt;br /&gt;
&lt;br /&gt;
Now, once such a piece of code needs to do something else, it would be typically copied/pasted and adapted, e.g. to change the range of the query, the type of symbol, the type of map or even just the type of query (VORs vs NDBs). Now, MapStructure encourages a more modular design, so that each stage is implemented via separate files that can be easily reused, and that can be augmented by adding new files to accomplish a related task. &lt;br /&gt;
&lt;br /&gt;
The next complication is that different front-ends may require different data to be shown - such as taxiways, runways, fixes, waypoints or routing (waypoints). Therefore, the framework works in terms of &amp;quot;layers&amp;quot;, where each layer manages its own set of symbols - symbols typically represent geographic positions (latitude/longitude), as well as a '''drawable''' (symbol, SVG file name or a callback handling the implementation).&lt;br /&gt;
&lt;br /&gt;
Control of individual layers is delegated to callbacks that can be overridden by the front-end, i.e. to hide/show a layer. &lt;br /&gt;
&lt;br /&gt;
[[File:MapStructureController.png|thumb|MapStructure Controllers]]&lt;br /&gt;
&lt;br /&gt;
There's basically several '''drivers''' at work here:&lt;br /&gt;
* one driver determines what is to be drawn (e.g. VORs)&lt;br /&gt;
* one driver determines where it is to be drawn (e.g. lat/lon)&lt;br /&gt;
* one driver determines how it is to be drawn (e.g. scaled SVG/PNG images) &lt;br /&gt;
* another driver determines how the layer responds to events (such as e.g. recurring updates or mouse clicks)&lt;br /&gt;
&lt;br /&gt;
The primary concern here is to ensure that the DRY-principle (don't-repeat-yourself) is not violated, so that maps, layers and symbols can be easily reused without requring any copy&amp;amp;paste- and so that new use-cases can be easily supported by adding custom controllers that interact with maps/layers/symbols as needed (these are typically '''boilerplate''' files that are roughly ~30-50 lines of code, of which 5-10 lines may need customizing). &lt;br /&gt;
&lt;br /&gt;
By establishing this separation of concerns, the design is heavily focused on gathering new contributions in a single place ($FG_ROOT/Nasal/canvas/map), rather than having custom-coded solutions in various places - such as GUI dialogs or aircraft, which used to be the predominant practice prior to this framework, and which often meant that useful code was only available for certain purposes.&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-UseCases.png|600px]]&lt;br /&gt;
&lt;br /&gt;
Thus, to the framework itself it doesn't matter if you need a layered map to be shown on an instrument or in a GUI dialog, or even as part of the scenery (VGDS) or  maybe as a livery. By following this approach, we can use a shared back-end to implement layered maps for different needs, without having to duplicate any code, where people usually would use &amp;quot;copy &amp;amp; paste&amp;quot; and customize things afterwards, a very error-prone and tedious process, that doesn't lend itself to long-term maintenance.&lt;br /&gt;
&lt;br /&gt;
As of 05/2015, all this still is work in progress and not yet completely finished, but we're hoping to completely replace the old map.nas code by FG 3.2 and provide a pure Canvas-based re-implementation of the Map dialog in the 4.0 release.&lt;br /&gt;
&lt;br /&gt;
Aircraft developers working on airliners or modern biz jets (and the corresponding MFD avionics) will probably want to get in touch with Philosopher and Hooray via the forum/wiki to coordinate things a little. We also appreciate any related feature requests and other constructive feedback. Flexibility is the ultimate design goal of this effort.&lt;br /&gt;
&lt;br /&gt;
At the moment, the primary users of the framework are the 747-400 and the 777-200ER - both make use of the new [[NavDisplay|ND framework]], which internally uses the MapStructure framework.&lt;br /&gt;
&lt;br /&gt;
If you just need an ND, you won't need to deal with MapStructure directly, it is all done transparently by the [[NavDisplay]] code. &lt;br /&gt;
&lt;br /&gt;
However, if you'd like  to create custom charting displays, or GUI dialogs with embedded charts (map dialog, instructor console, ATC or RADAR displays etc), you'll probably want to use the MapStructure framework, because it reduces the amount of specialized Nasal code significantly - typically to ~10-15 lines of configuration code per layer.&lt;br /&gt;
&lt;br /&gt;
To learn more about the motivation of using a MVC design, see [[Canvas Map API]].&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-Internals.png|thumb|MapStructure Internals]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
== Performance Considerations ==&lt;br /&gt;
&lt;br /&gt;
[[File:Map-canvas-mapstructure-performance.png|thumb|stress-testing [[MapStructure]] using the ufo @ KSFO circling at 2000 kts in a climb, see {{forum link|p=211611}}]]&lt;br /&gt;
&lt;br /&gt;
Recently, it has been two years since [[Canvas]], our 2D rendering API, got integrated. And even today, there are people claiming that aircraft using Nasal and Canvas are generally slow and don't provide sufficient performance, suggesting not to adopt Canvas yet, so here are some comments about that, to help put some context around such statements:&lt;br /&gt;
&lt;br /&gt;
Canvas is a relatively new technology in FlightGear, and it not just a single technology, it's built on top of other technologies, like the [[Property Tree]], ShivaVG/OpenVG, OSG and OpenGL. As you may have noticed, we didn't even mention [[Nasal]] yet - that's because Nasal scripting, strictly speaking, isn't even required to use [[Canvas]] - the whole thing can be used without touching any Nasal - just by using the property tree, the built-in httpd or even the [[Telnet usage|telnet]] interface.&lt;br /&gt;
&lt;br /&gt;
In theory, you could create a canvas just by setting properties, and even add placements (GUI/scenery/aircraft) by doing just that.&lt;br /&gt;
&lt;br /&gt;
Canvas being a relatively new technology, we obviously still have to learn what works and what doesn't, and often that also means finding things that are too slow for some reason. And these days, there are easily a handful of abstraction layers involved, all of which may contribute to things being too slow. And debugging/profiling isn't always straightforward, especially when several boundaries are crossed (Nasal, property tree, C++, OSG/OpenGL).&lt;br /&gt;
&lt;br /&gt;
But whenever we've seen dead-slow Nasal/Canvas code it was usually not the problem of Nasal or Canvas per se, but due the original code/implementation in the first place, including our own code by the way  :D &lt;br /&gt;
&lt;br /&gt;
Just because there are means available to add scripted graphics to FlightGear, doesn't mean that people automatically know how to use this technology properly.&lt;br /&gt;
&lt;br /&gt;
For example, the very first version of the airport selection dialog was much slower than necessary, simply because it would redraw stuff unnecessarily, and not even use separate layers (canvas groups) for different features like airports, taxiways, runways etc.&lt;br /&gt;
&lt;br /&gt;
Since then, we've learned a lot about using Canvas properly, but also about the combination of using Nasal and Canvas, including other subsystems.&lt;br /&gt;
&lt;br /&gt;
So, as Canvas is a fairly recent feature, there's sometimes some really naive stuff done in various areas, that makes people think that Nasal and/or Canvas are generally slow. &lt;br /&gt;
&lt;br /&gt;
However, that is not generally true. Aircraft like the m2000-5 or the extra500 are indeed fairly slow - not primarily because of Nasal/Canvas, but due a combination of factors, including several other FG technologies. And the 747 or 777 are slow even without any Nasal/Canvas code running; we can actually measure now how little impact our Nasal/Canvas code has, and how significant the impact is due to complex cockpit modeling (many polygons and high resolution textures). Understandably, under such circumstances, improper use of Nasal and/or Canvas may cause additional performance issues. And people may easily draw false conclusions under these circumstances.&lt;br /&gt;
&lt;br /&gt;
Anyway, nobody should simply &amp;quot;port/convert&amp;quot; stuff like -for example- the Garmin196 to use Canvas, because that's exactly where all those problems come from: Canvas and Nasal themselves are simply too low-level for these purposes, people still need to be very experienced to come up with fast code here, very familiar with FG and certain Nasal technologies like timers and listeners (or the whole process inevitably involves lots of trial &amp;amp; error). Typically, that really only applies to core developers still, or fgdata committers who have extensively worked with both Nasal and Canvas over the years.&lt;br /&gt;
&lt;br /&gt;
To use Nasal and Canvas properly, there are several layers that need to be understood, such as:&lt;br /&gt;
# Nasal&lt;br /&gt;
# Canvas scripting bindings&lt;br /&gt;
# Canvas elements (OpenVG paths, raster images, maps)&lt;br /&gt;
# FlightGear internals (navdb, timers, listeners etc)&lt;br /&gt;
# OpenSceneGraph&lt;br /&gt;
# OpenGL&lt;br /&gt;
&lt;br /&gt;
Most people only understand #1 (well, partially), even though writing really fast code may very well involve looking at several layers in combination to see how they affect each other.&lt;br /&gt;
&lt;br /&gt;
So writing &amp;quot;fast&amp;quot; Nasal/Canvas code still is quite an undertaking despite things being accessible from scripting space these days. And even our most experienced Nasal/Canvas contributors are still learning new things each day. &lt;br /&gt;
&lt;br /&gt;
Which is exactly the point of having [[MapStructure]] and the [[NavDisplay]] frameworks: those are designed to handle things efficiently, e.g. by using smarter queries, smarter updates, and techniques like caching or selective/delta searches. &lt;br /&gt;
&lt;br /&gt;
Imagine it like &amp;quot;Ruby on Rails&amp;quot; for aviation charts - that's what the whole MapStructure framework is all about: maps and charts. &lt;br /&gt;
&lt;br /&gt;
The NavDisplay framework also used to be fairly inefficient in its early days - since then, we've significantly reworked it to use MapStructure. That first introduced some ugly hacks because we didn't want to rewrite it completely, but now its layers are mostly using MapStructure. And most of the original ND stuff has been refactored, generalized and integrated with the MapStructure framework, so that this very code can be used in other places, not just other aircraft, but also GUI dialogs.  &lt;br /&gt;
&lt;br /&gt;
Very fast Nasal/Canvas code will typically do very little at runtime/frame rate, but instead use pre-created data structures and dynamically toggle canvas layers on/off (show/hide groups) and update the underlying data selectively, while using cached images instead of having lots of identical OpenVG paths. &lt;br /&gt;
&lt;br /&gt;
We've arrived at these conclusions based on testing and lots of profiling.&lt;br /&gt;
&lt;br /&gt;
Frame rates &amp;gt;= 60 fps are not impossible to achieve like this - but coding such a design is obviously much more involved than simply drawing/updating once per frame, which is what people will typically do. This is very similar to people running timer callbacks at frame rate instead of using using split-frame loops or listeners to do certain computations and processing selectively. A simple design can get you only so far, but an efficient design requires much more thinking, and time to get right.&lt;br /&gt;
&lt;br /&gt;
For instance, D-LEON has done quite some Nasal/Canvas benchmarking and confirmed that MapStructure/Canvas are sufficiently fast already (and we're still exploring additional options for making it even faster). Also, we're looking into augmenting/re-implementing certain features through native code additions, such as having dedicated extension functions, Nasal library functions or new Canvas extensions to make things even faster.&lt;br /&gt;
&lt;br /&gt;
The key point here is that having a set of generic abstraction layers allows us to easily generalize, but also optimize, things - without having to rewrite all the places where Nasal/Canvas are used for mapping purposes. Things like the [[Avidyne Entegra R9]] don't use the [[MapStructure]] framework at all currently - which also means that the code doesn't benefit from recent MapStructure optimizations unfortunately.&lt;br /&gt;
&lt;br /&gt;
Imagine we were to optimize positioned queries (NavDB stuff like VORs, NDBs, airports etc) to become 50% faster - all instruments and dialogs using the MapStructure framework would get this speedup for free, while custom solutions would need to separately implement the optimized query. And that applies to pretty much any effort that doesn't favor a framework-centric development approach.&lt;br /&gt;
&lt;br /&gt;
By the way, this is exactly the reason why the MapStructure framework lives under $FG_ROOT/Nasal, to help grow a shared library of '''fast''' components that can be easily reused, but also maintained - freeing aircraft and GUI developers from such chores and obligations, so that they don't have to track development and update all their own work in order to avoid breakage, but still benefit from recent optimizations. This is a lesson that we've learned from all the feedback that some of the most active airliner developers, like omega95 and redneck, have provided over time.&lt;br /&gt;
&lt;br /&gt;
Unless someone is a really experienced programmer, working with &amp;quot;low-level&amp;quot; Nasal &amp;amp; Canvas for mapping purposes is probably not a good idea, because we already have a handful of people working on a single framework that is dedicated to just that, and we actually do regularly profile things and we're talking to the Canvas guys to sneak out more performance, better speed, less lag etc. &lt;br /&gt;
&lt;br /&gt;
But whenever someone comes up with a custom mapping solution -solving the same problem that MapStructure and Gijs' ND frameworks solve- that doesn't use those frameworks, they need to do all the hard work from scratch, and code sharing/reuse and maintenance is made increasingly difficult, too. Basically, we end up &amp;quot;competing&amp;quot; without meaning to, by working on fairly similar projects, without coordinating our work to generalize and reuse existing solutions.  &lt;br /&gt;
&lt;br /&gt;
Which is why we talked with the [[Avidyne Entegra R9]] developers to team up with us: their code is much more elegant than the original ND/MapStructure code, but the latter is much more generic (aircraft/instrument agnostic), and also fairly optimized already.&lt;br /&gt;
&lt;br /&gt;
Admittedly, we already spent a while examining all the [[Avidyne Entegra R9]] code there - we were kinda surprised seeing OOP code using design patterns solving a problem that we had been working on for several months. Actually, had we seen that code earlier, it would have been a much more mature foundation for a generic ND/MapStructure framework, because of its OO nature. But in the meantime, we've come up with something fairly generic - mostly thanks to Philosopher's MapStructure framework.&lt;br /&gt;
&lt;br /&gt;
As mentioned elsewhere, if we could have had a look at the Avidyne code it would have been a great/better foundation for MapStructure and the ND framework - but we had to work off a completely different code base (several actually),  so things are a little inconsistent and less elegant - but those frameworks are '''really''' designed to be generic, not just intended to be used with different aircraft, but also different GUI dialogs. And whenever we add a new optimized feature, all people/aircraft/dialogs using those frameworks will benefit automatically (backward compatibility is handled by US).&lt;br /&gt;
&lt;br /&gt;
We literally depend on having many different use cases and front-end scenarios, i.e. for these two frameworks to evolve properly, we need many different aircraft developers to adopt them, do regular testing/profiling and provide feedback - and GUI use-cases are just as important. In fact, just for the sake of generalizing things, both frameworks contain support for being not just driven by the main/FDM aircraft, but even by AI aircraft.&lt;br /&gt;
&lt;br /&gt;
Technically, MapStructure layers are a bit more sophisticated than having pre-created SVG/OpenVG groups, because symbols can be cached in a separate canvas via a texture map, so that there's true &amp;quot;instancing&amp;quot; support for each cached symbol. This is something that we've been working on in the last two weeks. Also, positioned queries are handled by an abstraction layer to ensure that things are sufficiently fast using selective delta-updating. Besides, the ND/MapStructure code was intended to be reusable right from the beginning.&lt;br /&gt;
&lt;br /&gt;
Quite a few of the problems people, interested in mapping, are likely to encounter are already solved via MapStructure, i.e. identical paths for different symbols are simply instanced by using a raster image (canvas) as a cache.&lt;br /&gt;
&lt;br /&gt;
The bottom line is: if there's someone who actually IS experienced enough to know how to write good and fast Nasal/Canvas code, that person should better team up with us, to help improve the generic framework, rather than develop some niche instrument or dialog that will only ever be used by a few aircraft, work that may never make it into fgdata.&lt;br /&gt;
&lt;br /&gt;
Nasal &amp;amp; Canvas are really just tools, and tools can be misused obviously. Using them properly still requires certain knowledge, or slow code may result from it. We've been trying to make this easier by providing a few wrappers on top of Nasal/Canvas, that are intended to help with the creation of mapping displays, so that unnecessarily slow code can be avoided, while also allowing front-end code to directly benefit from optimized features.&lt;br /&gt;
&lt;br /&gt;
Badly written code will remain slow no matter how optimized underlying technologies like Nasal or Canvas are, even if they should be completely replaced at some point. Those are algorithmic issues, and MapStructure/NavDisplay are intended to help people focus on non-algorithmic stuff.&lt;br /&gt;
&lt;br /&gt;
{{cquote&lt;br /&gt;
  |&amp;lt;nowiki&amp;gt;Claiming that Nasal/Canvas would be &amp;quot;a failure as a tool&amp;quot; just because people can still implement slow code, is far too short-sighted - just because you are allowed to drive a car (or fly an airplane) doesn't make you an expert in car engines or airplane turbines - things like Nasal and Canvas are really just enablers, that are truly powerful in the hands of people who know how to use them, but that can still be misused by less-experienced contributors.&lt;br /&gt;
&lt;br /&gt;
That is exactly why people are working towards more targeted frameworks on top of Nasal/Canvas - but it's a process that is very much still in progress, and probably will be for at least another 2-3 release cycles. Pointing out obvious shortcomings isn't going to help anyone though - certainly not as much as getting involved in one way or another&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  |{{cite web |url={{forum url|p=211169}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Does FlightGear has Multiplayer Combat mode?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri May 30&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Demonstration ==&lt;br /&gt;
To easily show a moving map layer with all AI/MP traffic within a range of 25 nm, only 10-15 lines of code are needed. Paste this into the [[Nasal Console]]:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var MapStructure_demo = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); &lt;br /&gt;
  &lt;br /&gt;
    # this will center the map&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    # TFC, APT and APS are the layer names as per $FG_ROOT/Nasal/canvas/map and the names used in each .lcontroller file&lt;br /&gt;
    # in this case, it will load the traffic layer (TFC), airports (APT) and render an airplane symbol (APS)&lt;br /&gt;
    foreach (var type; [r('TFC'), r('APT'), r('APS')]) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_demo&lt;br /&gt;
&lt;br /&gt;
MapStructure_demo();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As can be seen, using just ~10-15 lines of copied Nasal code, creates a fully working map that shows AI/MP traffic and airports. Next,  you can add/replace additional layers, such as: '''FIX''', '''VOR''', '''NDB''' etc. For a complete overview, check out [[Canvas MapStructure Layers]].&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructureWaypoints.png|thumb|Initial Waypoint support for Philosopher's MapStructure framework]]&lt;br /&gt;
&lt;br /&gt;
== Layers ==&lt;br /&gt;
&lt;br /&gt;
See [[Canvas MapStructure Layers]]&lt;br /&gt;
&lt;br /&gt;
== Issues ==&lt;br /&gt;
(link to canvas-navdisplay label in issue tracker)&lt;br /&gt;
&lt;br /&gt;
We currently have quite a few old files doing basically identical stuff, just with different draw routines - such as e.g. &amp;quot;runway-nd&amp;quot;, &amp;quot;airports&amp;quot;, &amp;quot;airports-nd&amp;quot;. It would probably be a good idea to generalize this by implementing LOD and styling support - so that we can use a single APT layer that supports all necessary customizations.&lt;br /&gt;
&lt;br /&gt;
Basically, we could unify things a bit by using LOD support to show APT in different modes, so that taxiways etc would be only shown if necessary. It would still make sense to maintain separate draw/symbol  files for these, so that things can be easily reused.&lt;br /&gt;
&lt;br /&gt;
=== Scale/Ratio Handling ===&lt;br /&gt;
Need to take original canvas texture dimensions into account, and also keep in mind that layers may be shown not in &amp;quot;fullscreen&amp;quot; mode (using the whole texture), but just a sub-area, i.e. a clipped canvas-region, and setTranslation/view/size calls must be adjusted accordingly. The GPSMap196 and Avidyne are instruments that support partial-screen modes.&lt;br /&gt;
&lt;br /&gt;
[[File:Gpsmap196-mapstructure-layers-ratio.png|thumb|GPSMap196 with [[MapStructure]] layers (currently exhibiting some scaling/LOD issues due to hard-coded assumptions in some of the layers)]]&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The extra500 developers just posted a few screen shots of the Avidyne Entegra R9 in &amp;quot;moving map&amp;quot; mode, which demonstrates a few use-cases that we do not currently support in MapStructure (the ND being a different matter for now):&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
[[Extra_EA-500#Map_:_Moving_Map]]&amp;lt;br/&amp;gt;&lt;br /&gt;
[[File:IFD_FMS-Map_FPL.png|250px]]&amp;lt;br/&amp;gt;&lt;br /&gt;
[[File:IFD_MAP-Map%2B.png|250px]]&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
Specifically, this probably means that we may need to revamp/resurrect symbol controllers and maybe accept an optional callback to pre-/post-transform individual symbols (they're rotated apparently according to runway orientation), and allow them to be overridden when instantiating the layer (i.e. via the ctor if SVG-based). And we may also want to explore SVG styling by patching svg.nas to run a &amp;quot;transform&amp;quot; callback to customize/colorize certain elements of the SVG. The instrument itself is meanwhile making fairly aggressive use of texture-map based caching for basically ALL symbols.&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
The other thing is that translation (centered/off center mode) should always be based on the parent group's dimensions (bounding box) and that we really shouldn't be using the top-level canvas, because a MFD very well be split into several screen areas (which also applies to the GPSMap196 and most other modern avionics).&lt;br /&gt;
  |{{cite web |url={{forum url|p=213354}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Evolving the MapStructure &amp;amp; NavDisplay Frameworks ...&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Jun 24&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-ND-Profiling.png|thumb|MapStructure/NavDisplay testbed with integrated profiling support for each layer]]&lt;br /&gt;
&lt;br /&gt;
the performance issue related to the map-canvas.xml dialog pointed out by another user (whenooming) has nothing to do with Canvas or Nasal - it is C++ code that is causing this, and this is no surprise - it's a known issue actually. As a matter of fact, the hard-coded NavDisplay, as well as the hard-coded Map dialog both suffered from the exact same issue, until Gijs solved it by rewriting the projection handling code. &lt;br /&gt;
However, the original Canvas projection code is still using the old implementation, i.e. the update got never back-ported: [[Canvas MapStructure#Performance]]&lt;br /&gt;
We're talking here about roughly 50 lines of C++ code, which exist already and which would need to be turned into a Canvas::Map::Projection&lt;br /&gt;
Note that this would benefit any, and all, Canvas-based NavDisplay/Map applications, too - also note that this information is readily available in various places, so there is no need to draw any wrong conclusions or spread any misinformation here.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=300979}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: Any plans for a new GUI? &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Dec 10th, 2016 &lt;br /&gt;
  |added  =  Dec 10th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |We've already fixed that in the (old) map dialog, by using an azimuthal equidistant projection (see [[images/7/71/Map_north_pole_route.jpg|screenshot]]). Porting the projection to Canvas is on my todo list. Such a projection is much much better for navigational use.&lt;br /&gt;
Curves in routes are not calculated by Canvas, nor by the ND though. It's the route manager that splits up a route in segments in order to get smooth transitions.&lt;br /&gt;
  |{{cite web |url={{forum url|p=227838}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Canvas ND performance issues with route-manager&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Gijs&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Dec 23&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The new ND uses the actual route-manager paths, which allows it to draw holdings, flyby waypoints (thanks to James recent work) etc. But we'll need the azimuthal projection anyway, so I'll bump my todo list&lt;br /&gt;
  |{{cite web |url={{forum url|p=227845}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Canvas ND performance issues with route-manager&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Gijs&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Dec 23&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |I do agree that it would make sense to sub-class the Canvas projection class and implement Gijs' changes there, like we originally discussed in the merge request: {{gitorious url|fg|flightgear|commit=3f433e2c35ef533a847138e6ae10a5cb398323d7}}&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
{{gitorious url|fg|flightgear|commit=96a2673dd8a08b70396e2be1e567c0e89d8cf6e3|path=src/GUI/MapWidget.cxx|line=1573}}&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
Ideally, we would expose the projection as a property for each Map so that it can be changed dynamically.&lt;br /&gt;
  |{{cite web |url={{forum url|p=227932}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Canvas ND performance issues with route-manager&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Wed Dec 24&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
These are the  most likely jobs ahead to improve MapStructure performance:&lt;br /&gt;
* adopt our caching scheme (i.e. referencing precreated raster images rather than having hundreds of openVG paths) {{Progressbar|80}}&lt;br /&gt;
** port existing MapStructure layers to make use of the cache (VOR, NDB, DME etc)&lt;br /&gt;
* optimize our use of navcache queries as per {{Issue|1320}}&lt;br /&gt;
* use cppbind to turn props.nas APIs into native code ?&lt;br /&gt;
* investigate exposing systime() stats for each loop/callback per layer ?&lt;br /&gt;
* expose canvas-specific stats via SGTimeStamp to each canvas element ?&lt;br /&gt;
&lt;br /&gt;
=== Porting the map dialog === &lt;br /&gt;
{{Progressbar|80}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |is anyone working on replacing the map dialog based upon this code? Because that seems like the most important use case to demonstrate that MVC pattern can support, aside from the NavDisplays.&lt;br /&gt;
  |{{cite web |url={{forum url|p=193275}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: How to display Airport Chart?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;zakalawe&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Mon Nov 04&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |I was just reviewing some UI changes, and realised we have three map options, which seems a little excessive. I’d like to deprecate my ‘manual' OpenGL map in favour of the canvas variant, but the Canvas variant is missing some features:&lt;br /&gt;
* can’t be panned with the mouse&amp;lt;br/&amp;gt;&lt;br /&gt;
* dialog doesn’t resize nicely&amp;lt;br/&amp;gt;&lt;br /&gt;
* no course + distance computation (although this feature also seems to be broken in the OpenGL version)&lt;br /&gt;
Is anyone actively working on the Canvas map? I’d be happy to collaborate to address remaining pieces so we can drop the old map (probably after 3.4 I guess, given the timeframe)&lt;br /&gt;
  |{{cite web |url=http://sourceforge.net/p/flightgear/mailman/message/33119678/&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;[Flightgear-devel] Canvas map dialog status&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;James Turner&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;2014-12-06&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  | I noticed a couple of bugs in the Canvas-in-PUI map in 3.3 git:&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;ol style{{=}}&amp;quot;list-style-type: decimal&amp;quot;&amp;gt;&amp;lt;li&amp;gt; The VOR radial is always true. It doesn't listen to the &amp;quot;Magnetic headings&amp;quot; checkbutton. In northern part of Canada true heading is used (because of the extreme magnetic variation), but anywhere else magnetic.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; When I click &amp;quot;Aircraft heading up&amp;quot;, the radial of the VOR doesn't get rotated.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; I cannot click and drag the map.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; The blue &amp;quot;flight history&amp;quot; line is very thick.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;When I zoom in, the map moves and display certain symbols to the edges for a split second, but then hides them. It should instead hide them before doing the translation of the symbol.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;The map hides too much symbols at the edges in any case. It should be more like the PUI map IMHO, which displays a symbol if it could be visible in the GUI widget.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;When I zoom in or out, the relative location of various symbols changes. For example, go to CYRB and zoom in on the map. The VOR seems to be between the 2 NDB's.&amp;lt;/li&amp;gt; &amp;lt;li&amp;gt;If you zoom all the way out, the VOR moves to the left of the 2 NDB's. The old PUI map doesn't have this problem.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Sometimes labels of waypoints are difficult to read.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ol&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  |{{cite web |url={{forum url|p=227124}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Phasing out MapWidget post 3.2&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;onox&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Mon Dec 15&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
this is now committed to fgdata&lt;br /&gt;
{{Git Topic Branch}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|The dialog as shown below will be part of FlightGear 3.2, future changes (mostly additional/refined layers and controllers) are planned for the subsequent release cycle, i.e. post 3.2}}&lt;br /&gt;
&amp;lt;gallery mode=packed widths=230px heights=230px&amp;gt;&lt;br /&gt;
Map-canvas-dialog.png|The upcoming Canvas/MapStructure based dialog (purely scripted) &lt;br /&gt;
Map-canvas-dialog-flightpath.png|Philosopher's new FLT (flight path/history) layer for the MapStructure framework (flight path exposed to Nasal by TheTom)&lt;br /&gt;
Map-canvas-dialog-route-mgr.png|The new Canvas Map dialog showing the waypoint/route layers originally developed by Gijs, and now ported to MapStructure by Philosopher&lt;br /&gt;
Map-canvas-dialog-storms-overlay.png|Gijs' original wxr (storms) layer ported to Philosopher's MapStructure framework (still experimental)&lt;br /&gt;
Map-canvas-chain-home-editor.png|Experimental interactive MapStructure layer for visually placing 3D objects&lt;br /&gt;
Map-canvas-dialog-native.png|native [[Canvas]] map widget windows without any PUI (legacy GUI) involved, supporting multiple independent instances - good for quick regression testing, but also profiling and benchmarking&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |the recent GUI work committed by TheTom means that we can phase out the PUI map-canvas.xml dialog and create the whole dialog procedurally using a native canvas window, because we really only need two types of widgets: buttons and checkboxes, which are both supported now, thanks to the new &amp;quot;Aircraft Center&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
[[File:Native Canvas MapWidget Dialog.png|450px|thumb|Native Canvas MapWidget dialog using just Canvas Widgets, the Map has been turned into a Canvas Widget itself, so that it can be easily embedded in other dialogs (obviously the whole thing still needs to be cleaned up, layouting isn't currently used yet). See {{forum link|p=213435}}]]&lt;br /&gt;
&lt;br /&gt;
[[File:Native Canvas MapWidget Dialog Prototyping.png|400px|thumb|More Canvas/MapWidget prototyping. See {{forum link|p=213435}}]]&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure dialog with clipping and event handling applied.png|400px|thumb|Screen shot showing TheTom's modified demo dialog with a [[MapStructure]] based map that has clipping and event handling applied, i.e. can respond to common mouse events in order to either display tooltips or support drag&amp;amp;drop-style GUI dialogs for editing map-like displays, e.g. for creating an [[Advanced weather]] GUI {{forum link|t=17642}}. Particular care must be taken to formalize z-index handling (rendering priority) for each MapStructure layer, which is something we still need to work out, especially for possibly overlapping symbols...]]&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-based Weather Editor Widget for Canvas using interactive and editable lcontroller files.png|500px|thumb|This screen shot shows an experimental MapStructure-based Weather Editor Widget for [[Canvas]] using interactive and editable lcontroller files that respond to GUI events so that map symbols can be interactively placed on a map.]]&lt;br /&gt;
&lt;br /&gt;
  |{{cite web |url={{forum url|p=213083}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: NavDisplay &amp;amp; MapStructure discussion (previously via PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Sat Jun 21&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Porting the airports.xml dialog ===&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The rationale being that we're in the process of turning our map-canvas.xml dialog into a generic, and reusable, widget for $FG_ROOT/Nasal/canvas/gui/widgets - the widget is using the new Layout engine to align other widgets (checkboxes &amp;amp;amp; buttons for now). And one of our goals is to also modernize airports.xml (the airport selection dialog) such that it can use MapStructure. The simplest way would be to simply turn the PUI CanvasWidget into a Canvas.Window() so that layouting etc works as expected, and so that we can simply show the new scripted MapWidget there.&lt;br /&gt;
  |{{cite web |url={{forum url|p=213493}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Using a PUI CanvasWidget as a Canvas.Window ?&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Thu Jun 26&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[File:The legacy PUI-airport selection dialog using the new Canvas MapWidget.png|450px|thumb|This is a screen shot showing Stuart's PUI/XML based airport selection dialog with an embedded Canvas MapWidget that is driven by Philosopher's [[MapStructure]] framework using TheTom's latest GUI/Layouting changes (e.g. checkbox widgets).]]&lt;br /&gt;
&lt;br /&gt;
For runways.draw it would make sense to split up the function to have a helper function that draws a single runway.&lt;br /&gt;
Runway-drawing is fairly modular already, so we should be able to populate a SymbolCache with a handful of building blocks for '''any''' runway, and then scale/transform it as needed, while custom stuff would be rendered on top. That should help speed up things rather significantly. Also, we could then have a shared AirportCache that keeps a FIFO of 4x4 airports by using a single 1024x1024 texture. Multiple ND/dialog instances could then access the same cache to speed up things. Taxiways are a different beast, would need to be rendered and cached in full, because they're too custom drawing-wise.&lt;br /&gt;
&lt;br /&gt;
Regarding generic-canvas-map.xml: it would be awesome to remove this, but doing so will break Stuart's airport selection dialog ...&lt;br /&gt;
We need to get rid of map.nas first of all, i.e. port airport-selection to use MapStructure - because airport-select includes and parameterizes the corresponding file.&lt;br /&gt;
&lt;br /&gt;
I suppose we should continue this discussion in public, either on the forum or via the wiki - where we have a section on porting this dialog, and getting rid of map.nas&lt;br /&gt;
&lt;br /&gt;
I'd love to see all this stuff removed obviously - but there's still some work ahead AFAIU&lt;br /&gt;
But it should be perfectly doable within the upcoming release cycle. It's not exactly a lot of work either. Mostly refactoring and generalization.&lt;br /&gt;
The whole workaround is documented at: http://wiki.flightgear.org/Canvas_Map_API#Full_Example:_Creating_dialogs_with_embedded_Canvas_Maps&lt;br /&gt;
&lt;br /&gt;
The first step would be porting the missing layers to MapStructure *.symbol files, i.e. stuff like towers, airports (parking, runways, taxiways).&lt;br /&gt;
Then, we'll need to add another map controller for panning support.&lt;br /&gt;
The XML file itself will also procedurally add &amp;quot;toggle&amp;quot; checkboxes for each requested layer, so that's why there's so much code in it.&lt;br /&gt;
map-canvas.xml can probably become 100% Canvas based pretty soon. But the airport-selection dialog contains a ton of PUI widgets for which do not yet have Canvas equivalents. So I am more inclined to &amp;quot;just&amp;quot; port the 3-4 missing layers, add a map controller for panning and then adopt MapStructure there, at which point the original map.nas stuff can be deleted, it will have served its purpose by then, i.e. establishing the MVC separation.&lt;br /&gt;
&lt;br /&gt;
Also, some missing layers may benefit from caching to some extent, and styling should probably be supported by all of them.&lt;br /&gt;
&lt;br /&gt;
=== TFC ===&lt;br /&gt;
* generalize aircraft position controller to support AI/MP traffic&lt;br /&gt;
* investigate adopting our caching scheme to make the layer faster&lt;br /&gt;
&lt;br /&gt;
=== Hooray's Todo List ===&lt;br /&gt;
* 06/2014 MapStructure.nas:&lt;br /&gt;
** CENTERED/HDG/MAG TRK view modes (navdisplay.mfd) ?&lt;br /&gt;
** ILS &amp;amp; WIND layers are missing (see Map dialog, see navdisplay.mfd for WIND code)&lt;br /&gt;
** add StationaryObjectLayerController helper where update semantics are wrapper properly (i.e. used by the new NavaidSymbolLayer)&lt;br /&gt;
** introduce a LegSymbolLayer for LineSymbols (RTE/FLT)  ?&lt;br /&gt;
** instead of directly accessing properties like AP/RM/RADIO, encapsulate in driver hash (conceptually, we could read/display an AI flight plan for example)?&lt;br /&gt;
** encapsulate '''main'''-only layers (RTE,WPT) and depending features (AP/RM/RADIO stuff in VOR/DME etc) that won't work for AI/MP traffic (driver hash)&lt;br /&gt;
** LayerController: provide a method to enable/disable layers when paused (/sim/freeze), e.g. FLT doesn't make much sense to constantly redraw here ?&lt;br /&gt;
** the SAT (fetched image overlay) stuff is too hacky, need to introduce an '''OverlayLayer''' helper that wraps remotely fetched raster images&lt;br /&gt;
** displaying TCAS/AIS feeds can now be done via Nasal http bindings and running JSON queries, better sub-class MultiSymbolLayer to support feeds where the fetching method is the equivalent of searchCmd, but can be re-implemented for different URL schemes using Philosoper's/TheTom's demo code &lt;br /&gt;
** interactive stuff is not yet mature enough to work, unless it's just tooltips for each symbol, but could help us implement DATA ?&lt;br /&gt;
&lt;br /&gt;
* layers should probably encode information about being &amp;quot;safe&amp;quot; for non-main aircraft, i.e. a few layers don't make much sense for AI/MP traffic due to references to properties/APIs that are n/a, such as flight plan/routing, autopilot, instrumentation properties - those lookups should be disabled by the &amp;quot;driver&amp;quot; hash (or position controller).  &lt;br /&gt;
* SymbolLayer should probably be sub-classed for &amp;quot;static&amp;quot; layers (navaids) and &amp;quot;volatile&amp;quot; layers with movign objects as per Philosopher's comments&lt;br /&gt;
* LOD handling needs to be refined, cannot be just setScale() in complex cases like taxiways/runways - we better add an interface that receives notifications once the range is changed (MapController) and use different callbacks for certain ranges ? {{Progressbar|60}}&lt;br /&gt;
* work out a good way to integrate caching and styling, where each style variation would be a separate cache entry {{Progressbar|40}}&lt;br /&gt;
* once that works, we can easily implement animations on top of cached entries with different styles and use a timer to change/hide/show groups&lt;br /&gt;
* extend the SymbolCache class to integrate the dialog for inspection purposes, i.e. via a method so that each cache can be quickly visualized/inspected&lt;br /&gt;
** maybe maintain a refcount for each referenced cache entry ?&lt;br /&gt;
** provide a method freeSpots() ?&lt;br /&gt;
** introduce a CacheManager class as the super class managing different SymbolCache objects ?&lt;br /&gt;
* need to extend the svg parser to provide hooks for registering callbacks so that we can also support styling for SVG images, based on looking up elements by ID and override colors &lt;br /&gt;
* Symbol.Controller.getpos() is doing two things at once 1) checking if the obj is supported, 2) extracting lat/lon - it would be better to split this, so that we can reuse the ''is_supported'' check in other places, such as validating searchCmd() results&lt;br /&gt;
* MapController: need to expose some flags to determine if a map is used inside a GUI dialog or aircraft: This is  because we do not typically need to update most map layers shown in an aircraft when paused - but we may very well want to update layers when used inside a GUI dialog, i.e. check maketimer() use here.  &lt;br /&gt;
* make logging a part of the framework, overload print/printlog &lt;br /&gt;
* make profiling via systime() a part of the framework, for each controller/model/view (optional)&lt;br /&gt;
* investigate hardening, i.e. &lt;br /&gt;
** type-checking (typeof) &lt;br /&gt;
** e.g. searchCmd should always return a vector, and isa(type) should be supported by getpos {{Progressbar|60}}&lt;br /&gt;
** checking available symbols (and typeof) for scontroller/lcontroller and symbol files &lt;br /&gt;
* clean up existing files and add more comments {{Progressbar|20}}&lt;br /&gt;
** including a MapStructure URL (wiki) to each file so that people know where to look for further info {{Done}}&lt;br /&gt;
* add a new section: Porting Layers (i.e. from the old format to the new one)&lt;br /&gt;
* provide an option to suspend/restart MapStructure&lt;br /&gt;
* provide an option to reload MapStructure (layers) from disk (RAD)&lt;br /&gt;
* styling - will involve: {{Progressbar|30}} (currently being prototyped inside the DME layer)&lt;br /&gt;
** allow colors/fonts to be overridden, i.e. any draw .symbol callback would use a lookup hash that can be customized (instead of hard-coding things)&lt;br /&gt;
** allow size to be overridden (overlapping with LOD support)&lt;br /&gt;
** allow custom file names or callbacks to be provided for symbols (draw routines)&lt;br /&gt;
** i.e. come up with a &amp;quot;Styleable&amp;quot; class that exposes an interfaces that is implemented by StyleableColor, StyleableFont, StyleableSymbol&lt;br /&gt;
** this means that style-specific things should never be directly part of the draw routine itself, but need to be encapsulated, i.e. setColor/setColorFill/setSize/setText/setFont/setFontSize etc - so that these methods can be afterwards called on the canvas group - the styleable class should probably just sub-class Symbol.Controller to make this work&lt;br /&gt;
&lt;br /&gt;
== Roadmap post 3.2 ==&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Well, we've been talking about the lack of current navdata in FG recently. On the MapStructure side of things we really only need to encapsulate any NasalPositioned calls, to support arbitrary data - including even fetched (XML), or manually entered navaids/fixes. Such data could then  be centrally served or based on NaviGraph. So we wouldn't have to touch any of the existing NavDB stuff to work around its limitations.&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
So I am thinking about moving all NasalPositioned queries in MapStructure into a &amp;quot;driver&amp;quot; hash, but maybe more in line with the aircraftpos.controller stuff that Philosopher developed, just specific to some kind of &amp;quot;NavaidSource&amp;quot;. &amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
That would allow us to easily work around such limitations, so that I-NEMO, Hyde, Gijs etc can easily use the latest data on their EFB/ND. The other motivation  here is that I experimented with interactive layers, i.e. where objects could be visually placed on a map-these could be scenery objects, but also navaids. And once we move such assumptions out of the lcontroller files, we can trivially support other cool use-cases, not just GUI-based editing, but even simulated radio navigation or other &amp;quot;learning&amp;quot; tools:&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
(see the [http://www.sportys.com/morepics/17155r.jpg linked image]) (see the [http://a4.mzstatic.com/us/r30/Purple/v4/4f/00/fe/4f00fe9c-5ff7-142c-3e92-f71a23304471/screen480x480.jpeg linked image])&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; [http://www.visi.com/~mim/nav/ http://www.visi.com/~mim/nav/]&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; [http://www.pyrochta.ch/english/PageNavigationTrainer/PageVOR/vor.html http://www.pyrochta.ch/english/PageNavi ... R/vor.html]&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt; [http://www.luizmonteiro.com/Learning_VOR_Sim_1.aspx http://www.luizmonteiro.com/Learning_VOR_Sim_1.aspx]&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  |{{cite web |url={{forum url|p=213215}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: 777 EFB: initial feedback&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Mon Jun 23&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* port missing layers {{Progressbar|70}}&lt;br /&gt;
* the granularity of the maketimer()-created update timers should be configurable/exposed when creating a layer, i.e. to allow aircraft developers to adjust timing as required&lt;br /&gt;
* handle signals to trigger controller updates for radio, autopilot, route manager or AI/MP and weather changes {{Progressbar|60}} &lt;br /&gt;
* adapt [[NavDisplay]] framework to use MapStructure internally {{Progressbar|80}}&lt;br /&gt;
* look into extending the controller framework for different use cases (ND/airport selection dialog) {{Progressbar|30}}&lt;br /&gt;
* look into porting the [[Map]] dialog {{Progressbar|20}}&lt;br /&gt;
* look into updating the airport-selection dialog {{Not done}}&lt;br /&gt;
* add a MapStructure-based chart to the route manager dialog {{Not done}}&lt;br /&gt;
* integrate the ND into other airliner cockpits (757,767 and Airbus series) to ensure that things remain generic {{Progressbar|20}}&lt;br /&gt;
&lt;br /&gt;
=== Framework ===&lt;br /&gt;
* we should add some helper functions for people to more easily do RAD when developing new layers/controllers, this would include supporting reloading from disk, and creating a simple canvas window to show the corresponding layers, maybe with a &amp;quot;reload&amp;quot; button that suspends and restarts everything after reloading - this would probably even help us {{Not done}}&lt;br /&gt;
* investigate adding a dedicated &amp;quot;Filter&amp;quot; class for positionedSearches, so that things like custom object filters (think ATC/RADAR etc) can be more easily implemented by implementing a corresponding interface (mirage2000) {{forum link|p=199555}} {{Not done}}&lt;br /&gt;
* logging should probably be handled by the framework directly, so that messages can be redirected or stored {{Not done}}&lt;br /&gt;
* support benchmarking/profiling of layers via systime() - as mentioned by Philosopher on the forum {{Not done}}&lt;br /&gt;
* a simple form of unit testing (sanity checks) would make sense for regression testing purposes, i.e. to ensure that the number of drawables never grows beyond the total number of elements (ditto for layers) {{Not done}}&lt;br /&gt;
* properly implement (separate) init/update at the framework level {{Not done}}&lt;br /&gt;
* consider supporting loops that are split across frames {{Not done}}&lt;br /&gt;
* add LOD support {{Progressbar|20}}&lt;br /&gt;
* add styling support (colors, different SVG images or draw callbacks for symbols) {{Not done}} &lt;br /&gt;
* many features would greatly benefit from having a simple animation framework, i.e. for changing/scaling symbols etc (not specific to positioned objects) {{Not done}}&lt;br /&gt;
* implement a caching scheme {{Progressbar|70}}&lt;br /&gt;
&lt;br /&gt;
== Using Layers ==&lt;br /&gt;
&lt;br /&gt;
[[File:MapStructure-Layers.png|500px|Using MapStructure Layers]]&lt;br /&gt;
&lt;br /&gt;
the kind of object that is typically needed by MapStructure is just an geo-referenced position, which is just fancy lingo for anything that has a 3D position (lat,lon,altitude)&lt;br /&gt;
MapStructure will internally handle all the details to implement each layer efficiently.&lt;br /&gt;
&lt;br /&gt;
The framework works in terms of &amp;quot;symbols&amp;quot; and &amp;quot;layers&amp;quot; where each layer would have symbols, along with controllers for each symbol (i.e. to animate things, to change the color/style etc) - also, each layer can be controlled using a layer-controller, e.g. to change the range for example.&lt;br /&gt;
&lt;br /&gt;
I suggest to look at some of the simpler examples, e.g. the NDB symbol to see how everything is implemented.&lt;br /&gt;
You can basically load a SVG file or draw your own symbols using OpenVG instructions.&lt;br /&gt;
&lt;br /&gt;
Then, the layer controller will determine where symbols are to be drawn, and do any filtering (range, altitude, mountains, azimuth).&lt;br /&gt;
&lt;br /&gt;
So the result will just return a vector (resizable array) to the layer controller with drawables (symbols).&lt;br /&gt;
&lt;br /&gt;
You will probably want to experiment first with a really simple example to see how everything works.&lt;br /&gt;
&lt;br /&gt;
I suggest to try out the demo/example mentioned in the article.&lt;br /&gt;
&lt;br /&gt;
Ultimately, you can them combine many layers to form a single map, and each layer can have &amp;quot;many&amp;quot; different symbols&lt;br /&gt;
&lt;br /&gt;
== Creating new Layers ==&lt;br /&gt;
As you may have noticed, we have significantly grown the MapStructure docs meanwhile - the short-term goal here is that people should be able to come up with their own layers for MapStructure, or at least be able to help port the old files used by map.nas (*.model/*.layer/*.draw) to MapStructure (*.scontroller/*.lcontroller and *.symbol).&lt;br /&gt;
&lt;br /&gt;
Technically, there's not very much involved meanwhile. &lt;br /&gt;
&lt;br /&gt;
{{Note|&lt;br /&gt;
It makes sense to use existing layers as templates, i.e. copy a set of files that is close to what you want to implement - e.g.  a RADAR or ATC layer will typically involve processing AI/MP traffic (changing positions) - which is what the TCAS (TFC) layer is already doing - so it's a good idea to use that as a template for your new layer.&lt;br /&gt;
&lt;br /&gt;
Using other layers like VOR, NDB or DME would also be possible - but think about what these represent: navaids, with fixed geographic coordinates, while an ATC/RADAR layer would be all about showing live traffic, so it would be less work to reuse and customize a similar layer instead.&lt;br /&gt;
&lt;br /&gt;
Then again, using a complex layer to represent a simple thing would also be more customizing work than necessary obviously. You can also borrow things from different layers-for example, the VOR/DME layers contain support for animating symbols and range-selection based display modes.&lt;br /&gt;
Thus, it's a good idea to spend 5-10 minutes looking through existing files and playing with them, to see where you can borrow code from.}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
So, porting a simple layer can now be done within a few minutes. It mostly boils down to:&lt;br /&gt;
&lt;br /&gt;
* finding a similar layer (i.e. for static objects like navaids - just take an existing navaid layer, for dynamic non-static objects (AI/MP traffic), look at the TFC layer instead - e.g. this could be used for porting WXR/storms or to come up with a radar/ATC layer)&lt;br /&gt;
* copying a set of similar *.lcontroller/*.scontroller and *.symbol files (you can also combine things from different files, e.g. to reuse animated symbols (altitude arc)&lt;br /&gt;
* add either custom drawing routines (Nasal/Canvas) or Inkscape svg files&lt;br /&gt;
* write (or port) the corresponding draw() routine (inside the symbol) file&lt;br /&gt;
* registering the whole thing in MapStructure.nas to load the new layer files&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Adding-new-MapStructure-Layers.png]]&lt;br /&gt;
&lt;br /&gt;
Copy three files (all in Nasal/canvas/map/): &lt;br /&gt;
* *.symbol (drawing/update routine(s))&lt;br /&gt;
* *.scontroller (symbol controller)&lt;br /&gt;
* *.lcontroller (layer controller)&lt;br /&gt;
&lt;br /&gt;
People interested in developing/maintaining MapStructure layers, will probably want to start with a simple example first.&lt;br /&gt;
&lt;br /&gt;
The most straightforward starting points should be:&lt;br /&gt;
* airplaneSymbol&lt;br /&gt;
* tower&lt;br /&gt;
* parking&lt;br /&gt;
* wxr/storms&lt;br /&gt;
&lt;br /&gt;
You may also want to check out [[MapStructure Debugger]].&lt;br /&gt;
&lt;br /&gt;
=== Internals ===&lt;br /&gt;
&lt;br /&gt;
Each file represents a virtual class (no explicit hash needed - just use caller(0)[0]).&lt;br /&gt;
&lt;br /&gt;
First, we need to set up class things, so each file &amp;quot;bootstraps&amp;quot; itself.&lt;br /&gt;
&lt;br /&gt;
Class members/local variables:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| name&lt;br /&gt;
! scope=&amp;quot;col&amp;quot; width=&amp;quot;340&amp;quot; | Description&lt;br /&gt;
! *.symbol&lt;br /&gt;
! *.scontroller&lt;br /&gt;
! *.lcontroller&lt;br /&gt;
! *.controller&lt;br /&gt;
! Links&lt;br /&gt;
|-&lt;br /&gt;
! parents = &lt;br /&gt;
| ''Set up class inheritance.''&lt;br /&gt;
|align=&amp;quot;center&amp;quot;| &amp;lt;tt&amp;gt;[DotSym]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;| &amp;lt;tt&amp;gt;[Symbol.Controller]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;| &amp;lt;tt&amp;gt;[SymbolLayer.Controller]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;| &amp;lt;tt&amp;gt;[Map.Controller]&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! __self__ = &lt;br /&gt;
| ''Current class being generated.''&lt;br /&gt;
|align=&amp;quot;center&amp;quot; colspan=&amp;quot;4&amp;quot;| &amp;lt;tt&amp;gt;caller(0)[0]&amp;lt;/tt&amp;gt;&lt;br /&gt;
| [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.symbol|line=4}}] [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.scontroller|line=3}}] [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.lcontroller|line=3}}]&lt;br /&gt;
|-&lt;br /&gt;
! name = &lt;br /&gt;
| ''Name to use for referencing (via &amp;lt;tt&amp;gt;Symbol&amp;lt;wbr/&amp;gt;[SymbolLayer]&amp;lt;wbr/&amp;gt;[.Controller]&amp;lt;wbr/&amp;gt;.get(name)&amp;lt;/tt&amp;gt;, etc.) and console messages.''&lt;br /&gt;
|align=&amp;quot;center&amp;quot; colspan=&amp;quot;4&amp;quot;| name of file before the extension, or otherwise as appropriate; e.g. &amp;lt;tt&amp;gt;&amp;quot;VOR&amp;quot;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
! element_type = &lt;br /&gt;
| ''Type of [[Canvas Element]] to use; becomes &amp;lt;tt&amp;gt;me.element&amp;lt;/tt&amp;gt;.''&lt;br /&gt;
| &amp;lt;tt&amp;gt;&amp;quot;group&amp;quot;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;&amp;quot;path&amp;quot;&amp;lt;/tt&amp;gt; (usually)&lt;br /&gt;
| {{N/a}}&lt;br /&gt;
| {{N/a}}&lt;br /&gt;
| {{N/a}}&lt;br /&gt;
| {{gitorious source|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.symbol|line=7|text=VOR}} {{gitorious source|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/FIX.symbol|line=7|text=FIX}}&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Adding this instance to MapStructure's dictionaries and make it be the default controller (where &amp;quot;name&amp;quot; is an arbitrary-but unique-handle used to reference it inside of OOP abstraction):&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! *.symbol&lt;br /&gt;
! *.scontroller&lt;br /&gt;
! *.lcontroller&lt;br /&gt;
! *.controller&lt;br /&gt;
|-&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;DotSym.makeinstance( name, __self__ );&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;Symbol.registry[ name ].df_controller = __self__;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;SymbolLayer.Controller.add( name, __self__);&amp;lt;/tt&amp;gt;&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;Map.Controller.add( name, __self__);&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| {{N/a}}&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;Symbol.registry[ name ].df_controller = __self__;&amp;lt;/tt&amp;gt;&lt;br /&gt;
| {{N/a}}, see below regarding the SymbolLayer&lt;br /&gt;
|align=&amp;quot;center&amp;quot;|&amp;lt;tt&amp;gt;Map.df_controller = __self__;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Also in *.lcontroller, we need to set up the actual MultiSymbolLayer, whose methods are already handled by MapStructure, so we only need to specify a few items:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
SymbolLayer.add(name, {&lt;br /&gt;
    parents: [MultiSymbolLayer],&lt;br /&gt;
    type: name, # Symbol type, i.e. for Symbol.get( ... )&lt;br /&gt;
    df_controller: __self__, # controller to use by default -- this one&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|There's also a SingleSymbolLayer which has a slightly different API -- no searchCmd(), only a getModel(), see below}}&lt;br /&gt;
&lt;br /&gt;
=== Symbols ===&lt;br /&gt;
[[File:MapStructure-Symbol-UML.png]]&lt;br /&gt;
&lt;br /&gt;
Symbols are pretty simple - just stick to the members/methods outlined above: &amp;lt;tt&amp;gt;.init()&amp;lt;/tt&amp;gt; is called when the symbol is created, and may optionally call &amp;lt;tt&amp;gt;me.update()&amp;lt;/tt&amp;gt; to ensure the symbol is immediately ready to go; and &amp;lt;tt&amp;gt;.draw()&amp;lt;/tt&amp;gt; is called each time the symbol is updated. Do '''not''' overwrite .update()! It is already handled by MapStructure, so use .draw() instead.&lt;br /&gt;
&lt;br /&gt;
{{Note|Should we add a runtime/sanity check to ensure that symbol.update is a func and points to MapStructure, i.e. is not overridden ?}}&lt;br /&gt;
&lt;br /&gt;
For each object, one is provided with a handle to the model and controller of the symbol:&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;model&amp;quot; object (me.model) is ultimately specified by the one creating the Canvas.Symbol - aka the SymbolLayer.Controller. It is usually a [[List_of_Nasal_extension_functions#Positioned_Object_Queries|positioned object]], though it could be anything, as long as the methods required by MapStructure are provided by the programmer - specifically the latlon()/getPos() helpers.&lt;br /&gt;
&lt;br /&gt;
(In the case of the TFC layer (handling AI/MP traffic), it is a hash wrapping props.Node and geo.Coord).&lt;br /&gt;
&lt;br /&gt;
The position of the symbol is automatically handled by MapStructure by extracting position information from the object via the corresponding .getPos() call and applying it via .setGeoPosition() on me.element, so the programmer only has to worry about drawing it. The model, however, often provides other information relevant to drawing the symbol, like the frequency of a VOR, or how fast it is going.&lt;br /&gt;
&lt;br /&gt;
A &amp;quot;controller&amp;quot; object (me.controller) is typically obtained from the corresponding *.scontroller file (as a default, see Symbol.df_controller), but can be overridden by the creator of the symbol (hopefully it supports the same API to be compatible!). This will handle the rest of information ''not'' handled by the model, typically via &amp;quot;query&amp;quot; methods - stuff like whether this symbol is selected in some way (e.g., by radio setting), settings of the parent map (like range), etc.&lt;br /&gt;
&lt;br /&gt;
In both the .init() and .draw() methods, one can use the automatically created canvas element (me.element) to draw the actual symbol. These are usually static images that are procedurally created via the draw() callback, so it's best to set up lazily-rendered-yet-persistent elements that are simply hidden or shown as needed. &lt;br /&gt;
&lt;br /&gt;
If the symbol is really simple, such that it only needs to be drawn once and never updated, then draw it in .init() and don't define .draw() at all.&lt;br /&gt;
&lt;br /&gt;
Once we adopt the built-in caching scheme, some things will change here - specifically such that each variation in styling of a certain symbol is pre-created so that it will later on only need to be referenced as a canvas raster image (via a texture map lookup).&lt;br /&gt;
&lt;br /&gt;
{{Note|Stlying is currently being prototyped, see the DME.symbol file in the topics/canvas-radar branch for examples.}}&lt;br /&gt;
&lt;br /&gt;
To support LOD-handling and symbol-specific styling, we will also need to expose a handful of methods to encourage separation of scale/style-specific things into symbol files, such as the following methods which should ideally not be hard-coded in symbol files, but use hash lookups for customization purposes: &lt;br /&gt;
* setColor()&lt;br /&gt;
* setColorFill()&lt;br /&gt;
* setLineWidth()&lt;br /&gt;
* setScale()&lt;br /&gt;
* setSize()&lt;br /&gt;
* setFont()&lt;br /&gt;
* setFontSize()&lt;br /&gt;
* setText()&lt;br /&gt;
* setImage()&lt;br /&gt;
&lt;br /&gt;
Currently, we have a bunch of symbol files calling directly the corresponding canvas equivalents via method chaining - however, to allow LOD-handling and styling, we must encourage separating these things, so that things can be customized as needed, i.e. by calling the symbol's me.apply_styling() method after the draw/update routine has finished-which will allow aircraft developers to easily use custom fonts/size/colors or symbols - without having to edit -or worse- duplicate the .symbol-file itself&lt;br /&gt;
&lt;br /&gt;
{{Note|I am wondering if we could use some fancy metaprogramming to compile a draw() callback and ensure that it does NOT use certain Canvas APIs directly ? That would be kinda cool and useful, i.e. we could do a dry test-run to make sure that methods only use certain allowed APIs. One possible method would be overloading the canvas namespace to provide sstubs for each allowed/disallowed API and use call() to call each method and keep track via any disallowed APIs were called by using a counter or even just doing die()-so that people would get an error message along the lines of &amp;quot;please do not use setColor/setColorFill etc inside the draw() callback. Given that MapStructure itself handles positioning via setGeoPosition(me.element), we could even watch out for such mistakes that way}}&lt;br /&gt;
&lt;br /&gt;
=== The SymbolCache ===&lt;br /&gt;
&lt;br /&gt;
For the time being, the main optimization that helps speed up rendering Canvas/MapStructure-based displays like the [[NavDisplay]], is using caching. &lt;br /&gt;
&lt;br /&gt;
Caching is accomplished by a little helper framework called '''SymbolCache''' which sets up an empty Canvas texture for storing required symbols there. &lt;br /&gt;
&lt;br /&gt;
[[File:PropertyBrowser with Canvas Preview.png|thumb|Slightly extended Property Browser to show a preview for each Canvas for troubleshooting/debugging purposes, as per {{forum link|p=195232}}.]]&lt;br /&gt;
&lt;br /&gt;
This is where symbols for VORs, DMEs, FIXes and waypoints will be kept. &lt;br /&gt;
&lt;br /&gt;
Internally, each *.symbol file will still contain all the logic required to actually render the corresponding symbol, which may include  hard-coded OpenVG drawing commands, but also SVG or raster images. &lt;br /&gt;
&lt;br /&gt;
[[File:Early MapStructure Caching Experiments.png|thumb|MapStructure/SymbolCache experiments for caching symbols and symbol instancing {{forum link|p=193672}}]]&lt;br /&gt;
&lt;br /&gt;
However, the cache will be dynamically populated according to all the symbols that are required for each layer. This approach proved quite efficient and straightforward so that even the extra500 developers adopted this method on their [[Avidyne Entegra R9]] instrument. &lt;br /&gt;
&lt;br /&gt;
In the meantime, the SymbolCache has been significantly extended to also support styling: in other words, the SymbolCache now even works for *.symbol files supporting styling by being aware of styling-relevant attributes (think width, symbols, colors etc) and will create distinct cache entries for each symbol variant. &lt;br /&gt;
&lt;br /&gt;
Under the hood, this is using some fancy meta-programming that Philosopher came up with last year - but all you need to know as a MapStructure/ND contributor is that styling and caching work in conjunction as long as you follow a few simple rules - which can easily translate into significant frame rate gains when compared to the old method. This section is intended to describe the basic method, as well as provide a few examples/pointers - we're hoping to grow this over time.&lt;br /&gt;
&lt;br /&gt;
First of all, let's consider an existing layer/example already using caching properly: VOR.symbol:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5,12.5)&lt;br /&gt;
        .lineTo(7.5,12.5)&lt;br /&gt;
        .lineTo(15,0)&lt;br /&gt;
        .lineTo(7.5,-12.5)&lt;br /&gt;
        .lineTo(-7.5,-12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width)&lt;br /&gt;
        .setColor(color);&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is a function named '''drawVOR''' (note, that the name is arbitrary) which accepts a single argument: a canvas group.&lt;br /&gt;
The function body itself then uses the passed group to create the relevant geometry for the corresponding symbol (in this case a VOR symbol).&lt;br /&gt;
There are two configurable (&amp;quot;styling&amp;quot;) settings: &lt;br /&gt;
* line_width&lt;br /&gt;
* color&lt;br /&gt;
&lt;br /&gt;
Styling defaults for things like line_width and color are set up in each symbol file - for example, refer to VOR.symbol:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
SymbolLayer.get(name).df_style = { # style to use by default&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    range_line_width: 3,&lt;br /&gt;
    radial_line_width: 3,&lt;br /&gt;
    range_dash_array: [5, 15, 5, 15, 5],&lt;br /&gt;
    radial_dash_array: [15, 5, 15, 5, 15],&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    active_color: [0, 1, 0],&lt;br /&gt;
    inactive_color: [0, 0.6, 0.85],&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''df_style''' is a new hash that contains styling defaults for this particular layer.&lt;br /&gt;
&lt;br /&gt;
Otherwise, everything else in the drawVOR() function can be considered to be &amp;quot;hard-coded&amp;quot;. When looking at other examples, the only other thing worth keeping in mind here is that  Nasal will implicitly return the last expression to the caller absent any explicit return statements - i.e. the group will be returned to the caller (for the sake of clarity, you could also add an explicit return statement, as is done in the snippet above).&lt;br /&gt;
&lt;br /&gt;
However, the code snippet above is just a drawing routine that is still unknown to the system, so that needs to be done is to set up a corresponding cache entry, which can be seen below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var cache = StyleableCacheable.new(&lt;br /&gt;
    name: name,&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: SymbolCache32x32,&lt;br /&gt;
    draw_mode: SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: [&amp;quot;line_width&amp;quot;, &amp;quot;color&amp;quot;],&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What is happening here is that a new cache entry supporting styling is set up (refer to MapStructure.nas for details), specifically:&lt;br /&gt;
* a new cache is set up using the name specified at the top of the file: '''VOR'''&lt;br /&gt;
* drawVOR is passed as the drawing function &lt;br /&gt;
* the cache texture to be used is SymbolCache32x32 (which is always available, provided by MapStructure itself)&lt;br /&gt;
* next, a draw_mode is set up (refer to  MapStructure.nas for etails)&lt;br /&gt;
&lt;br /&gt;
And finally, the really cool stuff is happening in the last line: This is where we meet again the two styling-related variables we saw earlier in the actual drawVOR() implementation: there's an argument called '''relevant_keys''' which should be a vector of symbols that are styling-related. This can be used by the SymbolCache/MapStructure framework to tell if a styled symbol is already cached or not.&lt;br /&gt;
&lt;br /&gt;
All of these steps may look complicated at first, but the difficult stuff is happening behind the scenes - now, when it comes to actually using/accessing our &amp;quot;style-able cache&amp;quot;, the only thing you need to know is how to get a certain pre-cached symbol out of the cache, which is why we'll take another look at VOR.symbol and its draw() implementation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# determine the color to be used (remember, this is relevant for styling!) &lt;br /&gt;
me.style.color = active ? me.style.active_color : me.style.inactive_color; &lt;br /&gt;
&lt;br /&gt;
# look up the correct symbol from the cache and render it into the group as a raster image, applying custom scaling&lt;br /&gt;
me.icon_vor = cache.render(me.element, me.style).setScale(me.style.scale_factor);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
What is happening here is that the cache entry is looked up for the selected style using '''me.style''', the textured quad (sub-texture) is dynamically retrieved from the SymbolCache and rendered into the canvas group specified via '''me.element''' - finally, scaling is applied to the raster image.&lt;br /&gt;
&lt;br /&gt;
Another example can be seen in FIX.symbol&lt;br /&gt;
&lt;br /&gt;
There are still several layers where caching+styling isn't widely used yet - however, over time this is the correct method to lighten the canvas workload quite a bit, and doesn't require any C++ level modifications. If you're seeing heavy impact on performance with complex layers, we suggest you explore adding styling and caching support as described above.&lt;br /&gt;
&lt;br /&gt;
Originally, the whole point of the cache was to simplify symbol management - meanwhile, it also supports styling. Under the hood, all it is dealing with is Canvas elements rendered to a single canvas, with each element having look-up coordinates conveniently stored in a hash. Which means that the SymbolCache could also be easily adapted to become a '''LayerCache''' at some point.&lt;br /&gt;
&lt;br /&gt;
This may be very useful in order not to re-draw redundant layers: for instance, the compass rose on the ND can be considered &amp;quot;redundant&amp;quot;: no matter how many NDs you are displaying - it makes sense to treat the compass rose as a dedicated layer on its own (see APS.* for examples) and then render that into its own texture map - we could easily set up a LayerCache analogous to the existing SymbolCache - transformations (rotating) would then merely be applied to the referenced raster image - all of a sudden, there will be less work for shivaVG (the OpenVG rendering back-end) to do, because the compass rose will rarely -if ever- need to be updated at all.&lt;br /&gt;
&lt;br /&gt;
All variations will be rendered into a layer cache and the COMPASS.symbol file would merely reference the correct sub-texture (e.g. plan/arc), and merely update/rotate the raster image child referenced by the actual ND. &lt;br /&gt;
&lt;br /&gt;
We kinda discussed the technique a few times already - i.e. introducing the concept of an &amp;quot;Overlay&amp;quot;-layer would make sense at some point- typically, this could be shared among multiple instances of a MFD (think ND/PFD) - this would even be more efficient than the existing hard-coded od_gauge based instruments are currently.&lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
Basically, you should look at Canvas-based MFDs and ask yourself which elements/layers are likely to be identical (or close enough), so that it would make sense to share certain elements (imagine background images, symbols, overlays and so on). Usually, this will reduce the workload for the Canvas system quite significantly, because an otherwise &amp;quot;complex&amp;quot; layer containing -for instance- OpenVG primitives will only ever be drawn/updated once during initialization and then merely referenced by instances of the actual instrument using it. You'll quickly see the merits of using this approach once you imagine rendering many (think 10+) instances of an ND or PFD.&lt;br /&gt;
&lt;br /&gt;
The basics on turning our existing SymbolCache framework into a LayerCache will be covered below:&lt;br /&gt;
&lt;br /&gt;
{{Caution|This code is still experimental and hasn't been tested yet ...}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# this sets up a new Canvas texture 1024x1024 with 4 locations for cached layers (each 512x512) &lt;br /&gt;
var CompassLayerCache = canvas.SymbolCache.new(1024, 512); &lt;br /&gt;
&lt;br /&gt;
# FIXME: check what else is required to cache SVG files loaded into a texture map  ...&lt;br /&gt;
CompassLayerCache.add(name: 'compass', callback: func(group) {&lt;br /&gt;
    canvas.parsesvg(group, &amp;quot;Nasal/canvas/map/Images/boeingND.svg&amp;quot;, { 'font-mapper': me.nd_style.font_mapper });&lt;br /&gt;
    group.getElementById('compass').updateCenter();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# TODO: at some point, this could be a foreach loop moving all compass variants&lt;br /&gt;
# into the LayerCache as per navdisplay.styles (compass, compassApp, compassMapCtr)&lt;br /&gt;
&lt;br /&gt;
# troubleshooting code to inspect the contents of the LayerCache ...&lt;br /&gt;
var window = canvas.Window.new([1024, 1024], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(CompassLayerCache.canvas_texture);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|We should explore what's required in order to support &amp;quot;animated-style-able layers&amp;quot; - for instance, we could register a single callback to animate a texture and avoid redundant updates this way. Equally, this would allow us to register an update callback along with a layer - e.g. for transforming/rotating the compass rose).}}&lt;br /&gt;
&lt;br /&gt;
=== Controllers ===&lt;br /&gt;
[[File:MapStructure-Controller-UML.png]]&lt;br /&gt;
&lt;br /&gt;
With &amp;quot;models&amp;quot; (=what is to be drawn) and &amp;quot;views&amp;quot; (=how it is to be drawn) being typically generic -and thus- shared, '''controllers''' are meant to handle all the specific implementation details and behaviors of the corresponding object itself (Symbol, SymbolLayer, Canvas Map, etc.). The idea being that people will normally only need to parametrize an existing controller, or at worst, copy and customize an existing controller file to be able to use existing layers.&lt;br /&gt;
&lt;br /&gt;
For most of the API, the .new() should be optional and return nil, but if resource management is required (listeners or timers), set up listeners/timers during .new(), store them in a member list, and remove them in .del() using removelistener(). &lt;br /&gt;
&lt;br /&gt;
Timers should be added using the maketimer API.&lt;br /&gt;
&lt;br /&gt;
All model objects or canvas map objects should be passed as the first argument to controllers' methods (FIXME: need to make sure this holds, see comment: [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/MapStructure.nas|line=218}}]).&lt;br /&gt;
&lt;br /&gt;
==== Symbol Controller ====&lt;br /&gt;
[[File:MapStructure-SymbolController-UML.png]]&lt;br /&gt;
&lt;br /&gt;
{{Note|In FlightGear 3.1+ there's one additional step between init/draw which creates cache entries for symbols during initialization and may only use a cache lookup in draw/update. Obviously, caching makes only sense for symbols that are fairly static - i.e. text labels or animated elements won't typically use caching at all. You could implement a simple animation by toggling between different cache elements though.}}&lt;br /&gt;
&lt;br /&gt;
This is very simple: default symbol controllers can just be wrappers for the corresponding SymbolLayer controller, e.g. [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/VOR.scontroller}}].&lt;br /&gt;
&lt;br /&gt;
There's a simple API, which currently just passes data on to the layer controller.&lt;br /&gt;
&lt;br /&gt;
In general, you will want to make sure that your classes implement the '''geo.Coord''' interface (easy to do by inheriting from geo.coord) - otherwise, you will typically need to add special code to handle custom classes, to extract the required values, such as latitude-deg and longitude-deg.&lt;br /&gt;
&lt;br /&gt;
For this, you will want to refer to the getpos method in Symbol.Controller (see MapStructure.nas).&lt;br /&gt;
This is also where Nasal ghosts are handled (see for example: positioned/Navaid or Fix).&lt;br /&gt;
&lt;br /&gt;
Another example, [{{gitorious url|fg|hoorays-fgdata|path=Nasal/canvas/map/TFC.lcontroller}}], uses the controller as a wrapper for the model, but this should really be moved to the model object itself.&lt;br /&gt;
&lt;br /&gt;
FIXME: we need wrapper objects for positioned, so that a class can handle higher-level operations  (e.g. like .isActive()). Something like [http://docs.python.org/3/library/collections.html#collections.UserDict collections.UserDict] in Python.&lt;br /&gt;
&lt;br /&gt;
Yet another use would be to have the controller manage listeners for updating its symbol, like the SymbolLayer Controller does for the whole layer. This would be useful for, e.g. keeping track of if a certain symbol changes place, or such. Make sure to implement .new() and .del() functions!&lt;br /&gt;
&lt;br /&gt;
FIXME: I don't think that updating a single symbol can be handled currently.&lt;br /&gt;
&lt;br /&gt;
==== searchCmd: Filtering ====&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
{{WIP}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:Canvas-MapStructure-Demo-Layer-with-custom-searchCmd.png|550px|thumb|A simple MapStructure layer called DEMO that renders a circle of NDB symbols (SVGs) in the vicinity of the aircraft using geo.nas and its  helpers like geo.aircraft_position() and the geo.Coord.apply_course_distance(course, dist) method as per {{forum link|p=213177}}. See [[Canvas Radar]] to learn more. ]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The '''searchCmd()''' method is responsible for populating a vector with results that are to be drawn by the '''draw()''' method in the *.symbol file. Each object will typically be either a NasalPositioned ghost (i.e. a C++ FGPositioned object returned from a positioned query) or a Nasal hash with latitude/longitude fields/methods, or just a geo.Coord object create via geo.Coord.new().&lt;br /&gt;
&lt;br /&gt;
For example, for navaid-based layers, this will normally serve as the back-end for a '''positionedSearch''' using a delta of the current result, compared to the previous result. This is done to enable a layer to tell how many results are new/missing, to selectively update those - rather than having to always delete all previous results and add all new ones.&lt;br /&gt;
&lt;br /&gt;
Whenever a new element (object) is added to the vector, the '''.onAdded()''' method will be invoked to add a new symbol to list of managed symbols - once an element is removed, the '''.onRemoved()''' method will be called to call the symbol's destructor.&lt;br /&gt;
&lt;br /&gt;
Some of the more straightforward examples are to be found in the implementation of navaid layers:&lt;br /&gt;
* NDB&lt;br /&gt;
* VOR&lt;br /&gt;
* DME&lt;br /&gt;
* FIX&lt;br /&gt;
&lt;br /&gt;
Here, searchCmd is typically just a one-liner calling an existing positionedSearch API, such as '''findNavaidsWithinRange()''', which always returns a vector of positioned ghosts (which are automatically supported by MapStructure). &lt;br /&gt;
&lt;br /&gt;
{{Note|For the sake of simplicity, the MapStructure framework exposes a generator function that returns a proper callback for the most common needs as long as the lcontroller itself inherits from NavaidSymbolLayer. Which is why most navaid lcontroller files will typically just invoke '''make(TYPE_OF_NAVAID)'''  to obtain a proper searchCmd callback. In the future, we'll probably further generalize lcontroller files accordingly, i.e. by coming up with generators and/or base classes for the more common purposes and needs (post 3.2).}}&lt;br /&gt;
&lt;br /&gt;
The only thing that's typically done there is to get the query range (1st argument of the API) via a delegate-callback out of the layer controller, so that navaid range can be easily provided by the MapStructure front-end - such as a GUI dialog or a cockpit display like the ND. in a custom, non-navaid layer, the whole query-type thing can be ignored - it's not even used anywhere, it's really only used to &amp;quot;make&amp;quot; a searchCmd() for navaids like vor, ndb, dme etc - but for that to work, you would have to inherit from &amp;quot;NavaidSymbolLayer&amp;quot;, whereas you're probably using &amp;quot;MultiSymbolLayer&amp;quot; now - which is why it's not having any effect. It's really only used in a single place. So &amp;quot;query_type&amp;quot; is really just for navaids.&lt;br /&gt;
&lt;br /&gt;
A more sophisticated example is to be found in the traffic layer (TFC) which handles AI/MP traffic and processes the corresponding properties. This is also where you can see a bunch of helper functions used to &amp;quot;filter&amp;quot; results, e.g. based on range. So you could add other filtering heuristics there.&lt;br /&gt;
&lt;br /&gt;
For other more involved examples, see the implementation of the ROUTE (RTE) and WAYPOINT (WPT) layers.&lt;br /&gt;
&lt;br /&gt;
Support for simple animations can be provided by changing size/color of a symbol if required, i.e. to use a different style for &amp;quot;active/close&amp;quot; symbols (e.g. waypoints). Such simple animations can be pre-created by populating a custom SymbolCache with instances of each required variation in style, more complex animations are better manually implemented by customizing the draw() routine inside the .symbol file.&lt;br /&gt;
&lt;br /&gt;
==== SymbolLayer Controller ====&lt;br /&gt;
For now, VOR.lcontroller simply handles the individual controller operations on a per-layer scale, e.g. looking up if the VOR is a selected frequency via a list of current frequencies copied from property tree.&lt;br /&gt;
&lt;br /&gt;
For MultiSymbolLayers: this is also responsible for the searchCmd (mandatory!), which handles searching for the model objects used by symbols, and returns a current list of models. The models are compared to the previous list, and models are added/removed to match the new list. If custom equality comparison is needed (i.e. outside of &amp;lt;tt&amp;gt;id(a) == id(b)&amp;lt;/tt&amp;gt;), set layer.searcher._equals(a,b) to an appropriate function during construction.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For SingleSymbolLayers: this is responsible for the mandatory getModel() to provide a model object. This is called once and used for the lifetime of the layer, so the object should be dynamic (this works particularly well with a simple property node that supplies position/latitude-deg + position/longitude-deg or latitude-deg + longitude-deg -- this can of course be extended, see [{{gitorious url|fg|fgdata|commit=4732a3b620cd0df36ebffdba76a3872c43539bf8|path=Nasal/canvas/MapStructure.nas#L243-246}}]). If the returned object has an update() method, this is called before the position is retrieved.&lt;br /&gt;
&lt;br /&gt;
{{Note|the _equals line adds a new function to the layer's searcher hash - we need to provide a way to check for &amp;quot;equality&amp;quot;, i.e. for navaids that could be position and/or the ID (name). For custom/new layers, we need to provide a custom equality check function. What you are doing there is just adding a custom equality check function that always returns &amp;quot;false&amp;quot; (not equal). This is used by MapStructure to &amp;quot;smartly&amp;quot; identify and differentiate between old and new objects, i.e. to reduce workload and improve performance - imagine the &amp;quot;FIX&amp;quot; layer, which may have hundreds of fixes - while flying, a few dozen will be &amp;quot;new&amp;quot; ones, while most others will be &amp;quot;old&amp;quot; - we'll only remove the old ones, and only add new ones. If the custom definition is not provided, you should get an error suggesting that you add a corresponding method so that the underlying logic can check all objects for equality - see the bottom of the MapStructure article for details, or search for &amp;quot;_equals&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |the real reason behind the _equals is because it's a design hack: the geo difference search function (whatever it's called) originally supported almost any object through Nasal, but since Tom extended it in C++ space it doesn't really work with non-positioned objects (he uses cppbind typecasting into positioned objects), so I retained the old capability by allowing the user to specify a custom equals function to use the old Nasal-space difference engine. (For non-positioned layers this allows us to use roughly the same mechanism with Nasal objects/classes.)&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
Setting _equals to a &amp;quot;return 0&amp;quot; function simply means that each object will be taken off and recreated on each search. But, when there's a better id to use, MapStructure can remove only the old ones and add only the new ones. How that is done depends on both the model object and the drawing – for instance, if a *.symbol file says to completely redraw the Canvas group based on the model, that means it potentially could be used with different models during its lifetime, whereas if the symbol is only set up once on initialization, it would not reflect changes in it's model object and thus should be removed when it is gone.&lt;br /&gt;
  |{{cite web |url={{forum url|p=213308}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Get objects to show up on Map/Radar&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Philosopher&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Jun 24&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The other ugly thing about _equals is that it's essentially duplicated by MapStructure {{gitorious source|fg|fgdata|commit=fcf18d70e20ef629d96367cd0907dae6178e1997|path=Nasal/canvas/MapStructure.nas|line=516|text=here}}, and is used {{gitorious source|fg|fgdata|commit=fcf18d70e20ef629d96367cd0907dae6178e1997|path=Nasal/canvas/MapStructure.nas|line=894|text=here}} to find models inside of the Layer's internal list, while the geo search obviously uses a competing approach. I might get around to fixing that ;). Basically MapStructure's whole flow is: layer searchCmd -&amp;amp;gt; models -&amp;amp;gt; symbols -&amp;amp;gt; canvas drawings.&lt;br /&gt;
  |{{cite web |url={{forum url|p=213308}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Get objects to show up on Map/Radar&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Philosopher&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Tue Jun 24&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |The main thing to keep in mind here is that many MapStructure layers deal with positioned objects, i.e. objects that have lat/lon/altitude - internally, the system uses a &amp;quot;diff&amp;quot; (delta) method to compare the current result set against the previous result set to tell how many new/removed items are there - so that things can be selectived updated (added/removed), i.e. to only partially redraw/update things for the sake of efficiency.&lt;br /&gt;
  |{{cite web |url={{forum url|p=216407}}&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Live WXRadar MapStructure Layer Development&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Sun Aug 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
TFC (TCAS) is a bit problematic to test. Assuming that the map's range is set to ~ 50 nm, we could simply use the .apply_course_distance() method in geo.nas to create 10 objects in a range of 20 nm with a 36 degree spacing using something along the lines of.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var searchCmdTest = func {&lt;br /&gt;
var mainPos = geo.aircraft_position();&lt;br /&gt;
var results = [];&lt;br /&gt;
&lt;br /&gt;
for(var course=0; course&amp;lt;360;course+=36) {&lt;br /&gt;
 var newPos = mainPos&lt;br /&gt;
 # TODO: add offset altitude here, i.e. +/-500 ft&lt;br /&gt;
 newPos.apply_course_distance(course, 25*NM2M);&lt;br /&gt;
 append(results,newPos);&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
return results;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This should result in an evenly-spaced circle of objects around the aircraft, no matter where you're going.&lt;br /&gt;
&lt;br /&gt;
To make this a bit more fancy, we could just use TrafficModel objects instead of plain geo.Coord instances here&lt;br /&gt;
&lt;br /&gt;
Thinking about it, we could make such a testing facility a part of MapStructure itself, such as that lcontrollers need to provide a &amp;quot;testSearch&amp;quot; implementation, so that MapStructure developers do not need to be familiar with each and every FG system...--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Canvas Map Controller ====&lt;br /&gt;
&amp;lt;!--{{WIP}}--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This has the most interesting jobs: it manages how the whole map is positioned and re-rendered. Example: [{{gitorious url|fg|hoorays-fgdata|commit=591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14|path=Nasal/canvas/map/aircraftpos.controller}}]. It will have to manage its own updating routines, i.e. keeping track of timers/listeners that are hooked to update it. For example, like in the above, one can make a timer object which calls an &amp;quot;update_pos&amp;quot; method, which will reposition the map (via me.map.setPos) and call update on all layers if necessary (via me.map.update()), which will often call positioned searches and thus should be spaced out, e.g. ~4 or more seconds apart. Obviously one should also check other conditions other than time, like difference in position since last query.&lt;br /&gt;
&lt;br /&gt;
Some things to keep in mind when working on Map Controllers:&lt;br /&gt;
* we may also want to support optional mouse-panning (see airport-select dialog)&lt;br /&gt;
* we may also want to support optional tooltips for layer elements&lt;br /&gt;
&lt;br /&gt;
== Styling ==&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
{{WIP}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For a list of MapStructure files (meanwhile incomplete), see: [[Canvas MapStructure Layers]]&lt;br /&gt;
Once you have found a layer that is not yet style-able, you need to encode style-able attributes using the relevant_keys vector.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=297586}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [SOLVED] How to modify symbols in MapStructure layers &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 27th, 2016 &lt;br /&gt;
  |added  =  Oct 27th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
some layers are still missing styling-support - which basically means identifying everything that is currently hard-coded but styling related (think colors, fonts, sie, images/artwork) and replacing that with a variable, the same variable should be added to the df_style (default style) of the symbol, and then you need to set up a cache entry specifiying the symbol specific variables (e.g. color and fontsize), so that the styleable-cache can tell if it has a certain variant of a symbol or not&lt;br /&gt;
map-canvas.xml is a simple example, artix's Airbus style is more sophisticated.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=297587}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [SOLVED] How to modify symbols in MapStructure layers &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 27th, 2016 &lt;br /&gt;
  |added  =  Oct 27th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For example, open the VOR.symbol file and see how it sets up a default style hash (df_style) here: https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/Nasal/canvas/map/VOR.symbol&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=297586}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [SOLVED] How to modify symbols in MapStructure layers &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 27th, 2016 &lt;br /&gt;
  |added  =  Oct 27th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In the drawVOR callback it is then using those variables (as per the hash).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  {{forum url|p=297586}} &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [SOLVED] How to modify symbols in MapStructure layers &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Hooray &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 27th, 2016 &lt;br /&gt;
  |added  =  Oct 27th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var cache = StyleableCacheable.new(&lt;br /&gt;
    name:name,&lt;br /&gt;
    draw_func: drawVOR,&lt;br /&gt;
    cache: SymbolCache32x32,&lt;br /&gt;
    draw_mode: SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: [&amp;quot;line_width&amp;quot;, &amp;quot;color&amp;quot;], # supported style-able attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
You can find more sophisticated examples in the Airbus style created by  Artix, he also had to touch a bunch of MapStructure files to make that work.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Example: Tutorial Layer TUT ==&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
{{WIP}}&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This section will cover the main steps for implementing a new layer whose purpose is showing all targets of a selectable [[Tutorials|tutorial]] on a Canvas/MapStructure map. &lt;br /&gt;
&lt;br /&gt;
First of all, we need to find an aircraft that comes with a tutorial that contains targets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;cd $FG_ROOT/Aircraft/c172p/Tutorials &amp;amp;&amp;amp; grep -nr targets&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
taxiing.xml:29:    &amp;lt;targets&amp;gt;&lt;br /&gt;
taxiing.xml:55:    &amp;lt;/targets&amp;gt;&lt;br /&gt;
taxiing.xml:221:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:225:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:245:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:249:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:267:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:271:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:290:                    &amp;lt;property&amp;gt;/sim/tutorials/targets/j2/distance-m&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:306:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j3/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:310:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/j3/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:329:                    &amp;lt;property&amp;gt;/sim/tutorials/targets/j3/distance-m&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:342:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a1/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:346:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a1/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:356:                    &amp;lt;property&amp;gt;/sim/tutorials/targets/a1/distance-m&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:370:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:374:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:385:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:389:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:407:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:411:                        &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/direction-deg&amp;lt;/property&amp;gt;&lt;br /&gt;
taxiing.xml:421:                    &amp;lt;property&amp;gt;/sim/tutorials/targets/a2/distance-m&amp;lt;/property&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, for testing/development purposes, we will be using the default FlightGear aircraft, i.e. the c172p, because it comes with well-maintained tutorials, and because its '''Taxiing''' tutorials contains a number of &amp;lt;code&amp;gt;targets&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[File:PropertyBrowser-TutorialsNode.png|thumb|Screenshot showing the tutorials node in the property browser]]&lt;br /&gt;
&lt;br /&gt;
[[File:C172p-taxiing-tutorial-selection.png|thumb|Screen shot showing the tutorials dialog with the c172p taxiing tutorial selected]]&lt;br /&gt;
&lt;br /&gt;
For starters, we need to take a look at $FG_ROOT/Nasal/tutorial/tutorial.nas to see how to get a list of tutorials, so we open the corresponding file and search for '''tutorials''' and see this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
tutorialN = nil;&lt;br /&gt;
foreach (var c; props.globals.getNode(&amp;quot;/sim/tutorials&amp;quot;).getChildren(&amp;quot;tutorial&amp;quot;)) {&lt;br /&gt;
    if (c.getNode(&amp;quot;name&amp;quot;).getValue() == name) {&lt;br /&gt;
        tutorialN = c;&lt;br /&gt;
        break;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (tutorialN == nil) {&lt;br /&gt;
    screen.log.write('Unable to find tutorial &amp;quot;' ~ name ~ '&amp;quot;');&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As can be seen, this requires only a single variable: '''name''' - so we can easily turn this into a helper function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var getTutorialNode = func(name) { &lt;br /&gt;
    var tutorialN = nil;&lt;br /&gt;
    foreach (var c; props.globals.getNode(&amp;quot;/sim/tutorials&amp;quot;).getChildren(&amp;quot;tutorial&amp;quot;)) {&lt;br /&gt;
        if (c.getNode(&amp;quot;name&amp;quot;).getValue() == name) {&lt;br /&gt;
            tutorialN = c;&lt;br /&gt;
            return tutorialN;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tutorialN == nil) {&lt;br /&gt;
        screen.log.write('Unable to find tutorial &amp;quot;' ~ name ~ '&amp;quot;');&lt;br /&gt;
        return nil;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, we need to check tutorials.nas to see how it gets a list of targets for the corresponding tutorial, which can be seen below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
set_targets(tutorialN.getNode(&amp;quot;targets&amp;quot;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, basically, all we need to do is calling &amp;lt;code&amp;gt;tutorialN.getNode(&amp;quot;targets&amp;quot;);&amp;lt;/code&amp;gt; to get a list of targets for the corresponding tutorial.&lt;br /&gt;
&lt;br /&gt;
Now, let's look up the definition of the set_targets() function, which is processing all targets, to see what we need to do to extract the latitude/longitude for each target:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
##&lt;br /&gt;
# For each &amp;lt;target&amp;gt;&amp;lt;*&amp;gt;&amp;lt;longitude-deg|latitude-deg&amp;gt; calculate and update&lt;br /&gt;
# /sim/tutorials/targets/*/...&lt;br /&gt;
#   heading-deg   ... absolute heading to target  (0 -&amp;gt; North)&lt;br /&gt;
#   direction-deg ... relative angle to target    (0 -&amp;gt; ahead, 90 -&amp;gt; to the right)&lt;br /&gt;
#   distance-m    ... distance in meters&lt;br /&gt;
#   eta-min       ... estimated time of arrival (assuming aircraft flies in&lt;br /&gt;
#                     in current speed towards target)&lt;br /&gt;
#&lt;br /&gt;
var set_targets = func(node) {&lt;br /&gt;
    if (node == nil) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var time = time_elapsedN.getValue();&lt;br /&gt;
    var dest = props.globals.getNode(&amp;quot;/sim/tutorials/targets&amp;quot;, 1);&lt;br /&gt;
    var aircraft = geo.aircraft_position();&lt;br /&gt;
    var hdg = headingN.getValue() + slipN.getValue();&lt;br /&gt;
&lt;br /&gt;
    foreach (var t; node.getChildren()) {&lt;br /&gt;
        var lon = t.getNode(&amp;quot;longitude-deg&amp;quot;);&lt;br /&gt;
        var lat = t.getNode(&amp;quot;latitude-deg&amp;quot;);&lt;br /&gt;
        if (lon == nil or lat == nil) {&lt;br /&gt;
            die(&amp;quot;target coords undefined&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var target = geo.Coord.new().set_latlon(lat.getValue(), lon.getValue());&lt;br /&gt;
        var dist = aircraft.distance_to(target);&lt;br /&gt;
        var course = aircraft.course_to(target);&lt;br /&gt;
        var angle = geo.normdeg(course - hdg);&lt;br /&gt;
        if (angle &amp;gt;= 180) {&lt;br /&gt;
            angle -= 360;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var d = dest.getChild(t.getName(), t.getIndex(), 1);&lt;br /&gt;
        d.getNode(&amp;quot;heading-deg&amp;quot;, 1).setDoubleValue(course);&lt;br /&gt;
        d.getNode(&amp;quot;direction-deg&amp;quot;, 1).setDoubleValue(angle);&lt;br /&gt;
        var distN = d.getNode(&amp;quot;distance-m&amp;quot;, 1);&lt;br /&gt;
        var lastdist = distN.getValue();&lt;br /&gt;
        distN.setDoubleValue(dist);&lt;br /&gt;
        if (lastdist != nil) {&lt;br /&gt;
            var speed = (lastdist - dist) / (time - last_step_time) + 0.00001;  # m/s&lt;br /&gt;
            d.getNode(&amp;quot;eta-min&amp;quot;, 1).setDoubleValue(dist / (speed * 60));&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    last_step_time = time;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most relevant/interesting part being the foreach loop where all targets are processed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
foreach (var t; node.getChildren()) {&lt;br /&gt;
    var lon = t.getNode(&amp;quot;longitude-deg&amp;quot;);&lt;br /&gt;
    var lat = t.getNode(&amp;quot;latitude-deg&amp;quot;);&lt;br /&gt;
    if (lon == nil or lat == nil) {&lt;br /&gt;
        die(&amp;quot;target coords undefined&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var target = geo.Coord.new().set_latlon(lat.getValue(), lon.getValue());&lt;br /&gt;
    # ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can easily turn this into a new helper function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var getTargets = func(node) {&lt;br /&gt;
    var results = [];&lt;br /&gt;
    foreach (var t; node.getChildren()) {&lt;br /&gt;
        var lon = t.getNode(&amp;quot;longitude-deg&amp;quot;);&lt;br /&gt;
        var lat = t.getNode(&amp;quot;latitude-deg&amp;quot;);&lt;br /&gt;
        if (lon == nil or lat == nil) {&lt;br /&gt;
            die(&amp;quot;target coords undefined&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    &lt;br /&gt;
        var target = geo.Coord.new().set_latlon(lat.getValue(), lon.getValue());&lt;br /&gt;
        append(results,target);&lt;br /&gt;
    }&lt;br /&gt;
    return results;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now, let's use the c172p and the [[Nasal Console]] to see if our code works as expected:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var getTutorialNode = func(name) { &lt;br /&gt;
    var tutorialN = nil;&lt;br /&gt;
    foreach (var c; props.globals.getNode(&amp;quot;/sim/tutorials&amp;quot;).getChildren(&amp;quot;tutorial&amp;quot;)) {&lt;br /&gt;
        if (c.getNode(&amp;quot;name&amp;quot;).getValue() == name) {&lt;br /&gt;
            tutorialN = c;&lt;br /&gt;
            return tutorialN;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (tutorialN == nil) {&lt;br /&gt;
        screen.log.write('Unable to find tutorial &amp;quot;' ~ name ~ '&amp;quot;');&lt;br /&gt;
        return nil;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var tutorial = getTutorialNode(&amp;quot;Taxiing&amp;quot;);&lt;br /&gt;
var allTargets = nil;&lt;br /&gt;
&lt;br /&gt;
if (tutorial != nil) {&lt;br /&gt;
    allTargets = tutorial.getNode(&amp;quot;targets&amp;quot;);&lt;br /&gt;
    props.dump(allTargets);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Once you start fgfs with the c172p, this snippet of code should load the &amp;quot;Taxiing&amp;quot; tutorial (it having a number of targets) and dump all info to the console:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
targets {NONE} = nil&lt;br /&gt;
targets/j1 {NONE} = nil&lt;br /&gt;
targets/j1/longitude-deg {UNSPECIFIED} = -121.81664&lt;br /&gt;
targets/j1/latitude-deg {UNSPECIFIED} = 37.6949&lt;br /&gt;
targets/j2 {NONE} = nil&lt;br /&gt;
targets/j2/longitude-deg {UNSPECIFIED} = -121.82258&lt;br /&gt;
targets/j2/latitude-deg {UNSPECIFIED} = 37.6949&lt;br /&gt;
targets/j3 {NONE} = nil&lt;br /&gt;
targets/j3/longitude-deg {UNSPECIFIED} = -121.8250&lt;br /&gt;
targets/j3/latitude-deg {UNSPECIFIED} = 37.69498&lt;br /&gt;
targets/a1 {NONE} = nil&lt;br /&gt;
targets/a1/longitude-deg {UNSPECIFIED} = -121.8251&lt;br /&gt;
targets/a1/latitude-deg {UNSPECIFIED} = 37.694616&lt;br /&gt;
targets/a2 {NONE} = nil&lt;br /&gt;
targets/a2/longitude-deg {UNSPECIFIED} = -121.8294&lt;br /&gt;
targets/a2/latitude-deg {UNSPECIFIED} = 37.69459&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Now, let's turn the whole thing into a searchCmd() function that we can us in our MapStructure layer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var getTutorialNode = func(name) { &lt;br /&gt;
    var tutorialN = nil;&lt;br /&gt;
    foreach (var c; props.globals.getNode(&amp;quot;/sim/tutorials&amp;quot;).getChildren(&amp;quot;tutorial&amp;quot;)) {&lt;br /&gt;
        if (c.getNode(&amp;quot;name&amp;quot;).getValue() == name) {&lt;br /&gt;
            tutorialN = c;&lt;br /&gt;
            return tutorialN;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    if (tutorialN == nil) {&lt;br /&gt;
        screen.log.write('Unable to find tutorial &amp;quot;' ~ name ~ '&amp;quot;');&lt;br /&gt;
        return nil;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var getTargets = func(node) {&lt;br /&gt;
    var results = [];&lt;br /&gt;
    foreach (var t; node.getChildren()) {&lt;br /&gt;
        var lon = t.getNode(&amp;quot;longitude-deg&amp;quot;);&lt;br /&gt;
        var lat = t.getNode(&amp;quot;latitude-deg&amp;quot;);&lt;br /&gt;
        if (lon == nil or lat == nil) {&lt;br /&gt;
            die(&amp;quot;target coords undefined&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
 &lt;br /&gt;
        var target = geo.Coord.new().set_latlon(lat.getValue(), lon.getValue());&lt;br /&gt;
        append(results,target);&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    return results;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var searchCmd = func() {&lt;br /&gt;
    var tutorial = getTutorialNode(&amp;quot;Taxiing&amp;quot;);&lt;br /&gt;
    var allTargets = nil;&lt;br /&gt;
&lt;br /&gt;
    if (tutorial != nil) {&lt;br /&gt;
        allTargets = tutorial.getNode(&amp;quot;targets&amp;quot;);&lt;br /&gt;
        return getTargets(allTargets);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
debug.dump(searchCmd());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, we need to come up with some boilerplate code for creating a new MapStructure layer, and then populate the searchCmd() method using our two helper fnctions&lt;br /&gt;
&lt;br /&gt;
{{MapStructure lcontroller|layername=TUT}}&lt;br /&gt;
&lt;br /&gt;
Now, here's some code to actually test the new layer (contributed by ludomotico {{forum link|p=259306}}):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var temp = {};&lt;br /&gt;
temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
testMap.setRange(25);&lt;br /&gt;
&lt;br /&gt;
testMap.setTranslation(&lt;br /&gt;
    temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
    temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
    return caller(0)[0];&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach (var type; [r('TUT'), r('APT'), r('APS')]) {&lt;br /&gt;
    TestMap.addLayer(&lt;br /&gt;
        factory: canvas.SymbolLayer, &lt;br /&gt;
        type_arg: type.name, &lt;br /&gt;
        visible: type.vis, &lt;br /&gt;
        priority: type.zindex,&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To test the new layer, we need to review the taxiing tutorial to check where all those targets are located, specifically see the presets section below:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;presets&amp;gt;&lt;br /&gt;
    &amp;lt;airport-id&amp;gt;KLVK&amp;lt;/airport-id&amp;gt;&lt;br /&gt;
    &amp;lt;on-ground&amp;gt;1&amp;lt;/on-ground&amp;gt;&lt;br /&gt;
    &amp;lt;runway&amp;gt;12&amp;lt;/runway&amp;gt;&lt;br /&gt;
    &amp;lt;altitude-ft&amp;gt;-9999&amp;lt;/altitude-ft&amp;gt;&lt;br /&gt;
    &amp;lt;latitude-deg&amp;gt;37.6952&amp;lt;/latitude-deg&amp;gt;&lt;br /&gt;
    &amp;lt;longitude-deg&amp;gt;-121.8167&amp;lt;/longitude-deg&amp;gt;&lt;br /&gt;
    &amp;lt;heading-deg&amp;gt;175.0&amp;lt;/heading-deg&amp;gt;&lt;br /&gt;
    &amp;lt;airspeed-kt&amp;gt;0&amp;lt;/airspeed-kt&amp;gt;&lt;br /&gt;
    &amp;lt;glideslope-deg&amp;gt;0&amp;lt;/glideslope-deg&amp;gt;&lt;br /&gt;
    &amp;lt;offset-azimuth-deg&amp;gt;0&amp;lt;/offset-azimuth-deg&amp;gt;&lt;br /&gt;
    &amp;lt;offset-distance-nm&amp;gt;0&amp;lt;/offset-distance-nm&amp;gt;&lt;br /&gt;
&amp;lt;/presets&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:Fgfs-MapStructure-tutorial-layer.png|thumb|Custom MapStructure layer for displaying [[Tutorials]] targets - showing the c172p taxiing tutorial at KLVK]]&lt;br /&gt;
This tells us that we need to start fgfs using &amp;lt;code&amp;gt;--airport=KLVK --runway=12&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143062</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=143062"/>
		<updated>2025-11-25T00:28:27Z</updated>

		<summary type="html">&lt;p&gt;PlayeRom: /* caller() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=45|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=638|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=376|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=540|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b) {&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func() {&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a) {&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: true, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
    var temp = {};&lt;br /&gt;
    temp.dlg = canvas.Window.new([600, 400], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
    temp.canvas = temp.dlg.createCanvas().setColorBackground(1, 1, 1, 0.5);&lt;br /&gt;
    temp.root = temp.canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
    var testMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
    testMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
    testMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
    testMap.setTranslation(&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[0]&amp;quot;) / 2,&lt;br /&gt;
        temp.canvas.get(&amp;quot;view[1]&amp;quot;) / 2,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    var r = func(name, vis = true, zindex = nil) {&lt;br /&gt;
        return caller(0)[0];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    # TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
    # TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry directly ?&lt;br /&gt;
    # maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
    var types = [&lt;br /&gt;
        r('TFC', false),&lt;br /&gt;
        r('APT'),&lt;br /&gt;
        r('DME'),&lt;br /&gt;
        r('VOR'),&lt;br /&gt;
        r('NDB'),&lt;br /&gt;
        r('FIX', false),&lt;br /&gt;
        r('RTE'),&lt;br /&gt;
        r('WPT'),&lt;br /&gt;
        r('FLT'),&lt;br /&gt;
        r('WXR'),&lt;br /&gt;
        r('APS'),&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    foreach (var type; types) {&lt;br /&gt;
        testMap.addLayer(&lt;br /&gt;
            factory: canvas.SymbolLayer,&lt;br /&gt;
            type_arg: type.name,&lt;br /&gt;
            visible: type.vis,&lt;br /&gt;
            priority: type.zindex,&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=240|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=557|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the namespace of get_math_e&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=169|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=349|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
|example3text = The &amp;lt;code&amp;gt;compile()&amp;lt;/code&amp;gt; function also allows you to parse a JSON string into a hash. Simplest example:&lt;br /&gt;
|example3 =&lt;br /&gt;
var jsonFunc = compile('{&amp;quot;version&amp;quot;:&amp;quot;1.2.0&amp;quot;,&amp;quot;author&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;John&amp;quot;} }');&lt;br /&gt;
var json = jsonFunc(); # return hash&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;version&amp;quot;]); # print &amp;quot;1.2.0&amp;quot;&lt;br /&gt;
logprint(LOG_ALERT, json[&amp;quot;author&amp;quot;][&amp;quot;name&amp;quot;]); # print &amp;quot;John&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=249|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a vector&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=118|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=417|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=586|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=336|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=706|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=125|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=36|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=214|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=85|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== range() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = range([start, ]stop[, step]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=756|t=Source}}&lt;br /&gt;
|text = Creates a vector of numbers starting from &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; (default 0) up to but not including &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;, incremented by &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt; (default 1). Note: To specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;, you must also provide &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt;; otherwise the second argument is interpreted as &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Optional. The starting number of the sequence. Defaults to 0 if omitted. Required if you want to specify &amp;lt;code&amp;gt;step&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = stop&lt;br /&gt;
|param2text = Required. The number at which to stop the sequence (not included).&lt;br /&gt;
|param3 = step&lt;br /&gt;
|param3text = Optional. The increment between each number in the sequence. Defaults to 1. Only valid if &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; is also specified.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vec = range(5); # Creates a vector [0, 1, 2, 3, 4]&lt;br /&gt;
var vec = range(2, 5); # Creates a vector [2, 3, 4]&lt;br /&gt;
var vec = range(0, 10, 2); # Creates a vector [0, 2, 4, 6, 8]&lt;br /&gt;
var vec = range(0, 0); # Creates an empty vector []&lt;br /&gt;
var vec = range(0, 1); # Creates a vector [0]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== remove() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = remove(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=53|t=Source}}&lt;br /&gt;
|text = Removes all elements equal to the given value from a vector and returns the modified vector.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which elements are to be removed.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = A value to remove from the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
remove(vector, 2); # Remove the value 2 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[1, 3]&amp;quot;.&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [&amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;, &amp;quot;text&amp;quot;]; # Initialize the vector.&lt;br /&gt;
remove(vector, &amp;quot;text&amp;quot;); # Remove the value &amp;quot;text&amp;quot; from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== removeat() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removeat(vector, index);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=72|t=Source}}&lt;br /&gt;
|text = Removes the element at the specified index from the vector and returns it.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector from which the element will be removed.&lt;br /&gt;
|param2 = index&lt;br /&gt;
|param2text = Index of the item to be deleted.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector.&lt;br /&gt;
removeat(vector, 0); # Remove the index 0 from the vector.&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector: &amp;quot;[2, 3]&amp;quot;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=226|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=91|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2, 3, nil]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=26|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=678|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    } else {&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b) {&lt;br /&gt;
    if (a &amp;lt; b) {&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    } elsif (a == b) {&lt;br /&gt;
        return 0;&lt;br /&gt;
    } else {&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;, &amp;quot;27R&amp;quot;, &amp;quot;26L&amp;quot;, &amp;quot;09L&amp;quot;, &amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways, func(a, b) cmp(a, b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L', '09R', '15', '26L', '27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=596|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring. See [[Nasal_library/string#string.join(sep,list)|string.join()]].&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=491|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23)); # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   #prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   #prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); #prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); #prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); #prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); #prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=164|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=194|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=98|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=287|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. These functions are:&lt;br /&gt;
** '''findBestRunwayForPos()''' return runway as ghost by given geo position, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().findBestRunwayForPos(geo.aircraft_position());&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runway()''' return runway as ghost by runway ID, e.g. &amp;lt;code&amp;gt;var rwy = airportinfo().runway(&amp;quot;18R&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''runwaysWithoutReciprocals()''' return vector of runways as ghost.&lt;br /&gt;
** '''helipad()''' return helipad as ghost by helipad ID, e.g. &amp;lt;code&amp;gt;var heli = airportinfo().helipad(&amp;quot;H19&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tower()''' return hash with &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''comms()''' return vector of hashes, each hash with &amp;lt;code&amp;gt;frequency&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ident&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''sids()''' return vector of strings with SID IDs.&lt;br /&gt;
** '''stars()''' return vector of strings with STAR IDs.&lt;br /&gt;
** '''getApproachList()''' return vector of string with approaches.&lt;br /&gt;
** '''parking()''' return vector of hashes, each hash with  &amp;lt;code&amp;gt;lat&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;lon&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;elevation&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
** '''getSid()''' return SID as ghost, e.g. &amp;lt;code&amp;gt;var sid = airportinfo().getSid(&amp;quot;SA1A&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getStar()''' return STAR as ghost, e.g. &amp;lt;code&amp;gt;var star = airportinfo().getStar(&amp;quot;KEILA1&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getIAP()''' return IAP as ghost, e.g. &amp;lt;code&amp;gt;var iap = airportinfo().getIAP(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''getApproach()''' return approach as ghost, e.g. &amp;lt;code&amp;gt;var approach = airportinfo().getApproach(&amp;quot;ILS02&amp;quot;);&amp;lt;/code&amp;gt;.&lt;br /&gt;
** '''tostring()''' just return string &amp;quot;an airport ICAO&amp;quot;.&lt;br /&gt;
** See also {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1902}}.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': True heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': True heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{caution|This function is deprecated and doesn't work. Please use the new one {{func link|logprint()}}.}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, [1, 2, 3], &amp;quot;d&amp;quot;];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, [1, 2, 3], d]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>PlayeRom</name></author>
	</entry>
</feed>