C

Qt Quick Ultralite map example

Demonstrates how to use the Map item to display a map in Qt Quick Ultralite.

Overview

This example demonstrates how to display a map in Qt Quick Ultralite application using the Map item.

The example provides map tiles for four zoom levels (from 0 to 3). To zoom in, select the button, and to zoom out, select the button. To rotate the map clockwise, at any zoom level, select the button. To rotate the map anticlockwise, select the button. You can also pan around by dragging the map.

The example implements Qul::MapTileFetcher to fetch the map tiles from a file system. The file system implementation is provided from the fileloading example. The map tiles are in JPEG format, which requires a JPEG decoder. The JPEG decoder implementation is provided from imagedecoder example.

Target platforms

Running the example on a device

To run the example on the target platform, you will need a FAT32 formatted SD Card. Copy the tiles/ directory, found in the example directory under offline_tiles_provider/, to the root directory of your SD Card.

Hardware requirements

A device that supports accelerated imagedecoding and accelerated rotation is cruicial for good performance, high frame rate and smooth user experience.

The RAM requirement varies depending on the size of the map. A full screen map on a STM32F769i device with screen size of 800 x 480 consumes 32 KB of heap and stack memory. For the same screen size, 3840 KB of SDRAM is required for caching the map tiles images. See Image Caching for more information.

Map tiles

The example includes four zoom levels of Web Mercator projected map tiles of the world. Each tile is a 256 x 256 pixels image in a JPEG format. The number of tiles in each zoom level is calculated using 2zoomlevel x 2zoomlevel.

The map tiles can be found in the example directory under offline_tiles_provider/tiles/ directory.

Fetching map tiles (Technical Preview)

The example fetches map tiles from an offline external storage.

OfflineTileFetcher class

The OfflineTileFetcher class is a reference implementation of the Qul::MapTileFetcher class. You can find the implementation in offline_tile_provider/offlinetilefetcher.h and offline_tile_provider/offlinetilefetcher.cpp files.

The Qul::MapTileFetcher class has a Qul::MapTileFetcher::getTileImage function that must be implemented. The OfflineTileFetcher class implements it as follows:

bool OfflineTileFetcher::getTileImage(const Qul::Private::TileSpec &spec, Qul::Private::TileImage &tileImage)
{
    // user has to configure url to point to tiles images root dir.
    QulString url = generateTileUrl(TILES_BASE_DIR, spec);
    if (url.empty()) {
        url = PLACEHOLDER_TILE;
        Qul::PlatformInterface::log("warning: tile url is empty. using placeholder tile at '%s'.\n", PLACEHOLDER_TILE);
    }

    const TileCacheMap::iterator it = tileCache.find(url);
    if (it != tileCache.end()) {
        tileImage.texture = it->second.texture();
        return true;
    }

    if (tileCache.size() >= maxCacheSize)
        removeOldestImage();

    Qul::SharedImage newTileImage = m_fileCache->get(url.c_str());
    if (!newTileImage && url != PLACEHOLDER_TILE) {
#ifndef NDEBUG
        Qul::PlatformInterface::log("warning: could not fetch a tile with url '%s'. using placeholder tile.\n",
                                    url.c_str());
#endif

        newTileImage = m_fileCache->get(PLACEHOLDER_TILE);

        if (!newTileImage) {
            Qul::PlatformInterface::log("error: could not fetch a placeholder tile from '%s'.\n", PLACEHOLDER_TILE);
            return false;
        }
    } else if (!newTileImage) {
        Qul::PlatformInterface::log("error: could not fetch a placeholder tile from '%s'.\n", PLACEHOLDER_TILE);
        return false;
    }

    tileImage.texture = newTileImage.texture();
    tileCache[url] = newTileImage;
    cacheOrder.push_back(url);

    return true;
}

The OfflineTileFetcher class implements a member function generateTileUrl to compose the URI of the map tile using spec parameter and TILES_BASE_DIR defined in the CMakeLists.txt file of the example.

OfflineTileFetcher::QulString OfflineTileFetcher::generateTileUrl(const char *baseDir,
                                                                  const Qul::Private::TileSpec &spec) const
{
    const int size = std::snprintf(nullptr, 0, "%s%d/%d/%d.jpeg", baseDir, spec.zoom, spec.x, spec.y) + 1;
    if (size <= 0) {
        Qul::PlatformInterface::log("error: failed to calculate the required buffer size for the tile url.");
        return QulString();
    }

    char *buffer = static_cast<char *>(Qul::Platform::qul_malloc(sizeof(char) * size));
    if (!buffer) {
        Qul::PlatformInterface::log("error: failed to allocate memory for the tile url.");
        return QulString();
    }

    std::snprintf(buffer, size, "%s%d/%d/%d.jpeg", baseDir, spec.zoom, spec.x, spec.y);

    QulString url(buffer);

    Qul::Platform::qul_free(buffer);

    return url;
}

The OfflineTileFetcher class uses FileCache::get to access, decode and cache the decoded map tile image into RAM. FileCache::get returns the Qul::SharedImage object.

    ...
    Qul::SharedImage newTileImage = m_fileCache->get(url.c_str());
    ...

OfflineTileFetcher implements a cache facility to store an already accessed map tiles.

    ...
    tileCache[url] = newTileImage;
    ...

