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 geoCoordinate prevCoordinate: QtPositioning.coordinate(65.05877,
                                                                    25.45545)
    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
        routeInfo.visible = true
        currentLocationAnimation.start()
    }

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

    onStartDemoMode: startDemo(paused)
    onEndDemoMode: endDemo()

    PositionSource {
        id: posSource
        active: false
        updateInterval: 1000

        onPositionChanged: {
            bearingAnimation.start()
            changeLocationAnimation.start()
        }

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

    Map {
        id: map

        anchors.fill: parent
        center: QtPositioning.coordinate(mapParameters.latitude,
                                         mapParameters.longitude)
        bearing: mapParameters.bearing
        zoomLevel: root.zoomLevel
        minimumZoomLevel: mapParameters.minimumZoomLevel
        maximumZoomLevel: mapParameters.maximumZoomLevel

        MapRoute {
            id: myroute

            property real distance: Math.round(myroute.route.distance / 1000)
            property int travelTime: Math.round(myroute.route.travelTime / 60)

            route: RouteService.routeData
            line.color: "#12834b"
            line.width: 5
        }

        MapMarker {
            id: currentLocation

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

        MapMarker {
            id: poiQt

            imageSource: "location-marker.png"
            markerText: "The Qt Company"
            coordinate: QtPositioning.coordinate(65.05877, 25.45545)
            visible: !mapParameters.showButtons
        }

        MapMarker {
            id: poiMarket

            imageSource: "location-marker.png"
            markerText: "Supermarket"
            coordinate: QtPositioning.coordinate(65.054274, 25.456213)
            visible: !mapParameters.showButtons
        }

        MapMarker {
            id: poiStation

            imageSource: "location-marker.png"
            markerText: "Gas Station"
            coordinate: QtPositioning.coordinate(65.055253, 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
                } else {
                    mapParameters.showButtons = true
                    root.zoomLevel = mapParameters.zoomLevel
                    map.minimumZoomLevel = mapParameters.minimumZoomLevel
                    map.maximumZoomLevel = mapParameters.maximumZoomLevel
                    map.bearing = mapParameters.bearing
                }
            }
        }

        MapText {
            id: copyrighttxt

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

        MapText {
            id: routeInfo

            visible: false
            anchors.top: parent.top
            anchors.left: parent.left
            text: "distance: %1 km \ntravel time: %2 mins".arg
                      (myroute.distance).arg(myroute.travelTime)
            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
    }

    CoordinateAnimation {
        id: currentLocationAnimation
        target: map
        property: "center"
        from: map.center
        to: currentLocation.coordinate
        duration: 2000
    }

    ParallelAnimation {
        id: changeLocationAnimation
        running: false

        CoordinateAnimation {
            target: map
            property: "center"
            from: prevCoordinate
            to: posSource.position.coordinate
        }

        CoordinateAnimation {
            target: currentLocation
            property: "coordinate"
            from: prevCoordinate
            to: posSource.position.coordinate
        }

        onStopped: {
            prevCoordinate = map.center
        }
    }
}