Skip to content

GPX NMEA Playback

In this guide you will learn how to calculate and render a route based on a GPX track as input waypoints.
Also, playback a pre-recorded navigation GPS position log in NMEA format matching the route.
Pause, resume, stop and restart the pre-recorded navigation playback.
Graphic control interface using ImGui.

GpxNmeaPlayback - cpp example screenshot

Setup

First, get an API key token, see the Getting Started guide.

Prerequisites

It is required that you complete the Environment Setup - CPP Examples guide before starting this guide.

Setting your API key token

To set your API key token, define it like this:

#define API_TOKEN "YOUR_API_KEY_TOKEN"

replacing YOUR_API_KEY_TOKEN with your actual API key token text, within the quotes.

This can be done in the main() function, before the following line:

#if defined(API_TOKEN)

or outside the main() function, further up, or in a header file.

Although it is also possible to define your API key token as the text value of the GEM_TOKEN environment variable, this is not recommended, as it is not secure.

Build and run

In Visual Studio, right-click on the GpxNmeaPlayback project, and select Set as Startup Project then press F5 to run.

How it works

 1int main( int argc, char** argv )
 2{
 3   std::string projectApiToken = "";
 4#define API_TOKEN "YOUR_API_KEY_TOKEN"
 5#if defined(API_TOKEN)
 6   projectApiToken = std::string( API_TOKEN );
 7#endif
 8   // Sdk objects can be created & used below this line
 9   Environment::SdkSession session(projectApiToken, { argc > 1 ? argv[1] : "" });
10
11   // Create an interactive map view
12   CTouchEventListener pTouchEventListener;
13   gem::StrongPointer<gem::MapView> mapView = gem::MapView::produce(session.produceOpenGLContext(
14      Environment::WindowFrameworks::ImGUI, "GpxNmeaPlayback", &pTouchEventListener, getUiRender()));
15   if ( !mapView )
16   {
17      GEM_LOGE( "Error creating gem::MapView: %d", GEM_GET_API_ERROR() );
18   }
19   WAIT_UNTIL_WINDOW_CLOSE();
20   return 0;
21}

First, the API key token is set.

Next, the SDK environment is initialized, passing in the API key token string, and the SDK API debug logging path.
Environment::SdkSession session(projectApiToken, { argc > 1 ? argv[1] : "" });
The debug logging path is set from the first command line argument, if there is one, otherwise it can be given as a hardcoded string.
A touch event listener is instantiated, to support touch input for the interactive map, such as pan and zoom.
CTouchEventListener pTouchEventListener;
The MapView interactive map instance is created, using an OpenGL context for rendering, and selecting ImGui for the graphic control interface.
gem::StrongPointer<gem::MapView> mapView = gem::MapView::produce(session.produceOpenGLContext(Environment::WindowFrameworks::ImGUI,
The window title is specified, and the touch event listener instanced is passed in.
"GpxNmeaPlayback", &pTouchEventListener, getUiRender()));

Finally, the getUiRender() function is passed in, which uses ImGui to render GUI elements, capture user control input, and call the appropriate SDK functions.

 1auto getUiRender()
 2{
 3   std::string sdkResrcPath = Environment::GetInstance().GetResourcesPath();
 4   std::string projectRelativePath = "/Data/Res/";
 5   std::string projectAbsolutePath = sdkResrcPath + projectRelativePath;
 6   std::string inputGPXlogFile = projectAbsolutePath +
 7       "testParis.gpx";
 8   std::string inputNMEAlogFile = projectAbsolutePath +
 9       "strasbourg.nmea";
10   gem::String logsdir(sdkResrcPath + "GPSLogs");
11   std::pair<gem::sense::DataSourcePtr, int> dataSourceGPX = gem::sense::produceLogDataSource(inputGPXlogFile.c_str());
12   std::pair<gem::sense::DataSourcePtr, int> dataSourceNMEA = gem::sense::produceLogDataSource(inputNMEAlogFile.c_str());
13
14   return std::bind([dataSourceGPX, dataSourceNMEA, logsdir](gem::StrongPointer<gem::MapView> mapView)
15   {
16      ImGuiIO& io = ImGui::GetIO();
17      const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
18      ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 0, main_viewport->WorkPos.y + 20), ImGuiCond_FirstUseEver);
19      ImGui::Begin("panel", nullptr, ImGuiWindowFlags_NoMove
20          | ImGuiWindowFlags_NoDecoration
21          | ImGuiWindowFlags_AlwaysAutoResize
22          | ImGuiWindowFlags_NoSavedSettings);
23     if (ImGui::Button("Click to play back a pre-recorded GPX route"))
24     {
25         if (dataSourceGPX.first)
26         {
27             gem::PositionService().setDataSource(dataSourceGPX.first);
28         }
29     }
30     if (ImGui::Button("Click to play back a pre-recorded NMEA route"))
31     {
32         if (dataSourceNMEA.first)
33         {
34             gem::PositionService().setDataSource(dataSourceNMEA.first);
35         }
36     }
37     if (!mapView->isFollowingPosition())
38     {
39         if (ImGui::Button("Follow position"))
40         {
41             mapView->startFollowingPosition();
42         }
43     }
44     else
45     {
46         ImGui::Text("Following position!");
47     }
48      ImGui::End();
49   }
50   , std::placeholders::_1);
51}

Outside the lambda function, the paths and the GPS navigation position log input file names are defined. Inside the lambda function:

First, the main ImGui viewport is obtained, and the x,y position of the ImGui window, with the identifier “panel”, in pixels, is set within the SDK OpenGL viewport.

The ImGui graphical control elements are within the ImGui window/panel, rendered on top of the SDK OpenGL viewport, which contains the interactive map. The ImGui panel is created using the ImGui::Begin() function.
The first two control elements in this case, are 2 ImGui::Button() which the user can click to start playback of a .gpx or an .nmea GPS position log, respectively.
This is done by setting the data source to the desired input:
gem::PositionService().setDataSource(dataSourceNMEA.first);
The 2 data sources, for GPX and NMEA input, respectively, were defined above, before the lambda function - this is the definition for the NMEA input data:
std::pair<gem::sense::DataSourcePtr, int> dataSourceNMEA = gem::sense::produceLogDataSource(inputNMEAlogFile.c_str());
The following position button is shown only if the camera is not following the navigation playback position - for example, if the user pans the map.
A click on the follow position button causes the camera to start following the playback GPS position again:
mapView->startFollowingPosition();
While in follow position mode, an ImGui::Text("Following position!"); is displayed instead of the button, to indicate this mode.

C++ Examples

Maps SDK for C++ Examples can be downloaded or cloned with Git