Getting latitude and longitude from coordinate in QML - qt

I am having a QML code which shows map, it has a MapQuickItem for an image.
MapQuickItem {
id: transMarker
sourceItem: Image {
id: transImage
width: 50
height: 50
source: "trans.png"
}
}
When I clicks on the map, it should paste that image on the map, I could achieve that by below code
transMarker.coordinate = map.toCoordinate(Qt.point(mouse.x,mouse.y))
I want to save the position permanently, but the problem is I am trying to print map.toCoordinate(Qt.point(mouse.x,mouse.y))
It prints in degree and minutes (Coordinate: 8° 29' 21.4" N, 76° 57' 41.9" E)
I want to get that as decimal latitude and longitude (Coordinate: 76.9616344 8.4892798).
How this can be accomplished?

You have to use the latitude and longitude properties of coordinate:
Map {
id: map
anchors.fill: parent
plugin: Plugin {
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75)
zoomLevel: 10
MapQuickItem {
id: transMarker
sourceItem: Image {
id: transImage
width: 50
height: 50
source: "trans.png"
}
}
MouseArea{
anchors.fill: parent
onClicked: {
var coord = map.toCoordinate(Qt.point(mouse.x,mouse.y));
transMarker.coordinate = coord;
console.log(coord.latitude, coord.longitude)
}
}
}
Output:
qml: 59.969159320456804 10.824157714841107
qml: 59.98427615215763 10.895568847649372
qml: 59.989771470871446 10.780212402338407
qml: 59.965722714293186 10.652496337891108

Related

Qt Select At Most 1 Marker on Map

In my code every marker that I clicked are selected(turn into green from red). I want just 1 can change. When I click another marker the marker I clicked before turns red again. Or When I click an empty area the marker I clicked before turns red again.
In qml my Item's code:
Component {
id: hazardous_img
MapQuickItem {
id: hazardousitem
anchorPoint.x: image.width/4
anchorPoint.y: image.height
coordinate: position
property bool isClicked: false
MouseArea {
anchors.fill: parent
onDoubleClicked: {
mainwindow.hazardousIconClicked(mapview.toCoordinate(Qt.point(mouse.x,mouse.y)))
}
onClicked: {
if (isClicked === false) {
image.source = "qrc:/grn-pushpin.png"
isClicked = true
} else {
image.source = "qrc:/red-pushpin.png"
isClicked = false
}
}
}
sourceItem: Image {
id: image
source: "qrc:/red-pushpin.png"
}
}
}
In QML this is usually done with using a ButtonGroup, but as you're not using AbstractButtons you need to write it yourself. Here is my solution for it.
I've used the ListModel to not only store the coordinates of each marker, but also a selected flag which is set to false by default. In the delegate I'm using the selected data role to show if a marker is selected or not.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: window
width: 640
height: 480
visible: true
title: qsTr("Map")
ListModel { id: markerModel }
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: {
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
var jsonObject = JSON.parse(JSON.stringify(coordinate))
jsonObject["selected"] = false
markerModel.append(jsonObject)
}
onClicked: map.deselectAll()
}
MapItemView {
model: markerModel
delegate: markerDelegate
}
function deselectAll() {
for (var i = 0; i < markerModel.count; ++i)
markerModel.setProperty(i, "selected", false)
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
required property bool selected
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: markerItem.selected ? "red" : "gray"
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: {
map.deselectAll()
markerModel.setProperty(markerItem.index, "selected", true)
}
}
}
}
}
}
I came up with yet another solution without looping over all items in the model. It just stores the index of the selected marker in a dedicated property. This has the drawback that if the model order changes the index can become invalid, also potential multi selection is hard to handle, but on the other hand it is faster because it doesn't need to iterate over all items.
I experimented a lot with DelegateModel, it seems to be a perfect match if one could use it in combination with MapItemView, because of the groups and the attached properties like inGroupName.
After that I've tried ItemSelectionModel, but it seems it is only intended to be used in combination with a view, e.g. TreeView. I couldn't find out how to generate a QModelIndex in QML without a TreeView.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Map")
property int selectedMarker: -1
Map {
id: map
anchors.fill: parent
plugin: Plugin {
id: mapPlugin
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: {
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
markerModel.append(JSON.parse(JSON.stringify(coordinate)))
}
onClicked: root.selectedMarker = -1
}
MapItemView {
model: ListModel { id: markerModel }
delegate: markerDelegate
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: markerItem.index === root.selectedMarker ? "red" : "gray"
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: root.selectedMarker = markerItem.index
}
}
}
}
}
I promise this is the last answer on that question.
This one is using an ItemSelectionModel and a few undocumented functions, e.g. ListModel.index(row, col).
itemSelectionModel.hasSelection is used in the color binding to trigger a reevaluation in order to call isRowSelected and set the color accordingly whenever the selection has changed.
If the user clicks on the background the clear() is called to clear the selection.
I think out of the three this is the best solution. It can be easily upgraded to allow multi selection as shown below. Also the ItemSelectionModel can be used by other views to show the data and selection.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
import QtQml.Models 2.15
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Map")
Map {
id: map
anchors.fill: parent
plugin: Plugin {
id: mapPlugin
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: function(mouse) {
markerModel.append(map.toCoordinate(Qt.point(mouse.x, mouse.y)))
}
onClicked: itemSelectionModel.clear()
}
MapItemView {
model: ListModel { id: markerModel }
delegate: markerDelegate
}
ItemSelectionModel {
id: itemSelectionModel
model: markerModel
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: {
itemSelectionModel.hasSelection
return itemSelectionModel.isRowSelected(markerItem.index) ? "red" : "gray"
}
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: itemSelectionModel.select(markerModel.index(markerItem.index, 0),
ItemSelectionModel./*ClearAnd*/Select)
}
}
}
}
}

