Skip to content

Play Record GPS log

In this guide you will learn how to play back a previously recorded NMEA GPS navigation position log, and record the playback to a separate new log, to demonstrate GPS log recording during actual navigation.
Graphic control interface using ImGui.

PlayRecordGPSLog - 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

The .nmea file should be copied from the directory of this example to the Data/Res/ directory within the SDK install path.
Example: C:\MAGICLANE\Data\Res\
where C:\MAGICLANE\ is the SDK install path.
The GPSLogs/ directory should also be created within the SDK install path.
Example: C:\MAGICLANE\GPSLogs\
Then compile and run the example from Visual Studio:

In Visual Studio, right-click on the PlayRecordGPSLog 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, "PlayRecordGPSLog", &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.
"PlayRecordGPSLog", &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 inputGPSlogFile = projectAbsolutePath + "strasbourg.nmea";
 7   gem::String logsdir(sdkResrcPath + "GPSLogs");
 8   std::pair<gem::sense::DataSourcePtr, int> dataSource = gem::sense::produceLogDataSource(inputGPSlogFile.c_str());
 9
10   return std::bind([dataSource, logsdir](gem::StrongPointer<gem::MapView> mapView)
11   {
12      static gem::StrongPointer<gem::Recorder> rec = nullptr;
13      ImGuiIO& io = ImGui::GetIO();
14      const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
15      ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 0, main_viewport->WorkPos.y + 20), ImGuiCond_FirstUseEver);
16      ImGui::Begin("panel", nullptr, ImGuiWindowFlags_NoMove
17          | ImGuiWindowFlags_NoDecoration
18          | ImGuiWindowFlags_AlwaysAutoResize
19          | ImGuiWindowFlags_NoSavedSettings);
20      if (ImGui::Button("Click to play back a pre-recorded route"))
21      {
22         if (dataSource.first)
23         {
24            gem::PositionService().setDataSource(dataSource.first);
25            gem::sense::DataTypeList datatypes;
26            datatypes.push_back(gem::sense::EDataType::Position);
27            datatypes.push_back(gem::sense::EDataType::ImprovedPosition);
28
29            gem::RecorderConfigurationPtr recsrcconfig = gem::StrongPointerFactory<gem::RecorderConfiguration>();
30            recsrcconfig->logsDir = logsdir;
31            recsrcconfig->dataSource = dataSource.first;
32            recsrcconfig->recordedTypes = datatypes;
33            recsrcconfig->chunkDurationSeconds = 60;
34            recsrcconfig->bContinuousRecording = true;
35            recsrcconfig->deleteOlderThanKeepMin = false;
36            recsrcconfig->keepMinSeconds = 3600;
37            rec = gem::Recorder::produce(recsrcconfig);
38         }
39      }
40      if (!mapView->isFollowingPosition())
41      {
42         if (ImGui::Button("Follow position"))
43         {
44            mapView->startFollowingPosition();
45         }
46      }
47      else
48      {
49         ImGui::Text("Following position!");
50      }
51      if (rec)
52      {
53         auto recstat = rec.get()->getStatus();
54         char* str = "UNDEFINED";
55         switch (recstat)
56         {
57         case gem::ERecorderStatus::Restarting: str = "gem::ERecorderStatus::Restarting"; break;
58         case gem::ERecorderStatus::Starting:   str = "gem::ERecorderStatus::Starting"; break;
59         case gem::ERecorderStatus::Recording:  str = "gem::ERecorderStatus::Recording"; break;
60         case gem::ERecorderStatus::Stopped:    str = "gem::ERecorderStatus::Stopped"; break;
61         case gem::ERecorderStatus::Stopping:   str = "gem::ERecorderStatus::Stopping"; break;
62         }
63         GEM_LOGE("GPS LOG RECORDER STATUS %s ( %d )", str, recstat);
64         ImGui::Text("GPS LOG RECORDER STATUS %s ( %d )", str, recstat);
65      }
66      if (rec && gem::ERecorderStatus::Stopped == rec.get()->getStatus())
67      {
68         if (ImGui::Button("Start recording GPS log"))
69         {
70            gem::PositionService().setDataSource(dataSource.first);
71            rec.get()->startRecording();
72         }
73      }
74      else if (rec && gem::ERecorderStatus::Recording == rec.get()->getStatus())
75      {
76         if (ImGui::Button("Stop recording GPS log"))
77         {
78            // The GPS log (recording of the playback) is written when the window is closed
79            rec.get()->stopRecording();
80         }
81      }
82      ImGui::End();
83   }
84   , std::placeholders::_1);
85}

Outside the lambda function, the paths and the GPS navigation position log input file name is 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.

Playback

The first control element in this case, is an ImGui::Button() which the user can click to start playback of an .nmea GPS position log.
This is done by setting the data source to the desired input:
gem::PositionService().setDataSource(dataSource.first);
The input data source was defined above, before the lambda function - this is the definition for the NMEA input data:
std::pair<gem::sense::DataSourcePtr, int> dataSource = gem::sense::produceLogDataSource(inputGPSlogFile.c_str());

Follow position

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!"); element is displayed instead of the button, to indicate this mode.

Record GPS log

The last imgui button checks the status of the recorder:
if (rec && gem::ERecorderStatus::Recording == rec.get()->getStatus())
and offers to start recording the pre-recorded navigation that is being played back, if recording is stopped:
rec.get()->startRecording();
or offers to stop recording (and saves the recorded input, if more than 60 seconds long - a preset internal SDK threshold) if recording is already active:
rec.get()->stopRecording();

C++ Examples

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