Architecture¶
This chapter describes XCSoar’s internal code architecture.
Source Organisation¶
XCSoar’s source code is stored in the src directory. This
section tries to give a rough overview where you can find what.
util/: generic C++ utilities that do not depend on external libraries, such as data structures, string operationsMath/: math data types (fixed-point math, angles) and generic formulasGeo/: geographic data structures and formulastime/: clocks, civil time, andValidity(last-changed timestamps)Atmosphere/: ICAO ISA pressure, density, and indicated airspeed from pitotRadio/: VHF frequency and transponder code/mode valuesUnits/: SI conversion factors and user-unit tablesOperation/: cancel/progress for long-running workFormatter/: code that formats internal values to stringsUnits/: conversion from SI units (“System” units) to configured user unitsNMEA/: data structures for values parsed from NMEAProfile/: user profiles, loading from and saving toIGC/: support for the IGC file formatLogger/: all loggers (NMEA, IGC, flights)thread/: multi-threading support (OS specific)ui/: base library for the graphical user interfaceRenderer/: various graphical renderers, for map and analysisMapWindow/: the mapForm/: modal dialogs and their controls (based on the screen library)Dialogs/: modal dialogs implementations (based on the form library)net/: networking code (OS specific)Operation/: generic code to support cancellable long-running operationsAndroid/: code specific to Android (the native part only; Java code is inandroid/src/)Engine/PathSolvers/: an implementation of Dijkstra’s path finding algorithm, for task and contest optimisationEngine/Airspace/: airspace data structures and airspace warningsEngine/Waypoint/: waypoint data structuresEngine/Observation/: thermal and wave marks, tagged by feedEngine/GlideSolvers/: a MacCready implementationEngine/Task/: task data structures and calculationsEngine/Contest/: contest optimisationEngine/Route/: the route planner (airspace and terrain)io/: stream and file I/O (readers, writers, archives). Keep this layer free of UI and backend singletons; device-specific listing belongs inStorage/Repository/: filename patterns and typed data directories (FileType); see XCSoar data directory for the on-disk layoutStorage/: removable storage enumeration, hotplug monitors, and theStorageDeviceabstraction (platform code inStorage/linux/,Storage/win/,Storage/android/)Device/,Computer/,Blackboard/: sensor drivers, glide computer, and thread-specific data copiesDialogs/DataManagement/: data management UI (import, export, backup, file explorer)
Layer dependencies¶
Rough dependency direction (see also project rules in
.cursor/rules/xcsoar-project-rules.mdc):
Foundation (
util/,Math/,Geo/,io/,system/,time/,Atmosphere/,Radio/,Units/,Operation/) must not include Engine, Backend, or UI headers.Engine uses Foundation only.
Backend (
Device/,Computer/,Storage/,NOTAM/, …) uses Foundation and Engine. Access UI only through event queues (InputEvents,UI::Notify), not dialogs.UI (
Dialogs/,Form/,Interface.hpp) may use all layers below it. Helpers such asStorage/StorageUtil.cppthat readBackendComponentsare backend/UI glue, not Foundation.
Threads and Locking¶
Threads¶
XCSoar runs on multiple threads, to make the UI responsive but still allow expensive background calculations.
This is how it looks like on Windows and Linux/SDL (software rendering):
![digraph threads {
graph [fontsize=10];
node [fontsize=10];
edge [fontsize=8];
Devices -> MergeThread [label="sensors"];
MergeThread -> CalcThread [label="sensors"];
CalcThread -> UIThread [label="results"];
IOThread -> UIThread [label="data"];
UIThread -> DrawThread [dir=both, label="redraw"];
}](_images/graphviz-03d41697d787e47bfbc8a7028ec1b2e702a87872.png)
The UI thread is the main thread. It starts the other threads and is
responsible for the UI event loop. No other thread is allowed to
manipulate windows. The UI thread has a timer which does regular house
keeping twice per second (ProcessTimer.cpp).
The calculation thread (CalculationThread.cpp,
GlideComputer*.cpp) does all the expensive calculations in
background. It gets data from the devices (through MergeThread) and
forwards it together with calculation results to the drawing thread and
the main thread.
Each device has its own thread (SerialPort.cpp). Blocking
serial I/O is isolated from the UI thread. The thread is stopped
during task declaration (which happens in the UI thread).
When new data arrives on the serial port, the MergeThread gets
notified, which will merge all sensor values into one data structure. It
will then run cheap calculations, and forwards everything to the
CalculationThread.
With OpenGL, the map is rendered live without a buffer. There is no DrawThread.
On Android, the UI thread is not the main thread - the main thread is
implemented in Java, managed by Android itself. The UI thread listens
for events which the Java part drops into the event queue
(NativeView.java and others). The internal GPS does not need a
thread, it is implemented with Java callbacks. For Bluetooth I/O, there
are two threads implemented in Java (InputThread.java and
OutputThread.java, managed by BluetoothHelper.java).
Network thread and background HTTP¶
In addition to the sensor and UI threads above, XCSoar runs a dedicated
asio event-loop thread (io/async/GlobalAsioThread.cpp,
started from XCSoar.cpp). It hosts CurlGlobal and runs
coroutines injected with Co::InjectTask / Net::AsyncTask.
Typical uses:
NOTAM fetches (
NOTAM/NOTAMGlue.cpp)EDL tile downloads (
Weather/EDL/DownloadGlue.cpp)TIM thermal index, LiveTrack24, SkyLines, and similar clients in
NetComponents.hpp
The UI thread (main event loop) must not perform blocking network
I/O during flight. Long-lived background work belongs on the network
thread; modal downloads tied to one dialog may still use
ShowCoDialog on the UI thread.
Thread rules (same as for Interface.hpp elsewhere):
Do not call
CommonInterface,ActionInterface, or other UI-only APIs from the network thread.InMainThread()checks will fail.Do read any UI state needed for a download on the main thread before starting the coroutine (for example forecast time and isobar for EDL tiles), and pass snapshots into the network work.
Do report completion to the UI with
UI::Notify(ui/event/Notify.cpp), which queues a callback on the main event loop. Apply overlays, update status labels, and callActionInterface::SendUIStateonly from that callback.
NetComponents (NetComponents.hpp) owns long-lived network
clients. Each client that uses Net::AsyncTask should implement
BeginShutdown() and be stopped from
NetComponents::BeginShutdown() before the map and other UI are
torn down. The global pointer is cleared later in Startup.cpp
(DestroyNetComponents).
Shutdown order (see Startup.cpp; simplified):
NetComponents::BeginShutdown()— cancel coroutines and queued downloads; clear the network clients’ pointer to the observation storeMainWindow::BeginShutdown(); stop merge and calculation threads; join them; deinitialise map and devicesMainWindow::DeinitialiseStorage()— unregister storage UI listenersStorageManager::StopMonitoring()— stop hotplug; destructor joins the enumeration worker whendelete backend_componentsrunsdelete backend_components; clear the map’s observation pointer anddelete data_componentsDestroyNetComponents(); destroyMainWindowOn process exit,
Net::DeinitialisedestroysCurlGlobalon the asio thread (DrainCurlinnet/http/Init.cpp)
Deferred UI refresh: callbacks such as async terrain load or
blackboard updates must not call PageActions::Update or
ActionInterface::SendUIState synchronously if that can re-enter
layout while InfoBoxes are being created. Use
MainWindow::SchedulePageActionsUpdate and
ScheduleRefreshInfoBoxes instead (next event-loop iteration).
InfoBoxManager skips work until Create() has finished
(infoboxes_ready).
Weather overlays: map overlays may combine a blackboard listener
(ongoing GPS/time sync, for example Weather/EDL/Glue.cpp) with a
download glue in NetComponents (HTTP fetch and cache, for
example Weather/EDL/DownloadGlue.cpp). New providers (such as
XCTherm) should follow the same split: listener on the UI thread,
network I/O on the asio thread, UI updates via UI::Notify.
Origin stores¶
Waypoints, airspaces, thermals, and waves each live in one list. Every row is tagged with the feed that produced it: a waypoint file, the Thermal Information Map, Cloud, SkyLines, or XCSoar’s own calculation. Replacing a feed deletes only that feed’s rows. A live packet appends or updates one row and leaves the other feeds in place.
The engine types (Engine/Waypoint/Waypoints,
Engine/Airspace/Airspaces, Engine/Observation/Store)
have no lock and no thread. Waypoint and airspace reloads run on the
UI thread while the other threads are suspended
(ScopeSuspendAllThreads). Thermals and waves are published
while the map is drawing, so Observation/Database holds the
mutex. DataComponents owns that database. The network thread
may call ReplaceThermals, AppendThermal, and the wave
equivalents directly. ReceiveCalculated publishes own-ship rows
from the UI thread through the same mutex. The draw thread and the
map click list copy a snapshot with CopyThermals / CopyWaves
and then work on the copy.
UI::Notify is for windows, status text, and
ActionInterface. A new thermal or wave appears on the next map
draw.
Traffic (FLARM/List) is a different store. One slot per
aircraft id is updated and expired in place. Replacing a whole feed
does not fit a single traffic sentence.
Shutdown clears each network client’s store pointer in its
BeginShutdown() before that client’s work is cancelled, and clears
the map pointer before delete data_components.
Loading configured files¶
A file on disk is not the in-memory object. Three kinds of source feed the lists in Origin stores.
Map zip. The configured .xcm supplies terrain.jp2 and
topology.tpl. It may also contain waypoints.cup or
waypoints.xcw, airspace.txt, and airfields.txt. Those
members are still read: map waypoints only when no primary or watched
waypoint file produced a row; airspace.txt is merged with the
configured OpenAir files; airfields.txt is applied as waypoint
details. SettingsLeave therefore treats MapFileChanged as
a waypoint, airspace, airfield, terrain, and topography change.
Profile path. A Site files entry is one source: the map file, a waypoint or airspace list, FLARMNet, RASP, the checklist, or the input file. Reloading one origin deletes only that origin’s rows.
Live feed. NOTAM, traffic, Cloud, and SkyLines are not files.
NOTAM replaces AirspaceOrigin::NOTAM and leaves file airspace in
place. Traffic (FLARM/List) keeps one slot per aircraft id,
updated and expired in place.
The reload runs on the UI thread. A download callback queues the
path and UI::Notify delivers it. Waypoint and airspace edits
suspend the other threads (ScopeSuspendAllThreads, or
SuspendAllThreads inside SettingsLeave) and bump
Serial on the list.
Map zip finished loading. MainWindow::LoadTerrain reads
the zip on a worker. OnTerrainLoaded then installs the
terrain, fills waypoint elevations that were missing, updates home,
and sets airspace ground levels. Topography and the waypoint and
airspace files stay as they were.
File Manager, or startup auto-update.
ApplyConfiguredDataFiles compares each downloaded path with
the Site files profile keys. A path that is not selected is left on
disk. A match sets the same flags Setup uses and calls
SettingsEnter / SettingsLeave. An updated .xcm
still reloads the zip’s waypoint, airspace, and airfield members. An
OpenAir update drops file and map airspace rows, rereads them, and
clears airspace warnings; NOTAM rows stay. Startup auto-update skips
that reload while flying: the new file stays on disk until the next
start.
Background file jobs¶
Not all background work uses the network thread. Local file jobs (tar backup/restore, import/export copies, device enumeration) use other mechanisms:
Modal progress on the UI thread:
JobDialogruns aJobsubclass on a short-lived worker thread (Job/Thread.cpp) while showingProgressDialog. The UI thread stays responsive; progress is reported throughOperationEnvironment.Modal network work:
ShowCoDialogruns a coroutine on the asio thread (see above). Do not useJobDialogfor HTTP.Fire-and-forget helpers: some UI actions start a detached
std::threadfor a single task (for example deleting a file on removable media). Keep captured state alive (for examplestd::shared_ptr<StorageDevice>) and avoid UI calls from that thread.
When modifying shared backend data (waypoints, airspaces) during such
jobs, use ScopeSuspendAllThreads from Protection.hpp where
appropriate.
Storage and removable media¶
BackendComponents (BackendComponents.hpp) owns backend
singletons including storage_manager (StorageManager).
NetComponents is separate and holds long-lived HTTP clients only.
StorageManager (Storage/StorageManager.cpp):
Owns the platform hotplug monitor and storage enumerator.
Receives topology events (from a platform worker or the UI event loop, depending on the backend).
Runs device re-enumeration on a dedicated worker thread so sysfs, Win32, or SAF walks do not block the UI.
Invokes a constructor-supplied
NotifyCallback(wired inStartup.cpptoMainWindow::SendStorageNotification) so the UI thread callsProcessPendingChanges()and dispatchesStorageEventnotifications to listeners.
UI code registers StorageEventListener instances on the main
thread (for example StorageLocationPickerDialog). Use
StorageUtil (FindDeviceByName, FormatStorageCaption,
EnumerateTarFiles) from UI or backend glue — not from Foundation
io/ (stream-only tar create/restore lives in io/TarBackup).
Thread lifetime: when starting a new storage worker, join any
finished previous std::thread before move-assigning a new one;
otherwise the C++ runtime calls std::terminate(). The destructor
joins the worker after StopMonitoring().
Locking¶
Some data structures are rarely modified. There is no lock for them. For a modifications, all threads must be suspended. Example: waypoints, airspaces.
Other data structures are modified so often that correct locking would
be too much overhead. Each thread and each instance has its own copy.
The lock needs to be obtained only for making the private copy. The
private copy can be used without locking. Example: NMEA_INFO,
DERIVED_INFO.
There are objects which are too expensive to copy. Normal locking
applies to them. We have a template class called Guard to enforce
proper read/write locking. Example: the task.
Accessing Sensor Data¶
Much of XCSoar deals with obtaining sensor data and visualising it.
Suppose you want to write a dialog that needs the current GPS location,
where do you get it? The short and simple answer is: from
CommonInterface::Basic() (the InterfaceBlackboard). Example:
#include "Interface.hpp"
...
const auto &basic = CommonInterface::Basic();
if (basic.location_available)
current_location = basic.location;
This is true for the main thread (aka the “user interface thread”).
Other threads must not use the Interface.hpp library, because the
InterfaceBlackboard is not protected in any way. It contains copies
of various data structures just for the main thread.
This is how sensor data moves inside XCSoar:
![digraph threads {
graph [fontsize=10];
node [fontsize=10];
edge [fontsize=8];
Devices [label="Devices"];
MergeThread [label="MergeThread\nBasicComputer\nDeviceBlackboard"];
CalcThread [label="CalcThread\nGlideComputer\nGlideComputerBlackboard"];
UIThread [label="UIThread\nInterfaceBlackboard\nBlackboardListener"];
DrawThread [label="DrawThread\nMapWindow\nMapWindowBlackboard"];
Devices -> MergeThread [label="NMEAInfo"];
MergeThread -> CalcThread [label="MoreData"];
CalcThread -> UIThread [label="DerivedInfo"];
UIThread -> DrawThread [label="DerivedInfo"];
}](_images/graphviz-900910051193243c33d818a28de8f049f1f6f5b9.png)
The device driver parses input received from its device into its own
NMEAInfo instance inside DeviceBlackboard (i.e.
per_device_data). Then it wakes up the MergeThread to merge the
new data into the central NMEAInfo instance. The MergeThread
hosts the BasicComputer which attempts to calculate missing data
(for example, derives vario from GPS altitude).
The CalculationThread wakes up and receives the MoreData object
from DeviceBlackboard. Here, expensive calculations are performed
(GlideComputer: task engine, airspace warnings, …), resulting in a
DerivedInfo object. The CalculationThread runs no more than
twice per second.
Finally, the UI thread wakes up and receives MoreData and
DerivedInfo via DeviceBlackboard. This updates InfoBoxes and
other UI elements. On Windows, the map is drawn in a separate thread, so
there’s another layer.
Let’s get back to the question: where do I get sensor data? That depends on who you are:
you are the user interface: (InfoBoxes, dialogs, any Window callback):
InterfaceBlackboard(see above). To get notified on changes, register aBlackboardListener(and don’t forget to unregister it).you are the MapWindow: depends! If you’re being called from
OnPaintBuffer(i.e. inside theDrawThread), you must use theMapWindowBlackboard, all others must use theInterfaceBlackboard.you are a “computer” library: you will get the values as a parameter. Don’t try to use the
GlideComputerBlackboarddirectly.you are a device driver: implement the method
OnSensorUpdateorOnCalculatedUpdateif you need to know values from other devices or calculation results.everybody else may use the
DeviceBlackboard, but be sure to lock it while using its data.
Developing¶
Debugging XCSoar¶
See also Debugging Tips for replay, simulator, and utility workflows.
The XCSoar source repository contains a module for the GNU debugger
(gdb). It contains pretty-printers for various XCSoar types,
including Angle, GeoPoint and others. These are helpful when you
print values in the debugger. To use it, start the debugging session and
load the module:
$ gdb -ex "source tools/gdb.py" output/UNIX/bin/xcsoar
(gdb) run
The module will automatically convert fixed-point to floating point, radian angles to degrees and more. You can now do fancy stuff like:
(gdb) p basic.location
$1 = GeoPoint(7.93911242887 51.1470221074)
(gdb) p basic.date_time_utc
$2 = DateTime(2012/12/23 21:41:57)
(gdb) p basic.track
$3 = 55.2254197961
(gdb) p basic.external_wind
$4 = GeoVector::ZERO
(gdb) p current_leg.vector_remaining
$5 = GeoVector(267.899420345 107957.109724)
User interface guidelines¶
General¶
Minimise the number of colours, and re-use colour groups already defined.
Too much use of colour where it is not required serves only to reduce the effectiveness of bright colours for important items.
High colour saturation elements should be reserved for high importance items
High contrast against background should be reserved for high importance items
Attempt to adopt colours that are intuitive based the function of the item
Minimise the clutter where possible — readibility is essential for use in flight
Use colours defined in
Graphicsaccording to functional name, not their actual colour.Try to maintain consistent use of colours in all uses of that function, such as dialogue graphics as well as map overlays and infoboxes.
Text should always be monochrome.
Use aviation conventions or adopt best aviation human factors standards where possible, in particular:
ICAO Internation Standards and Recommended Practices, Annex 4 to the Convention on International Civil Aviation (Aeronautical Charts).
DOT/FAA/AM-01/17 Human Factors Design Guidelines for Multifunction Displays
Check for performance with respect to colour blindness. This site has a useful tool that can be used to convert screenshots to how they would look to a person with common color blindness: http://www.etre.com/tools/colourcheck/
For safety purposes, avoid use of elements that may encourage or require the user to stare at the screen continuously.
For safety purposes, avoid user controls that have significant risk of producing unsafe results if misconfigured by the pilot.
General colour conventions¶
Colour conventions generally in use throughout the program:
Red for indicator of warning
Orange for indicator of caution
Green for positive indicator of safety
Blue for neutral indicator of safety
Displayed data¶
Where data is invalid, indicate this by not presenting the data or showing dashes.
Present data in user-defined units.
Display numerical data with significant digits appropriate to the accuracy of the calculations, or its functional use by the pilot, whichever is lower.
Screen rectangles¶
Full screen draws the map to the edge of the glass. Several rectangles are smaller than that window, and they are not the same rectangle. Use the one that matches the element.
A rectangle from MainWindow is in main-window coordinates.
MapWindow::GetHudRect() is in map-window coordinates. Intersect
map->GetPosition() with a main-window rectangle before placing a
child of the main window on the map.
GetClientRect()The whole window. On Android in full screen this includes the status bar, the display cutout, the home indicator and the rounded corners. On iOS it is that full screen only while full screen mode is on; otherwise it is already the safe area. Use it for pixels that are allowed to run under system UI: the map itself, a screen-filling backdrop (the Fly/Simulator background, the dimming behind the InfoBox arrange cards).
GetSafeAreaRect()The window minus the status bar, the navigation bar, the display cutout and the rounded corners. On a rectangular display with no system bars it equals
GetClientRect(). Android reports these insets only while full screen is on, because outside full screen the surface is already laid out inside them. The reserved bar size is kept even while full screen hides the bars. Use it for anything the pilot must be able to read or press that is not allowed to slide under system UI: dialogue windows (title and buttons included) and the Fly/Simulator controls. Do not inset a dialogue by the shade band as well; it is already below the status bar.MainWindow::GetInfoBoxAreaRect()Per edge, the screen border where Stretch InfoBox area is on, and the safe edge where it is off. A visible status bar clears the top stretch, because nothing can be drawn behind that bar. Use it for the InfoBoxes, the menu, and gauge positions that do not avoid the InfoBoxes. Positions that do avoid them, and a page that replaces the map, use the HUD rectangle below.
MainWindow::GetAreaStackRect()The hole the InfoBoxes leave inside the InfoBox area. When the InfoBoxes are hidden, or before the first layout, it is the InfoBox area. The top and bottom areas (cross section, airspace and NOTAM warnings) are placed in this rectangle. Callers rarely need it directly; it is the input to the HUD rectangle.
MainWindow::GetHudRect()The area stack minus the top and bottom areas. This is where in-flight map chrome belongs: the compass, the map scale and the title beside it (Simulator, PAN, replay, the page name), the final glide bar, the vario bar, the thermal profile, the GPS status, the flight mode icon, the gesture label, the page indicator, the pan elevation and coordinates, the overlay buttons (menu, QuickMenu, zoom), status messages, and a widget that replaces the map.
LayoutMapArea()then stores the same hole on the map asMapWindow::GetHudRect(). Draw map overlays with that function, not with the map’sGetClientRect(): the map is larger than the hole and runs under the InfoBoxes and the system UI.Two extra margins sit inside the map HUD and are not part of the rectangle itself.
top_right_marginis the overlay-button column; the north arrow and the pan readout move left by it, and the margin goes to zero when those buttons are hidden.bottom_marginis the portrait pan menu (and the InfoBox access dialogue).Edge chrome is laid out once in
MapHudLayout(viaGlueMapWindow::GetHudLayout()): HUD, text-padding inset (the same gap the overlay buttons leave), optional top-right and bottom clearances, the compass slot height, and the scale/title clearance for the GPS status. Draw code reads those slots; it does not recompose the insets. The compass slot comes fromCompassRenderer, and the GPS clearance fromGetMapScaleAndTitleClearance(). The gesture label and the page indicator keep their own optical offset from the HUD edge. Projection-space items (crosshair, aircraft, wind at the aircraft) stay off this layout.Android::ContentRectBelowTopGesture()The notification-shade swipe band. It is not the safe area: it can extend below the status bar, and it remains when full screen hides the bars. The value is how far the top of the XCSoar view still lies inside that band, or zero when the surface already starts below it. Use it only for content the pilot drags in that band.
The InfoBox arrange overlay fills the client rectangle, so the backdrop still covers the band. Its cards, text and buttons use the safe area, then the one-argument helper: a stretched outer column must not sit in the edge-gesture strip, and only the part of the shade band that still covers the safe area moves the cards. The Sets editor is a dialogue already placed in the safe area; pass the two-argument helper the dialogue’s Y in the view, so a band that ends at the status bar does not move the cards.
Touch interaction¶
Adopt DOT/FAA/AR-03/67 (EFB) and FAA flight-deck HF (Cardosi and Murphy; NASA 9.3.3.4.7). Do not invent a parallel gesture language.
Acknowledge a press within 100 ms (pressed look, and haptic if the user setting allows). That is “touch received”, not a hold.
Prefer lift-off (last contact). The action commits when the finger leaves the target. Sliding off cancels. Do not fire a hold action while the finger is still down; the timer only arms the hold.
One press has one winner: tap (lift before the hold is armed), drag (movement past slop before the hold is armed), or hold then lift. After a drag has started, a further hold does nothing.
Show a busy indicator if input cannot be processed for more than 0.5 s (SAE ARP 4791). That 0.5 s is “the system is occupied”, not a gesture timeout. XCSoar’s hold-to-arm time (
InfoBoxArrange::LONG_PRESS, 500 ms) is a deliberate hold, not that busy limit.Hold progress is a one-way fill that follows the control’s shape. Do not pulse, flash, or use warning colours for a normal hold. Progress must be truthful; pilots ignore misleading bars (AR-03/67).
Size hit targets with
Layout::for touch and turbulence. Leave a dead band: movement of aboutLayout::Scale(20)on a touch screen, orLayout::Scale(10)with a mouse, before the hold is armed is a drag or a cancel, not a tap.Cursor keys and a remote stick must reach the same actions as touch. On a selected InfoBox, the arrow keys move to the InfoBox in that screen direction (
InfoBoxArrange::FindNeighbour): left is left, right is right, and they do not follow the slot number. Enter does what a short press does. Holding Enter until the hold is armed, then releasing, opens arrange, the same as a long press. In arrange mode that hold opens the InfoBox list. A short Enter there takes or puts down the selected card, and does not also open the list. Escape on the map, with no dialogue, menu, or special page open, selects the first InfoBox. Escape on a selected InfoBox returns to the map. A long press, and the arrange screen, are the same action. E-paper skips hold animation (HasEPaper()/IsSlowCPU()).
Main graphics¶
Colors¶
Colour conventions in use, in order of priority, are:
Aircraft black and white, for neutrality but clear identification
Traffic (FLARM) use alarm green, orange, and red.
Lift is vibrant green, sink is copper orange.
Aircraft navigation (route, best cruise track) is (ICAO) dark purple-blue
Task navigation lines and areas are (ICAO) magenta.
Updraft sources and other updraft derived data is sky blue.
(Todo) airspace alert colours
Map culture (topography) and terrain rendering should conform to ICAO Annex 4 where appropriate. Note that some modifications are reasonable for electronic use given that Annex 4 deals with paper charts. Nevertheless, the colour conventions are useful to adopt as they are likely to be intuitive and are designed for aviation use.
Pen styles¶
Map culture should be rendered with a thin pen
Thicker pens used for important (e.g. task, navigational, airspace) lines
Dashed lines are used to increase perceptual priority
Map overlays¶
Elements on the map that are not part of the map layer, such as additional informational widgets (final glide bar, wind, north arrow) should be rendered so as to help those elements be visually separated from the map:
Generally adopt higher contrast (higher colour saturation or darker shade) than the background map layer elements.
For elements covering an area (non line), draw the entire element or a border with a luminosity contrasting pen, of width
IBLSCALE(1).Consider whether the widget is required in all flying states and display modes. if it does not serve a direct functional purpose in some states/modes, do not render it.
Avoid locating widgets at the aircraft symbol (ownship symbol). It is important to keep this area clear so the aircraft symbol can be easily found.
Elements that may be rendered over each other should be organised in order of priority, particularly with alert warning items above caution items above non-alert items.
Terminology¶
Glide Ratio¶
’Glide ratio’ is a non-specific term which can refer to the ratio of horizontal to vertical motion with reference to either the surrounding airmass or the ground.
To reduce confusion, ground-referenced glide ratios (eg distance travelled over ground vs altitude lost) should be referred to by the term ’glide ratio over ground’ when space allows, or ’glide ratio’ / ’GR’.
Air-referenced glide ratios (eg airspeed vs sink rate) should be specified as ’lift/drag ratio’ / ’L/D ratio’ / ’LD’. The lift/drag ratio is numerically equal to the air-referenced glide ratio when flying at constant speed.
If usage spans both air-referenced and ground-referenced glide ratios, the non-specific term ’glide ratio’ / ’GR’ should be used. ’Lift/drag ratio’ should never be used to refer to ground-referenced glide ratios.