Skip to content

02 Basic map drawing

Overview

We will learn how to display a simple map and how to add map interactions like zooming or panning.

Base

You can use your results from Tutorial01 as a base for our new project.

Prerequisites

We put the map drawing related functions of our tutorials in a class called MapWidget. Please copy the following files from the source directory "Tutorial02" to the "Tutorial" directory and add them to the CMakeLists.txt:

  • MapWidget.cpp
  • MapWidget.h

The edited CMakeLists.txt sections should look like this:

...
set(Tutorial_SRCS
    main.cpp
    MainWindow.cpp
    Constants.cpp
    MapWidget.cpp
)

set(Tutorial_HEADER    
    MainWindow.h
    Constants.h
    MapWidget.h
    ${PROJECT_SOURCE_DIR}/navigationsdk/SDKInterface.h
    ${PROJECT_SOURCE_DIR}/navigationsdk/SDKError.h
    ${PROJECT_SOURCE_DIR}/navigationsdk/SDKDef.h
)

Call cmake ../ in the build directory or build the ALL_BUILD project of the solution to update it.
Also, always trigger a rebuild after copying to ensure that replaced files will be newly built.

Adding a map view

For showing a map, we have to add the above mentioned MapWidget to our MainWindow. We also add a menu for switching some map parameters. To do this the short way, copy the mainwindow.ui from the "Tutorial02" folder to your "Tutorial" folder and override the old version.

!!! note: When replacing an .ui file, the build system sometimes doesn't recognize that the file has changed. So edit the file (for example add and remove a space) and save it so that it's time stamp changes.

You also can add the MapWidget with the designer:
Open the mainwindow.ui with the Qt Designer and add a standard QWidget to the MainWindow. Stretch it so it fills the whole area. Right click the new QWidget and click "user defined classes...". In the upcoming window, add a new class with base "QWidget" and the name MapWidget. Click on ok. The widget is now a placeholder for our MapWidget. Name the widget "map". We will add some menu entries for showing the map in 2D or 3D, for switching the night mode and for switching the grey mode. For this, we add a menu entry called "Map". Add submenu entries to "Map" called "Show in 3D", "Show grey mode" and "Show night mode". Rename the actions created by the Designer to "action3D", "actionShowGreyMode" and "actionShowNightMode". Check the "checkable" box for all three actions.

The MainWindow in the Designer view should look like this:

Add the following to the MainWindow class:

MainWindow.h:

...
protected slots:
    void action3DToggled(bool checked);
    void actionGreyModeToggled(bool checked);
    void actionDayModeToggled(bool checked);

MainWindow.cpp:

...
MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{
    ui.setupUi(this);

    SDK_ERROR rc = SDK_Initialize(Constants::getMapPath().toStdWString().c_str(), L"deu", 
                                  Constants::getAddrPath().toStdWString().c_str(), 
                                  Constants::getDataPath().toStdWString().c_str(), SDK_FALSE);

    ui.map->initialize();

    SDK_Position pos;
    pos.x = 934790;
    pos.y = 6269440; // this should be around karlsruhe...
    ui.map->setCenter(pos);

    connect(ui.action3D, SIGNAL(toggled(bool)), this, SLOT(action3DToggled(bool)));
    connect(ui.actionShowGreyMode, SIGNAL(toggled(bool)), this, SLOT(actionGreyModeToggled(bool)));
    connect(ui.actionShowNightMode, SIGNAL(toggled(bool)), this, SLOT(actionDayModeToggled(bool)));
}


MainWindow:: ~MainWindow()
{
}

void MainWindow::action3DToggled(bool checked)
{
    ui.map->set3d(checked);
}

void MainWindow::actionGreyModeToggled(bool checked)
{
    ui.map->setGreyMode(checked);
}

void MainWindow::actionDayModeToggled(bool checked)
{
    ui.map->setDayMode(!checked);
}

We initialize our MapWidget in the constructor and set the center position of the map initially to Karlsruhe/Germany. After that, we connect the actions we created in the Designer. Build the app and start it, it should show something like this:

Initializing a map view

To show a map, we need a map view. The SDK has three possibilites to initialize a map view:

