无法在鼠标单击时将地图圆圈添加到 QML 地图

cannot add MapCircles to QML Map on MouseClick

我正在尝试创建一些将在 QML 地图上动态移动的标记。然而,在此之前,我需要使用鼠标将它们全部绘制在地图上,以便稍后更新它们。我一直在使用 pyqtProperty 将我需要的坐标发送到 QML,但是当我尝试将它们添加到 MapItemView 时,它们是未定义的。下面的代码演示了我希望完成的事情。问题是我将 pyqtProperty 从另一个 python 对象传递给 QML,这个对象没有像 main.py 中那样用 setContextProperty() 添加吗?或者我是否错误地使用了 MapItemView 委托?

main.qml

import QtQuick 2.7
import QtQml 2.5
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import QtQuick.Window 2.2
import QtQuick.Layouts 1.2
import QtPositioning 5.9
import QtLocation 5.3
import QtQuick.Dialogs 1.1

ApplicationWindow {
    id: root
    width: 640
    height: 480
    visible: true

    ListModel {
        id: markers
    }

    Plugin {
        id: mapPlugin
        name: "osm" //"mapboxgl" "osm" "esri"
    }

    Map {
        id: map
        anchors.fill: parent
        plugin: mapPlugin
        center: atc.location
        zoomLevel: 14

        MapItemView {
            model: markers
            delegate: MapCircle {
                radius: 50
                color: 'red'
                center: markLocation //issue here? 
            }
        }

        MapCircle {
            id: home
            center: atc.location
            radius: 40
            color: 'white'
        }

        MouseArea {
            id: mousearea
            anchors.fill: map
            acceptedButtons: Qt.LeftButton | Qt.RightButton
            hoverEnabled: true
            property var coord: map.toCoordinate(Qt.point(mouseX, mouseY))

            onDoubleClicked: {
                if (mouse.button === Qt.LeftButton)
                {
                    //Capture information for model here
                    atc.plot_mark(
                        "marker",
                        mousearea.coord.latitude,
                        mousearea.coord.longitude)
                    markers.append({
                        name: "markers",
                        markLocation: atc.get_marker_center("markers")
                    })
                }
            }
        }
    }
}

atc.py

import geocoder
from DC import DC
from PyQt5.QtPositioning import QGeoCoordinate
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot

class ATC(QObject):
    #pyqt Signals
    locationChanged = pyqtSignal(QGeoCoordinate)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._location = QGeoCoordinate()
        self.dcs = {}
        g = geocoder.ip('me')
        self.set_location(QGeoCoordinate(*g.latlng))


    def set_location(self, coordinate):
        if self._location != coordinate:
            self._location = coordinate
            self.locationChanged.emit(self._location)


    def get_location(self):
        return self._location


    #pyqt Property
    location = pyqtProperty(QGeoCoordinate,
        fget=get_location,
        fset=set_location,
        notify=locationChanged)


    @pyqtSlot(str, str, str)
    def plot_mark(self, mark_name, lat, lng):
            dc = DC(mark_name)
            self.dcs[mark_name] = dc
            self.dcs[mark_name].set_location(
                QGeoCoordinate(float(lat), float(lng)))


    @pyqtSlot(str)
    def get_marker_center(self, mark_name):
        return self.dcs[mark_name].location

DC.py

from PyQt5.QtPositioning import QGeoCoordinate
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot

class DC(QObject):
    #pyqt Signals
    locationChanged = pyqtSignal(QGeoCoordinate)

    def __init__(self, name, parent=None):
        super().__init__(parent)
        self.name = name
        self._location = QGeoCoordinate()        


    def set_location(self, coordinate):
        if self._location != coordinate:
            self._location = coordinate
            self.locationChanged.emit(self._location)


    def get_location(self):
        return self._location


    #pyqt Property
    location = pyqtProperty(QGeoCoordinate,
        fget=get_location,
        fset=set_location,
        notify=locationChanged)

main.py

from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
from PyQt5.QtPositioning import QGeoCoordinate

from ATC import ATC

if __name__ == "__main__":
    import os
    import sys

    app = QGuiApplication(sys.argv)

    engine = QQmlApplicationEngine()

    atc = ATC()

    engine.rootContext().setContextProperty("atc", atc)

    qml_path = os.path.join(os.path.dirname(__file__), "main.qml")
    engine.load(QUrl.fromLocalFile(qml_path))

    if not engine.rootObjects():
        sys.exit(-1)

    engine.quit.connect(app.quit)
    sys.exit(app.exec_())

您必须在 python 中创建模型,而不是在 QML 中创建模型,以便能够直接处理它,您必须从 QAbstractListModel 继承。为了平滑移动,您应该使用 QxxxAnimation 作为 QPropertyAnimation。

在下面的例子中,每次插入一个标记,都会调用on_markersInserted函数,将标记移向window的中心。

main.py

from functools import partial
from PyQt5 import QtCore, QtGui, QtQml, QtPositioning
import geocoder

