예제 #1
0
def set_map(layout: QgsPrintLayout, item_id: str, layers: List[QgsLayer]) -> QgsLayoutItem:
  '''Adds layer(s) to the map'''
  logging.info(f'setting map, id={item_id}')
  map_ = layout.itemById(item_id)

  # add layer(s) to map
  map_.setLayers(layers)
  return map_
예제 #2
0
def set_label(layout: QgsPrintLayout, item_id: str, text: str):
  '''Set the text for existing layout items'''
  logging.info(f'setting text with label id={item_id}')
  item = layout.itemById(item_id)

  if item is not None:
    item.setText(text)
  else:
    raise baseException(f'item_id {item_id} does not exist', 
                        baseException.ERR_CODE_LEVEL)
예제 #3
0
def format_label(layout: QgsPrintLayout, item_id: str, params: List[Union[int, str]]):
  '''Formats the text for existing layout items'''
  logging.info(f'formatting text with label id={item_id}')

  item = layout.itemById(item_id)
  text = item.text().format(*params)

  if item is not None:
    item.setText(text)
  else:
    raise baseException(f'item_id {item_id} does not exist', 
                        baseException.ERR_CODE_LEVEL)
예제 #4
0
def set_legend(layout: QgsPrintLayout, tree: QgsLayerTree, layer: QgsLayer, item_id: str):
  '''Sets the Legend items'''
  logging.info(f'setting legend: {item_id}')
  item = layout.itemById(item_id)

  # set layer as root for legend
  tree.addLayer(layer)
  model = item.model()
  model.setRootGroup(tree)
  root = model.rootGroup().findLayer(layer)
  
  # hide the node title
  QgsLegendRenderer.setNodeLegendStyle(root, QgsLegendStyle.Hidden)

  # hide the node with label: Band 1 (Gray)
  if isinstance(layer, QgsRasterLayer):
    nodes = model.layerLegendNodes(root)
    if nodes[0].data(0) == 'Band 1 (Gray)':
      indexes = list(range(1, len(nodes)))
      QgsMapLayerLegendUtils.setLegendNodeOrder(root, indexes)
      model.refreshLayerLegend(root)