We use SDK_InitMapViewFB() for initializing the map view. It is independent from the underlying system API and therefore works on all platforms. On the Windows platform, you also have the possibility to use a window handle or a HDC to render the map to, but we do not cover these methods here. See the documentation for more details.

SDK_InitMapViewFB() has the following parameters:

Parameter type Parameter name Description
SDK_UINT2* pixelData The pixel buffer used for rendering the map to
SDK_UINT2 width The width of the pixel buffer, must be a multiple of 2
SDK_UINT2 height The height of the pixel buffer
SDK_BOOL upsideDown Change vertical rendering direction
SDK_INT4* pHandle A mapview id (>= 0) or value <0 in case of error

!!! note: The internal renderer uses 2 Bytes per pixel in RGB565 format. So the size of the buffer for a window with the size "width * height" in pixel must be width * height * 2 [Byte]. The width of the buffer and the width parameter of the SDK_InitMapViewFB() function must be a multiple of 2 to avoid rendering distortions.

So, let's have a look at the MapWidget class. There are some steps that need to be done to show a map:

  • Create a render buffer in the correct size. As mentioned above, every pixel internally has a size of 16bit (in the format RGB565). So the size of the buffer must be ViewportX * ViewportY * 2 in [Byte]. Also, ViewportX must be a multiple of 2. The SDK will render the map into this buffer
  • Initialize the map view by calling SDK_InitMapViewFB() with the created buffer
  • Render the map (to the buffer) with SDK_SendMapViewEvent() and SDK_wmPAINT as parameter "message" in the sent SDK_MapViewEventData struct
  • Blit the buffer with the rendered map to your view
  • Resize the map view if needed by deleting the old buffer, creating a new buffer in the correct size and calling SDK_SendMapViewEvent() with SDK_wmSize as the message and the correct width and height

The initialization of the map view is done in the initialize() function of our MapWidget class:

MapWidget.cpp:

void MapWidget::initialize()
{
    resizeBuffers();

    SDK_ERROR rc = SDK_InitMapViewFB((SDK_UINT2*)mBuffer, mWidth, mHeight, SDK_TRUE, &mMapId);
    if(mUseMapViewControls) // enable the internal controls on the map
        rc = SDK_SetMapViewElements(mMapId, (SDK_UINT4)(SDK_mapMEASURECTRL | SDK_mapPANNINGCTRL | SDK_mapROTATIONCTRL | SDK_mapSCALINGCTRL), 0);
    else // disable the internal controls
        rc = SDK_SetMapViewElements(mMapId, 0, (SDK_UINT4)(SDK_mapMEASURECTRL | SDK_mapPANNINGCTRL | SDK_mapROTATIONCTRL | SDK_mapSCALINGCTRL));
    //setMapDesign();
}

The buffer will be initialized (and resized in case of a window resize) in the resizeBuffers() method:

void MapWidget::resizeBuffers()
{
    if (mImage)
        delete mImage;
    if (mBuffer)
        delete[] mBuffer;

    mWidth = width();
    mHeight = height();

    // set the internal width of the buffer to a multiple of 2 which is needed by the internal renderer
    if ((mWidth % 2) != 0)
        mWidth--;

    mBuffer = new uchar[mWidth * mHeight * 2];
    mImage = new QImage(mBuffer, mWidth, mHeight, QImage::Format_RGB16);
}

We retrieve the width and the height of our MapWidget and create a buffer with the appropriate size. If the width is not a multiple of two, we substract 1 from it and use the new width to create the buffer. Have in mind that the width and the height parameters of the SDK_InitMapViewFB() method must have the same values as the buffer creation width and height, otherwise you will likely get a crash when the SDK tries to access the map view.

For showing the map, we have to blit the rendered map from our buffer to the screen. Because we use Qt in our examples, we will use a QImage for this. We create a QImage with the newly created buffer as the background buffer. So when rendering the map later in the paintEvent(), the image will hold the rendered map. A pixel in the buffer will be rendered in 16 bit RGB565 format. So we set the QImage to this format.

After creation of the buffer, we call

SDK_InitMapViewFB(SDK_UINT2 *pixelData, SDK_UINT2 width, SDK_UINT2 height, SDK_BOOL upsideDown,
                  SDK_INT4 *pHandle)

