Michael Janson
Michael Janson

Reputation: 111

Creating and adding MapQuickItem to Map in PyQt

I'm using PyQt5 to access QML code through a QQuickWidget in a UI file. My QML file creates a map and plots points. I want to add/modify these points from my python code. I'm able to access the Map object in QML in python, but PyQt treats it and the MapQuickItem as QQuickItems. I'm not sure how to actually create a new MapQuickItem in python and add it to the Map object. I've tried creating a QQuickItem with the necessary properties and then using the addMapItem method, but receive this error:

TypeError: unable to convert argument 0 of QQuickItem.addMapItem from 'QQuickItem' to 'QDeclarativeGeoMapItemBase*'"

I don't know how to create a QDeclarativeGeoMapItemBase object in PyQt or if I should be going about this another way.

As you can see, I'm also having some trouble correctly referencing the object in the QML file. self.map or self.map.rootObject() gets me the QQuickWidget in the UI, with self.map.rootObject().children()[1] getting me the Map object within the QML. I'd prefer to locate the items by their ID using findChild(), but haven't been able to. Is there a better method? Should a create a Python object which replicates the structure of my QML file?

This is a sample of the QML code. I've referenced this QML file as a QQuickWidget in a UI file.

 Rectangle {
id:rectangle

Plugin {
    id: osmPlugin
    name: "osm"
}

property variant locationTC: QtPositioning.coordinate(44.951, -93.192)

Map {
    id: map
    anchors.fill: parent
    plugin: osmPlugin
    center: locationTC
    zoomLevel: 10

    MapQuickItem {
        coordinate: QtPositioning.coordinate(44.97104,-93.46055)
        anchorPoint.x: image.width * 0.5
        anchorPoint.y: image.height
        sourceItem:
            Image { id: image; source: "marker.png" }

    }
  }
}

Below is a sample of the PyQt code where I attempt to create a MapQuickItem and add it to the map.

from PyQt5 import QtCore, uic, QtWidgets, QtPositioning, QtLocation, QtQml, QtQuick

form_class = uic.loadUiType("TTRMS.ui")[0]     

class MainWindow(QtWidgets.QMainWindow, form_class):
    '''
    classdocs
    '''
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.setupUi(self)        

        tmc = QQuickItem()
        new_coordinate = QtPositioning.QGeoCoordinate()
        new_coordinate.setLatitude(44.951)
        new_coordinate.setLongitude(-93.192)
        tmc.setProperty("coordinate",new_coordinate)
        tmc.setProperty("anchorPoint",QtCore.QPointF(12.5, 32.0))
        image = QQuickItem()
        image.setProperty("source", QtCore.QUrl.fromLocalFile(("marker.png")))
        tmc.setProperty("sourceItem", image)
        image.setParent(tmc)
        self.map.rootObject().children()[1].addMapItem(tmc)

I'm running everything on Windows 7. The PyQt5 development is done in Eclipse using PyDev and Python 3.4 (32 bit), the QML coding in Qt Creator 5.5 and the UI in Qt Designer 5.5.

Upvotes: 4

Views: 3558

Answers (1)

eyllanesc
eyllanesc

Reputation: 244291

In the cases that you want to interact with C++/Python with QML it is better to expose some object to QML that allows to transmit the data of C++/Python to QML since the latter has a different life cycle.

In this particular case I will create a model that stores the data, send it to QML through setContextProperty(), and on the QML side use MapItemView with a delegate so you can have many markers.

main.py

import os
from PyQt5 import QtCore, QtWidgets, QtQuickWidgets, QtPositioning

class MarkerModel(QtCore.QAbstractListModel):
    PositionRole, SourceRole = range(QtCore.Qt.UserRole, QtCore.Qt.UserRole + 2)

    def __init__(self, parent=None):
        super(MarkerModel, self).__init__(parent)
        self._markers = []

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self._markers)

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if 0 <= index.row() < self.rowCount():
            if role == MarkerModel.PositionRole:
                return self._markers[index.row()]["position"]
            elif role == MarkerModel.SourceRole:
                return self._markers[index.row()]["source"]
        return QtCore.QVariant()

    def roleNames(self):
        return {MarkerModel.PositionRole: b"position_marker", MarkerModel.SourceRole: b"source_marker"}

    def appendMarker(self, marker):
        self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
        self._markers.append(marker)
        self.endInsertRows()

class MapWidget(QtQuickWidgets.QQuickWidget):
    def __init__(self, parent=None):
        super(MapWidget, self).__init__(parent,
            resizeMode=QtQuickWidgets.QQuickWidget.SizeRootObjectToView)
        model = MarkerModel(self)
        self.rootContext().setContextProperty("markermodel", model)
        qml_path = os.path.join(os.path.dirname(__file__), "main.qml")
        self.setSource(QtCore.QUrl.fromLocalFile(qml_path))

        positions = [(44.97104,-93.46055), (44.96104,-93.16055)]
        urls = ["http://maps.gstatic.com/mapfiles/ridefinder-images/mm_20_gray.png", 
                "http://maps.gstatic.com/mapfiles/ridefinder-images/mm_20_red.png"]

        for c, u in zip(positions, urls):
            coord = QtPositioning.QGeoCoordinate(*c)
            source = QtCore.QUrl(u)
            model.appendMarker({"position": coord , "source": source})


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = MapWidget()
    w.show()
    sys.exit(app.exec_())

main.qml

import QtQuick 2.11
import QtPositioning 5.11
import QtLocation 5.11

Rectangle {
    id:rectangle
    width: 640
    height: 480
    Plugin {
        id: osmPlugin
        name: "osm"
    }
    property variant locationTC: QtPositioning.coordinate(44.951, -93.192)
    Map {
        id: map
        anchors.fill: parent
        plugin: osmPlugin
        center: locationTC
        zoomLevel: 10
        MapItemView{
            model: markermodel
            delegate: MapQuickItem {
                coordinate: model.position_marker
                anchorPoint.x: image.width
                anchorPoint.y: image.height
                sourceItem:
                    Image { id: image; source: model.source_marker }
            }
        }
    }
}

enter image description here

Upvotes: 2

Related Questions