QtLocation QML: MouseArea stop Updating on hover

I have a qml map application and I added a marker as MapQuickItem.
I have a lat lon display connected to map Mousearea, when I move the mouse on the map i read lat/lon in realtime, and all works well.
marker.qml is a mapquickItem with its Mousearea, when the mouse is "hover" the marker I write a string into the "labelLatLon" of the map.
ALL Works well, but when I exited the mouse from the marker, the "labelLatLon" is no more been updated with the mouse coordinate, it stops updating lat lon. I move the mouse but no more lat/lon update... It seems the main MouseArea stop "hearing" mouse onhover..
This is the snipping code to test: the CROSS IMAGE is from resources.
Rectangle {
id: mainWindow
visible: true
Plugin {
id: mapPlugin
name: "osm"
PluginParameter {
name: "osm.mapping.providersrepository.disabled"
value: "true"
}
PluginParameter {
name: "osm.mapping.providersrepository.address"
value: "http://maps-redirect.qt.io/osm/5.6/"
}
}
function addMarker(latitude,longitude) {
var Component = Qt.createComponent("qrc:///qml/marker.qml")
var item = Component.createObject(Item, {
coordinate: QtPositioning.coordinate(latitude, longitude)
})
map.addMapItem(item);
}
function setLatLonBox(coordinate) {
labelLatLon.text= "Lat: %1; Lon:%2".arg(coordinate.latitude).arg(coordinate.longitude)
}
Map {
id: map
gesture.enabled: true
copyrightsVisible : true
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(44.0, 9.3) // La Spezia
zoomLevel: 10
Component.onCompleted:addMarker(44.0, 9.3)
MouseArea {
id: mapMouseArea
property int pressX : -1
property int pressY : -1
property int jitterThreshold : 10
property int lastX: -1
property int lastY: -1
property var coordinate: map.toCoordinate(Qt.point(mouseX, mouseY))
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled : true
ColumnLayout {
id: layout
spacing: 5
z: 5 // ordine alto
Rectangle {
id: latLonArea
z: 5 // ordine alto
width: 320
height: 40
color: "grey"
opacity: 0.7
border.color: "black"
border.width: 1
Label {
id: labelLatLon
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.bold: true
color: "black"
text: "Lat: %1; Lon:%2".arg(mapMouseArea.coordinate.latitude).arg(mapMouseArea.coordinate.longitude)
}
}
}
}
}
}
and marker.qml
MapQuickItem {
id: marker
z: 2 //ordine basso
anchorPoint.x: marker.width / 2
anchorPoint.y: marker.height /2
property int idx
sourceItem: Image{
id: icon
source: "../symbols/Red_Cross.png"
sourceSize.width: 40
sourceSize.height: 40
opacity: markerMouseArea.pressed ? 0.6 : 1.0
}
MouseArea {
id: markerMouseArea
property int pressX : -1
property int pressY : -1
property int jitterThreshold : 10
property int lastX: -1
property int lastY: -1
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled : true
drag.target: marker
onEntered: {
var coordinate = map.toCoordinate(Qt.point(mouseX, mouseY));
setLatLonBox(coordinate);
}
}
}
Your problem is the label text property
text: "Lat: %1; Lon:%2".arg(mapMouseArea.coordinate.latitude).arg(mapMouseArea.coordinate.longitude)
Once you overwrite it with setLatLonBox, all the bindings on it are gone.
You have to re-set it after you exit the MQI mouse area (or you re-enter map mouse area)