upsideDown is used to change the vertical rendering direction. Under Windows, we set this to SDK_TRUE. If the map will be rendered upside down, change this flag appropriately. The pHandle is a pointer to an integer. The integer value is used to identify a map view. You can create as many map views as you want as long as memory is available.
Different map views can show simultanously different views of a given map in different scales. But be aware that some SDK functions will alter all map views at once (like adding an image with SDK_AddImage()).

Rendering the map

MapWidget.cpp:

void MapWidget::paintEvent(QPaintEvent *event)
{
    SDK_MapViewEventData e;
    SDK_InitMapViewEventData(&e);
    e.message = SDK_wmPAINT;

    SDK_ERROR rc = SDK_SendMapViewEvent(mMapId, mNavigationInformation, &e, mBuffer);

    QPainter painter(this);
    QPixmap pixmap = QPixmap::fromImage(*(mImage));
    painter.drawPixmap(0, 0, pixmap);
}

In the paintEvent() function, the actual map rendering is triggered by calling SDK_SendMapViewEvent() with our former created buffer. SDK_SendMapViewEvent() is used for sending map events like interactions and resize and paint requests to the map view.

SDK_SendMapViewEvent() has the following parameters:

Parameter type Parameter name Description
SDK_INT4 MapView the map view we want to change
const SDK_NavigationInformation* pNavigationInformation Optional pointer to a NavigationInformation object. If non-NULL and the information belongs to a valid navigation situation the route is painted as a blue trace into the map. If NULL, a route trace will not be rendered, even if one is available!
const SDK_MapViewEventData* pMapViewEvent Pointer to event structure
void* pixelData Pixel buffer, only used when the map view was initialized by SDK_InitMapViewFB and the event is SDK_wmSize

The struct SDK_MapViewEventData is used to hold the type of the event and the event parameters. It has the following members:

Type Name Description
SDK_HDRINFO size size of the structure - for controlling
SDK_HDRINFO version Version of the structure - for controlling. Use SDK_MapViewEventDataVERSION
SDK_UINT4 message Message event code
SDK_INT4 x x coordinate
SDK_INT4 y y coordinate
SDK_INT4 width width, only used if message==SDK_wmSIZE
SDK_INT4 height height, only used if message==SDK_wmSIZE
SDK_INT4 delta delta value, only used if message=SDK_wmWWHEEL

The SDK renders the map into the pixelData buffer. This buffer now must be painted to our MapWidget background so we can see it. We use the QImage that we created earlier for this. We convert the QImage to a QPixmap and paint that pixmap to our window by calling painter.drawPixmap(). This is Qt specific, in other frameworks you have to blit the buffer to the window with the given API functions.

Changing viewing parameters

You can change the center of the map, the 2D/3D viewing, the scale of the map, the orientation (rotation) of the map and the pitch by calling SDK_SetMapViewParameter().

SDK_SetMapViewParameter() function has the following parameters:

Parameter type Parameter name Description
SDK_INT4 MapView the map view we want to change
SDK_INT4 Mask A bit mask of the parameters that should be changed, see the available parameters below
SDK_INT4 CenterX the new map center x position in mercator (only used if the bit flag SDK_SV_Center is set in Mask)
SDK_INT4 CenterY the new map center y position in mercator (only used if the bit flag SDK_SV_Center is set in Mask)
SDK_INT4 Scale The new scale. The lowest value is 100, the lower the nearer. (only used if the bit flag SDK_SV_Scale is set in Mask)
SDK_INT4 Orientation The new orientation of the map (only used if the bit flag SDK_SV_Orientation is set in Mask)
SDK_BOOL Mode3d If set to SDK_TRUE, the map will be rendered in 3D, else in 2D (only used if the bit flag SDK_SV_Mode3d is set in Mask)
SDK_INT4 Pitch Sets the pitch of the map view (only used if the bit flag SDK_SV_Pitch is set in Mask)

List of available viewing parameters:

| Map View Parameter | Description | |------------|-----------------|-------------| |SDK_SV_ZoomToRoute | Sets the scale of the map view to show the complete route, uses none the value parameters given | |SDK_SV_Center | Sets the center of the map view | |SDK_SV_Scale | Sets the scale of the map view | |SDK_SV_Orientation | Sets the orientation of the map view | |SDK_SV_Mode3d | Sets the 3d mode of the map view | |SDK_SV_Pitch | Sets the pitch angle of the map view | |SDK_SV_All| shortcut for: (SDK_SV_Center | SDK_SV_Scale | SDK_SV_Orientation | SDK_SV_Mode3d | SDK_SV_Pitch) |