class Marker(QtCore.QObject):
    locationChanged = QtCore.pyqtSignal(QtPositioning.QGeoCoordinate)

    def __init__(self, location=QtPositioning.QGeoCoordinate(), parent=None):
        super().__init__(parent)
        self._location = location

    def set_location(self, coordinate):
        if self._location != coordinate:
            self._location = coordinate
            self.locationChanged.emit(self._location)

    def get_location(self):
        return self._location

    location = QtCore.pyqtProperty(QtPositioning.QGeoCoordinate,
        fget=get_location,
        fset=set_location,
        notify=locationChanged)

    def move(self, location, duration=1000):
        animation = QtCore.QPropertyAnimation(
            targetObject=self, 
            propertyName=b'location',
            startValue=self.get_location(),
            endValue=location,
            duration=duration,
            parent=self
        )
        animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)

    def moveFromTo(self, start, end, duration=1000):
        self.set_location(start)
        self.move(end, duration)

class MarkerModel(QtCore.QAbstractListModel):
    markersInserted = QtCore.pyqtSignal(list)

    PositionRole = QtCore.Qt.UserRole + 1000

    def __init__(self, parent=None):
        super().__init__(parent)
        self._markers = []
        self.rowsInserted.connect(self.on_rowsInserted)

    def rowCount(self, parent=QtCore.QModelIndex()):
        return 0 if parent.isValid() else len(self._markers)

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if index.isValid() and 0 <= index.row() < self.rowCount():
            if role == MarkerModel.PositionRole:
                return self._markers[index.row()].get_location()
        return QtCore.QVariant()

    def roleNames(self):
        roles = {}
        roles[MarkerModel.PositionRole] = b'position'
        return roles

    @QtCore.pyqtSlot(QtPositioning.QGeoCoordinate)
    def appendMarker(self, coordinate):
        self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
        marker = Marker(coordinate)
        self._markers.append(marker)
        self.endInsertRows()
        marker.locationChanged.connect(self.update_model)

    def update_model(self):
        marker = self.sender()
        try:
            row = self._markers.index(marker)
            ix = self.index(row)
            self.dataChanged.emit(ix, ix, (MarkerModel.PositionRole,))
        except ValueError as e:
            pass

    @QtCore.pyqtSlot(QtCore.QModelIndex, int, int)
    def on_rowsInserted(self, parent, first, end):
        markers = []
        for row in range(first, end+1):
            markers.append(self.get_marker(row))
        self.markersInserted.emit(markers)


    def get_marker(self, row):
        if 0 <= row < self.rowCount():
            return self._markers[row]

class ManagerMarkers(QtCore.QObject):
    locationChanged = QtCore.pyqtSignal(QtPositioning.QGeoCoordinate)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._center= Marker(parent=self)
        self._model = MarkerModel(self)  
        g = geocoder.ip('me')
        self.moveCenter(QtPositioning.QGeoCoordinate(*g.latlng)) 

    def model(self):
        return self._model     

    def center(self):
        return self._center

    def moveCenter(self, position):
        self._center.set_location(position)

    center = QtCore.pyqtProperty(QtCore.QObject, fget=center, constant=True)
    model = QtCore.pyqtProperty(QtCore.QObject, fget=model, constant=True)


# testing
# When a marker is added
# it will begin to move toward
# the center of the window
def on_markersInserted(manager, markers):
    end = manager.center.get_location()
    for marker in markers:
        marker.move(end, 5*1000)

if __name__ == "__main__":
    import os
    import sys

    app = QtGui.QGuiApplication(sys.argv)
    manager = ManagerMarkers()
    engine = QtQml.QQmlApplicationEngine()
    engine.rootContext().setContextProperty("manager", manager)
    qml_path = os.path.join(os.path.dirname(__file__), "main.qml")
    engine.load(QtCore.QUrl.fromLocalFile(qml_path))
    if not engine.rootObjects():
        sys.exit(-1)
    manager.model.markersInserted.connect(partial(on_markersInserted, manager))
    engine.quit.connect(app.quit)
    sys.exit(app.exec_())

main.qml

import QtQuick 2.7
import QtQuick.Controls 2.2
import QtPositioning 5.9
import QtLocation 5.3

ApplicationWindow {
    id: root
    width: 640
    height: 480
    visible: true

    Plugin {
        id: mapPlugin
        name: "osm" // "mapboxgl" "osm" "esri"
    }

    Map {
        id: map
        anchors.fill: parent
        plugin: mapPlugin
        center: manager.center.location
        zoomLevel: 14

        MapCircle {
            id: home
            center: manager.center.location
            radius: 40
            color: 'white'
        }

        MapItemView {
            model: manager.model
            delegate: MapCircle {
                radius: 50
                color: 'red'
                center: model.position
            }
        }

        MouseArea {
            id: mousearea
            anchors.fill: map
            acceptedButtons: Qt.LeftButton | Qt.RightButton
            onDoubleClicked: if (mouse.button === Qt.LeftButton)  
                                manager.model.appendMarker(map.toCoordinate(Qt.point(mouseX, mouseY)))
        }
    }
}