无法在鼠标单击时将地图圆圈添加到 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)))
}
}
}
我正在尝试创建一些将在 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)))
}
}
}