For example, setting the scale of the map to 1000 (100 is nearest) and the orientation to 230 degrees, we call

SDK_ERROR rc = SDK_SetMapViewParameter(mMapId, SDK_SV_Scale | SDK_SV, 0, 0, 1000, 230, 0, 0)

The first parameter is the id of the map view we want to alter. The second is the bit mask to select which of the above mentioned parameters we would like to change. The fith is the actual scale we want to set. The sixth is the value for the orientation. All parameters that are not used are ignored, so we set them to 0. There are two special viewing parameters:

  • SDK_SV_ZoomToRoute will zoom the map so that the complete route is visible, this flag needs no value parameters, so set them all to 0.
  • SDK_SV_All is a shortcut for (SDK_SV_Center | SDK_SV_Scale | SDK_SV_Orientation | SDK_SV_Mode3d | SDK_SV_Pitch).

Changing map view colors for day/night mode
To set the map view to night mode, call:

SDK_ERROR rc = SDK_SetMapViewDayNight(mMapId, isDay ? 100 : 0);

The second parameter of SDK_SetMapViewDayNight() is a factor which means 100 for day and 0 for night. Values between will gradually dim the map.

Changing map view colors to grey mode
To set the map view to grey mode, call SDK_SetMapViewGrayMode():

SDK_ERROR rc = SDK_SetMapViewGrayMode(mMapId, isGrey);

Map interaction

There are two possibilities to interact with the map:

  • use the built in standard map controls
  • manually set the pan, scale, zoom and rotation values

The source code in the MapWidget class shows exemplarily how you could interact with the map with or without the built in interactors. Just set the variable mUseMapViewControls in the constructor of the MapWidget to enable or disable the interactors.

Standard map controls
The NavigationSDK map view provides some controls which can be used to pan, zoom, scale or rotate the map. The only thing to do is to redirect the mouse events to the SDK using SDK_SendMapViewEvent() with the appropriate parameters. If you do not want to implement the gestures for yourself and you are using a mouse, the easiest way to interact with the map is to use these controls.

List of available controls:

Control Id Type of control Description
SDK_mapMEASURECTRL measure control Shows a measure ruler on the map
SDK_mapPANNINGCTRL panning control Used for panning the map, has size of the entire map view
SDK_mapROTATIONCTRL rotation 2D control Used to rotate the map in 2D mode
SDK_mapSCALINGCTRL scaling control Used to scale the map in 2D mode
SDK_mapROTATION3dCTRL rotation 3D control Used to rotate the map in 3D mode
SDK_mapHEIGHT3dCTRL height control in 3D Not used, scale control has same functionality

There is one more control, the wheel mouse control to change the scale with the wheel mouse. This control is not deactivatable, so it has no entry in the above table. The panning control and the wheel control are not visible on the map, but they cover the whole area of it.

The controls are by default "on" in 2D and 3D, so enabling them is unnecessary. Also, if you switch from 2D to 3D and back, you do not have to enable or disable the specific 2D/3D controls. The SDK will choose automatically the correct one.

An example for using the wheel control:

void MapWidget::wheelEvent( QWheelEvent *event )
{
    SDK_MapViewEventData eventData;
    SDK_InitMapViewEventData(&eventData);
    eventData.message = SDK_wmWHEEL;
    eventData.x = event->x();
    eventData.y = event->y();
    eventData.delta = event->delta();
    SDK_ERROR rc = SDK_SendMapViewEvent(mMapId, 0, &eventData, 0);
    update();
}

In case of a wheel movement, we create a struct of type SDK_MapViewEventData, set the message to SDK_wmWHEEL, the x and y position to the event position of the mouse and the delta of it to the event delta. Then we call SDK_SendMapViewEvent() with the map id and the filled struct. The NavigationSDK will zoom the map accordingly by the delta value.

