Skip to content

Simulate Navigation

In this guide you will learn how to calculate a route, render it on an interactive map, and simulate navigation on that route.
Graphic control interface using ImGui.

SimulateNavigation - 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 SimulateNavigation 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, "SimulateNavigation", &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.
"SimulateNavigation", &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   auto uiElems = std::make_shared<CUIElements>();
 4      return UICallbacks( std::bind([=]()
 5   {
 6      return uiElems->initialize();
 7   } ),
 8   std::bind([=](gem::StrongPointer<gem::MapView> mapView)
 9   {
10      //////////////////////////////////////////////////////////
11      // PANEL TO RESUME FOLLOWING POSITION, TOGGLE KM/MI AND STOP SIMULATION
12      //////////////////////////////////////////////////////////
13      static bool iskm = true;
14      const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
15      ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 0, main_viewport->WorkPos.y + 120), ImGuiCond_FirstUseEver);
16      ImGui::Begin("panel", nullptr, ImGuiWindowFlags_NoMove
17          | ImGuiWindowFlags_NoDecoration
18          | ImGuiWindowFlags_AlwaysAutoResize
19          | ImGuiWindowFlags_NoSavedSettings);
20      auto navListener = gem::StrongPointerFactory<MyNavigationListener>();
21      ImGui::PushFont(uiElems->font1);
22      if (!gem::NavigationService().isNavigationActive()
23         && !gem::NavigationService().isSimulationActive())
24      {
25         if (ImGui::Button("Click to simulate navigation on a route"))
26         {
27            gem::LandmarkList waypoints({ { "San Francisco", { 37.77903, -122.41991 } }, { "San Jose", { 37.33619, -121.89058 } } });
28
29            // Compute route using these preferences: car / fastest / without alternatives in result
30            gem::RouteList routes;
31            ProgressListener routeListener;
32            gem::RoutingService().calculateRoute(routes, waypoints,
33               gem::RoutePreferences().setTransportMode(gem::RTM_Car).setRouteType(gem::RT_Fastest).setAlternativesSchema(gem::AS_Never), &routeListener);
34
35            // Wait until route calculation finished & check success
36            if (WAIT_UNTIL(std::bind(&ProgressListener::IsFinished, &routeListener), 30000) && routeListener.GetError() == gem::KNoError && !routes.empty())
37            {
38               // Add the first resulting route (at index 0) to map view
39               mapView->preferences().routes().add(routes[0]);
40
41               // Start simulated navigation along the route
42               gem::NavigationService().startSimulation(routes[0], navListener, gem::ProgressListener());
43
44               // Start follow GPS positions ( generated by the simulation ) - camera follows the position along the route
45               mapView->startFollowingPosition();
46            }
47         }
48      }
49      if (!mapView->isFollowingPosition())
50      {
51         if (ImGui::Button("Follow position"))
52         {
53            mapView->startFollowingPosition();
54         }
55      }
56      else
57      {
58         ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 1.f, 1.f));
59         ImGui::PushFont(uiElems->font1);
60         ImGui::Text("Following position!");
61         ImGui::PopFont();
62         ImGui::PopStyleColor(1);
63      }
64      if (iskm)
65      {
66         if (ImGui::Button("Switch to miles"))
67         {
68            iskm = false;
69         }
70      }
71      else
72      {
73         if (ImGui::Button("Switch to kilometers"))
74         {
75            iskm = true;
76         }
77      }
78      if (gem::NavigationService().isNavigationActive()
79         || gem::NavigationService().isSimulationActive())
80      {
81         if (ImGui::Button("Stop navigation"))
82            gem::NavigationService().cancelNavigation(navListener);
83      }
84      ImGui::PopFont();
85      ImGui::End();

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 control element in this case is an ImGui::Button() which the user can click to calculate a predefined route, add the first (at index 0) resulting route to the map, and start simulated navigation on that route.

At least 2 waypoints define a route. The first is the departure position, and the last is the destination. There can be zero or more intermediate waypoints through which the route passes in the order they are listed. The coordinates are {latitude,longitude} in degrees; the landmark name is optional and can be an empty string.

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.

