9. استخدام لوحة الخريطة¶
تلميح
تحتاج مقتطفات الشيفرة في هذه الصفحة إلى الاستيرادات التالية إذا كنت خارج وحدة تحكم pyqgis:
1from qgis.PyQt.QtGui import (
2 QColor,
3)
4
5from qgis.PyQt.QtCore import Qt, QRectF
6
7from qgis.PyQt.QtWidgets import QMenu
8
9from qgis.core import (
10 QgsVectorLayer,
11 QgsPoint,
12 QgsPointXY,
13 QgsProject,
14 QgsGeometry,
15 QgsMapRendererJob,
16 QgsWkbTypes,
17)
18
19from qgis.gui import (
20 QgsMapCanvas,
21 QgsVertexMarker,
22 QgsMapCanvasItem,
23 QgsMapMouseEvent,
24 QgsRubberBand,
25)
ربما يكون عنصر واجهة لوحة الخريطة أهم عنصر واجهة في QGIS، لأنه يعرض الخريطة المكوّنة من طبقات الخريطة المتراكبة ويتيح التفاعل مع الخريطة والطبقات. تعرض اللوحة دائمًا جزءًا من الخريطة يحدده الامتداد الحالي للوحة. ويتم التفاعل عبر استخدام أدوات الخريطة: هناك أدوات للتحريك، والتكبير/التصغير، والتعرّف على الطبقات، والقياس، وتحرير البيانات المتجهية وغيرها. وكما في برامج الرسوميات الأخرى، توجد دائمًا أداة واحدة نشطة ويستطيع المستخدم التبديل بين الأدوات المتاحة.
تُنفَّذ لوحة الخريطة بواسطة الصنف QgsMapCanvas في الوحدة qgis.gui. ويعتمد التنفيذ على إطار عمل Qt Graphics View. يوفّر هذا الإطار عمومًا سطحًا وعرضًا تُوضع فيهما عناصر رسومية مخصّصة ويمكن للمستخدم التفاعل معها. سنفترض أنك على دراية كافية بـ Qt لفهم مفاهيم المشهد الرسومي والعرض والعناصر. وإن لم تكن كذلك، فيُرجى قراءة نظرة عامة على الإطار.
كلما حُرّكت الخريطة أو كُبّرت/صُغّرت (أو أي إجراء آخر يستدعي التحديث)، يُعاد رسم الخريطة ضمن الامتداد الحالي. تُرسم الطبقات إلى صورة (باستخدام الصنف QgsMapRendererJob) وتُعرض تلك الصورة على اللوحة. ويتحكم الصنف QgsMapCanvas أيضًا في تحديث الخريطة المرسومة. وإلى جانب هذا العنصر الذي يعمل كخلفية، قد توجد عناصر أخرى للوحة الخريطة.
من عناصر لوحة الخريطة النموذجية الأشرطة المطاطية (rubber bands) (المستخدمة في القياس وتحرير البيانات المتجهية وغيرها) أو علامات الرؤوس. تُستخدم عناصر اللوحة عادةً لتقديم تغذية راجعة مرئية لأدوات الخريطة، فمثلًا عند إنشاء مضلع جديد، تُنشئ أداة الخريطة عنصر شريط مطاطي على اللوحة يعرض الشكل الحالي للمضلع. جميع عناصر لوحة الخريطة أصناف فرعية من QgsMapCanvasItem الذي يضيف بعض الوظائف إلى الكائنات الأساسية QGraphicsItem.
باختصار، تتكوّن بنية لوحة الخريطة من ثلاثة مفاهيم:
لوحة الخريطة --- لعرض الخريطة
عناصر لوحة الخريطة --- عناصر إضافية يمكن عرضها على لوحة الخريطة
أدوات الخريطة --- للتفاعل مع لوحة الخريطة
9.1. تضمين لوحة الخريطة¶
لوحة الخريطة عنصر واجهة مثل أي عنصر واجهة آخر في Qt، لذا فاستخدامها بسيط بقدر إنشائها وعرضها.
canvas = QgsMapCanvas()
canvas.show()
ينتج عن ذلك نافذة مستقلة تحتوي على لوحة خريطة. ويمكن أيضًا تضمينها في عنصر واجهة أو نافذة موجودة. عند استخدام ملفات .ui وبرنامج Qt Designer، ضع QWidget على النموذج ورقِّه إلى صنف جديد: عيّن QgsMapCanvas اسمًا للصنف و qgis.gui ملفًا للترويسة. وستتولى الأداة pyuic5 الأمر. هذه طريقة ملائمة جدًا لتضمين اللوحة. والاحتمال الآخر هو كتابة الشيفرة يدويًا لبناء لوحة الخريطة وعناصر الواجهة الأخرى (بوصفها أبناء لنافذة رئيسية أو نافذة حوار) وإنشاء تخطيط.
افتراضيًا، تكون خلفية لوحة الخريطة سوداء ولا تستخدم تنعيم الحواف (anti-aliasing). لتعيين خلفية بيضاء وتفعيل تنعيم الحواف للحصول على رسم سلس
canvas.setCanvasColor(Qt.white)
canvas.enableAntiAliasing(True)
(إن كنت تتساءل، فإن Qt تأتي من الوحدة PyQt.QtCore و Qt.white إحدى نسخ QColor المعرّفة مسبقًا.)
حان الوقت الآن لإضافة بعض طبقات الخريطة. سنفتح أولًا طبقة ونضيفها إلى المشروع الحالي. ثم سنعيّن امتداد اللوحة وقائمة الطبقات الخاصة باللوحة.
1vlayer = QgsVectorLayer("testdata/data/data.gpkg|layername=airports", "Airports layer", "ogr")
2if not vlayer.isValid():
3 print("Layer failed to load!")
4
5# add layer to the registry
6QgsProject.instance().addMapLayer(vlayer)
7
8# set extent to the extent of our layer
9canvas.setExtent(vlayer.extent())
10
11# set the map canvas layer set
12canvas.setLayers([vlayer])
بعد تنفيذ هذه الأوامر، يُفترض أن تعرض اللوحة الطبقة التي حمّلتها.
9.2. الأشرطة المطاطية وعلامات الرؤوس¶
لعرض بيانات إضافية فوق الخريطة في اللوحة، استخدم عناصر لوحة الخريطة. يمكن إنشاء أصناف مخصّصة لعناصر اللوحة (سنتناولها أدناه)، إلا أن هناك صنفين مفيدين لعناصر اللوحة للتسهيل: QgsRubberBand لرسم الخطوط المتعددة أو المضلعات، و QgsVertexMarker لرسم النقاط. وكلاهما يعمل بإحداثيات الخريطة، لذا يُحرَّك الشكل أو يُغيَّر مقياسه تلقائيًا عند تحريك اللوحة أو تكبيرها.
لعرض خط متعدد:
r = QgsRubberBand(canvas, QgsWkbTypes.LineGeometry) # line
points = [QgsPoint(-100, 45), QgsPoint(10, 60), QgsPoint(120, 45)]
r.setToGeometry(QgsGeometry.fromPolyline(points), None)
لعرض مضلع
r = QgsRubberBand(canvas, QgsWkbTypes.PolygonGeometry) # polygon
points = [[QgsPointXY(-100, 35), QgsPointXY(10, 50), QgsPointXY(120, 35)]]
r.setToGeometry(QgsGeometry.fromPolygonXY(points), None)
لاحظ أن نقاط المضلع ليست قائمة بسيطة: بل هي في الواقع قائمة حلقات تحتوي على الحلقات الخطية للمضلع: الحلقة الأولى هي الحد الخارجي، والحلقات التالية (الاختيارية) تقابل الثقوب في المضلع.
تتيح الأشرطة المطاطية بعض التخصيص، وتحديدًا تغيير لونها وعرض خطها
r.setColor(QColor(0, 0, 255))
r.setWidth(3)
ترتبط عناصر اللوحة بمشهد اللوحة. لإخفائها مؤقتًا (ثم إظهارها مجددًا)، استخدم الثنائي hide() و show(). ولإزالة العنصر نهائيًا، عليك إزالته من مشهد اللوحة
canvas.scene().removeItem(r)
(في C++ يمكن ببساطة حذف العنصر، أما في Python فإن del r سيحذف المرجع فقط وسيبقى الكائن موجودًا لأن اللوحة تملكه)
يمكن أيضًا استخدام الشريط المطاطي لرسم النقاط، لكن الصنف QgsVertexMarker أنسب لذلك (فالصنف QgsRubberBand سيرسم فقط مستطيلًا حول النقطة المطلوبة).
يمكنك استخدام علامة الرأس هكذا:
m = QgsVertexMarker(canvas)
m.setCenter(QgsPointXY(10,40))
سيرسم هذا علامة تقاطع حمراء عند الموضع [10,45]. ويمكن تخصيص نوع الأيقونة وحجمها ولونها وعرض القلم
m.setColor(QColor(0, 255, 0))
m.setIconSize(5)
m.setIconType(QgsVertexMarker.ICON_BOX) # or ICON_CROSS, ICON_X
m.setPenWidth(3)
لإخفاء علامات الرؤوس مؤقتًا وإزالتها من اللوحة، استخدم الدوال نفسها المستخدمة مع الأشرطة المطاطية.
9.3. استخدام أدوات الخريطة مع اللوحة¶
يبني المثال التالي نافذة تحتوي على لوحة خريطة وأدوات خريطة أساسية لتحريك الخريطة وتكبيرها/تصغيرها. تُنشأ إجراءات لتفعيل كل أداة: يتم التحريك باستخدام QgsMapToolPan، والتكبير/التصغير بزوج من نسخ QgsMapToolZoom. تُعيَّن الإجراءات قابلةً للتأشير ثم تُسند لاحقًا إلى الأدوات للسماح بالمعالجة التلقائية لحالة التأشير/عدم التأشير للإجراءات -- فعند تفعيل أداة خريطة، يُعلَّم إجراؤها على أنه محدد ويُلغى تحديد إجراء أداة الخريطة السابقة. تُفعَّل أدوات الخريطة باستخدام الدالة setMapTool().
1from qgis.gui import *
2from qgis.PyQt.QtWidgets import QAction, QMainWindow
3from qgis.PyQt.QtCore import Qt
4
5class MyWnd(QMainWindow):
6 def __init__(self, layer):
7 QMainWindow.__init__(self)
8
9 self.canvas = QgsMapCanvas()
10 self.canvas.setCanvasColor(Qt.white)
11
12 self.canvas.setExtent(layer.extent())
13 self.canvas.setLayers([layer])
14
15 self.setCentralWidget(self.canvas)
16
17 self.actionZoomIn = QAction("Zoom in", self)
18 self.actionZoomOut = QAction("Zoom out", self)
19 self.actionPan = QAction("Pan", self)
20
21 self.actionZoomIn.setCheckable(True)
22 self.actionZoomOut.setCheckable(True)
23 self.actionPan.setCheckable(True)
24
25 self.actionZoomIn.triggered.connect(self.zoomIn)
26 self.actionZoomOut.triggered.connect(self.zoomOut)
27 self.actionPan.triggered.connect(self.pan)
28
29 self.toolbar = self.addToolBar("Canvas actions")
30 self.toolbar.addAction(self.actionZoomIn)
31 self.toolbar.addAction(self.actionZoomOut)
32 self.toolbar.addAction(self.actionPan)
33
34 # create the map tools
35 self.toolPan = QgsMapToolPan(self.canvas)
36 self.toolPan.setAction(self.actionPan)
37 self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
38 self.toolZoomIn.setAction(self.actionZoomIn)
39 self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
40 self.toolZoomOut.setAction(self.actionZoomOut)
41
42 self.pan()
43
44 def zoomIn(self):
45 self.canvas.setMapTool(self.toolZoomIn)
46
47 def zoomOut(self):
48 self.canvas.setMapTool(self.toolZoomOut)
49
50 def pan(self):
51 self.canvas.setMapTool(self.toolPan)
يمكنك تجربة الشيفرة أعلاه في محرّر وحدة تحكم Python. لاستدعاء نافذة اللوحة، أضف الأسطر التالية لإنشاء نسخة من الصنف MyWnd. وستعرض الطبقة المحددة حاليًا على اللوحة المنشأة حديثًا
w = MyWnd(iface.activeLayer())
w.show()
9.3.1. تحديد معلم باستخدام QgsMapToolIdentifyFeature¶
يمكنك استخدام أداة الخريطة QgsMapToolIdentifyFeature لتطلب من المستخدم تحديد معلم سيُرسَل إلى دالة استدعاء راجع (callback).
1def callback(feature):
2 """Code called when the feature is selected by the user"""
3 print("You clicked on feature {}".format(feature.id()))
4
5canvas = iface.mapCanvas()
6feature_identifier = QgsMapToolIdentifyFeature(canvas)
7
8# indicates the layer on which the selection will be done
9feature_identifier.setLayer(vlayer)
10
11# use the callback as a slot triggered when the user identifies a feature
12feature_identifier.featureIdentified.connect(callback)
13
14# activation of the map tool
15canvas.setMapTool(feature_identifier)
9.4. كتابة أدوات خريطة مخصّصة¶
يمكنك كتابة أدواتك المخصّصة لتنفيذ سلوك مخصّص للإجراءات التي يقوم بها المستخدمون على اللوحة.
يجب أن ترث أدوات الخريطة من الصنف QgsMapTool أو أي صنف مشتق منه، وأن تُختار أدواتٍ نشطة في اللوحة باستخدام الدالة setMapTool() كما رأينا سابقًا.
فيما يلي مثال على أداة خريطة تتيح تعريف امتداد مستطيل بالنقر والسحب على اللوحة. وعند تعريف المستطيل، تطبع إحداثيات حدوده في وحدة التحكم. وتستخدم عناصر الشريط المطاطي الموصوفة سابقًا لعرض المستطيل المحدد أثناء تعريفه.
1class RectangleMapTool(QgsMapToolEmitPoint):
2 def __init__(self, canvas):
3 self.canvas = canvas
4 QgsMapToolEmitPoint.__init__(self, self.canvas)
5 self.rubberBand = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
6 self.rubberBand.setColor(Qt.red)
7 self.rubberBand.setWidth(1)
8 self.reset()
9
10 def reset(self):
11 self.startPoint = self.endPoint = None
12 self.isEmittingPoint = False
13 self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
14
15 def canvasPressEvent(self, e):
16 self.startPoint = self.toMapCoordinates(e.pos())
17 self.endPoint = self.startPoint
18 self.isEmittingPoint = True
19 self.showRect(self.startPoint, self.endPoint)
20
21 def canvasReleaseEvent(self, e):
22 self.isEmittingPoint = False
23 r = self.rectangle()
24 if r is not None:
25 print("Rectangle:", r.xMinimum(),
26 r.yMinimum(), r.xMaximum(), r.yMaximum()
27 )
28
29 def canvasMoveEvent(self, e):
30 if not self.isEmittingPoint:
31 return
32
33 self.endPoint = self.toMapCoordinates(e.pos())
34 self.showRect(self.startPoint, self.endPoint)
35
36 def showRect(self, startPoint, endPoint):
37 self.rubberBand.reset(QgsWkbTypes.PolygonGeometry)
38 if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
39 return
40
41 point1 = QgsPointXY(startPoint.x(), startPoint.y())
42 point2 = QgsPointXY(startPoint.x(), endPoint.y())
43 point3 = QgsPointXY(endPoint.x(), endPoint.y())
44 point4 = QgsPointXY(endPoint.x(), startPoint.y())
45
46 self.rubberBand.addPoint(point1, False)
47 self.rubberBand.addPoint(point2, False)
48 self.rubberBand.addPoint(point3, False)
49 self.rubberBand.addPoint(point4, True) # true to update canvas
50 self.rubberBand.show()
51
52 def rectangle(self):
53 if self.startPoint is None or self.endPoint is None:
54 return None
55 elif (self.startPoint.x() == self.endPoint.x() or \
56 self.startPoint.y() == self.endPoint.y()):
57 return None
58
59 return QgsRectangle(self.startPoint, self.endPoint)
60
61 def deactivate(self):
62 QgsMapTool.deactivate(self)
63 self.deactivated.emit()
9.5. كتابة عناصر مخصّصة للوحة الخريطة¶
فيما يلي مثال على عنصر لوحة مخصّص يرسم دائرة:
1class CircleCanvasItem(QgsMapCanvasItem):
2 def __init__(self, canvas):
3 super().__init__(canvas)
4 self.center = QgsPoint(0, 0)
5 self.size = 100
6
7 def setCenter(self, center):
8 self.center = center
9
10 def center(self):
11 return self.center
12
13 def setSize(self, size):
14 self.size = size
15
16 def size(self):
17 return self.size
18
19 def boundingRect(self):
20 return QRectF(self.center.x() - self.size/2,
21 self.center.y() - self.size/2,
22 self.center.x() + self.size/2,
23 self.center.y() + self.size/2)
24
25 def paint(self, painter, option, widget):
26 path = QPainterPath()
27 path.moveTo(self.center.x(), self.center.y());
28 path.arcTo(self.boundingRect(), 0.0, 360.0)
29 painter.fillPath(path, QColor("red"))
30
31
32# Using the custom item:
33item = CircleCanvasItem(iface.mapCanvas())
34item.setCenter(QgsPointXY(200,200))
35item.setSize(80)