!!! note: The SDK offers initialization functions to setup the SDK structs with the correct version and size informations and to set all other values to 0. It is mandatory to initialize a struct correctly when calling any SDK function, because every function checks for the version and size fields and returns an error if they are not set correctly. SDK_InitMapViewEventData() above is such a helper function. We have initialize functions for almost all structs used in the SDK. All of these functions can be found in the SDKDef.h header.

Enabling or disabling the standard controls

With a call to SDK_SetMapViewElements() you can enable or disable individual interactors. The second parameter of the function is a bit mask that describes which of the controls should be enabled. The third parameter is a bit mask for the controls that should not be enabled. Removing all interactors is done by:

    SDK_SetMapViewElements(mMapId, 0, (SDK_UINT4)(SDK_mapMEASURECTRL | SDK_mapPANNINGCTRL | SDK_mapROTATIONCTRL | SDK_mapSCALINGCTRL | SDK_mapROTATION3dCTRL |  SDK_mapHEIGHT3dCTRL));
    // shorter version with same result: SDK_SetMapViewElements(mMapId, 0, (SDK_UINT4)0xFFFF);

Removing a control does not mean that you cannot e.g. pan the map anymore. It does mean that you cannot pan the map by the control anymore and that you have to do it manually by some other function as described in the next chapter.

Handling map interaction manually
Deactivating the standard controls and manually controlling the interactions with the map is mostly useful when there is no mouse support or you want to have full control over the gestures. For instance, if you have a device that supports touch gestures, you will likely not use the standard interactors, because they didn't support all features that are needed for touch events.

Setting the scale and orientation values manually are straight forward with the above described SDK_SetMapViewParameter() function. Panning the map can also be done manually by calling SDK_TranslateMapByPixelPos().
An example for manually panning the map without the use of the panning interactor will be described in the next chapter.

Panning the map

Here we show how to pan the map either with the standard controls (mUseMapViewControls == SDK_TRUE) or manually:

void MapWidget::mousePressEvent(QMouseEvent *event) 
{
    if(event->buttons() == Qt::LeftButton) 
    {
        if (mUseMapViewControls)
        {
            SDK_MapViewEventData e;
            SDK_InitMapViewEventData(&e);
            e.message = SDK_wmLBUTTONDOWN;
            e.x = event->x();
            e.y = event->y();

            SDK_ERROR rc = SDK_SendMapViewEvent(mMapId, 0, &e, 0);
        }
        else
        {
            mMouseX = event->x();
            mMouseY = event->y();
            mDoDrag = true;
        }
    }
}

void MapWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() == Qt::LeftButton)
    {
        if (mUseMapViewControls)
        {
            SDK_MapViewEventData e;
            SDK_InitMapViewEventData(&e);
            e.message = SDK_wmMOUSEMOVE;
            e.x = event->x();
            e.y = event->y();

            SDK_ERROR rc = SDK_SendMapViewEvent(mMapId, 0, &e, 0);
        }
        else
        {
            if (mDoDrag)
            {
                SDK_ERROR rc = SDK_TranslateMapByPixelPos(mMapId, mMouseX, mHeight - mMouseY, event->x(), mHeight - event->y());

                mMouseX = event->x();
                mMouseY = event->y();
            }
        }
        update();

    }
}

void MapWidget::mouseReleaseEvent(QMouseEvent *event) 
{
    if (mUseMapViewControls)
    {
        SDK_MapViewEventData e;
        SDK_InitMapViewEventData(&e);
        e.message = SDK_wmLBUTTONUP;
        e.x = event->x();
        e.y = event->y();

        SDK_ERROR rc = SDK_SendMapViewEvent(mMapId, 0, &e, 0);
    }
    else
        mDoDrag = false;
}

In case that the standard map interactors are enabled, the only thing to do is to redirect the mouse press/move/release events to the map view via the function SDK_SendMapViewEvent().

If we are not using the standard pan interactor, we have to move the map manually with SDK_TranslateMapByPixelPos(). It needs as parameters the last coordinates and the current coordinates of the mouse (or touch) position. It will move the map relatively to the last position by the delta of the two values. In our example, the vertical values are reversed by substracting the y-coordinates of the mouse from the view height, because the coordinate system is upside down in relation to the internal coordinate system under Windows.

Freeing the map view

Freeing the map view has to be done when the map view should be destroyed. Call SDK_ReleaseMapView() to free all internal resources that has been created during the SDK_InitMapViewFB() call.