예제 #5
0
class ScangisAtlas:
    def __init__(self, iface):
        self.iface = iface
        self.virtualLayerName = u'deelnemers_atlas'
        self.layoutName = u'Scangis Atlas'

        #print(__file__)
        self.pluginDir = os.path.dirname(__file__)
        #print(self.pluginDir)
        self.templateFile = os.path.join(self.pluginDir, u'data', u'a4.qpt')
        self.scangisStyle = os.path.join(self.pluginDir, u'data',
                                         u'pakketten_atlas_alz.qml')
        #print(self.templateFile)

    def initGui(self):
        self.action = QAction(u'Scangis Atlas', self.iface.mainWindow())
        self.action.triggered.connect(self.run)
        self.iface.addToolBarIcon(self.action)

    def unload(self):
        self.iface.removeToolBarIcon(self.action)
        del self.action

    def run(self):
        project = QgsProject.instance()

        # set qml
        scangisLayer = self.iface.activeLayer()
        #print(scangisLayer.name())
        scangisLayer.loadNamedStyle(self.scangisStyle)

        # delete existing deelnemers virtual layer
        oldLayers = project.mapLayersByName(self.virtualLayerName)
        for oldLayer in oldLayers:
            project.removeMapLayer(oldLayer.id())

        # create new virtual layer
        query = u'?query=SELECT min(ID) AS ID, DEELN_NAAM, st_union(geometry) AS geometry FROM "{}" GROUP BY DEELN_NAAM'.format(
            scangisLayer.name())
        virtualLayer = QgsVectorLayer(query, self.virtualLayerName, "virtual")
        project.addMapLayer(virtualLayer)

        ltr = QgsProject.instance().layerTreeRoot()
        rootLayer = ltr.findLayer(virtualLayer.id())
        rootLayer.setItemVisibilityChecked(False)

        self.iface.mapCanvas().refreshAllLayers()

        # delete existing atlas layout
        layoutManager = project.layoutManager()
        #print(layoutManager)
        #print(layoutManager.layouts())
        oldLayout = layoutManager.layoutByName(self.layoutName)
        if oldLayout is not None:
            layoutManager.removeLayout(oldLayout)

        # create new layout
        self.atlasLayout = QgsPrintLayout(project)

        # load layout template
        fl = QFile(self.templateFile)
        doc = QDomDocument()
        doc.setContent(fl)
        rw_context = QgsReadWriteContext()
        rw_context.setPathResolver(project.pathResolver())
        self.atlasLayout.loadFromTemplate(doc, rw_context)

        # set name
        self.atlasLayout.setName(self.layoutName)

        # add to project
        layoutManager.addLayout(self.atlasLayout)

        # turn on atlas
        atl = self.atlasLayout.atlas()
        atl.setEnabled(True)
        atl.setCoverageLayer(virtualLayer)
        atl.setHideCoverage(True)
        itemId = u'map1'
        item = self.getLayoutItem(itemId, QgsLayoutItemMap)
        item.setAtlasDriven(True)

        # set title
        itemId = u'label_title'
        item = self.getLayoutItem(itemId, QgsLayoutItemLabel)
        #item.setText(u'[% attribute(@atlas_feature, \'DEELN_NAAM\') %]')
        item.setText(u'[% "DEELN_NAAM" %]')

        # set source info
        itemId = u'label_source'
        item = self.getLayoutItem(itemId, QgsLayoutItemLabel)
        exp = u"[% format_date(now(), 'dd-MM-yyyy (hh:mm:ss)') || '\\n' || 'pagina ' || @atlas_featurenumber || '/' ||  @atlas_totalfeatures %]"
        item.setText(exp)
        item.setHAlign(2)  # right
        item.setVAlign(64)  # bottom

        # set legend filter
        itemId = u'legend1'
        item = self.getLayoutItem(itemId, QgsLayoutItemLegend)
        #print(item)
        item.setLegendFilterByMapEnabled(True)

        #QMessageBox.information(None, u'Scangis Atlas', u'Atlas finished! :)')
        designer = self.iface.openLayoutDesigner(self.atlasLayout)

        # new in qgis 3.3
        if qgis.utils.Qgis.QGIS_VERSION_INT >= 30300:
            designer.setAtlasPreviewEnabled(True)

    def getLayoutItem(self, itemId, itemType):
        item = self.atlasLayout.itemById(itemId)
        if item is None:
            #print(u'Layout does not contain item: \'{0}\''.format(itemId))
            return None
        if not type(item) == u'qgis._core.{0}'.format(itemType.__name__):
            item = sip.cast(item, itemType)
        return item