Next is a button to toggle display between miles and kilometers. Next is a button to stop navigation, and this button appears only if navigation is active.

 1      //////////////////////////////////////////////////////////
 2      // TOP STATUS PANEL
 3      //////////////////////////////////////////////////////////
 4      if (gem::NavigationService().isNavigationActive()
 5         || gem::NavigationService().isSimulationActive())
 6      {
 7         ImGui::SetNextWindowBgAlpha(1.0f);
 8         ImGui::SetNextWindowPos(ImVec2(main_viewport->WorkPos.x + 0, main_viewport->WorkPos.y + 0), ImGuiCond_FirstUseEver);
 9         ImGui::SetNextWindowSize(ImVec2(main_viewport->WorkSize.x, 108));
10         ImGui::GetStyle().WindowRounding = 0.0f;
11         ImGui::Begin("panel2", nullptr, ImGuiWindowFlags_NoMove
12            | ImGuiWindowFlags_NoDecoration
13            | ImGuiWindowFlags_AlwaysAutoResize
14            //| ImGuiWindowFlags_NoBackground
15            | ImGuiWindowFlags_NoSavedSettings);
16         const ImU32 colorblack = ImGui::GetColorU32(ImVec4(0, 0, 0, 1));
17         ImVec2 wPos = ImGui::GetWindowPos();
18         ImVec2 wSize = ImGui::GetWindowSize();
19         ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(wPos.x + 0, wPos.y + 0), ImVec2(wSize.x, wSize.y), colorblack);
20         auto navinstruct = gem::NavigationService().getNavigationInstruction();
21         gem::String instructionText = navinstruct.getNextTurnInstruction();
22         instructionText.fallbackToLegacyUnicode();
23
24         if (ImGui::BeginTable("top_instruction_panel", 2))
25         {
26            ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, NEXT_TURN_ICON_SIZE_PIXELS + 12);
27            ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, wSize.x - 12 - NEXT_TURN_ICON_SIZE_PIXELS);
28
29            ImGui::TableNextRow();
30            ImGui::TableSetColumnIndex(0);
31
32            //////////////////////////////////////////////////////////
33            // TURN ICON
34            //////////////////////////////////////////////////////////
35            {
36               const ImVec2 nextTurnIconSize{ NEXT_TURN_ICON_SIZE_PIXELS, NEXT_TURN_ICON_SIZE_PIXELS };
37               gem::Rgba color(255, 0, 255, 255);
38               gem::AbstractGeometryImageRenderSettings settings(gem::Rgba::white(), gem::Rgba::black(), color);
39               auto bitmap = gem::StrongPointerFactory<BitmapImpl>(70, 70);
40               navinstruct.getNextTurnDetails().getAbstractGeometryImage().render(*bitmap, settings);
41               unsigned int textureId = BitmapImpl::LoadTextureIntoGPU(bitmap->size().width, bitmap->size().height, bitmap->begin());
42               ImGui::Image((void*)textureId, nextTurnIconSize);
43            }
44
45            ImGui::TableSetColumnIndex(1);
46
47            //////////////////////////////////////////////////////////
48            // NEXT STREET NAME
49            //////////////////////////////////////////////////////////
50            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f));
51            ImGui::PushFont(uiElems->font3);
52            ImGui::Text(navinstruct.getNextStreetName().toStdString().c_str());
53            ImGui::PopFont();
54            ImGui::PopStyleColor(1);
55
56            //////////////////////////////////////////////////////////
57            // NEXT TURN INSTRUCTION TEXT
58            //////////////////////////////////////////////////////////
59            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f));
60            ImGui::PushFont(uiElems->font2);
61            ImGui::Text("%s", instructionText.toStdString().c_str());
62
63            auto remainingDistanceToNextTurn = navinstruct.getTimeDistanceToNextTurn().getTotalDistance();
64            auto remainingTimeToNextTurn = navinstruct.getTimeDistanceToNextTurn().getTotalTime();
65
66            ImGui::SameLine();
67            ImGui::Text("   ");
68            ImGui::SameLine();
69
70            //////////////////////////////////////////////////////////
71            // NEXT TURN INSTRUCTION DISTANCE
72            //////////////////////////////////////////////////////////
73            if (iskm)
74            {
75               ImGui::Text("%.1f ", (float)(remainingDistanceToNextTurn));
76               ImGui::SameLine();
77               ImGui::Text("m ");
78               ImGui::SameLine();
79            }
80            else
81            {
82               ImGui::Text("%.1f ", (float)(remainingDistanceToNextTurn / 1609.344 * 5280));
83               ImGui::SameLine();
84               ImGui::Text("ft ");
85               ImGui::SameLine();
86            }
87            ImGui::PopFont();
88            ImGui::PopStyleColor(1);
89
90            ImGui::EndTable();
91
92         } // end table
93
94         ImGuiStyle& style = ImGui::GetStyle();
95         ImVec4* colors = style.Colors;
96         ImGui::End();

