Skip to content

GPX Routing Simulation

In this guide you will learn how to calculate and render a route based on a GPX track as input waypoints;
simulate navigation along the route;
use a custom navigation listener to receive navigation events, such as started, waypoint reached, or destination reached;
dynamic detection if position arrow indicator is inside or outside the viewport.

GPX Routing Simulation - 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 GpxRoutingSimulation 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, "GpxRoutingSimulation", &pTouchEventListener, getUiRender()));
15   if ( !mapView )
16   {
17      GEM_LOGE( "Error creating gem::MapView: %d", GEM_GET_API_ERROR() );
18   }
19   std::thread myNavThread(navThread, "navThread", mapView);
20   WAIT_UNTIL_WINDOW_CLOSE();
21   navThreadStop = true;
22   myNavThread.join();
23   return 0;
24}

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.
"GpxRoutingSimulation", &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.

 1bool navThreadStop = false;
 2bool isPositionArrowVisible = true;
 3void navThread(std::string navThread, gem::StrongPointer<gem::MapView> mapView)
 4{
 5 auto positionArrow = gem::MapSceneObject::getDefPositionTracker().first;
 6 while (!navThreadStop)
 7 {
 8     auto currentTime = std::chrono::system_clock::now().time_since_epoch();
 9     auto currentTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime).count();
10     if (currentTimeMs % 200 == 0)
11     {
12         isPositionArrowVisible = mapView.get()->checkObjectVisibility(*positionArrow);
13         GEM_LOGE("POSITION ARROW IS %s THE VIEWPORT # # #", isPositionArrowVisible ? "WITHIN" : "OUTSIDE");
14     }
15     std::this_thread::sleep_for(std::chrono::milliseconds(10));
16 }
17}

To demonstrate dynamic detection whether the position indicator arrow is inside or outside the viewport, which can happen if the map is panned, thus deactivating follow position mode, a separate thread is started. The separate thread, navThread, gets a pointer to the position indicator arrow using gem::MapSceneObject::getDefPositionTracker().first and then checks whether it is in the viewport using mapView.get()->checkObjectVisibility()

The UI render function:

 1auto getUiRender()
 2{
 3 auto sdkExamplesPath = Environment::GetInstance().GetSDKExamplesPath();
 4 auto sdkCachePath = Environment::GetInstance().GetCachePath();
 5
 6 auto dstCacheResPath = gem::FileSystem().makePath(sdkCachePath.c_str(), u"Data", u"Res/");
 7 gem::FileSystem().createFolder(dstCacheResPath, true);
 8
 9 auto srcGPXPath = gem::FileSystem().makePath(sdkExamplesPath.c_str(), u"Examples", u"Interactive", u"GpxRoutingSimulation", u"strasbourg_points.gpx");
10 int ret;
11 if ((ret = gem::FileSystem().copyFile(srcGPXPath, dstCacheResPath)) != gem::KNoError)
12 {
13     GEM_LOGE("Error copy GPX resource (%d)", GEM_GET_API_ERROR());
14 }
15 srcGPXPath = gem::FileSystem().makePath(dstCacheResPath, "strasbourg_points.gpx");
16
17 ////////////////////////////////////
18 // read waypoints from gpx
19 ////////////////////////////////////
20 gem::DataBuffer gpxDataBuf;
21 std::ifstream gpxDataStream;
22 gpxDataStream.open(srcGPXPath.toStdString().c_str(), std::ios::binary);
23 if (gpxDataStream.good())
24 {
25     gpxDataStream.seekg(0, std::ios::end);
26     gpxDataBuf.reserve(int(gpxDataStream.tellg()));
27     gpxDataStream.seekg(0, std::ios::beg);
28     gpxDataStream.read(gpxDataBuf.getBytes<char>(), gpxDataBuf.size());
29 }

The GPX track input file included with this example is copied into the Data/Res/ path within the SDK install directory. Then the waypoints are loaded from the GPX track input file.

The lambda function within the UI render function:

 1return std::bind([gpxDataBuf](gem::StrongPointer<gem::MapView> mapView)
 2{
 3   ImGuiIO& io = ImGui::GetIO();
 4   const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
 5   ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 0, main_viewport->WorkPos.y + 20), ImGuiCond_FirstUseEver);
 6   ImGui::Begin("panel", nullptr, ImGuiWindowFlags_NoMove
 7       | ImGuiWindowFlags_NoDecoration
 8       | ImGuiWindowFlags_AlwaysAutoResize
 9       | ImGuiWindowFlags_NoSavedSettings);
10   gem::RouteList routes;
11   // Instantiate a navigation event listener for either navigation (real or playback) or simulation
12   auto navListener = gem::StrongPointerFactory<MyNavigationListener>();
13   ImGui::Spacing();
14   if (ImGui::Button("Calculate route over GPX track"))
15   {
16       if (gem::NavigationService().isSimulationActive())
17       {
18           gem::NavigationService().cancelNavigation();
19       }
20       ////////////////////////////////////
21       // calculate and render GPX route
22       ////////////////////////////////////
23       gem::Path path(gpxDataBuf, gem::PFF_Gpx);
24       if (!path.getWayPoints().empty() && !path.getCoordinates().empty())
25       {
26           ProgressListener routeListener;
27           gem::Landmark lmk;
28           gem::LandmarkList waypoints;
29           waypoints.emplace_back();
30           gem::RouteBookmarks::setWaypointTrackData(waypoints.back_nc(), path);
31           gem::RoutingService().calculateRoute(routes, waypoints,
32              gem::RoutePreferences().setTransportMode(gem::RTM_Car).setRouteType(gem::RT_Fastest).setAlternativesSchema(gem::AS_Never), &routeListener);
33           // Wait until route calculation finished & check success
34           if (WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &routeListener), 30000) && routeListener.GetError() == gem::KNoError
35               && !routes.empty())
36           {
37               // Draw first resulting route (at index 0) on map view
38               mapView->preferences().routes().add(routes[0]);
39               mapView->centerOnRoute(routes[0], gem::Rect(), gem::Animation(gem::AnimationLinear, gem::ProgressListener(), 2000));
40           }
41       }
42   }
43   ImGui::End();
44} , std::placeholders::_1); }

Outside the lambda function, the paths are defined, and the GPX input file is read. 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.
There is a ImGui::Button() which the user can click to calculate and render a route based on waypoints read from the .gpx input file included in this example; the waypoints from the GPX file read outside the lambda function are loaded using gem::RouteBookmarks::setWaypointTrackData(), and then a route is calculated based on these waypoints using gem::RoutingService().calculateRoute()
The ProgressListener routeListener is used to be notified when the asynchronous route calculation is completed, and if there is at least one route in the resulting route list, gem::RouteList routes; then the first route, at index 0, is rendered on the interactive map: mapView->preferences().routes().add(routes[0]); and the map centers on the route, so it fits in the viewport: mapView->centerOnRoute()
There are also UI buttons (not shown above, see the source code) to start the simulation: gem::NavigationService().startSimulation() and to resume following position, if the map was panned, which causes the camera to stop following the position indicator as it moves along the route in the simulated navigation: mapView->startFollowingPosition();
The navigation listener receives navigation events, such as started, waypoint reached, or destination reached:
auto navListener = gem::StrongPointerFactory<MyNavigationListener>();

C++ Examples

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