예제 #6
0
 def _load_print_layout(self, layer_name, prod_id, dt=None):
     
     window_title = "Print"    # will be checked with a found composer title
     
     """
     Avoid creating a new PrintComposer again and again here.
     Otherwise more and more composers will be added to the list - even with the same name.
     """
     
     project = QgsProject.instance()
     # From it, you can get the current layoutManager instance and deduce the layouts
     layout_manager = project.layoutManager()
     
     layout = layout_manager.layoutByName(window_title)
     
     if not layout:
         self.out("no composer found; creating one...")
         
         # Load the template into the composer
         # QGIS 2:
         #active_composer = self.iface.createNewComposer(window_title)    #createNewComposer()
         #active_composer = QgsComposition(QgsProject.instance())
         
         #layout = QgsLayout(project)
         layout = QgsPrintLayout(project)
         layout.initializeDefaults()    # initializes default settings for blank print layout canvas
         
         
         q_xmldoc = self._create_qdocument_from_print_template_content()
         
         # load layout from template and add to Layout Manager
         #layout.composition().loadFromTemplate(q_xmldoc)    # QGIS 2
         layout.loadFromTemplate(q_xmldoc, QgsReadWriteContext())
         layout.setName(window_title)
         
         layout_manager.addLayout(layout)
         
         
         # Update Logo:
         
         #logo_item = layout.getComposerItemById('logo')    # QGIS 2
         logo_item = layout.itemById('logo')
         #self.out(">>> logo_item: '{}'".format(logo_item))    # gibt nur die Objekt-ID aus
         logo_image = self._model.logo_path
         self.out("Logo: {}".format(logo_image))
         if logo_image.exists():
             logo_item.setPicturePath(str(logo_image))
         else:
             self.out("  ERROR: logo '{}' not found!".format(logo_image), False)
     # if
     
     
     """
     Hier versuche ich ein für die Überschrift mit einer ID ('headline') versehenes
     QgsLabel aus dem Template ausfindig zu machen. Ich mache das hier sehr kompliziert,
     es gibt bestimmt einen einfacheren Weg.
     Folgendes hat NICHT funktioniert:
     map_item = active_composer.getComposerItemById('headline')
     print(active_composer.items())
     liefert: [<PyQt4.QtGui.QGraphicsRectItem object at 0x124c60e8>, <qgis._core.QgsComposerLabel object at 0x124c65a8>, ... ]
     """
     
     ''' other possibility:
     for item in list(layout.items()):
         #if type(item) != QgsComposerLabel:
     '''
     
     # QgsComposerLabel:
     composer_label = layout.itemById('headline')    # a QgsComposerLabel was provided with the ID 'headline' in the template
     # -> None if not found
     
     if composer_label:
         title    = self._model.title(prod_id, dt)
         subtitle = ""
         if prod_id != 'RW':
             subtitle = "\n{}-Produkt (Basis: RW)".format(prod_id)
         
         composer_label.setText(title + subtitle)
     else:
         # A note that the template needs to be revised:
         self.out("no element with id 'headline' found!", False)
     
     
     
     legend = layout.itemById('legend')
     
     if not legend:
         self.out("legend couldn't created!", False)
         return
     
     
     #
     # Layer für die Legende ausfindig machen
     #
     
     # You would just need to make sure your layer has a name you can distinguish from others. Instead of:
     # Vorherige Version:
     #active_raster_layer = self.iface.activeLayer()
     
     # do:
     l_layer = project.mapLayersByName(layer_name)
     
     if not l_layer:
         self.out("legend: no layer found with name '{}'!".format(layer_name), False)
         return
     
     active_raster_layer = l_layer[0]
     
     #print("Legend active_raster_layer id:",   active_raster_layer.id())     # ok
     #print("Legend active_raster_layer name:", active_raster_layer.name())   # ok
     #legend.model().setLayerSet([layer.id() for layer in layers])
     #legend.model().setLayerSet([active_raster_layer.id()])    # bringt nichts
     # DAS ist es! Dies fügt zumindest erstmal das interessierende Rasterlayer hinzu:
     #legend.modelV2().rootGroup().addLayer(active_raster_layer)
     #legend.updateLegend()
     
     #for layout in layout_manager.printLayouts():    # iterate layouts
     
     ''' would be ok, if we want to create a new legend -> then legend appears at the upper left corner
     legend = QgsLayoutItemLegend(layout)
     #legend.setTitle('Legend')
     legend.setAutoUpdateModel(False)
     group = legend.model().rootGroup()
     group.clear()
     group.addLayer(active_raster_layer)
     layout.addItem(legend)
     legend.adjustBoxSize()
     #legend.refresh()    # avoids adding all other layers 
     '''
     
     # uses existing legend object (see above), so we preserve it's layout position:
     legend.setAutoUpdateModel(False)
     group = legend.model().rootGroup()
     group.clear()
     group.addLayer(active_raster_layer)
     legend.adjustBoxSize()
     
     """ By default the newly created composer items have zero position (top left corner of the page) and zero size.
     The position and size are always measured in millimeters.
     # set label 1cm from the top and 2cm from the left of the page
     composerLabel.setItemPosition(20, 10)
     # set both label’s position and size (width 10cm, height 3cm)
     composerLabel.setItemPosition(20, 10, 100, 30)
     A frame is drawn around each item by default. How to remove the frame:
     composerLabel.setFrame(False)
     """
     #print(active_composer.rect().width(), active_composer.rect().height())                   # 1054 911
     #print(self.iface.mapCanvas().size().width(), self.iface.mapCanvas().size().height())     # 1517 535
     # "Leinwandgröße": habe keine vernünftigen Werte oben ermittelt (vielleicht Pixel; ich brauche mm).
     # selbst, mittels Mauszeiger ermittelt:
     width  = 210
     height = 297
     # Rand neben der Legende (mm):
     dw = 10
     dh = 14
     
     """
예제 #7
0
    def test_read_write_project_with_layout(self):
        """
        Test saving/restoring dialog state of layout plot in project
        """
        # print('read write project with layout test')

        # create project and layout
        project = QgsProject.instance()
        layout = QgsPrintLayout(project)
        layout_name = "PrintLayoutReadWrite"
        layout.initializeDefaults()
        layout.setName(layout_name)
        layout_plot = PlotLayoutItem(layout)
        layout_plot.setId('plot_item')
        plot_item_id = layout_plot.id()
        self.assertEqual(len(layout_plot.plot_settings), 1)
        # self.assertEqual(len(layout.items()), 0)
        layout.addLayoutItem(layout_plot)
        # self.assertEqual(len(layout.items()), 1)
        plot_dialog = PlotLayoutItemWidget(None, layout_plot)

        # add second plot
        plot_dialog.add_plot()
        self.assertEqual(len(layout_plot.plot_settings), 2)

        # edit first plot
        plot_dialog.setDockMode(True)
        plot_dialog.show_properties()
        plot_property_panel = plot_dialog.panel
        plot_property_panel.set_plot_type('violin')
        self.assertEqual(plot_property_panel.ptype, 'violin')
        plot_property_panel.acceptPanel()
        plot_property_panel.destroy()

        # edit second plot
        plot_dialog.plot_list.setCurrentRow(1)
        plot_dialog.show_properties()
        plot_property_panel = plot_dialog.panel
        plot_property_panel.set_plot_type('bar')
        self.assertEqual(plot_property_panel.ptype, 'bar')
        plot_property_panel.acceptPanel()
        plot_property_panel.destroy()

        # write xml

        xml_doc = QDomDocument('layout')
        element = layout.writeXml(xml_doc, QgsReadWriteContext())

        layout_plot.remove_plot(0)
        self.assertEqual(len(layout_plot.plot_settings), 1)
        self.assertEqual(layout_plot.plot_settings[0].plot_type, 'bar')

        layout_plot.remove_plot(0)
        self.assertEqual(len(layout_plot.plot_settings), 0)

        # read xml
        layout2 = QgsPrintLayout(project)
        self.assertTrue(
            layout2.readXml(element, xml_doc, QgsReadWriteContext()))
        layout_plot2 = layout2.itemById(plot_item_id)
        self.assertTrue(layout_plot2)

        self.assertEqual(len(layout_plot2.plot_settings), 2)
        self.assertEqual(layout_plot2.plot_settings[0].plot_type, 'violin')
        self.assertEqual(layout_plot2.plot_settings[1].plot_type, 'bar')
예제 #8
0
    def run(self,
            templatePath,
            entityFieldName,
            entityFieldValue,
            outputMode,
            filePath=None,
            dataFields=None,
            fileExtension=None,
            data_source=None):
        """
        :param templatePath: The file path to the user-defined template.
        :param entityFieldName: The name of the column for the specified entity which
        must exist in the data source view or table.
        :param entityFieldValue: The value for filtering the records in the data source
        view or table.
        :param outputMode: Whether the output composition should be an image or PDF.
        :param filePath: The output file where the composition will be written to. Applies
        to single mode output generation.
        :param dataFields: List containing the field names whose values will be used to name the files.
        This is used in multiple mode configuration.
        :param fileExtension: The output file format. Used in multiple mode configuration.
        :param data_source: Name of the data source table or view whose
        row values will be used to name output files if the options has been
        specified by the user.
        """
        if dataFields is None:
            dataFields = []

        if fileExtension is None:
            fileExtension = ''

        if data_source is None:
            data_source = ''

        templateFile = QFile(templatePath)

        if not templateFile.open(QIODevice.ReadOnly):
            return False, QApplication.translate("DocumentGenerator",
                                                 "Cannot read template file.")

        templateDoc = QDomDocument()

        if templateDoc.setContent(templateFile):
            composerDS = ComposerDataSource.create(templateDoc)
            spatialFieldsConfig = SpatialFieldsConfiguration.create(
                templateDoc)
            composerDS.setSpatialFieldsConfig(spatialFieldsConfig)

            # Check if data source exists and return if it doesn't
            if not self.data_source_exists(composerDS):
                msg = QApplication.translate(
                    "DocumentGenerator",
                    "'{0}' data source does not exist in the database."
                    "\nPlease contact your database "
                    "administrator.".format(composerDS.name()))
                return False, msg

            # Set file name value formatter
            self._file_name_value_formatter = EntityValueFormatter(
                name=data_source)

            # Register field names to be used for file naming
            self._file_name_value_formatter.register_columns(dataFields)

            # TODO: Need to automatically register custom configuration collections
            # Photo config collection
            ph_config_collection = PhotoConfigurationCollection.create(
                templateDoc)

            # Table configuration collection
            table_config_collection = TableConfigurationCollection.create(
                templateDoc)

            # Create chart configuration collection object
            chart_config_collection = ChartConfigurationCollection.create(
                templateDoc)

            # Create QR code configuration collection object
            qrc_config_collection = QRCodeConfigurationCollection.create(
                templateDoc)

            # Load the layers required by the table composer items
            self._table_mem_layers = load_table_layers(table_config_collection)

            entityFieldName = self.format_entity_field_name(
                composerDS.name(), data_source)

            # Execute query
            dsTable, records = self._exec_query(composerDS.name(),
                                                entityFieldName,
                                                entityFieldValue)

            if records is None or len(records) == 0:
                return False, QApplication.translate(
                    "DocumentGenerator", "No matching records in the database")
            """
            Iterate through records where a single file output will be generated for each matching record.
            """

            for rec in records:
                composition = QgsPrintLayout(self._map_settings)
                context = QgsReadWriteContext()
                composition.loadFromTemplate(templateDoc, context)
                ref_layer = None
                # Set value of composer items based on the corresponding db values
                for composerId in composerDS.dataFieldMappings().reverse:
                    # Use composer item id since the uuid is stripped off
                    composerItem = composition.itemById(composerId)
                    if composerItem is not None:
                        fieldName = composerDS.dataFieldName(composerId)
                        fieldValue = getattr(rec, fieldName)
                        self._composeritem_value_handler(
                            composerItem, fieldValue)

                # Extract photo information
                self._extract_photo_info(composition, ph_config_collection,
                                         rec)

                # Set table item values based on configuration information
                self._set_table_data(composition, table_config_collection, rec)

                # Refresh non-custom map composer items
                self._refresh_composer_maps(
                    composition,
                    list(spatialFieldsConfig.spatialFieldsMapping().keys()))

                # Set use fixed scale to false i.e. relative zoom
                use_fixed_scale = False

                # Create memory layers for spatial features and add them to the map
                for mapId, spfmList in spatialFieldsConfig.spatialFieldsMapping(
                ).items():

                    map_item = composition.itemById(mapId)

                    if map_item is not None:
                        # Clear any previous map memory layer
                        # self.clear_temporary_map_layers()
                        for spfm in spfmList:
                            # Use the value of the label field to name the layer
                            lbl_field = spfm.labelField()
                            spatial_field = spfm.spatialField()

                            if not spatial_field:
                                continue

                            if lbl_field:
                                if hasattr(rec, spfm.labelField()):
                                    layerName = getattr(rec, spfm.labelField())
                                else:
                                    layerName = self._random_feature_layer_name(
                                        spatial_field)
                            else:
                                layerName = self._random_feature_layer_name(
                                    spatial_field)

                            # Extract the geometry using geoalchemy spatial capabilities
                            geom_value = getattr(rec, spatial_field)
                            if geom_value is None:
                                continue

                            geom_func = geom_value.ST_AsText()
                            geomWKT = self._dbSession.scalar(geom_func)

                            # Get geometry type
                            geom_type, srid = geometryType(
                                composerDS.name(), spatial_field)

                            # Create reference layer with feature
                            ref_layer = self._build_vector_layer(
                                layerName, geom_type, srid)

                            if ref_layer is None or not ref_layer.isValid():
                                continue
                            # Add feature
                            bbox = self._add_feature_to_layer(
                                ref_layer, geomWKT)

                            zoom_type = spfm.zoom_type
                            # Only scale the extents if zoom type is relative
                            if zoom_type == 'RELATIVE':
                                bbox.scale(spfm.zoomLevel())

                            # Workaround for zooming to single point extent
                            if ref_layer.wkbType() == QgsWkbTypes.Point:
                                canvas_extent = self._iface.mapCanvas(
                                ).fullExtent()
                                cnt_pnt = bbox.center()
                                canvas_extent.scale(1.0 / 32, cnt_pnt)
                                bbox = canvas_extent

                            # Style layer based on the spatial field mapping symbol layer
                            symbol_layer = spfm.symbolLayer()
                            if symbol_layer is not None:
                                ref_layer.renderer().symbols(
                                )[0].changeSymbolLayer(0, spfm.symbolLayer())
                            '''
                            Add layer to map and ensure its always added at the top
                            '''
                            self.map_registry.addMapLayer(ref_layer)
                            self._iface.mapCanvas().setExtent(bbox)

                            # Set scale if type is FIXED
                            if zoom_type == 'FIXED':
                                self._iface.mapCanvas().zoomScale(
                                    spfm.zoomLevel())
                                use_fixed_scale = True

                            self._iface.mapCanvas().refresh()
                            # Add layer to map memory layer list
                            self._map_memory_layers.append(ref_layer.id())
                            self._hide_layer(ref_layer)
                        '''
                        Use root layer tree to get the correct ordering of layers
                        in the legend
                        '''
                        self._refresh_map_item(map_item, use_fixed_scale)

                # Extract chart information and generate chart
                self._generate_charts(composition, chart_config_collection,
                                      rec)

                # Extract QR code information in order to generate QR codes
                self._generate_qr_codes(composition, qrc_config_collection,
                                        rec)

                # Build output path and generate composition
                if filePath is not None and len(dataFields) == 0:
                    self._write_output(composition, outputMode, filePath)

                elif filePath is None and len(dataFields) > 0:
                    entityFieldName = 'id'
                    docFileName = self._build_file_name(
                        data_source, entityFieldName, entityFieldValue,
                        dataFields, fileExtension)

                    # Replace unsupported characters in Windows file naming
                    docFileName = docFileName.replace('/', '_').replace(
                        '\\', '_').replace(':', '_').strip('*?"<>|')

                    if not docFileName:
                        return (
                            False,
                            QApplication.translate(
                                "DocumentGenerator",
                                "File name could not be generated from the data fields."
                            ))

                    outputDir = self._composer_output_path()
                    if outputDir is None:
                        return (
                            False,
                            QApplication.translate(
                                "DocumentGenerator",
                                "System could not read the location of the output directory in the registry."
                            ))

                    qDir = QDir()
                    if not qDir.exists(outputDir):
                        return (False,
                                QApplication.translate(
                                    "DocumentGenerator",
                                    "Output directory does not exist"))

                    absDocPath = "{0}/{1}".format(outputDir, docFileName)
                    self._write_output(composition, outputMode, absDocPath)

            return True, "Success"

        return False, "Document composition could not be generated"
예제 #9
0
def set_image(layout: QgsPrintLayout, item_id: str, fp: str):
  '''Adds references to the layout image items'''
  logging.info(f'setting image source for {item_id} using {fp}')
  item = layout.itemById(item_id)
  item.setPicturePath(fp)
예제 #10
0
def hide_item(layout: QgsPrintLayout, item_id: str):
  '''Hides an item by item_id from Layout'''
  logging.info(f'hiding {item_id} in layout')
  item = layout.itemById(item_id)
  item.setVisibility(False)