C

Qt Quick Ultralite map example

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
import QtQuick
import QtPositioning
import QtLocation

Item {
    id: root

    property real zoomLevel: mapParameters.zoomLevel
    property real nextZoomLevel: root.zoomLevel + 1
    property real prevZoomLevel: root.zoomLevel - 1
    signal startDemoMode(bool paused)
    signal endDemoMode

    MapParameters {
        id: mapParameters
    }

    function zoom(zoomType: string) {
        if (zoomType === "In" && root.zoomLevel < map.maximumZoomLevel) {
            zoomInAnimation.stop()
            zoomInAnimation.start()
        } else if (zoomType === "Out" && root.zoomLevel > map.minimumZoomLevel) {
            zoomOutAnimation.stop()
            zoomOutAnimation.start()
        }
    }

    function incrementZoomLevel() {
        if (root.zoomLevel < map.maximumZoomLevel)
            root.zoomLevel += 1
    }

    function decrementZoomLevel() {
        if (root.zoomLevel > map.minimumZoomLevel)
            root.zoomLevel -= 1
    }

    function startDemo(paused: bool) {
        if (paused) {
            posSource.active = false
            demoStartPause.imageSource = "start-resume.png"
            demoStartPause.demoPaused = true
            return
        }

        // start/resume
        posSource.active = true
        demoStartPause.imageSource = "pause.png"
        demoStartPause.demoPaused = false
        demoStop.visible = true
    }

    function endDemo() {
        posSource.active = false
        IndexManager.setIndex(0)
        demoStartPause.imageSource = "start-resume.png"
        demoStartPause.demoPaused = true
        demoStop.visible = false
    }

    onStartDemoMode: startDemo(paused)
    onEndDemoMode: endDemo()

    PositionSource {
        id: posSource
        active: false
        updateInterval: 1000

        onPositionChanged: {
            map.center.latitude = posSource.position.coordinate.latitude
            map.center.longitude = posSource.position.coordinate.longitude
            bearingAnimation.start()
        }

        onSourceErrorChanged: {
            if (sourceError === PositionSource.ClosedError)
                endDemo()
        }
    }

    Map {
        id: map

        anchors.fill: parent
        center {
            latitude: mapParameters.latitude
            longitude: mapParameters.longitude
        }
        bearing: mapParameters.bearing
        zoomLevel: root.zoomLevel
        minimumZoomLevel: mapParameters.minimumZoomLevel
        maximumZoomLevel: mapParameters.maximumZoomLevel

        MapMarker {
            id: currentLocation

            imageSource: "nav-arrow.png"
            latitude: posSource.position.coordinate.latitude
            longitude: posSource.position.coordinate.longitude
            visible: !mapParameters.showButtons
        }

        MapMarker {
            id: poiQt

            imageSource: "location-marker.png"
            markerText: "The Qt Company"
            latitude: 65.05877
            longitude: 25.45545
            visible: !mapParameters.showButtons
        }

        MapMarker {
            id: poiMarket

            imageSource: "location-marker.png"
            markerText: "Supermarket"
            latitude: 65.054274
            longitude: 25.456213
            visible: !mapParameters.showButtons
        }

        MapMarker {
            id: poiStation

            imageSource: "location-marker.png"
            markerText: "Gas Station"
            latitude: 65.055253
            longitude: 25.456561
            visible: !mapParameters.showButtons
        }

        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)
            }
        }

        MapButton {
            id: zoomIn

            visible: mapParameters.showButtons
            width: root.width / 10
            height: width
            anchors.top: parent.top
            anchors.right: parent.right
            anchors.topMargin: 10
            anchors.rightMargin: 10
            imageSource: "plus.png"
            backgroundColor: "#e0e0e0"
            onButtonClicked: {
                if (zoomOutAnimation.running) {
                    zoomOutAnimation.stop()
                }

                zoom("In")
            }
        }

        MapButton {
            id: zoomOut

            visible: mapParameters.showButtons
            width: root.width / 10
            height: width
            anchors.top: zoomIn.bottom
            anchors.right: parent.right
            anchors.topMargin: 5
            anchors.rightMargin: 10
            imageSource: "minus.png"
            backgroundColor: "#e0e0e0"
            onButtonClicked: {
                if (zoomInAnimation.running) {
                    zoomInAnimation.stop()
                }

                zoom("Out")
            }
        }

        MapButton {
            id: rotateRight

            visible: mapParameters.showButtons
            width: root.width / 10
            height: width
            anchors.top: zoomOut.bottom
            anchors.right: parent.right
            anchors.topMargin: 5
            anchors.rightMargin: 10
            imageSource: "rotate-right.png"
            backgroundColor: "#e0e0e0"
            onButtonClicked: {
                map.bearing -= 5
            }
        }

        MapButton {
            id: rotateLeft

            visible: mapParameters.showButtons
            width: root.width / 10
            height: width
            anchors.top: zoomOut.bottom
            anchors.right: rotateRight.left
            anchors.topMargin: 5
            anchors.rightMargin: 10
            imageSource: "rotate-left.png"
            backgroundColor: "#e0e0e0"
            onButtonClicked: {
                map.bearing += 5
            }
        }

        MapButton {
            id: demoStartPause

            property bool demoPaused: true

            width: root.width / 10
            height: width
            anchors.top: parent.top
            anchors.right: zoomIn.left
            anchors.topMargin: 10
            anchors.rightMargin: 10
            imageSource: "start-resume.png"
            backgroundColor: "#e0e0e0"
            onButtonClicked: {
                startDemoMode(!demoPaused)
            }
        }

        MapButton {
            id: demoStop

            visible: false
            width: root.width / 10
            height: width
            anchors.top: parent.top
            anchors.left: demoStartPause.right
            anchors.topMargin: 10
            anchors.leftMargin: 10
            imageSource: "stop.png"
            backgroundColor: "#e0e0e0"
            onButtonClicked: {
                endDemoMode()
            }
            onVisibleChanged: {
                if (visible) {
                    // only on demo start not on resume
                    mapParameters.showButtons = false
                    root.zoomLevel = 17
                    map.minimumZoomLevel = 17
                    map.maximumZoomLevel = 17.9
                    demoZoomInTimer.start()
                } else {
                    mapParameters.showButtons = true
                    root.zoomLevel = mapParameters.zoomLevel
                    map.minimumZoomLevel = mapParameters.minimumZoomLevel
                    map.maximumZoomLevel = mapParameters.maximumZoomLevel
                    map.bearing = mapParameters.bearing
                }
            }
        }

        CopyrightText {
            id: copyrighttxt

            anchors.bottom: parent.bottom
            anchors.right: parent.right
            copyrightText: "© OpenStreetMap contributors"
            backgroundColor: "#e0e0e0"
        }
    }

    PropertyAnimation {
        id: zoomInAnimation
        target: map
        property: "zoomLevel"
        from: root.zoomLevel
        to: root.nextZoomLevel
        running: false
        alwaysRunToEnd: false
        duration: 500
        onStopped: incrementZoomLevel()
    }

    PropertyAnimation {
        id: zoomOutAnimation
        target: map
        property: "zoomLevel"
        from: root.zoomLevel
        to: root.prevZoomLevel
        running: false
        alwaysRunToEnd: false
        duration: 500
        onStopped: decrementZoomLevel()
    }

    PropertyAnimation {
        id: bearingAnimation
        target: map
        property: "bearing"
        from: map.bearing
        to: posSource.position.direction
        running: true
        duration: 2000
    }

    Timer {
        id: demoZoomInTimer
        interval: 2000
        running: false
        repeat: false
        onTriggered: zoomInAnimation.start()
    }
}