Caching the accessed map tiles keeps a reference to Qul::SharedImage and returns the already decoded tile image if it was found in the RAM cache.

    ...
    const TileCacheMap::iterator it = tileCache.find(url);
    if (it != tileCache.end()) {
        tileImage.texture = it->second.texture();
        return true;
    }
    ...

OfflineTileFetcher uses MAX_CACHE_SIZE defined in CMakeLists.txt to set the maxCacheSize, after which the oldest cache entries are removed.

OfflineTileFetcher::OfflineTileFetcher()
    ...
    , maxCacheSize(MAX_CACHE_SIZE)
{}

void OfflineTileFetcher::removeOldestImage()
{
    for (int i = 0; i < MAX_CACHE_SIZE / 5 && !cacheOrder.empty(); ++i) {
        const QulString oldestUrl = cacheOrder.front();
        cacheOrder.pop_front();
        tileCache.erase(oldestUrl);
    }
}

Project structure

Cmake project file

The CMakeLists.txt contains configurations for both desktop and STM32F769i platforms.

CMake compile definitions
  • TILES_BASE_DIR which points to the root directory where the map tiles are.
  • PLACEHOLDER_TILE which points to a placeholder image in case the user pans outside of the generated map area.
  • MAX_CACHE_SIZE which sets the size after which old cache entries are deleted.
Desktop configuration

The desktop configuration depends on the POSIX file system implementation of the fileloading example to access the images of the map tiles and desktop JPEG imagedecoder implementation of the imagedecoder example to decode the images of the map tiles.

    target_sources(map PRIVATE
    ...
                          ../imagedecoder/desktop/desktopimagedecoder.cpp
                          ../fileloading/posix/posixfilesystem.cpp
                      )
STM32F769i configuration

The platform configuration depends on the FATFS file system implementation of the fileloading example to access the images of the map tiles in the SD Card, and the hardware JPEG imagedecoder of the imagedecoder example to decode the images of the map tiles.

    # file system sources
    target_sources(map PRIVATE
            ../fileloading/3rdparty/FatFs/src/diskio.c
            ../fileloading/3rdparty/FatFs/src/ff.c
            ../fileloading/3rdparty/FatFs/src/ff_gen_drv.c
            ../fileloading/3rdparty/FatFs/src/sd_diskio.c
            ../fileloading/3rdparty/FatFs/src/option/unicode.c
            ${QUL_BOARD_SDK_DIR}/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_ll_sdmmc.c
            ${QUL_BOARD_SDK_DIR}/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_sd.c
            ${QUL_BOARD_SDK_DIR}/Drivers/BSP/STM32F769I-Discovery/stm32f769i_discovery_sd.c
        )
    # jpeg decoder sources
    target_sources(map PRIVATE
            ${QUL_BOARD_SDK_DIR}/Drivers/STM32F7xx_HAL_Driver/Src/stm32f7xx_hal_jpeg.c
            ../imagedecoder/stm/${STM32XX}/stm32f7xx_hal_msp.c
            ../imagedecoder/stm/${STM32XX}/buffer_config.cpp
            ../imagedecoder/stm/stmimagedecoder.cpp
            ../imagedecoder/common/jpeg.cpp
            ../imagedecoder/3rdparty/stm/Utilities/JPEG/jpeg_utils.c
        )
QmlProject file

The example QmlProject file lists the required QML files, image files and Qt Quick Ultralite modules. It also sets MCU.Config.maxResourceCacheSize to a value that works on the target platform.

import QmlProject

Project {
    mainFile: "map.qml"

    QmlFiles {
        files: [
            "MapButton.qml",
            "CopyrightText.qml"
        ]
    }

    ModuleFiles {
        MCU.qulModules: ["Positioning", "Location"]
    }

    MCU.Config {
        maxResourceCacheSize: 4000000
    }

    ImageFiles {
        MCU.base: "icons/24px"
        files: [
            "icons/24px/plus.png",
            "icons/24px/minus.png",
            "icons/24px/rotate-right.png",
            "icons/24px/rotate-left.png"
        ]
    }
}
map.qml

The main QML file uses the Map item and sets the center of the map to Oulu city, Finland. It sets the initial zoomLevel of the map to 3, the minimumZoomLevel to 0, and the maximumZoomLevel to 3.

    Map {
        id: map

        anchors.fill: parent
        center {
            latitude: 65.044334
            longitude: 25.692558
        }
        zoomLevel: root.zoomLevel
        minimumZoomLevel: 0
        maximumZoomLevel: 3
    ...

It defines the MouseArea that is responsible for calculating the deltaX and deltaY parameters of the pan function.

        MouseArea {
            id: mapPan

            anchors.fill: parent

            property real pressPointX: 0
            property real pressPointY: 0
            property real translationX: 0
            property real translationY: 0

            onPressed: {
                pressPointX = mouse.x
                pressPointY = mouse.y
                translationX = 0
                translationY = 0
            }
            onPositionChanged: {
                var x = mouse.x - pressPointX
                var y = mouse.y - pressPointY

                var deltaX = x - translationX
                var deltaY = y - translationY
                translationX = x
                translationY = y

                map.pan(-deltaX, -deltaY)
            }
        }

Files:

Images:

Available under certain Qt licenses.
Find out more.