how to update or reload MapQuickItem when Map is modified size?

Use MapQuickItem to display a component on a map, the quickitem's coordination is not changed, but when the map is modified size( width or height), the quickitem will dispear on a wrong coordination, how to reset quickitem's coordination(latitude, longitude)
Map {
id: map
height: 100 // for example, i change the height, marker's position will not update
width: 100 // but,,, if change, width , will auto update.
MapItemView {
model: xxxx
delegate: MapQuickItem {
id: marker
anchorPoint.x: image.width/4
anchorPoint.y: image.height
coordinate: object.coordinate
sourceItem: Image {
id: image
source: "xxxx.png"
}
}
}
}
i.e the marker does not adjust the position (not coordinate) as map's size changed.
A little late but here's how I did it, because I just had the same problem on macOS and Windows and it may help anyone.
As I am on Qt 5.11, I don't have access to onVisibleRegionChanged signal, so I used onHeightChanged and onWidthChanged signals to trigger a timer when resizing is done. To simulate map movement and trigger refresh, I used pan() function.
Map {
id: map
height: 500
width: 500
onHeightChanged: {
resizeBugTimer.restart();
}
onWidthChanged: {
resizeBugTimer.restart();
}
Timer {
id: resizeBugTimer
interval: 50
repeat: false
running: false
onTriggered: {
map.fixPositionOnResizeBug();
}
}
function fixPositionOnResizeBug() {
pan(1, 1);
pan(-1, -1);
}
MapItemView {
model: xxxx
delegate: MapQuickItem {
id: marker
anchorPoint.x: image.width/4
anchorPoint.y: image.height
coordinate: object.coordinate
sourceItem: Image {
id: image
source: "xxxx.png"
}
}
}
}
Hope this helps.

How to drag multiple targets on a map?

