10. رسم الخرائط وطباعتها¶
تلميح
تحتاج مقتطفات الشيفرة في هذه الصفحة إلى الاستيرادات التالية:
1import os
2
3from qgis.core import (
4 QgsGeometry,
5 QgsMapSettings,
6 QgsPrintLayout,
7 QgsMapSettings,
8 QgsMapRendererParallelJob,
9 QgsLayoutItemLabel,
10 QgsLayoutItemLegend,
11 QgsLayoutItemMap,
12 QgsLayoutItemPolygon,
13 QgsLayoutItemScaleBar,
14 QgsLayoutExporter,
15 QgsLayoutItem,
16 QgsLayoutPoint,
17 QgsLayoutSize,
18 QgsUnitTypes,
19 QgsProject,
20 QgsFillSymbol,
21 QgsAbstractValidityCheck,
22 check,
23)
24
25from qgis.PyQt.QtGui import (
26 QPolygonF,
27 QColor,
28)
29
30from qgis.PyQt.QtCore import (
31 QPointF,
32 QRectF,
33 QSize,
34)
هناك عمومًا منهجان عندما يجب رسم بيانات الإدخال على هيئة خريطة: إما القيام بذلك بالطريقة السريعة باستخدام QgsMapRendererJob، أو إنتاج مخرجات مضبوطة بدقة أكبر بتركيب الخريطة باستخدام الصنف QgsLayout.
10.1. الرسم البسيط¶
يتم الرسم بإنشاء كائن QgsMapSettings لتعريف إعدادات الرسم، ثم بناء QgsMapRendererJob بهذه الإعدادات. ويُستخدم الأخير بعد ذلك لإنشاء الصورة الناتجة.
إليك مثالًا:
1image_location = os.path.join(QgsProject.instance().homePath(), "render.png")
2
3vlayer = iface.activeLayer()
4settings = QgsMapSettings()
5settings.setLayers([vlayer])
6settings.setBackgroundColor(QColor(255, 255, 255))
7settings.setOutputSize(QSize(800, 600))
8settings.setExtent(vlayer.extent())
9
10render = QgsMapRendererParallelJob(settings)
11
12def finished():
13 img = render.renderedImage()
14 # save the image; e.g. img.save("/Users/myuser/render.png","png")
15 img.save(image_location, "png")
16
17render.finished.connect(finished)
18
19# Start the rendering
20render.start()
21
22# The following loop is not normally required, we
23# are using it here because this is a standalone example.
24from qgis.PyQt.QtCore import QEventLoop
25loop = QEventLoop()
26render.finished.connect(loop.quit)
27loop.exec_()
10.2. رسم طبقات بأنظمة إحداثيات مرجعية مختلفة¶
إذا كانت لديك أكثر من طبقة ولها أنظمة إحداثيات مرجعية (CRS) مختلفة، فلن يعمل المثال البسيط أعلاه على الأرجح: للحصول على القيم الصحيحة من حسابات الامتداد عليك تعيين نظام الإحداثيات المرجعي الهدف صراحةً
layers = [iface.activeLayer()]
settings = QgsMapSettings()
settings.setLayers(layers)
settings.setDestinationCrs(layers[0].crs())
10.3. الإخراج باستخدام تخطيط الطباعة¶
تخطيط الطباعة أداة مفيدة جدًا إذا أردت إخراجًا أكثر تطورًا من الرسم البسيط المعروض أعلاه. فمن الممكن إنشاء تخطيطات خرائط معقدة تتكوّن من عروض خريطة وتسميات ومفتاح خريطة وجداول وعناصر أخرى توجد عادةً في الخرائط الورقية. ويمكن بعد ذلك تصدير التخطيطات إلى PDF أو SVG أو صور شبكية أو طباعتها مباشرة على طابعة.
يتكوّن التخطيط من مجموعة من الأصناف، وجميعها تنتمي إلى مكتبة core. يحتوي تطبيق QGIS على واجهة رسومية ملائمة لوضع العناصر، لكنها غير متاحة في مكتبة GUI. إذا لم تكن على دراية بـ إطار عمل Qt Graphics View، فننصحك بمراجعة توثيقه الآن، لأن التخطيط مبني عليه.
الصنف المركزي للتخطيط هو الصنف QgsLayout، المشتق من الصنف QGraphicsScene في Qt. لننشئ نسخة منه:
project = QgsProject.instance()
layout = QgsPrintLayout(project)
layout.initializeDefaults()
يهيّئ هذا التخطيط ببعض الإعدادات الافتراضية، وتحديدًا بإضافة صفحة A4 فارغة إلى التخطيط. يمكنك إنشاء تخطيطات دون استدعاء الدالة initializeDefaults()، لكن سيتعيّن عليك الاهتمام بإضافة الصفحات إلى التخطيط بنفسك.
تُنشئ الشيفرة السابقة تخطيطًا "مؤقتًا" غير مرئي في الواجهة الرسومية. وقد يكون ذلك مفيدًا مثلًا لإضافة بعض العناصر بسرعة والتصدير دون تعديل المشروع نفسه أو إظهار هذه التغييرات للمستخدم. إذا أردت حفظ التخطيط/استعادته مع المشروع وإتاحته في مدير التخطيطات، فأضف:
layout.setName("MyLayout")
project.layoutManager().addLayout(layout)
يمكننا الآن إضافة عناصر متنوعة (خريطة، تسمية، ...) إلى التخطيط. جميع هذه الكائنات ممثلة بأصناف ترث من الصنف الأساسي QgsLayoutItem.
فيما يلي وصف لبعض عناصر التخطيط الرئيسية التي يمكن إضافتها إلى التخطيط.
الخريطة --- ننشئ هنا خريطة بحجم مخصّص ونرسم لوحة الخريطة الحالية
1map = QgsLayoutItemMap(layout) 2# Set map item position and size (by default, it is a 0 width/0 height item placed at 0,0) 3map.attemptMove(QgsLayoutPoint(5,5, QgsUnitTypes.LayoutMillimeters)) 4map.attemptResize(QgsLayoutSize(200,200, QgsUnitTypes.LayoutMillimeters)) 5# Provide an extent to render 6map.zoomToExtent(iface.mapCanvas().extent()) 7layout.addLayoutItem(map)
التسمية --- تتيح عرض التسميات. ويمكن تعديل خطها ولونها ومحاذاتها وهامشها
label = QgsLayoutItemLabel(layout) label.setText("Hello world") label.adjustSizeToText() layout.addLayoutItem(label)
legend
legend = QgsLayoutItemLegend(layout) legend.setLinkedMap(map) # map is an instance of QgsLayoutItemMap layout.addLayoutItem(legend)
مقياس الرسم الخطي
1item = QgsLayoutItemScaleBar(layout) 2item.setStyle('Numeric') # optionally modify the style 3item.setLinkedMap(map) # map is an instance of QgsLayoutItemMap 4item.applyDefaultSize() 5layout.addLayoutItem(item)
شكل قائم على العقد
1polygon = QPolygonF() 2polygon.append(QPointF(0.0, 0.0)) 3polygon.append(QPointF(100.0, 0.0)) 4polygon.append(QPointF(200.0, 100.0)) 5polygon.append(QPointF(100.0, 200.0)) 6 7polygonItem = QgsLayoutItemPolygon(polygon, layout) 8layout.addLayoutItem(polygonItem) 9 10props = {} 11props["color"] = "green" 12props["style"] = "solid" 13props["style_border"] = "solid" 14props["color_border"] = "black" 15props["width_border"] = "10.0" 16props["joinstyle"] = "miter" 17 18symbol = QgsFillSymbol.createSimple(props) 19polygonItem.setSymbol(symbol)
بعد إضافة عنصر إلى التخطيط، يمكن نقله وتغيير حجمه:
item.attemptMove(QgsLayoutPoint(1.4, 1.8, QgsUnitTypes.LayoutCentimeters))
item.attemptResize(QgsLayoutSize(2.8, 2.2, QgsUnitTypes.LayoutCentimeters))
يُرسم إطار حول كل عنصر افتراضيًا. يمكنك إزالته كما يلي:
# for a composer label
label.setFrameEnabled(False)
إلى جانب إنشاء عناصر التخطيط يدويًا، يدعم QGIS قوالب التخطيط، وهي في جوهرها تراكيب محفوظة بجميع عناصرها في ملف .qpt (بصيغة XML).
بعد أن يصبح التركيب جاهزًا (أي بعد إنشاء عناصر التخطيط وإضافتها إلى التركيب)، يمكننا المتابعة لإنتاج مخرجات شبكية و/أو متجهية.
10.3.1. التحقق من صلاحية التخطيط¶
يتكوّن التخطيط من مجموعة من العناصر المترابطة، وقد تنقطع هذه الروابط أثناء التعديلات (مفتاح خريطة مرتبط بخريطة محذوفة، أو عنصر صورة بملف مصدر مفقود، ...) أو قد ترغب في تطبيق قيود مخصّصة على عناصر التخطيط. يساعدك الصنف QgsAbstractValidityCheck على تحقيق ذلك.
يبدو الفحص الأساسي هكذا:
@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
def my_layout_check(context, feedback):
results = ...
return results
فيما يلي فحص يُصدر تحذيرًا كلما عُيّن عنصر خريطة في التخطيط على إسقاط web mercator:
1@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
2def layout_map_crs_choice_check(context, feedback):
3 layout = context.layout
4 results = []
5 for i in layout.items():
6 if isinstance(i, QgsLayoutItemMap) and i.crs().authid() == 'EPSG:3857':
7 res = QgsValidityCheckResult()
8 res.type = QgsValidityCheckResult.Warning
9 res.title = 'Map projection is misleading'
10 res.detailedDescription = 'The projection for the map item {} is set to <i>Web Mercator (EPSG:3857)</i> which misrepresents areas and shapes. Consider using an appropriate local projection instead.'.format(i.displayName())
11 results.append(res)
12
13 return results
وفيما يلي مثال أكثر تعقيدًا يُصدر تحذيرًا إذا عُيّن أي عنصر خريطة في التخطيط على نظام إحداثيات مرجعي لا يكون صالحًا إلا خارج الامتداد المعروض في عنصر الخريطة ذاك:
1@check.register(type=QgsAbstractValidityCheck.TypeLayoutCheck)
2def layout_map_crs_area_check(context, feedback):
3 layout = context.layout
4 results = []
5 for i in layout.items():
6 if isinstance(i, QgsLayoutItemMap):
7 bounds = i.crs().bounds()
8 ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem('EPSG:4326'), i.crs(), QgsProject.instance())
9 bounds_crs = ct.transformBoundingBox(bounds)
10
11 if not bounds_crs.contains(i.extent()):
12 res = QgsValidityCheckResult()
13 res.type = QgsValidityCheckResult.Warning
14 res.title = 'Map projection is incorrect'
15 res.detailedDescription = 'The projection for the map item {} is set to \'{}\', which is not valid for the area displayed within the map.'.format(i.displayName(), i.crs().authid())
16 results.append(res)
17
18 return results
10.3.2. تصدير التخطيط¶
لتصدير تخطيط، يجب استخدام الصنف QgsLayoutExporter.
1base_path = os.path.join(QgsProject.instance().homePath())
2pdf_path = os.path.join(base_path, "output.pdf")
3
4exporter = QgsLayoutExporter(layout)
5exporter.exportToPdf(pdf_path, QgsLayoutExporter.PdfExportSettings())
استخدم exportToSvg() أو exportToImage() إذا أردت التصدير إلى ملف SVG أو ملف صورة على الترتيب بدلًا من ملف PDF.
10.3.3. تصدير أطلس التخطيط¶
إذا أردت تصدير جميع الصفحات من تخطيط مُعدّ فيه خيار الأطلس ومفعّل، فعليك استخدام الدالة atlas() في أداة التصدير (QgsLayoutExporter) مع تعديلات بسيطة. في المثال التالي، تُصدَّر الصفحات إلى صور PNG:
exporter.exportToImage(layout.atlas(), base_path, 'png', QgsLayoutExporter.ImageExportSettings())
لاحظ أن المخرجات ستُحفظ في مجلد المسار الأساسي، باستخدام تعبير اسم ملف الإخراج المُعدّ في الأطلس.