The top panel, displaying the next turn icon, next turn instruction, and the distance to the next turn.

  1         //////////////////////////////////////////////////////////
  2         // BOTTOM STATUS PANEL
  3         //////////////////////////////////////////////////////////
  4         {
  5            ImGui::SetNextWindowBgAlpha(1.0f);
  6            ImGui::SetNextWindowPos(ImVec2(main_viewport->Pos.x + 20, main_viewport->Pos.y + main_viewport->WorkSize.y - 72));
  7            ImGui::SetNextWindowSize(ImVec2(main_viewport->WorkSize.x - 40, 64));
  8            ImGui::GetStyle().WindowRounding = 12.0f;
  9            ImGui::Begin("panel4", nullptr, ImGuiWindowFlags_NoMove
 10               | ImGuiWindowFlags_NoDecoration
 11               | ImGuiWindowFlags_NoSavedSettings);
 12            ImVec4 mycolor0{ 0,0,0,255 };
 13            ImVec4 mycolor1{ 255,0,0,255 };
 14            ImVec4 mycolor2{ 0,255,0,255 };
 15            ImVec4 mycolor3{ 0,0,255,255 };
 16            ImVec4 mycolor7{ 255,255,255,255 };
 17
 18            auto remainingTravelTime = navinstruct.getRemainingTravelTimeDistance().getTotalTime();
 19            auto remainingTravelDistance = navinstruct.getRemainingTravelTimeDistance().getTotalDistance();
 20
 21            time_t currentTimeSec = (time_t)(gem::Time::getLocalTime().asInt() / 1000);
 22            time_t eta = (time_t)(currentTimeSec + remainingTravelTime);
 23            struct tm tmcurrent, etatm;
 24            struct tm* tmptr = gmtime((const time_t*)&(currentTimeSec));
 25            memcpy(&tmcurrent, tmptr, sizeof(struct tm));
 26            tmptr = gmtime((const time_t*)&(eta));
 27            memcpy(&etatm, tmptr, sizeof(struct tm));
 28
 29            //////////////////////////////////////////////////////////
 30            // REMAINING TRAVEL HOURS, MIN, SEC: PRINT ONLY HOURS:MIN IF >= 1H OR MIN:SEC IF < 1H
 31            //////////////////////////////////////////////////////////
 32            int remainingTravelHours = (int)(remainingTravelTime / 3600);
 33            int remainingTravelMin = (int)((remainingTravelTime % 3600) / 60);
 34            int remainingTravelSec = (int)(remainingTravelTime % 60);
 35            //////////////////////////////////////////////////////////
 36            // COMPUTE BOTTOM STAT STR LENGTH TO ENABLE CENTERING STR HORIZONTALLY AND VERTICALLY!
 37            //////////////////////////////////////////////////////////
 38            ImGui::PushFont(uiElems->font1);
 39            float font_size1 = ImGui::GetFontSize();
 40            auto textWidth1 = ImGui::CalcTextSize("ETAminkm").x;
 41            ImGui::PopFont();
 42            ImGui::PushFont(uiElems->font2);
 43            float font_size2 = ImGui::GetFontSize();
 44            auto textWidth2 = ImGui::CalcTextSize("00:00   00").x;
 45            auto textHeight2 = ImGui::CalcTextSize("00").y;
 46            ImGui::PopFont();
 47            if (remainingTravelHours > 0)
 48            {
 49               int digitsInHours = (int)(log10(remainingTravelHours)) + 1;
 50               ImGui::PushFont(uiElems->font2);
 51               textWidth2 += digitsInHours * ImGui::CalcTextSize("0").x;
 52               ImGui::PopFont();
 53               ImGui::PushFont(uiElems->font1);
 54               textWidth1 += ImGui::CalcTextSize("h ").x;
 55               ImGui::PopFont();
 56
 57            }
 58            else
 59            {
 60               ImGui::PushFont(uiElems->font2);
 61               textWidth2 += ImGui::CalcTextSize(" 00").x;
 62               ImGui::PopFont();
 63            }
 64            int digitsInMeters = (int)(log10(remainingTravelDistance)) + 1;
 65            if (remainingTravelDistance > 1000)
 66            {
 67               digitsInMeters -= 1;//printed %.1f
 68            }
 69            ImGui::PushFont(uiElems->font2);
 70            textWidth2 += digitsInMeters * ImGui::CalcTextSize("0").x;
 71            ImGui::PopFont();
 72            ImGui::PushFont(uiElems->font1);
 73            textWidth1 += ImGui::CalcTextSize("km").x;
 74            ImGui::PopFont();
 75
 76            ImGui::SetCursorScreenPos(ImVec2((ImGui::GetWindowSize().x - textWidth2 - textWidth1) * 0.4f + ImGui::GetWindowPos().x,
 77               (ImGui::GetWindowSize().y - textHeight2) * 0.5f + ImGui::GetWindowPos().y));
 78
 79            //////////////////////////////////////////////////////////
 80            // ETA
 81            //////////////////////////////////////////////////////////
 82            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 1.f));
 83            //auto rpos = ImGui::GetCursorScreenPos();
 84            //ImGui::SetCursorScreenPos(ImVec2(rpos.x, rpos.y + 12));
 85            ImGui::PushFont(uiElems->font1);
 86            ImGui::Text("ETA");
 87            ImGui::PopFont();
 88            ImGui::SameLine();
 89            ImGui::PushFont(uiElems->font2);
 90            ImGui::Text("%02d:%02d   ", etatm.tm_hour, etatm.tm_min);
 91            ImGui::PopFont();
 92            ImGui::SameLine();
 93            ImGui::PopStyleColor(1);
 94
 95            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, .5f, 0.f, 1.f));
 96            if (remainingTravelHours > 0)
 97            {
 98               ImGui::PushFont(uiElems->font2);
 99               ImGui::Text("%d", remainingTravelHours);
100               ImGui::PopFont();
101               ImGui::SameLine();
102               ImGui::PushFont(uiElems->font1);
103               ImGui::Text("h ");
104               ImGui::PopFont();
105               ImGui::SameLine();
106            }
107            {
108               ImGui::PushFont(uiElems->font2);
109               ImGui::Text("%02d", remainingTravelMin);
110               ImGui::PopFont();
111               ImGui::SameLine();
112               ImGui::PushFont(uiElems->font1);
113               ImGui::Text("min");
114               ImGui::PopFont();
115               ImGui::SameLine();
116            }
117            if (remainingTravelHours == 0)
118            {
119               ImGui::PushFont(uiElems->font2);
120               ImGui::Text(" %02d", remainingTravelSec);
121               ImGui::PopFont();
122               ImGui::SameLine();
123            }
124            ImGui::PopStyleColor(1);
125
126            ImGui::PushFont(uiElems->font2);
127            ImGui::Text("   ");
128            ImGui::PopFont();
129            ImGui::SameLine();
130
131            //////////////////////////////////////////////////////////
132            // REMAINING TRAVEL DISTANCE KM
133            //////////////////////////////////////////////////////////
134            ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 1.f, 1.f));
135            if (iskm)
136            {
137               if (remainingTravelDistance > 1000)
138               {
139                  ImGui::PushFont(uiElems->font2);
140                  ImGui::Text("%.1f", (float)(remainingTravelDistance * .001));
141                  ImGui::PopFont();
142                  ImGui::SameLine();
143                  ImGui::PushFont(uiElems->font1);
144                  ImGui::Text("km");
145                  ImGui::PopFont();
146               }
147               else
148               {
149                  ImGui::PushFont(uiElems->font2);
150                  ImGui::Text("%d", (float)(remainingTravelDistance));
151                  ImGui::PopFont();
152                  ImGui::SameLine();
153                  ImGui::PushFont(uiElems->font1);
154                  ImGui::Text("m");
155                  ImGui::PopFont();
156               }
157            }
158            else
159            {
160               if (remainingTravelDistance > 1000)
161               {
162                  ImGui::PushFont(uiElems->font2);
163                  ImGui::Text("%.1f", (float)(remainingTravelDistance / 1609.344));
164                  ImGui::PopFont();
165                  ImGui::SameLine();
166                  ImGui::PushFont(uiElems->font1);
167                  ImGui::Text("mi");
168                  ImGui::PopFont();
169               }
170               else
171               {
172                  ImGui::PushFont(uiElems->font2);
173                  ImGui::Text("%.1f", (float)(remainingTravelDistance / 1609.344 * 5280));
174                  ImGui::PopFont();
175                  ImGui::SameLine();
176                  ImGui::PushFont(uiElems->font1);
177                  ImGui::Text("ft");
178                  ImGui::PopFont();
179               }
180            }
181            ImGui::PopStyleColor(1);
182            colors[ImGuiCol_WindowBg] = mycolor7;
183            ImGui::End();
184         }
185      }
186   }
187   , std::placeholders::_1);
188}

The bottom panel, displaying the remaining travel time, ETA (estimated time of arrival), and remaining distance to travel.

C++ Examples

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