Resizing the map view

Resizing the map view means that we have to change the render buffer size. To do that, we have to destroy the buffer and the corresponding QImage we used for showing the buffer and recreate them in the new size. Then we call SDK_SendMapViewEvent() to inform the SDK that the size has changed:

void MapWidget::resizeEvent(QResizeEvent *ev)
{
    resizeBuffers();

    SDK_MapViewEventData data;
    SDK_InitMapViewEventData(&data);

    data.message = SDK_wmSIZE;
    data.width = mWidth;
    data.height = mHeight;

    SDK_SendMapViewEvent(mMapId, 0, &data, mBuffer);
}

When the MapWidget will be destroyed, we delete all allocated buffers and call SDK_ReleaseMapView() to free the map view in MapWidget::finalize():

void MapWidget::finalize()
{
    SDK_ERROR rc = SDK_ReleaseMapView(mMapId);
    if(mImage)
        delete mImage;
    if(mBuffer)
        delete[] mBuffer;
}

Customizing map colors

If you wan't to use your own map colors you can load your own color definition files with MapWidget::setMapDesign(). Our tutorial comes with some example color definitions for day and night mode.

void MapWidget::setMapDesign()
{
    QString fileNameDay = Constants::getDataPath();
    fileNameDay += "renderer/Roma_day.ini";
    SDK_ERROR rc = SDK_SetMapViewColorsFromFile(mMapId, fileNameDay.toStdWString().c_str(), SDK_TRUE);

    QString filenNameNight = Constants::getDataPath();
    filenNameNight += "renderer/Roma_night.ini";
    rc = SDK_SetMapViewColorsFromFile(mMapId, filenNameNight.toStdWString().c_str(), SDK_FALSE);
}

The color definitions are ini files with a [Colors] section and several color parameters. Colors are defined in RGB format, namely r,g,b, where r is red, g is green, and b is blue. r,g,b each take values in the range 0 to 255.

The [Colors] section can define the following parameters:

Parameter Description
COLMAPBACK color for the map background
COLSTREET0 color for the outside contour of a motorway
COLSTREET1 color for the outside contour of a national road
COLSTREET2 color for the outside contour of a motorway slip road
COLSTREET3 color for the outside contour of a country road
COLSTREET4 color for the outside contour for a fast urban road
COLSTREET5 color for the outside contour for a normal urban road
COLSTREET6 color for the outside contour of a ferry
COLPEDESTRIAN color for the outside contour of a pedestrian zone/residential area
COLSTREET0_INNER inside color for a motorway
COLSTREET1_INNER inside color for a national road
COLSTREET2_INNER inside color for a motorway slip road
COLSTREET3_INNER inside color for a country road
COLSTREET4_INNER inside color for a fast urban road
COLSTREET5_INNER inside color for a normal urban road
COLSTREET6_INNER inside color for a ferry
COLPEDESTRIAN_INNER inside color for a pedestrian zone/residential area
COLROUTEINNER inside color for a route trace
COLROUTEOUTER outside color for a route trace
COLROUTEINNER_TARGETAREA inside color for a route trace in a target area
COLROUTEOUTER_TARGETAREA outside color for a route trace in a target area
COLROUTEINNER_ECOTAXE inside colorfor a traffic truck toll on-route
COLROUTEOUTER_ECOTAXE outside colorfor a traffic truck toll on-route
COLROUTEINNER_TOLL inside colorfor a traffic toll on-route
COLROUTEOUTER_TOLL outside colorfor a traffic toll on-route
COLTMCINNER inside color for a traffic jam on-route
COLTMCOUTER outside color for a traffic jam on-route
COLTMCINNEROFFROUTE inside color for a traffic jam off-route
COLTMCOUTEROFFROUTE outside color for a traffic jam off-route
COLTRUCKERSEGMENTS color of segments that are restricted for trucks
COLMANOVERARROWBACK shadow color for a maneuver arrow
COLMANOVERARROWFORE color of a maneuver arrow
COLWATER color for a lake, river, see
COLURBAN color for the town area
COLINDUSTRY color for an industrial zone
COLWOOD color for a forrest
COLPARK color for a park
COLSKY color for the sky
COLHORIZON color of the horizont