I wanted to implement a function that users can drag the marker (which is defined as MapQuickItem) on the map and automatically change its path (which is defined as MapPolyline). Currently I can only drag the marker but don't know how to change its path.
If I want to define a DropArea under the Map and call the MapPolyline.removeCoordinate() function to change the path, how to visit the index in the delegate? And I'm not sure if this idea will work.
Here is the code:
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
zoomLevel: 14
activeMapType: supportedMapTypes[7]
/* maker */
MouseArea {
anchors.fill: parent
onClicked: {
var crd = map.toCoordinate(Qt.point(mouseX, mouseY))
console.log(crd)
markerModel.append({ "latitude": crd.latitude, "longitude": crd.longitude})
line.addCoordinate(crd)
}
}
MapItemGroup {
MapPolyline {
id: line
line.width: 3
line.color: "#515151"
}
MapItemView {
add: Transition {}
remove: Transition {}
model: ListModel {
id: markerModel
}
delegate:
MapQuickItem {
id: marker
coordinate: QtPositioning.coordinate(latitude, longitude)
anchorPoint: Qt.point(markerImage.width * 0.5, markerImage.height * 0.5)
sourceItem: Image {
id: markerImage
z: 5
width: 30
height: 30
source: index <= 0 ? "Images/starting point.svg" : "Images/point black.svg"
MouseArea {
anchors.fill: parent
onClicked: {
line.removeCoordinate(index);
markerModel.remove(index);
}
drag.target: marker
}
}
}
/* maker */
}
}
}
I have tried several methods to solve the problem.
This method is not feasible cos the event (drag.onActiveChanged) is triggered only at the moment that the drag event happens.
drag.onActiveChanged: {
if(mouseArea.drag.active) {
line.replaceCoordinate(index, marker.coordinate);
}
}
I tried to define a DropArea under the Map and called the drag.ondragStarted() function to trigger the event, but I didn't figure out how to visit the index in the delegate, then I gave up.
It worked! When I dragged the marker on the map, the path automatically changed! The event (onPositionChanged) is triggered everytime the coordinate of marker changes.
onPositionChanged: {
line.replaceCoordinate(index, marker.coordinate);
}
Thank myself :-)

QML append new PathCurve elements to List<PathElements> in ShapePath

To be specific: I want to connect several Points on a Map with a spline curve. New points can be added with a Mouseclick and should also be connected to the existing Path. The points are stored within a model, so I can access them also in C++.
Unfortunately I can't figure out, how I can append new PathCurve elements to the existing List in the Shape::ShapePath Object.
I expected that something like this should work:
...
MapQuickItem {
coordinate: QtPositioning.coordinate(0.0000, 0.0000)
sourceItem: Shape {
id: myShape
anchors.fill: parent
vendorExtensionsEnabled: false
ShapePath {
id: myPath
strokeColor: "black"
strokeWidth: 2
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 0; startY: 0
}
}
zoomLevel: 15
}
MouseArea {
anchors.fill: parent
onClicked: {
var coord = parent.toCoordinate(Qt.point(mouse.x,mouse.y))
myPath.pathElements.push( new PathCurve(mouse.x, mouse.y) ) //does not work
}
}
I also tried to fill the PathElements from C++, but the PathCurve class seems to be private and is only usable from within QML. Hardcoding PathCurve Elements works just fine like in every online example, but I want to dynamically modify the list of pathelements.
Any help would be appreciated very much!
You must dynamically components using the function createQmlObject, but for this you must bear in mind that it depends on the zoomLevel that you apply to the MapQuickItem the relation of sizes since that depends on the painting, the PathCurve does not use coordinates of the window but the coordinates of the Shape, and the Shape is painted according to how it is configured. So for in this case the zoomLevel of the MapQuickItem must be 0 or the same as the map. Considering the above, the solution is:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Shapes 1.12
import QtPositioning 5.9
import QtLocation 5.3
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl" "osm" "esri"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
zoomLevel: 14
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
MapQuickItem {
id: map_item
coordinate: QtPositioning.coordinate(59.91, 10.75)
anchorPoint.x : 0
anchorPoint.y : 0
zoomLevel: map.zoomLevel
sourceItem: Shape {
id: myShape
anchors.fill: parent
vendorExtensionsEnabled: false
ShapePath {
id: myPath
strokeColor: "black"
strokeWidth: 2
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 0; startY: 0
}
}
}
}
MouseArea {
id: mousearea
anchors.fill: map
onClicked: {
var item_pos = map.fromCoordinate(map_item.coordinate, false)
var pathcurve = Qt.createQmlObject('import QtQuick 2.12; PathCurve {}',
myPath);
pathcurve.x = mouse.x - item_pos.x
pathcurve.y = mouse.y - item_pos.y
myPath.pathElements.push(pathcurve)
}
}
}

Resources