aboutsummaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2023-03-07 14:57:02 +0100
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2023-03-16 22:41:35 +0100
commit012e40e6af6355ff05feee899e60e041458ea741 (patch)
treeda34dc945dc5a783f02ac85975842fbfc0652b01 /examples
parentf091bb7237c4bcd01b1e173bbab46803a68b37a6 (diff)
Add the QtLocation/MapView example
Task-number: PYSIDE-2206 Change-Id: I2eea18a5105e545a1582ecb4ca91bb089f43f7f7 Reviewed-by: Shyamnath Premnadh <Shyamnath.Premnadh@qt.io> Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io> Reviewed-by: Christian Tismer <tismer@stackless.com>
Diffstat (limited to 'examples')
-rw-r--r--examples/location/mapviewer/doc/mapviewer.rst12
-rw-r--r--examples/location/mapviewer/doc/mapviewer.webpbin0 -> 79588 bytes
-rw-r--r--examples/location/mapviewer/forms/Geocode.qml42
-rw-r--r--examples/location/mapviewer/forms/GeocodeForm.ui.qml136
-rw-r--r--examples/location/mapviewer/forms/Locale.qml45
-rw-r--r--examples/location/mapviewer/forms/LocaleForm.ui.qml116
-rw-r--r--examples/location/mapviewer/forms/Message.qml21
-rw-r--r--examples/location/mapviewer/forms/MessageForm.ui.qml69
-rw-r--r--examples/location/mapviewer/forms/ReverseGeocode.qml38
-rw-r--r--examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml103
-rw-r--r--examples/location/mapviewer/forms/RouteAddress.qml105
-rw-r--r--examples/location/mapviewer/forms/RouteAddressForm.ui.qml160
-rw-r--r--examples/location/mapviewer/forms/RouteCoordinate.qml41
-rw-r--r--examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml136
-rw-r--r--examples/location/mapviewer/forms/RouteList.qml50
-rw-r--r--examples/location/mapviewer/forms/RouteListDelegate.qml42
-rw-r--r--examples/location/mapviewer/forms/RouteListHeader.qml47
-rw-r--r--examples/location/mapviewer/helper.js44
-rw-r--r--examples/location/mapviewer/main.py76
-rw-r--r--examples/location/mapviewer/map/MapComponent.qml497
-rw-r--r--examples/location/mapviewer/map/MapSliders.qml282
-rw-r--r--examples/location/mapviewer/map/Marker.qml64
-rw-r--r--examples/location/mapviewer/map/MiniMap.qml78
-rw-r--r--examples/location/mapviewer/mapviewer.pyproject27
-rw-r--r--examples/location/mapviewer/mapviewer.qml462
-rw-r--r--examples/location/mapviewer/menus/ItemPopupMenu.qml19
-rw-r--r--examples/location/mapviewer/menus/MainMenu.qml122
-rw-r--r--examples/location/mapviewer/menus/MapPopupMenu.qml30
-rw-r--r--examples/location/mapviewer/menus/MarkerPopupMenu.qml38
-rw-r--r--examples/location/mapviewer/resources/marker.pngbin0 -> 752 bytes
-rw-r--r--examples/location/mapviewer/resources/marker_blue.pngbin0 -> 3523 bytes
-rw-r--r--examples/location/mapviewer/resources/scale.pngbin0 -> 98 bytes
-rw-r--r--examples/location/mapviewer/resources/scale_end.pngbin0 -> 93 bytes
33 files changed, 2902 insertions, 0 deletions
diff --git a/examples/location/mapviewer/doc/mapviewer.rst b/examples/location/mapviewer/doc/mapviewer.rst
new file mode 100644
index 000000000..d30544991
--- /dev/null
+++ b/examples/location/mapviewer/doc/mapviewer.rst
@@ -0,0 +1,12 @@
+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.
+
+.. image:: mapviewer.webp
+ :width: 400
+ :alt: Map Viewer Screenshot
diff --git a/examples/location/mapviewer/doc/mapviewer.webp b/examples/location/mapviewer/doc/mapviewer.webp
new file mode 100644
index 000000000..6571a6c89
--- /dev/null
+++ b/examples/location/mapviewer/doc/mapviewer.webp
Binary files differ
diff --git a/examples/location/mapviewer/forms/Geocode.qml b/examples/location/mapviewer/forms/Geocode.qml
new file mode 100644
index 000000000..885357dd3
--- /dev/null
+++ b/examples/location/mapviewer/forms/Geocode.qml
@@ -0,0 +1,42 @@
+// 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
+ }
+}
diff --git a/examples/location/mapviewer/forms/GeocodeForm.ui.qml b/examples/location/mapviewer/forms/GeocodeForm.ui.qml
new file mode 100644
index 000000000..cb56370ea
--- /dev/null
+++ b/examples/location/mapviewer/forms/GeocodeForm.ui.qml
@@ -0,0 +1,136 @@
+// 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
+ }
+ }
+ }
+}
diff --git a/examples/location/mapviewer/forms/Locale.qml b/examples/location/mapviewer/forms/Locale.qml
new file mode 100644
index 000000000..9ba7dd7f0
--- /dev/null
+++ b/examples/location/mapviewer/forms/Locale.qml
@@ -0,0 +1,45 @@
+// 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
+ }
+ }
+}
diff --git a/examples/location/mapviewer/forms/LocaleForm.ui.qml b/examples/location/mapviewer/forms/LocaleForm.ui.qml
new file mode 100644
index 000000000..9e1ec1807
--- /dev/null
+++ b/examples/location/mapviewer/forms/LocaleForm.ui.qml
@@ -0,0 +1,116 @@
+// 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
+ }
+
+
+ }
+ }
+}
diff --git a/examples/location/mapviewer/forms/Message.qml b/examples/location/mapviewer/forms/Message.qml
new file mode 100644
index 000000000..583bc2dda
--- /dev/null
+++ b/examples/location/mapviewer/forms/Message.qml
@@ -0,0 +1,21 @@
+// 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
+ }
+}
diff --git a/examples/location/mapviewer/forms/MessageForm.ui.qml b/examples/location/mapviewer/forms/MessageForm.ui.qml
new file mode 100644
index 000000000..426c72757
--- /dev/null
+++ b/examples/location/mapviewer/forms/MessageForm.ui.qml
@@ -0,0 +1,69 @@
+// 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
+ }
+ }
+ }
+}
+
diff --git a/examples/location/mapviewer/forms/ReverseGeocode.qml b/examples/location/mapviewer/forms/ReverseGeocode.qml
new file mode 100644
index 000000000..31122a2e9
--- /dev/null
+++ b/examples/location/mapviewer/forms/ReverseGeocode.qml
@@ -0,0 +1,38 @@
+// 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;
+ }
+ }
+}
diff --git a/examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml b/examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml
new file mode 100644
index 000000000..1d937ee90
--- /dev/null
+++ b/examples/location/mapviewer/forms/ReverseGeocodeForm.ui.qml
@@ -0,0 +1,103 @@
+// 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
+ }
+ }
+ }
+}
diff --git a/examples/location/mapviewer/forms/RouteAddress.qml b/examples/location/mapviewer/forms/RouteAddress.qml
new file mode 100644
index 000000000..3676c1374
--- /dev/null
+++ b/examples/location/mapviewer/forms/RouteAddress.qml
@@ -0,0 +1,105 @@
+// 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;
+ }
+ }
+ }
+ }
+}
diff --git a/examples/location/mapviewer/forms/RouteAddressForm.ui.qml b/examples/location/mapviewer/forms/RouteAddressForm.ui.qml
new file mode 100644
index 000000000..ee9227013
--- /dev/null
+++ b/examples/location/mapviewer/forms/RouteAddressForm.ui.qml
@@ -0,0 +1,160 @@
+// 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
+ }
+ }
+ }
+}
diff --git a/examples/location/mapviewer/forms/RouteCoordinate.qml b/examples/location/mapviewer/forms/RouteCoordinate.qml
new file mode 100644
index 000000000..003556c51
--- /dev/null
+++ b/examples/location/mapviewer/forms/RouteCoordinate.qml
@@ -0,0 +1,41 @@
+// 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
+ }
+}
diff --git a/examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml b/examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml
new file mode 100644
index 000000000..88ff94dc1
--- /dev/null
+++ b/examples/location/mapviewer/forms/RouteCoordinateForm.ui.qml
@@ -0,0 +1,136 @@
+// 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
+ }
+ }
+ }
+}
diff --git a/examples/location/mapviewer/forms/RouteList.qml b/examples/location/mapviewer/forms/RouteList.qml
new file mode 100644
index 000000000..8dbda7c01
--- /dev/null
+++ b/examples/location/mapviewer/forms/RouteList.qml
@@ -0,0 +1,50 @@
+// 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]
diff --git a/examples/location/mapviewer/forms/RouteListDelegate.qml b/examples/location/mapviewer/forms/RouteListDelegate.qml
new file mode 100644
index 000000000..680318ac3
--- /dev/null
+++ b/examples/location/mapviewer/forms/RouteListDelegate.qml
@@ -0,0 +1,42 @@
+// 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"
+ }
+}
diff --git a/examples/location/mapviewer/forms/RouteListHeader.qml b/examples/location/mapviewer/forms/RouteListHeader.qml
new file mode 100644
index 000000000..4f8308091
--- /dev/null
+++ b/examples/location/mapviewer/forms/RouteListHeader.qml
@@ -0,0 +1,47 @@
+// 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
+ }
+ }
+}
diff --git a/examples/location/mapviewer/helper.js b/examples/location/mapviewer/helper.js
new file mode 100644
index 000000000..a42040518
--- /dev/null
+++ b/examples/location/mapviewer/helper.js
@@ -0,0 +1,44 @@
+// 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
+}
diff --git a/examples/location/mapviewer/main.py b/examples/location/mapviewer/main.py
new file mode 100644
index 000000000..cd0d13776
--- /dev/null
+++ b/examples/location/mapviewer/main.py
@@ -0,0 +1,76 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+"""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, QUrl, 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(":/imports")
+ qml_file = Path(__file__).parent / "mapviewer.qml"
+ engine.load(QUrl.fromLocalFile(qml_file))
+ 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)
diff --git a/examples/location/mapviewer/map/MapComponent.qml b/examples/location/mapviewer/map/MapComponent.qml
new file mode 100644
index 000000000..987455287
--- /dev/null
+++ b/examples/location/mapviewer/map/MapComponent.qml
@@ -0,0 +1,497 @@
+// 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]
diff --git a/examples/location/mapviewer/map/MapSliders.qml b/examples/location/mapviewer/map/MapSliders.qml
new file mode 100644
index 000000000..d9c8381b0
--- /dev/null
+++ b/examples/location/mapviewer/map/MapSliders.qml
@@ -0,0 +1,282 @@
+// 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
diff --git a/examples/location/mapviewer/map/Marker.qml b/examples/location/mapviewer/map/Marker.qml
new file mode 100644
index 000000000..c7494cf57
--- /dev/null
+++ b/examples/location/mapviewer/map/Marker.qml
@@ -0,0 +1,64 @@
+// 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]
diff --git a/examples/location/mapviewer/map/MiniMap.qml b/examples/location/mapviewer/map/MiniMap.qml
new file mode 100644
index 000000000..f8fc51547
--- /dev/null
+++ b/examples/location/mapviewer/map/MiniMap.qml
@@ -0,0 +1,78 @@
+// 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
+ }
+ }
+ }
+}
diff --git a/examples/location/mapviewer/mapviewer.pyproject b/examples/location/mapviewer/mapviewer.pyproject
new file mode 100644
index 000000000..868657d8b
--- /dev/null
+++ b/examples/location/mapviewer/mapviewer.pyproject
@@ -0,0 +1,27 @@
+{
+ "files": ["main.py",
+ "forms/Geocode.qml",
+ "forms/GeocodeForm.ui.qml",
+ "forms/Locale.qml",
+ "forms/LocaleForm.ui.qml",
+ "forms/Message.qml",
+ "forms/MessageForm.ui.qml",
+ "forms/ReverseGeocode.qml",
+ "forms/ReverseGeocodeForm.ui.qml",
+ "forms/RouteAddress.qml",
+ "forms/RouteAddressForm.ui.qml",
+ "forms/RouteCoordinate.qml",
+ "forms/RouteCoordinateForm.ui.qml",
+ "forms/RouteList.qml",
+ "forms/RouteListDelegate.qml",
+ "forms/RouteListHeader.qml",
+ "map/MapComponent.qml",
+ "map/MapSliders.qml",
+ "map/Marker.qml",
+ "map/MiniMap.qml",
+ "mapviewer.qml",
+ "menus/ItemPopupMenu.qml",
+ "menus/MainMenu.qml",
+ "menus/MapPopupMenu.qml",
+ "menus/MarkerPopupMenu.qml"]
+}
diff --git a/examples/location/mapviewer/mapviewer.qml b/examples/location/mapviewer/mapviewer.qml
new file mode 100644
index 000000000..daa28d763
--- /dev/null
+++ b/examples/location/mapviewer/mapviewer.qml
@@ -0,0 +1,462 @@
+// 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 "map"
+import "menus"
+import "helper.js" as Helper
+
+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: {
+ }
+ }
+ }
+ }
+}
diff --git a/examples/location/mapviewer/menus/ItemPopupMenu.qml b/examples/location/mapviewer/menus/ItemPopupMenu.qml
new file mode 100644
index 000000000..d559aca6c
--- /dev/null
+++ b/examples/location/mapviewer/menus/ItemPopupMenu.qml
@@ -0,0 +1,19 @@
+// 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)
+ }
+}
diff --git a/examples/location/mapviewer/menus/MainMenu.qml b/examples/location/mapviewer/menus/MainMenu.qml
new file mode 100644
index 000000000..3523b5c1a
--- /dev/null
+++ b/examples/location/mapviewer/menus/MainMenu.qml
@@ -0,0 +1,122 @@
+// 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")
+ }
+ }
+}
diff --git a/examples/location/mapviewer/menus/MapPopupMenu.qml b/examples/location/mapviewer/menus/MapPopupMenu.qml
new file mode 100644
index 000000000..335788df8
--- /dev/null
+++ b/examples/location/mapviewer/menus/MapPopupMenu.qml
@@ -0,0 +1,30 @@
+// 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")
+ }
+}
diff --git a/examples/location/mapviewer/menus/MarkerPopupMenu.qml b/examples/location/mapviewer/menus/MarkerPopupMenu.qml
new file mode 100644
index 000000000..338f23859
--- /dev/null
+++ b/examples/location/mapviewer/menus/MarkerPopupMenu.qml
@@ -0,0 +1,38 @@
+// 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")
+ }
+}
diff --git a/examples/location/mapviewer/resources/marker.png b/examples/location/mapviewer/resources/marker.png
new file mode 100644
index 000000000..2116dfdf5
--- /dev/null
+++ b/examples/location/mapviewer/resources/marker.png
Binary files differ
diff --git a/examples/location/mapviewer/resources/marker_blue.png b/examples/location/mapviewer/resources/marker_blue.png
new file mode 100644
index 000000000..70f0c2538
--- /dev/null
+++ b/examples/location/mapviewer/resources/marker_blue.png
Binary files differ
diff --git a/examples/location/mapviewer/resources/scale.png b/examples/location/mapviewer/resources/scale.png
new file mode 100644
index 000000000..c4f08122a
--- /dev/null
+++ b/examples/location/mapviewer/resources/scale.png
Binary files differ
diff --git a/examples/location/mapviewer/resources/scale_end.png b/examples/location/mapviewer/resources/scale_end.png
new file mode 100644
index 000000000..94510b125
--- /dev/null
+++ b/examples/location/mapviewer/resources/scale_end.png
Binary files differ