Map Viewer Example¶
The Map Viewer example shows how to display and interact with a map, search for an address, and find driving directions.
This is a large example covering many basic uses of maps, positioning, and navigation services in Qt Location.
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
"""PySide6 port of the location/mapviewer example from Qt v6.x"""
import os
import sys
from pathlib import Path
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtGui import QGuiApplication
from PySide6.QtNetwork import QSslSocket
from PySide6.QtCore import QCoreApplication, QMetaObject, Q_ARG
HELP = """Usage:
plugin.<parameter_name> <parameter_value> - Sets parameter = value for plugin"""
def parseArgs(args):
parameters = {}
while args:
param = args[0]
args = args[1:]
if param.startswith("--plugin."):
param = param[9:]
if not args or args[0].startswith("--"):
parameters[param] = True
else:
value = args[0]
args = args[1:]
if value in ("true", "on", "enabled"):
parameters[param] = True
elif value in ("false", "off", "disable"):
parameters[param] = False
else:
parameters[param] = value
return parameters
if __name__ == "__main__":
additionalLibraryPaths = os.environ.get("QTLOCATION_EXTRA_LIBRARY_PATH")
if additionalLibraryPaths:
for p in additionalLibraryPaths.split(':'):
QCoreApplication.addLibraryPath(p)
application = QGuiApplication(sys.argv)
name = "QtLocation Mapviewer example"
QCoreApplication.setApplicationName(name)
args = sys.argv[1:]
if "--help" in args:
print(f"{name}\n\n{HELP}")
sys.exit(0)
parameters = parseArgs(args)
if not parameters.get("osm.useragent"):
parameters["osm.useragent"] = name
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("supportsSsl",
QSslSocket.supportsSsl())
engine.addImportPath(Path(__file__).parent)
engine.loadFromModule("MapViewer", "Main")
engine.quit.connect(QCoreApplication.quit)
items = engine.rootObjects()
if not items:
sys.exit(-1)
QMetaObject.invokeMethod(items[0], "initializeProviders",
Q_ARG("QVariant", parameters))
ex = application.exec()
del engine
sys.exit(ex)
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtPositioning
GeocodeForm {
property variant address
signal showPlace(variant address)
signal closeForm()
goButton.onClicked: {
// fill out the Address element
address.street = street.text
address.city = city.text
address.state = stateName.text
address.country = country.text
address.postalCode = postalCode.text
showPlace(address)
}
clearButton.onClicked: {
street.text = ""
city.text = ""
stateName.text = ""
country.text = ""
postalCode.text = ""
}
cancelButton.onClicked: {
closeForm()
}
Component.onCompleted: {
street.text = address.street
city.text = address.city
stateName.text = address.state
country.text = address.country
postalCode.text = address.postalCode
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
property alias goButton: goButton
property alias clearButton: clearButton
property alias postalCode: postalCode
property alias street: street
property alias city: city
property alias stateName: stateName
property alias country: country
property alias cancelButton: cancelButton
Rectangle {
id: tabRectangle
y: 20
height: tabTitle.height * 2
color: "#46a2da"
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.left: parent.left
anchors.right: parent.right
Label {
id: tabTitle
color: "#ffffff"
text: qsTr("Geocode")
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
Item {
id: item2
anchors.rightMargin: 20
anchors.leftMargin: 20
anchors.bottomMargin: 20
anchors.topMargin: 20
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: tabRectangle.bottom
GridLayout {
id: gridLayout3
anchors.rightMargin: 0
anchors.bottomMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 0
rowSpacing: 10
rows: 1
columns: 2
anchors.fill: parent
Label {
id: label2
text: qsTr("Street")
}
TextField {
id: street
Layout.fillWidth: true
}
Label {
id: label3
text: qsTr("City")
}
TextField {
id: city
Layout.fillWidth: true
}
Label {
id: label4
text: qsTr("State")
}
TextField {
id: stateName
Layout.fillWidth: true
}
Label {
id: label5
text: qsTr("Country")
}
TextField {
id: country
Layout.fillWidth: true
}
Label {
id: label6
text: qsTr("Postal Code")
}
TextField {
id: postalCode
Layout.fillWidth: true
}
RowLayout {
id: rowLayout1
Layout.columnSpan: 2
Layout.alignment: Qt.AlignRight
Button {
id: goButton
text: qsTr("Proceed")
}
Button {
id: clearButton
text: qsTr("Clear")
}
Button {
id: cancelButton
text: qsTr("Cancel")
}
}
Item {
Layout.fillHeight: true
Layout.columnSpan: 2
}
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtPositioning
LocaleForm {
property string locale
signal selectLanguage(string language)
signal closeForm()
goButton.onClicked: {
if (!languageGroup.checkedButton) return
if (otherRadioButton.checked) {
selectLanguage(language.text)
} else {
selectLanguage(languageGroup.checkedButton.text)
}
}
clearButton.onClicked: {
language.text = ""
}
cancelButton.onClicked: {
closeForm()
}
Component.onCompleted: {
switch (locale) {
case "en":
enRadioButton.checked = true;
break
case "fr":
frRadioButton.checked = true;
break
default:
otherRadioButton.checked = true;
language.text = locale
break
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
property alias clearButton: clearButton
property alias goButton: goButton
property alias cancelButton: cancelButton
property alias tabTitle: tabTitle
property alias languageGroup: languageGroup
property alias enRadioButton: enRadioButton
property alias frRadioButton: frRadioButton
property alias otherRadioButton: otherRadioButton
property alias language: language
Rectangle {
id: tabRectangle
y: 20
height: tabTitle.height * 2
color: "#46a2da"
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.left: parent.left
anchors.right: parent.right
Label {
id: tabTitle
color: "#ffffff"
text: "Locale"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
Item {
id: item2
anchors.rightMargin: 20
anchors.leftMargin: 20
anchors.bottomMargin: 20
anchors.topMargin: 20
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: tabRectangle.bottom
GridLayout {
id: gridLayout3
anchors.rightMargin: 0
anchors.bottomMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 0
rowSpacing: 10
rows: 1
columns: 2
anchors.fill: parent
ButtonGroup { id: languageGroup }
RadioButton {
id: enRadioButton
text: qsTr("en")
ButtonGroup.group: languageGroup
Layout.columnSpan: 2
}
RadioButton {
id: frRadioButton
text: qsTr("fr")
ButtonGroup.group: languageGroup
Layout.columnSpan: 2
}
RadioButton {
id: otherRadioButton
text: qsTr("Other")
ButtonGroup.group: languageGroup
}
TextField {
id: language
Layout.fillWidth: true
placeholderText: qsTr("")
}
RowLayout {
id: rowLayout1
Layout.columnSpan: 2
Layout.alignment: Qt.AlignRight
Button {
id: goButton
text: qsTr("Proceed")
}
Button {
id: clearButton
text: qsTr("Clear")
}
Button {
id: cancelButton
text: qsTr("Cancel")
}
}
Item {
Layout.fillHeight: true
Layout.columnSpan: 2
}
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
MessageForm {
property string title
property string message
property variant backPage
signal closeForm(variant backPage)
button.onClicked: {
closeForm(backPage)
}
Component.onCompleted: {
messageText.text = message
messageTitle.text = title
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
property alias messageText: messageText
property alias messageTitle: messageTitle
property alias button: button
Rectangle {
id: tabRectangle
y: 20
height: messageTitle.height * 2
color: "#46a2da"
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.left: parent.left
anchors.right: parent.right
Label {
id: messageTitle
color: "#ffffff"
text: qsTr("type")
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
Item {
anchors.rightMargin: 20
anchors.leftMargin: 20
anchors.bottomMargin: 20
anchors.topMargin: 20
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: tabRectangle.bottom
ColumnLayout {
id: columnLayout1
spacing: 20
anchors.fill: parent
Label {
id: messageText
text: qsTr("message")
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
textFormat: Text.RichText
}
Button {
id: button
text: qsTr("OK")
Layout.alignment: Qt.AlignHCenter
}
Item {
Layout.fillHeight: true
}
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtPositioning
//Reverse Geocode Dialog
ReverseGeocodeForm {
property string title;
property variant coordinate
signal showPlace(variant coordinate)
signal closeForm()
goButton.onClicked: {
var coordinate = QtPositioning.coordinate(parseFloat(latitude.text),
parseFloat(longitude.text));
if (coordinate.isValid) {
showPlace(coordinate)
}
}
clearButton.onClicked: {
latitude.text = ""
longitude.text = ""
}
cancelButton.onClicked: {
closeForm()
}
Component.onCompleted: {
latitude.text = "" + coordinate.latitude
longitude.text = "" + coordinate.longitude
if (title.length != 0) {
tabTitle.text = title;
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
property alias clearButton: clearButton
property alias goButton: goButton
property alias longitude: longitude
property alias latitude: latitude
property alias cancelButton: cancelButton
property alias tabTitle: tabTitle
Rectangle {
id: tabRectangle
y: 20
height: tabTitle.height * 2
color: "#46a2da"
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.left: parent.left
anchors.right: parent.right
Label {
id: tabTitle
color: "#ffffff"
text: qsTr("Reverse Geocode")
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
Item {
id: item2
anchors.rightMargin: 20
anchors.leftMargin: 20
anchors.bottomMargin: 20
anchors.topMargin: 20
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: tabRectangle.bottom
GridLayout {
id: gridLayout3
anchors.rightMargin: 0
anchors.bottomMargin: 0
anchors.leftMargin: 0
anchors.topMargin: 0
rowSpacing: 10
rows: 1
columns: 2
anchors.fill: parent
Label {
id: label2
text: qsTr("Latitude")
}
TextField {
id: latitude
Layout.fillWidth: true
}
Label {
id: label3
text: qsTr("Longitude")
}
TextField {
id: longitude
Layout.fillWidth: true
placeholderText: qsTr("")
}
RowLayout {
id: rowLayout1
Layout.columnSpan: 2
Layout.alignment: Qt.AlignRight
Button {
id: goButton
text: qsTr("Proceed")
}
Button {
id: clearButton
text: qsTr("Clear")
}
Button {
id: cancelButton
text: qsTr("Cancel")
}
}
Item {
Layout.fillHeight: true
Layout.columnSpan: 2
}
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtLocation
import QtPositioning
RouteAddressForm {
property alias plugin : tempGeocodeModel.plugin;
property variant fromAddress;
property variant toAddress;
signal showMessage(string topic, string message)
signal showRoute(variant startCoordinate,variant endCoordinate)
signal closeForm()
goButton.onClicked: {
tempGeocodeModel.reset()
fromAddress.country = fromCountry.text
fromAddress.street = fromStreet.text
fromAddress.city = fromCity.text
toAddress.country = toCountry.text
toAddress.street = toStreet.text
toAddress.city = toCity.text
tempGeocodeModel.startCoordinate = QtPositioning.coordinate()
tempGeocodeModel.endCoordinate = QtPositioning.coordinate()
tempGeocodeModel.query = fromAddress
tempGeocodeModel.update();
goButton.enabled = false;
}
clearButton.onClicked: {
fromStreet.text = ""
fromCity.text = ""
fromCountry.text = ""
toStreet.text = ""
toCity.text = ""
toCountry.text = ""
}
cancelButton.onClicked: {
closeForm()
}
Component.onCompleted: {
fromStreet.text = fromAddress.street
fromCity.text = fromAddress.city
fromCountry.text = fromAddress.country
toStreet.text = toAddress.street
toCity.text = toAddress.city
toCountry.text = toAddress.country
}
GeocodeModel {
id: tempGeocodeModel
property int success: 0
property variant startCoordinate
property variant endCoordinate
onCountChanged: {
if (success == 1 && count == 1) {
query = toAddress
update();
}
}
onStatusChanged: {
if ((status == GeocodeModel.Ready) && (count == 1)) {
success++
if (success == 1) {
startCoordinate.latitude = get(0).coordinate.latitude
startCoordinate.longitude = get(0).coordinate.longitude
}
if (success == 2) {
endCoordinate.latitude = get(0).coordinate.latitude
endCoordinate.longitude = get(0).coordinate.longitude
success = 0
if (startCoordinate.isValid && endCoordinate.isValid)
showRoute(startCoordinate,endCoordinate)
else
goButton.enabled = true
}
} else if ((status == GeocodeModel.Ready) || (status == GeocodeModel.Error)) {
var st = (success == 0 ) ? "start" : "end"
success = 0
if ((status == GeocodeModel.Ready) && (count == 0 )) {
showMessage(qsTr("Geocode Error"),qsTr("Unsuccessful geocode"));
goButton.enabled = true;
}
else if (status == GeocodeModel.Error) {
showMessage(qsTr("Geocode Error"),
qsTr("Unable to find location for the") + " " +
st + " " +qsTr("point"))
goButton.enabled = true;
}
else if ((status == GeocodeModel.Ready) && (count > 1 )) {
showMessage(qsTr("Ambiguous geocode"),
count + " " + qsTr("results found for the") +
" " + st + " " +qsTr("point, please specify location"))
goButton.enabled = true;
}
}
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
property alias fromStreet: fromStreet
property alias fromCountry: fromCountry
property alias toStreet: toStreet
property alias toCity: toCity
property alias toCountry: toCountry
property alias fromCity: fromCity
property alias goButton: goButton
property alias clearButton: clearButton
property alias cancelButton: cancelButton
Rectangle {
id: tabRectangle
y: 20
height: tabTitle.height * 2
color: "#46a2da"
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.left: parent.left
anchors.right: parent.right
Label {
id: tabTitle
color: "#ffffff"
text: qsTr("Route Address")
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
Item {
id: item2
anchors.rightMargin: 20
anchors.leftMargin: 20
anchors.bottomMargin: 20
anchors.topMargin: 20
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: tabRectangle.bottom
GridLayout {
id: gridLayout3
rowSpacing: 10
rows: 1
columns: 2
anchors.fill: parent
Label {
id: label1
text: qsTr("From")
font.bold: true
Layout.columnSpan: 2
Layout.alignment: Qt.AlignHCenter
}
Label {
id: label2
text: qsTr("Street")
}
TextField {
id: fromStreet
Layout.fillWidth: true
}
Label {
id: label3
text: qsTr("City")
}
TextField {
id: fromCity
Layout.fillWidth: true
}
Label {
id: label7
text: qsTr("Country")
}
TextField {
id: fromCountry
Layout.fillWidth: true
}
Label {
id: label6
text: qsTr("To")
font.bold: true
Layout.columnSpan: 2
Layout.alignment: Qt.AlignHCenter
}
Label {
id: label4
text: qsTr("Street")
}
TextField {
id: toStreet
Layout.fillWidth: true
}
Label {
id: label5
text: qsTr("City")
}
TextField {
id: toCity
Layout.fillWidth: true
}
Label {
id: label8
text: qsTr("Country")
}
TextField {
id: toCountry
Layout.fillWidth: true
}
RowLayout {
id: rowLayout1
Layout.columnSpan: 2
Layout.alignment: Qt.AlignRight
Button {
id: goButton
text: qsTr("Proceed")
}
Button {
id: clearButton
text: qsTr("Clear")
}
Button {
id: cancelButton
text: qsTr("Cancel")
}
}
Item {
Layout.fillHeight: true
Layout.columnSpan: 2
}
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtPositioning
RouteCoordinateForm {
property variant toCoordinate
property variant fromCoordinate
signal showRoute(variant startCoordinate,variant endCoordinate)
signal closeForm()
goButton.onClicked: {
var startCoordinate = QtPositioning.coordinate(parseFloat(fromLatitude.text),
parseFloat(fromLongitude.text));
var endCoordinate = QtPositioning.coordinate(parseFloat(toLatitude.text),
parseFloat(toLongitude.text));
if (startCoordinate.isValid && endCoordinate.isValid) {
goButton.enabled = false;
showRoute(startCoordinate,endCoordinate)
}
}
clearButton.onClicked: {
fromLatitude.text = ""
fromLongitude.text = ""
toLatitude.text = ""
toLongitude.text = ""
}
cancelButton.onClicked: {
closeForm()
}
Component.onCompleted: {
fromLatitude.text = "" + fromCoordinate.latitude
fromLongitude.text = "" + fromCoordinate.longitude
toLatitude.text = "" + toCoordinate.latitude
toLongitude.text = "" + toCoordinate.longitude
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
property alias fromLatitude: fromLatitude
property alias fromLongitude: fromLongitude
property alias toLatitude: toLatitude
property alias toLongitude: toLongitude
property alias clearButton: clearButton
property alias goButton: goButton
property alias cancelButton: cancelButton
Rectangle {
id: tabRectangle
y: 20
height: tabTitle.height * 2
color: "#46a2da"
anchors.rightMargin: 0
anchors.leftMargin: 0
anchors.left: parent.left
anchors.right: parent.right
Label {
id: tabTitle
color: "#ffffff"
text: qsTr("Route Coordinates")
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
}
Item {
id: item2
anchors.rightMargin: 20
anchors.leftMargin: 20
anchors.bottomMargin: 20
anchors.topMargin: 20
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: tabRectangle.bottom
GridLayout {
id: gridLayout3
rowSpacing: 10
rows: 1
columns: 2
anchors.fill: parent
Label {
id: label1
text: qsTr("From")
font.bold: true
Layout.columnSpan: 2
Layout.alignment: Qt.AlignHCenter
}
Label {
id: label2
text: qsTr("Latitude")
}
TextField {
id: fromLatitude
Layout.fillWidth: true
}
Label {
id: label3
text: qsTr("Longitude")
}
TextField {
id: fromLongitude
Layout.fillWidth: true
}
Label {
id: label6
text: qsTr("To")
font.bold: true
Layout.columnSpan: 2
Layout.alignment: Qt.AlignHCenter
}
Label {
id: label4
text: qsTr("Latitude")
}
TextField {
id: toLatitude
Layout.fillWidth: true
}
Label {
id: label5
text: qsTr("Longitude")
}
TextField {
id: toLongitude
Layout.fillWidth: true
}
RowLayout {
id: rowLayout1
Layout.columnSpan: 2
Layout.alignment: Qt.AlignRight
Button {
id: goButton
text: qsTr("Proceed")
}
Button {
id: clearButton
text: qsTr("Clear")
}
Button {
id: cancelButton
text: qsTr("Cancel")
}
}
Item {
Layout.fillHeight: true
Layout.columnSpan: 2
}
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import "../helper.js" as Helper
//! [routeinfomodel0]
ListView {
//! [routeinfomodel0]
property variant routeModel
property string totalTravelTime
property string totalDistance
signal closeForm()
//! [routeinfomodel1]
interactive: true
model: ListModel { id: routeInfoModel }
header: RouteListHeader {}
delegate: RouteListDelegate{
routeIndex.text: index + 1
routeInstruction.text: instruction
routeDistance.text: distance
}
//! [routeinfomodel1]
footer: Button {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Close")
onClicked: {
closeForm()
}
}
Component.onCompleted: {
//! [routeinfomodel2]
routeInfoModel.clear()
if (routeModel.count > 0) {
for (var i = 0; i < routeModel.get(0).segments.length; i++) {
routeInfoModel.append({
"instruction": routeModel.get(0).segments[i].maneuver.instructionText,
"distance": Helper.formatDistance(routeModel.get(0).segments[i].maneuver.distanceToNextInstruction)
});
}
}
//! [routeinfomodel2]
totalTravelTime = routeModel.count == 0 ? "" : Helper.formatTime(routeModel.get(0).travelTime)
totalDistance = routeModel.count == 0 ? "" : Helper.formatDistance(routeModel.get(0).distance)
}
//! [routeinfomodel3]
}
//! [routeinfomodel3]
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
property bool checked: false
property alias routeInstruction: instructionLabel
property alias routeDistance: distanceLabel
property alias routeIndex: indexLabel
width: appWindow.width
height: indexLabel.height * 2
RowLayout {
spacing: 10
anchors.left: parent.left
anchors.leftMargin: 30
anchors.verticalCenter: parent.verticalCenter
Label {
id: indexLabel
}
Label {
id: instructionLabel
wrapMode: Text.Wrap
}
Label {
id: distanceLabel
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 15
height: 1
color: "#46a2da"
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
Item {
property alias travelTime: travelTimeLabel
property alias distance: distanceLabel
width: parent.width
height: tabTitle.height * 3.0
Rectangle {
id: tabRectangle
y: tabTitle.height
height: tabTitle.height * 2 - 1
color: "#46a2da"
anchors.left: parent.left
anchors.right: parent.right
Label {
id: tabTitle
color: "#ffffff"
text: qsTr("Route Information")
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Label {
id: travelTimeLabel
text: totalTravelTime
color: "#ffffff"
font.bold: true
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
Label {
id: distanceLabel
text: totalDistance
color: "#ffffff"
font.bold: true
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtLocation
import QtPositioning
import "../helper.js" as Helper
//! [top]
MapView {
id: view
//! [top]
property variant markers
property variant mapItems
property int markerCounter: 0 // counter for total amount of markers. Resets to 0 when number of markers = 0
property int currentMarker
property bool followme: false
property variant scaleLengths: [5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000]
property alias routeQuery: routeQuery
property alias routeModel: routeModel
property alias geocodeModel: geocodeModel
property alias slidersExpanded: sliders.expanded
signal showGeocodeInfo()
signal geocodeFinished()
signal routeError()
signal coordinatesCaptured(double latitude, double longitude)
signal showMainMenu(variant coordinate)
signal showMarkerMenu(variant coordinate)
signal showRouteMenu(variant coordinate)
signal showPointMenu(variant coordinate)
signal showRouteList()
function geocodeMessage()
{
var street, district, city, county, state, countryCode, country, postalCode, latitude, longitude, text
latitude = Math.round(geocodeModel.get(0).coordinate.latitude * 10000) / 10000
longitude =Math.round(geocodeModel.get(0).coordinate.longitude * 10000) / 10000
street = geocodeModel.get(0).address.street
district = geocodeModel.get(0).address.district
city = geocodeModel.get(0).address.city
county = geocodeModel.get(0).address.county
state = geocodeModel.get(0).address.state
countryCode = geocodeModel.get(0).address.countryCode
country = geocodeModel.get(0).address.country
postalCode = geocodeModel.get(0).address.postalCode
text = "<b>Latitude:</b> " + latitude + "<br/>"
text +="<b>Longitude:</b> " + longitude + "<br/>" + "<br/>"
if (street) text +="<b>Street: </b>"+ street + " <br/>"
if (district) text +="<b>District: </b>"+ district +" <br/>"
if (city) text +="<b>City: </b>"+ city + " <br/>"
if (county) text +="<b>County: </b>"+ county + " <br/>"
if (state) text +="<b>State: </b>"+ state + " <br/>"
if (countryCode) text +="<b>Country code: </b>"+ countryCode + " <br/>"
if (country) text +="<b>Country: </b>"+ country + " <br/>"
if (postalCode) text +="<b>PostalCode: </b>"+ postalCode + " <br/>"
return text
}
function calculateScale()
{
var coord1, coord2, dist, text, f
f = 0
coord1 = view.map.toCoordinate(Qt.point(0,scale.y))
coord2 = view.map.toCoordinate(Qt.point(0+scaleImage.sourceSize.width,scale.y))
dist = Math.round(coord1.distanceTo(coord2))
if (dist === 0) {
// not visible
} else {
for (var i = 0; i < scaleLengths.length-1; i++) {
if (dist < (scaleLengths[i] + scaleLengths[i+1]) / 2 ) {
f = scaleLengths[i] / dist
dist = scaleLengths[i]
break;
}
}
if (f === 0) {
f = dist / scaleLengths[i]
dist = scaleLengths[i]
}
}
text = Helper.formatDistance(dist)
scaleImage.width = (scaleImage.sourceSize.width * f) - 2 * scaleImageLeft.sourceSize.width
scaleText.text = text
}
function deleteMarkers()
{
var count = view.markers.length
for (var i = count-1; i>=0; i--){
view.map.removeMapItem(view.markers[i])
}
view.markers = []
}
function addMarker()
{
var count = view.markers.length
markerCounter++
var marker = Qt.createQmlObject ('Marker {}', map)
view.map.addMapItem(marker)
marker.z = view.map.z+1
marker.coordinate = tapHandler.lastCoordinate
markers.push(marker)
}
function deleteMarker(index)
{
//update list of markers
var myArray = []
var count = view.markers.length
for (var i = 0; i<count; i++){
if (index !== i) myArray.push(view.markers[i])
}
view.map.removeMapItem(view.markers[index])
view.markers[index].destroy()
view.markers = myArray
if (markers.length === 0) markerCounter = 0
}
function calculateMarkerRoute()
{
routeQuery.clearWaypoints();
for (var i = currentMarker; i< view.markers.length; i++){
routeQuery.addWaypoint(markers[i].coordinate)
}
routeQuery.travelModes = RouteQuery.CarTravel
routeQuery.routeOptimizations = RouteQuery.ShortestRoute
routeModel.update();
}
function calculateCoordinateRoute(startCoordinate, endCoordinate)
{
//! [routerequest0]
// clear away any old data in the query
routeQuery.clearWaypoints();
// add the start and end coords as waypoints on the route
routeQuery.addWaypoint(startCoordinate)
routeQuery.addWaypoint(endCoordinate)
routeQuery.travelModes = RouteQuery.CarTravel
routeQuery.routeOptimizations = RouteQuery.FastestRoute
//! [routerequest0]
//! [routerequest1]
routeModel.update();
//! [routerequest1]
//! [routerequest2]
// center the map on the start coord
view.map.center = startCoordinate;
//! [routerequest2]
}
function geocode(fromAddress)
{
//! [geocode1]
// send the geocode request
geocodeModel.query = fromAddress
geocodeModel.update()
//! [geocode1]
}
//! [coord]
map.zoomLevel: (maximumZoomLevel - minimumZoomLevel)/2
map.center {
// The Qt Company in Oslo
latitude: 59.9485
longitude: 10.7686
}
//! [coord]
focus: true
map.onCopyrightLinkActivated: Qt.openUrlExternally(link)
map.onCenterChanged:{
scaleTimer.restart()
if (view.followme)
if (view.map.center != positionSource.position.coordinate) view.followme = false
}
map.onZoomLevelChanged:{
scaleTimer.restart()
if (view.followme) view.map.center = positionSource.position.coordinate
}
onWidthChanged:{
scaleTimer.restart()
}
onHeightChanged:{
scaleTimer.restart()
}
Component.onCompleted: {
markers = [];
mapItems = [];
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Plus) {
view.map.zoomLevel++;
} else if (event.key === Qt.Key_Minus) {
view.map.zoomLevel--;
} else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right ||
event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
var dx = 0;
var dy = 0;
switch (event.key) {
case Qt.Key_Left: dx = view.map.width / 4; break;
case Qt.Key_Right: dx = -view.map.width / 4; break;
case Qt.Key_Up: dy = view.map.height / 4; break;
case Qt.Key_Down: dy = -view.map.height / 4; break;
}
var mapCenterPoint = Qt.point(view.map.width / 2.0 - dx, view.map.height / 2.0 - dy);
view.map.center = view.map.toCoordinate(mapCenterPoint);
}
}
PositionSource{
id: positionSource
active: followme
onPositionChanged: {
view.map.center = positionSource.position.coordinate
}
}
MapQuickItem {
id: mePoisition
parent: view.map
sourceItem: Rectangle { width: 14; height: 14; color: "#251ee4"; border.width: 2; border.color: "white"; smooth: true; radius: 7 }
coordinate: positionSource.position.coordinate
opacity: 1.0
anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2)
visible: followme
}
MapQuickItem {
parent: view.map
sourceItem: Text{
text: qsTr("You're here!")
color:"#242424"
font.bold: true
styleColor: "#ECECEC"
style: Text.Outline
}
coordinate: positionSource.position.coordinate
anchorPoint: Qt.point(-mePoisition.sourceItem.width * 0.5, mePoisition.sourceItem.height * 1.5)
visible: followme
}
MapQuickItem {
id: poiTheQtComapny
parent: view.map
sourceItem: Rectangle { width: 14; height: 14; color: "#e41e25"; border.width: 2; border.color: "white"; smooth: true; radius: 7 }
coordinate {
latitude: 59.9485
longitude: 10.7686
}
opacity: 1.0
anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2)
}
MapQuickItem {
parent: view.map
sourceItem: Text{
text: "The Qt Company"
color:"#242424"
font.bold: true
styleColor: "#ECECEC"
style: Text.Outline
}
coordinate: poiTheQtComapny.coordinate
anchorPoint: Qt.point(-poiTheQtComapny.sourceItem.width * 0.5, poiTheQtComapny.sourceItem.height * 1.5)
}
MapSliders {
id: sliders
z: view.map.z + 3
mapSource: map
edge: Qt.LeftEdge
}
Item {
id: scale
z: view.map.z + 3
visible: scaleText.text !== "0 m"
anchors.bottom: parent.bottom;
anchors.right: parent.right
anchors.margins: 20
height: scaleText.height * 2
width: scaleImage.width
Image {
id: scaleImageLeft
source: "../resources/scale_end.png"
anchors.bottom: parent.bottom
anchors.right: scaleImage.left
}
Image {
id: scaleImage
source: "../resources/scale.png"
anchors.bottom: parent.bottom
anchors.right: scaleImageRight.left
}
Image {
id: scaleImageRight
source: "../resources/scale_end.png"
anchors.bottom: parent.bottom
anchors.right: parent.right
}
Label {
id: scaleText
color: "#004EAE"
anchors.centerIn: parent
text: "0 m"
}
Component.onCompleted: {
view.calculateScale();
}
}
//! [routemodel0]
RouteModel {
id: routeModel
plugin : view.map.plugin
query: RouteQuery {
id: routeQuery
}
onStatusChanged: {
if (status == RouteModel.Ready) {
switch (count) {
case 0:
// technically not an error
view.routeError()
break
case 1:
view.showRouteList()
break
}
} else if (status == RouteModel.Error) {
view.routeError()
}
}
}
//! [routemodel0]
//! [routedelegate0]
Component {
id: routeDelegate
MapRoute {
id: route
route: routeData
line.color: "#46a2da"
line.width: 5
smooth: true
opacity: 0.8
//! [routedelegate0]
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onLongPressed: showRouteMenu(view.map.toCoordinate(tapHandler.point.position))
onSingleTapped: (eventPoint, button) => {
if (button === Qt.RightButton)
showRouteMenu(view.map.toCoordinate(tapHandler.point.position))
}
}
}
}
//! [geocodemodel0]
GeocodeModel {
id: geocodeModel
plugin: view.map.plugin
onStatusChanged: {
if ((status == GeocodeModel.Ready) || (status == GeocodeModel.Error))
view.geocodeFinished()
}
onLocationsChanged:
{
if (count === 1) {
view.map.center.latitude = get(0).coordinate.latitude
view.map.center.longitude = get(0).coordinate.longitude
}
}
}
//! [geocodemodel0]
//! [pointdel0]
Component {
id: pointDelegate
MapQuickItem {
id: point
parent: view.map
coordinate: locationData.coordinate
sourceItem: Image {
id: pointMarker
source: "../resources/marker_blue.png"
//! [pointdel0]
Text{
id: pointText
anchors.bottom: pointMarker.top
anchors.horizontalCenter: pointMarker.horizontalCenter
text: locationData.address.street + ", " + locationData.address.city
color:"#242424"
font.bold: true
styleColor: "#ECECEC"
style: Text.Outline
}
}
smooth: true
autoFadeIn: false
anchorPoint.x: pointMarker.width/4
anchorPoint.y: pointMarker.height
TapHandler {
onLongPressed: showPointMenu(point.coordinate)
//! [pointdel1]
}
}
}
//! [pointdel1]
//! [routeview0]
MapItemView {
parent: view.map
model: routeModel
delegate: routeDelegate
//! [routeview0]
autoFitViewport: true
}
//! [geocodeview]
MapItemView {
parent: view.map
model: geocodeModel
delegate: pointDelegate
}
//! [geocodeview]
Timer {
id: scaleTimer
interval: 100
running: false
repeat: false
onTriggered: view.calculateScale()
}
TapHandler {
id: tapHandler
property variant lastCoordinate
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressedChanged: (eventPoint, button) => {
if (pressed) {
lastCoordinate = view.map.toCoordinate(tapHandler.point.position)
}
}
onSingleTapped: (eventPoint, button) => {
if (button === Qt.RightButton) {
showMainMenu(lastCoordinate)
}
}
onDoubleTapped: (eventPoint, button) => {
var preZoomPoint = view.map.toCoordinate(eventPoint.position);
if (button === Qt.LeftButton) {
view.map.zoomLevel = Math.floor(view.map.zoomLevel + 1)
} else if (button === Qt.RightButton) {
view.map.zoomLevel = Math.floor(view.map.zoomLevel - 1)
}
var postZoomPoint = view.map.toCoordinate(eventPoint.position);
var dx = postZoomPoint.latitude - preZoomPoint.latitude;
var dy = postZoomPoint.longitude - preZoomPoint.longitude;
view.map.center = QtPositioning.coordinate(view.map.center.latitude - dx,
view.map.center.longitude - dy);
}
}
//! [end]
}
//! [end]
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
Row {
id: containerRow
property var mapSource
property real fontSize : 14
property color labelBackground : "transparent"
property int edge: Qt.RightEdge
property alias expanded: sliderToggler.checked
function rightEdge() {
return (containerRow.edge === Qt.RightEdge);
}
layoutDirection: rightEdge() ? Qt.LeftToRight : Qt.RightToLeft
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: rightEdge() ? parent.right : undefined
anchors.left: rightEdge() ? undefined : parent.left
AbstractButton {
id: sliderToggler
width: 32
height: 96
checkable: true
checked: true
anchors.verticalCenter: parent.verticalCenter
transform: Scale {
origin.x: rightEdge() ? 0 : sliderToggler.width / 2
xScale: rightEdge() ? 1 : -1
}
background: Rectangle {
color: "transparent"
}
property real shear: 0.333
property real buttonOpacity: 0.5
property real mirror : rightEdge() ? 1.0 : -1.0
Rectangle {
width: 16
height: 48
color: "seagreen"
antialiasing: true
opacity: sliderToggler.buttonOpacity
anchors.top: parent.top
anchors.left: sliderToggler.checked ? parent.left : parent.horizontalCenter
transform: Matrix4x4 {
property real d : sliderToggler.checked ? 1.0 : -1.0
matrix: Qt.matrix4x4(1.0, d * sliderToggler.shear, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0)
}
}
Rectangle {
width: 16
height: 48
color: "seagreen"
antialiasing: true
opacity: sliderToggler.buttonOpacity
anchors.top: parent.verticalCenter
anchors.right: sliderToggler.checked ? parent.right : parent.horizontalCenter
transform: Matrix4x4 {
property real d : sliderToggler.checked ? -1.0 : 1.0
matrix: Qt.matrix4x4(1.0, d * sliderToggler.shear, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0)
}
}
}
Rectangle {
id: sliderContainer
height: parent.height
width: sliderRow.width + 10
visible: sliderToggler.checked
color: Qt.rgba( 0, 191 / 255.0, 255 / 255.0, 0.07)
property var labelBorderColor: "transparent"
property var slidersHeight : sliderContainer.height
- rowSliderValues.height
- rowSliderLabels.height
- sliderColumn.spacing * 2
- sliderColumn.topPadding
- sliderColumn.bottomPadding
Column {
id: sliderColumn
spacing: 10
topPadding: 16
bottomPadding: 48
anchors.centerIn: parent
// the sliders value labels
Row {
id: rowSliderValues
spacing: sliderRow.spacing
width: sliderRow.width
height: 32
property real entryWidth: zoomSlider.width
Rectangle{
color: labelBackground
height: parent.height
width: parent.entryWidth
border.color: sliderContainer.labelBorderColor
Label {
id: labelZoomValue
text: zoomSlider.value.toFixed(3)
font.pixelSize: fontSize
rotation: -90
anchors.centerIn: parent
}
}
Rectangle{
color: labelBackground
height: parent.height
width: parent.entryWidth
border.color: sliderContainer.labelBorderColor
Label {
id: labelBearingValue
text: bearingSlider.value.toFixed(2)
font.pixelSize: fontSize
rotation: -90
anchors.centerIn: parent
}
}
Rectangle{
color: labelBackground
height: parent.height
width: parent.entryWidth
border.color: sliderContainer.labelBorderColor
Label {
id: labelTiltValue
text: tiltSlider.value.toFixed(2)
font.pixelSize: fontSize
rotation: -90
anchors.centerIn: parent
}
}
Rectangle{
color: labelBackground
height: parent.height
width: parent.entryWidth
border.color: sliderContainer.labelBorderColor
Label {
id: labelFovValue
text: fovSlider.value.toFixed(2)
font.pixelSize: fontSize
rotation: -90
anchors.centerIn: parent
}
}
} // rowSliderValues
// The sliders row
Row {
id: sliderRow
height: sliderContainer.slidersHeight
Slider {
id: zoomSlider
height: parent.height
orientation : Qt.Vertical
from : containerRow.mapSource.minimumZoomLevel
to : containerRow.mapSource.maximumZoomLevel
value : containerRow.mapSource.zoomLevel
onValueChanged: {
containerRow.mapSource.zoomLevel = value
}
}
Slider {
id: bearingSlider
height: parent.height
from: 0
to: 360
orientation : Qt.Vertical
value: containerRow.mapSource.bearing
onValueChanged: {
containerRow.mapSource.bearing = value;
}
}
Slider {
id: tiltSlider
height: parent.height
orientation : Qt.Vertical
from: containerRow.mapSource.minimumTilt;
to: containerRow.mapSource.maximumTilt
value: containerRow.mapSource.tilt
onValueChanged: {
containerRow.mapSource.tilt = value;
}
}
Slider {
id: fovSlider
height: parent.height
orientation : Qt.Vertical
from: containerRow.mapSource.minimumFieldOfView
to: containerRow.mapSource.maximumFieldOfView
value: containerRow.mapSource.fieldOfView
onValueChanged: {
containerRow.mapSource.fieldOfView = value;
}
}
} // Row sliders
// The labels row
Row {
id: rowSliderLabels
spacing: sliderRow.spacing
width: sliderRow.width
property real entryWidth: zoomSlider.width
property real entryHeight: 64
Rectangle{
color: labelBackground
height: parent.entryHeight
width: parent.entryWidth
border.color: sliderContainer.labelBorderColor
Label {
id: labelZoom
text: "Zoom"
font.pixelSize: fontSize
rotation: -90
anchors.centerIn: parent
}
}
Rectangle{
color: labelBackground
height: parent.entryHeight
width: parent.entryWidth
border.color: sliderContainer.labelBorderColor
Label {
id: labelBearing
text: "Bearing"
font.pixelSize: fontSize
rotation: -90
anchors.centerIn: parent
}
}
Rectangle{
color: labelBackground
height: parent.entryHeight
width: parent.entryWidth
border.color: sliderContainer.labelBorderColor
Label {
id: labelTilt
text: "Tilt"
font.pixelSize: fontSize
rotation: -90
anchors.centerIn: parent
}
}
Rectangle{
color: labelBackground
height: parent.entryHeight
width: parent.entryWidth
border.color: sliderContainer.labelBorderColor
Label {
id: labelFov
text: "FoV"
font.pixelSize: fontSize
rotation: -90
anchors.centerIn: parent
}
}
} // rowSliderLabels
} // Column
} // sliderContainer
} // containerRow
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtLocation
//! [mqi-top]
MapQuickItem {
id: marker
//! [mqi-top]
//! [mqi-anchor]
anchorPoint.x: image.width/4
anchorPoint.y: image.height
HoverHandler {
id: hoverHandler
}
TapHandler {
id: tapHandler
acceptedButtons: Qt.RightButton
gesturePolicy: TapHandler.WithinBounds
onTapped: {
mapview.currentMarker = -1
for (var i = 0; i< mapview.markers.length; i++){
if (marker == mapview.markers[i]){
mapview.currentMarker = i
break
}
}
mapview.showMarkerMenu(marker.coordinate)
}
}
DragHandler {
id: dragHandler
grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType
}
sourceItem: Image {
id: image
//! [mqi-anchor]
source: "../resources/marker.png"
opacity: hoverHandler.hovered ? 0.6 : 1.0
Text{
id: number
y: image.height/10
width: image.width
color: "white"
font.bold: true
font.pixelSize: 14
horizontalAlignment: Text.AlignHCenter
Component.onCompleted: {
text = mapview.markerCounter
}
}
//! [mqi-closeimage]
}
//! [mqi-closeimage]
//! [mqi-close]
}
//! [mqi-close]
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtPositioning
import QtLocation
Rectangle{
function clamp(num, min, max)
{
return num < min ? min : num > max ? max : num;
}
function minimumScaleFactor()
{
var hscalefactor = (400.0 / Math.max(Math.min(mapview.width, 1000), 400)) * 0.5
var vscalefactor = (400.0 / Math.max(Math.min(mapview.height, 1000), 400)) * 0.5
return Math.min(hscalefactor,vscalefactor)
}
function avgScaleFactor()
{
var hscalefactor = (400.0 / Math.max(Math.min(mapview.width, 1000), 400)) * 0.5
var vscalefactor = (400.0 / Math.max(Math.min(mapview.height, 1000), 400)) * 0.5
return (hscalefactor+vscalefactor) * 0.5
}
id: miniMapRect
width: Math.floor(mapview.width * avgScaleFactor()) + 2
height: Math.floor(mapview.height * avgScaleFactor()) + 2
anchors.right: (parent) ? parent.right : undefined
anchors.rightMargin: 10
anchors.top: (parent) ? parent.top : undefined
anchors.topMargin: 10
color: "#242424"
Map {
id: miniMap
anchors.top: parent.top
anchors.topMargin: 1
anchors.left: parent.left
anchors.leftMargin: 1
width: Math.floor(mapview.width * avgScaleFactor())
height: Math.floor(mapview.height * avgScaleFactor())
zoomLevel: clamp(mapview.map.zoomLevel - 4.5, 1.0, 5.0) //(map.zoomLevel > minimumZoomLevel + 3) ? minimumZoomLevel + 3 : 1.5
center: mapview.map.center
plugin: mapview.map.plugin
copyrightsVisible: false
property double mapZoomLevel : mapview.map.zoomLevel
// cannot use property bindings on map.visibleRegion in MapRectangle because it's non-NOTIFYable
onCenterChanged: miniMapRectangle.updateCoordinates()
onMapZoomLevelChanged: miniMapRectangle.updateCoordinates()
onWidthChanged: miniMapRectangle.updateCoordinates()
onHeightChanged: miniMapRectangle.updateCoordinates()
MapRectangle {
id: miniMapRectangle
color: "#44ff0000"
border.width: 1
border.color: "red"
autoFadeIn: false
function getMapVisibleRegion()
{
return mapview.map.visibleRegion.boundingGeoRectangle()
}
function updateCoordinates()
{
topLeft.latitude = getMapVisibleRegion().topLeft.latitude
topLeft.longitude= getMapVisibleRegion().topLeft.longitude
bottomRight.latitude = getMapVisibleRegion().bottomRight.latitude
bottomRight.longitude= getMapVisibleRegion().bottomRight.longitude
}
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
Menu {
property variant type
signal itemClicked(string item)
MenuItem {
text: qsTr("Info")
onTriggered: itemClicked("show" + type + "Info")
}
MenuItem {
text: qsTr("Delete")
onTriggered: itemClicked("delete" + type)
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtLocation
MenuBar {
id: menuBar
property variant providerMenu: providerMenu
property variant mapTypeMenu: mapTypeMenu
property variant toolsMenu: toolsMenu
property variant plugin
property alias isFollowMe: toolsMenu.isFollowMe
property alias isMiniMap: toolsMenu.isMiniMap
signal selectProvider(string providerName)
signal selectMapType(variant mapType)
signal selectTool(string tool);
signal toggleMapState(string state)
function clearMenu(menu)
{
while (menu.count)
menu.removeItem(menu.itemAt(0))
}
Menu {
id: providerMenu
title: qsTr("Provider")
function createMenu(plugins)
{
clearMenu(providerMenu)
for (var i = 0; i < plugins.length; i++) {
createProviderMenuItem(plugins[i]);
}
}
function createProviderMenuItem(provider)
{
var action = Qt.createQmlObject('import QtQuick.Controls; Action{ text: "' + provider + '"; checkable: true; onTriggered: function(){selectProvider("' + provider + '")} }', providerMenu)
addAction(action)
}
}
Menu {
id: mapTypeMenu
title: qsTr("MapType")
Component {
id: mapTypeMenuActionComponent
Action {
}
}
function createMenu(map)
{
clearMenu(mapTypeMenu)
for (var i = 0; i<map.supportedMapTypes.length; i++) {
createMapTypeMenuItem(map.supportedMapTypes[i], map.activeMapType === map.supportedMapTypes[i]);
}
}
function createMapTypeMenuItem(mapType, checked)
{
var action = mapTypeMenuActionComponent.createObject(mapTypeMenu, { text: mapType.name, checkable: true, checked: checked })
action.triggered.connect(function(){selectMapType(mapType)})
addAction(action)
}
}
Menu {
id: toolsMenu
property bool isFollowMe: false;
property bool isMiniMap: false;
property variant plugin: menuBar.plugin
title: qsTr("Tools")
Action {
text: qsTr("Reverse geocode")
enabled: plugin ? plugin.supportsGeocoding(Plugin.ReverseGeocodingFeature) : false
onTriggered: selectTool("RevGeocode")
}
MenuItem {
text: qsTr("Geocode")
enabled: plugin ? plugin.supportsGeocoding() : false
onTriggered: selectTool("Geocode")
}
MenuItem {
text: qsTr("Route with coordinates")
enabled: plugin ? plugin.supportsRouting() : false
onTriggered: selectTool("CoordinateRoute")
}
MenuItem {
text: qsTr("Route with address")
enabled: plugin ? plugin.supportsRouting() : false
onTriggered: selectTool("AddressRoute")
}
MenuItem {
text: isMiniMap ? qsTr("Hide minimap") : qsTr("Minimap")
onTriggered: toggleMapState("MiniMap")
}
MenuItem {
text: isFollowMe ? qsTr("Stop following") : qsTr("Follow me")
onTriggered: toggleMapState("FollowMe")
}
MenuItem {
text: qsTr("Language")
onTriggered: selectTool("Language")
}
MenuItem {
text: qsTr("Prefetch Map Data")
onTriggered: selectTool("Prefetch")
}
MenuItem {
text: qsTr("Clear Map Data")
onTriggered: selectTool("Clear")
}
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
Menu {
property variant coordinate
property int markersCount
property int mapItemsCount
signal itemClicked(string item)
MenuItem {
text: qsTr("Add Marker")
onTriggered: itemClicked("addMarker")
}
MenuItem {
text: qsTr("Get coordinate")
onTriggered: itemClicked("getCoordinate")
}
MenuItem {
text: qsTr("Fit Viewport To Markers")
onTriggered: itemClicked("fitViewport")
}
MenuItem {
text: qsTr("Delete all markers")
enabled: markersCount > 0
onTriggered: itemClicked("deleteMarkers")
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
Menu {
property int currentMarker
property int markersCount
signal itemClicked(string item)
MenuItem {
text: qsTr("Delete")
onTriggered: itemClicked("deleteMarker")
}
MenuItem {
text: qsTr("Coordinates")
onTriggered: itemClicked("getMarkerCoordinate")
}
MenuItem {
text: qsTr("Move to")
onTriggered: itemClicked("moveMarkerTo")
}
MenuItem {
text: currentMarker < markersCount-2 ? qsTr("Route to next markers")
: qsTr("Route to next marker")
enabled: currentMarker <= markersCount - 2
onTriggered: currentMarker < markersCount-2 ? itemClicked("routeToNextPoints")
: itemClicked("routeToNextPoint")
}
MenuItem {
text: currentMarker < markersCount-2 ? qsTr("Distance to next markers")
: qsTr("Distance to next marker")
enabled: currentMarker <= markersCount - 2
onTriggered: currentMarker < markersCount-2 ? itemClicked("distanceToNextPoints")
: itemClicked("distanceToNextPoint")
}
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
.pragma library
function roundNumber(number, digits)
{
var multiple = Math.pow(10, digits);
return Math.round(number * multiple) / multiple;
}
function formatTime(sec)
{
var value = sec
var seconds = value % 60
value /= 60
value = (value > 1) ? Math.round(value) : 0
var minutes = value % 60
value /= 60
value = (value > 1) ? Math.round(value) : 0
var hours = value
if (hours > 0) value = hours + "h:"+ minutes + "m"
else value = minutes + "min"
return value
}
function formatDistance(meters)
{
var dist = Math.round(meters)
if (dist > 1000 ){
if (dist > 100000){
dist = Math.round(dist / 1000)
}
else{
dist = Math.round(dist / 100)
dist = dist / 10
}
dist = dist + " km"
}
else{
dist = dist + " m"
}
return dist
}
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick.Controls
import QtLocation
import QtPositioning
import MapViewer
ApplicationWindow {
id: appWindow
property variant mapview
property variant minimap
property variant plugin
property variant parameters
//defaults
//! [routecoordinate]
property variant fromCoordinate: QtPositioning.coordinate(59.9483, 10.7695)
property variant toCoordinate: QtPositioning.coordinate(59.9645, 10.671)
//! [routecoordinate]
function createMap(provider)
{
if (parameters && parameters.length>0)
plugin = Qt.createQmlObject ('import QtLocation; Plugin{ name:"' + provider + '"; parameters: appWindow.parameters}', appWindow)
else
plugin = Qt.createQmlObject ('import QtLocation; Plugin{ name:"' + provider + '"}', appWindow)
if (minimap) {
minimap.destroy()
minimap = null
}
var zoomLevel = null
var tilt = null
var bearing = null
var fov = null
var center = null
var panelExpanded = null
if (mapview) {
zoomLevel = mapview.zoomLevel
tilt = mapview.tilt
bearing = mapview.bearing
fov = mapview.fieldOfView
center = mapview.center
panelExpanded = mapview.slidersExpanded
mapview.destroy()
}
mapview = mapComponent.createObject(page);
mapview.map.plugin = plugin;
if (zoomLevel != null) {
mapview.map.tilt = tilt
mapview.map.bearing = bearing
mapview.map.fieldOfView = fov
mapview.map.zoomLevel = zoomLevel
mapview.map.center = center
mapview.map.slidersExpanded = panelExpanded
} else {
// Use an integer ZL to enable nearest interpolation, if possible.
mapview.map.zoomLevel = Math.floor((mapview.map.maximumZoomLevel - mapview.map.minimumZoomLevel)/2)
// defaulting to 45 degrees, if possible.
mapview.map.fieldOfView = Math.min(Math.max(45.0, mapview.map.minimumFieldOfView), mapview.maximumFieldOfView)
}
mapview.forceActiveFocus()
}
function getPlugins()
{
var plugin = Qt.createQmlObject ('import QtLocation; Plugin {}', appWindow)
var myArray = new Array()
for (var i = 0; i<plugin.availableServiceProviders.length; i++) {
var tempPlugin = Qt.createQmlObject ('import QtLocation; Plugin {name: "' + plugin.availableServiceProviders[i]+ '"}', appWindow)
if (tempPlugin.supportsMapping())
myArray.push(tempPlugin.name)
}
myArray.sort()
return myArray
}
function initializeProviders(pluginParameters)
{
var parameters = new Array()
for (var prop in pluginParameters){
var parameter = Qt.createQmlObject('import QtLocation; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}',appWindow)
parameters.push(parameter)
}
appWindow.parameters = parameters
var plugins = getPlugins()
mainMenu.providerMenu.createMenu(plugins)
for (var i = 0; i<plugins.length; i++) {
if (plugins[i] === "osm")
mainMenu.selectProvider(plugins[i])
}
}
title: qsTr("Mapviewer")
height: 640
width: 360
visible: true
menuBar: mainMenu
//! [geocode0]
Address {
id :fromAddress
street: "Sandakerveien 116"
city: "Oslo"
country: "Norway"
state : ""
postalCode: "0484"
}
//! [geocode0]
Address {
id: toAddress
street: "Holmenkollveien 140"
city: "Oslo"
country: "Norway"
postalCode: "0791"
}
MainMenu {
id: mainMenu
plugin: appWindow.plugin
function toggleMiniMapState()
{
console.log("MiniMap with " + plugin)
if (minimap) {
minimap.destroy()
minimap = null
} else {
minimap = Qt.createQmlObject ('import "map"; MiniMap{ z: mapview.z + 2 }', mapview)
}
}
function setLanguage(lang)
{
mapview.map.plugin.locales = lang;
stackView.pop(page)
}
onSelectProvider: (providerName) => {
stackView.pop()
for (var i = 0; i < providerMenu.count; i++) {
providerMenu.actionAt(i).checked = providerMenu.actionAt(i).text === providerName
}
createMap(providerName)
if (mapview.error === mapview.NoError) {
selectMapType(mapview.map.activeMapType)
} else {
mainMenu.clearMenu(mapTypeMenu)
}
}
onSelectMapType: (mapType) => {
stackView.pop(page)
for (var i = 0; i < mapTypeMenu.count; i++) {
mapTypeMenu.actionAt(i).checked = mapTypeMenu.actionAt(i).text === mapType.name
}
mapview.map.activeMapType = mapType
}
onSelectTool: (tool) => {
switch (tool) {
case "AddressRoute":
stackView.pop({item:page, immediate: true})
stackView.push("forms/RouteAddress.qml" ,
{ "plugin": mapview.map.plugin,
"toAddress": toAddress,
"fromAddress": fromAddress})
stackView.currentItem.showRoute.connect(mapview.calculateCoordinateRoute)
stackView.currentItem.showMessage.connect(stackView.showMessage)
stackView.currentItem.closeForm.connect(stackView.closeForm)
break
case "CoordinateRoute":
stackView.pop({item:page, immediate: true})
stackView.push("forms/RouteCoordinate.qml" ,
{ "toCoordinate": toCoordinate,
"fromCoordinate": fromCoordinate})
stackView.currentItem.showRoute.connect(mapview.calculateCoordinateRoute)
stackView.currentItem.closeForm.connect(stackView.closeForm)
break
case "Geocode":
stackView.pop({item:page, immediate: true})
stackView.push("forms/Geocode.qml",
{ "address": fromAddress})
stackView.currentItem.showPlace.connect(mapview.geocode)
stackView.currentItem.closeForm.connect(stackView.closeForm)
break
case "RevGeocode":
stackView.pop({item:page, immediate: true})
stackView.push("forms/ReverseGeocode.qml",
{ "coordinate": fromCoordinate })
stackView.currentItem.showPlace.connect(mapview.geocode)
stackView.currentItem.closeForm.connect(stackView.closeForm)
break
case "Language":
stackView.pop({item:page, immediate: true})
stackView.push("forms/Locale.qml",
{ "locale": mapview.map.plugin.locales[0]})
stackView.currentItem.selectLanguage.connect(setLanguage)
stackView.currentItem.closeForm.connect(stackView.closeForm)
break
case "Clear":
mapview.map.clearData()
break
case "Prefetch":
mapview.map.prefetchData()
break
default:
console.log("Unsupported operation")
}
}
onToggleMapState: (state) => {
stackView.pop(page)
switch (state) {
case "FollowMe":
mapview.followme = !mapview.followme
break
case "MiniMap":
toggleMiniMapState()
isMiniMap = minimap
break
default:
console.log("Unsupported operation")
}
}
}
MapPopupMenu {
id: mapPopupMenu
function show(coordinate)
{
stackView.pop(page)
mapPopupMenu.coordinate = coordinate
mapPopupMenu.markersCount = mapview.markers.length
mapPopupMenu.mapItemsCount = mapview.mapItems.length
mapPopupMenu.popup()
}
onItemClicked: (item) => {
stackView.pop(page)
switch (item) {
case "addMarker":
mapview.addMarker()
break
case "getCoordinate":
mapview.coordinatesCaptured(coordinate.latitude, coordinate.longitude)
break
case "fitViewport":
mapview.map.fitViewportToMapItems()
break
case "deleteMarkers":
mapview.deleteMarkers()
break
default:
console.log("Unsupported operation:", item)
}
}
}
MarkerPopupMenu {
id: markerPopupMenu
function show(coordinate)
{
stackView.pop(page)
markerPopupMenu.markersCount = mapview.markers.length
markerPopupMenu.currentMarker = mapview.currentMarker
markerPopupMenu.popup()
}
function askForCoordinate()
{
stackView.push("forms/ReverseGeocode.qml",
{ "title": qsTr("New Coordinate"),
"coordinate": mapview.markers[mapview.currentMarker].coordinate})
stackView.currentItem.showPlace.connect(moveMarker)
stackView.currentItem.closeForm.connect(stackView.closeForm)
}
function moveMarker(coordinate)
{
mapview.markers[mapview.currentMarker].coordinate = coordinate;
mapview.map.center = coordinate;
stackView.pop(page)
}
onItemClicked: (item) => {
stackView.pop(page)
switch (item) {
case "deleteMarker":
mapview.deleteMarker(mapview.currentMarker)
break;
case "getMarkerCoordinate":
mapview.coordinatesCaptured(mapview.markers[mapview.currentMarker].coordinate.latitude,
mapview.markers[mapview.currentMarker].coordinate.longitude)
break;
case "moveMarkerTo":
askForCoordinate()
break;
case "routeToNextPoint":
case "routeToNextPoints":
mapview.calculateMarkerRoute()
break
case "distanceToNextPoint":
var coordinate1 = mapview.markers[mapview.currentMarker].coordinate;
var coordinate2 = mapview.markers[mapview.currentMarker+1].coordinate;
var distance = Helper.formatDistance(coordinate1.distanceTo(coordinate2));
stackView.showMessage(qsTr("Distance"),"<b>" + qsTr("Distance:") + "</b> " + distance)
break
default:
console.log("Unsupported operation:", item)
}
}
}
ItemPopupMenu {
id: itemPopupMenu
function show(type,coordinate)
{
stackView.pop(page)
itemPopupMenu.type = type
itemPopupMenu.popup()
}
onItemClicked: {
stackView.pop(page)
switch (item) {
case "showRouteInfo":
stackView.showRouteListPage()
break;
case "deleteRoute":
mapview.routeModel.reset();
break;
case "showPointInfo":
mapview.showGeocodeInfo()
break;
case "deletePoint":
geocodeModel.reset()
break;
default:
console.log("Unsupported operation")
}
}
}
StackView {
id: stackView
anchors.fill: parent
focus: true
initialItem: Item {
id: page
Text {
visible: !supportsSsl && map && mapview.activeMapType && activeMapType.metadata.isHTTPS
text: "The active map type\n
requires (missing) SSL\n
support"
horizontalAlignment: Text.AlignHCenter
font.pixelSize: appWindow.width / 12
font.bold: true
color: "grey"
anchors.centerIn: parent
z: 12
}
}
function showMessage(title,message,backPage)
{
push("forms/Message.qml",
{
"title" : title,
"message" : message,
"backPage" : backPage
})
currentItem.closeForm.connect(closeMessage)
}
function closeMessage(backPage)
{
pop(backPage)
}
function closeForm()
{
pop(page)
}
function showRouteListPage()
{
push("forms/RouteList.qml",
{
"routeModel" : mapview.routeModel
})
currentItem.closeForm.connect(closeForm)
}
}
Component {
id: mapComponent
MapComponent {
width: page.width
height: page.height
onFollowmeChanged: mainMenu.isFollowMe = followme
map.onSupportedMapTypesChanged: mainMenu.mapTypeMenu.createMenu(map)
onCoordinatesCaptured: (latitude, longitude) => {
var text = "<b>" + qsTr("Latitude:") + "</b> " + Helper.roundNumber(latitude,4) + "<br/><b>" + qsTr("Longitude:") + "</b> " + Helper.roundNumber(longitude,4)
stackView.showMessage(qsTr("Coordinates"),text);
}
onGeocodeFinished:{
if (geocodeModel.status == GeocodeModel.Ready) {
if (geocodeModel.count == 0) {
stackView.showMessage(qsTr("Geocode Error"),qsTr("Unsuccessful geocode"))
} else if (geocodeModel.count > 1) {
stackView.showMessage(qsTr("Ambiguous geocode"), geocodeModel.count + " " +
qsTr("results found for the given address, please specify location"))
} else {
stackView.showMessage(qsTr("Location"), geocodeMessage(),page)
}
} else if (geocodeModel.status == GeocodeModel.Error) {
stackView.showMessage(qsTr("Geocode Error"),qsTr("Unsuccessful geocode"))
}
}
onRouteError: stackView.showMessage(qsTr("Route Error"),qsTr("Unable to find a route for the given points"),page)
onShowGeocodeInfo: stackView.showMessage(qsTr("Location"),geocodeMessage(),page)
map.onErrorChanged: {
if (map.error != mapview.NoError) {
var title = qsTr("ProviderError")
var message = mapview.errorString + "<br/><br/><b>" + qsTr("Try to select other provider") + "</b>"
if (map.error == mapview.MissingRequiredParameterError)
message += "<br/>" + qsTr("or see") + " \'mapviewer --help\' "
+ qsTr("how to pass plugin parameters.")
stackView.showMessage(title,message);
}
}
onShowMainMenu: (coordinate) => mapPopupMenu.show(coordinate)
onShowMarkerMenu: (coordinate) => markerPopupMenu.show(coordinate)
onShowRouteMenu: (coordinate) => itemPopupMenu.show("Route",coordinate)
onShowPointMenu: (coordinate) => itemPopupMenu.show("Point",coordinate)
onShowRouteList: stackView.showRouteListPage()
TapHandler {
onTapped: {
}
}
}
}
}
module MapViewer
typeinfo mapviewer.qmltypes
Main 1.0 Main.qml
Helper 1.0 helper.js
MapComponent 1.0 map/MapComponent.qml
MapSliders 1.0 map/MapSliders.qml
Marker 1.0 map/Marker.qml
MiniMap 1.0 map/MiniMap.qml
ItemPopupMenu 1.0 menus/ItemPopupMenu.qml
MainMenu 1.0 menus/MainMenu.qml
MapPopupMenu 1.0 menus/MapPopupMenu.qml
MarkerPopupMenu 1.0 menus/MarkerPopupMenu.qml
Geocode 1.0 forms/Geocode.qml
GeocodeForm 1.0 forms/GeocodeForm.ui.qml
Message 1.0 forms/Message.qml
MessageForm 1.0 forms/MessageForm.ui.qml
ReverseGeocode 1.0 forms/ReverseGeocode.qml
ReverseGeocodeForm 1.0 forms/ReverseGeocodeForm.ui.qml
RouteCoordinate 1.0 forms/RouteCoordinate.qml
Locale 1.0 forms/Locale.qml
LocaleForm 1.0 forms/LocaleForm.ui.qml
RouteAddress 1.0 forms/RouteAddress.qml
RouteAddressForm 1.0 forms/RouteAddressForm.ui.qml
RouteCoordinateForm 1.0 forms/RouteCoordinateForm.ui.qml
RouteList 1.0 forms/RouteList.qml
RouteListDelegate 1.0 forms/RouteListDelegate.qml
RouteListHeader 1.0 forms/RouteListHeader.qml