GPX Routing Simulation¶
Setup¶
Prerequisites¶
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.
Environment::SdkSession session(projectApiToken, { argc > 1 ? argv[1] : "" });
CTouchEventListener pTouchEventListener;
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,
"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.
ImGui::Begin()
function.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()
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()
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();
auto navListener = gem::StrongPointerFactory<MyNavigationListener>();