def nodeOrder(self, group): nodeorder = [] layerTree = QgsLayerTree() for node in group: if QgsLayerTree.isGroup(node): groupname = node.name() nodeorder.append(groupname) for child in self.nodeOrder(node.children()): nodeorder.append(groupname + '-' + child) elif QgsLayerTree.isLayer(node): nodeorder.append(node.layer().name()) return nodeorder
def insertInto(self, origin, destination, reset_initial_visibility): for el in origin.children(): if QgsLayerTree.isLayer(el): node = destination.addLayer(el.layer()) node.setItemVisibilityChecked( self.treeRoot.findLayer(el.layer()).isVisible() ) elif QgsLayerTree.isGroup(el): node = destination.addGroup(el.name()) self.insertInto(el, node, reset_initial_visibility) if reset_initial_visibility: # overwrite visibility with previously saved visibility node.setItemVisibilityChecked(el.itemVisibilityChecked())
def set_legend_compositions(self, print_layout): non_ident = QgsProject.instance().nonIdentifiableLayers() self.legend_tree_root = QgsLayerTree() layer_nodes = [] for lyr in self.iface.mapCanvas().layers(): if lyr.id() not in non_ident: layer_nodes.append(QgsLayerTreeLayer(lyr)) self.legend_tree_root.insertChildNodes(-1, layer_nodes) # update the model for item in print_layout.items(): if not isinstance(item, QgsLayoutItemLegend): continue legend_model = item.model() legend_model.setRootGroup(self.legend_tree_root)
def replaceLayer(self, group, oldLayer, newLayer): index = 0 for child in group.children(): if QgsLayerTree.isLayer(child): if child.layerId() == oldLayer.id(): # insert new layer QgsProject.instance().addMapLayer(newLayer, False) newLayerNode = group.insertLayer(index, newLayer) newLayerNode.setVisible(child.isVisible()) # remove old layer QgsProject.instance().removeMapLayer( oldLayer.id()) msg = "Updated layer '%s' from old \ OpenLayers Plugin version" % newLayer.name() self.iface.messageBar().pushMessage( "OpenLayers Plugin", msg, level=Qgis.MessageLevel(0)) QgsMessageLog.logMessage( msg, "OpenLayers Plugin", QgsMessageLog.INFO) # layer replaced return True else: if self.replaceLayer(child, oldLayer, newLayer): # layer replaced in child group return True index += 1 # layer not in this group return False
def replaceLayer(self, group, oldLayer, newLayer): index = 0 for child in group.children(): if QgsLayerTree.isLayer(child): if child.layerId() == oldLayer.id(): # insert new layer QgsProject.instance().addMapLayer(newLayer, False) newLayerNode = group.insertLayer(index, newLayer) newLayerNode.setVisible(child.isVisible()) # remove old layer QgsProject.instance().removeMapLayer(oldLayer.id()) msg = "Updated layer '%s' from old OpenLayers Plugin version" % newLayer.name( ) self.iface.messageBar().pushMessage( "OpenLayers Plugin", msg, level=Qgis.MessageLevel(0)) QgsMessageLog.logMessage(msg, "OpenLayers Plugin", QgsMessageLog.INFO) # layer replaced return True else: if self.replaceLayer(child, oldLayer, newLayer): # layer replaced in child group return True index += 1 # layer not in this group return False
def layers_selected(self) -> bool: """Check if there are any layers selected and populate self.selected_layers. :return: Whether there are any layers selected. """ view = iface.layerTreeView() self.selected_layers = [n for n in view.selectedNodes() if QgsLayerTree.isLayer(n)] return len(self.selected_layers) > 0
def _getPosOfPrevGroup(self, groupName): topLevelGroups = [ child.name() for child in self.tocRoot.children() if QgsLayerTree.isGroup(child) ] if groupName in topLevelGroups: return topLevelGroups.index(groupName) else: return None
def testCustomLayerOrderChanged(self): layer = QgsVectorLayer("Point?field=fldtxt:string", "layer1", "memory") layer2 = QgsVectorLayer("Point?field=fldtxt:string", "layer2", "memory") layer_tree = QgsLayerTree() layer_order_changed_spy = QSignalSpy( layer_tree.customLayerOrderChanged) layer1_node = QgsLayerTreeLayer(layer) layer_tree.addChildNode(layer1_node) self.assertEqual(len(layer_order_changed_spy), 1) layer2_node = QgsLayerTreeLayer(layer2) layer_tree.addChildNode(layer2_node) self.assertEqual(len(layer_order_changed_spy), 2) # simulate a layer move in the tree layer3_node = QgsLayerTreeLayer(layer) layer_tree.addChildNode(layer3_node) self.assertEqual(len(layer_order_changed_spy), 3) layer_tree.removeChildNode(layer1_node) self.assertEqual(len(layer_order_changed_spy), 4)
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)
def createContextMenu(self): menu = QMenu() actions: QgsLayerTreeViewDefaultActions = self.layerTreeView.defaultActions( ) if not self.layerTreeView.currentIndex().isValid(): # 不在图层上右键 self.actionAddGroup = actions.actionAddGroup(menu) menu.addAction(self.actionAddGroup) menu.addAction('Expand All', self.layerTreeView.expandAll) menu.addAction('Collapse All', self.layerTreeView.collapseAll) return menu node: QgsLayerTreeNode = self.layerTreeView.currentNode() if QgsLayerTree.isGroup(node): # 图组操作 print('group') pass elif QgsLayerTree.isLayer(node): print('layer') self.actionZoomToLayer = actions.actionZoomToLayer( self.mapCanvas, menu) menu.addAction(self.actionZoomToLayer) # 图层操作 layer = self.layerTreeView.currentLayer() if layer.type() == QgsMapLayerType.VectorLayer: # 矢量图层 actionOpenAttributeDialog = QAction('open Attribute Table', menu) actionOpenAttributeDialog.triggered.connect( lambda: self.openAttributeDialog(layer)) menu.addAction(actionOpenAttributeDialog) else: # 栅格图层 pass else: print('node type is none') return menu
def isPluginLayerTreeNode(self, node: QgsLayerTree) -> bool: # for some reason even the normal nodes are behaving like groups... if QgsLayerTree.isGroup(node): # unfortunately this does not work in Python, it's cpp only... # group = QgsLayerTree.toGroup(node) # All my other attempts also failed miserably # group = self.layerTree.findGroup(self.groupName) # return group is get_group() pass else: layer = self.project.mapLayer(node.layerId()) if layer in (self.simplifiedSegmentsLayer, self.verticesLayer, self.candidatesLayer, self.finalLayer): return True if self.wasBaseRasterLayerInitiallyInLegend and layer is self.baseRasterLayer: return True if self.wasSegmentsLayerInitiallyInLegend and layer is self.segmentsLayer: return True return False
def testCustomLayerOrderChanged(self): layer = QgsVectorLayer("Point?field=fldtxt:string", "layer1", "memory") layer2 = QgsVectorLayer("Point?field=fldtxt:string", "layer2", "memory") layer_tree = QgsLayerTree() layer_order_changed_spy = QSignalSpy(layer_tree.customLayerOrderChanged) layer1_node = QgsLayerTreeLayer(layer) layer_tree.addChildNode(layer1_node) self.assertEqual(len(layer_order_changed_spy), 1) layer2_node = QgsLayerTreeLayer(layer2) layer_tree.addChildNode(layer2_node) self.assertEqual(len(layer_order_changed_spy), 2) # simulate a layer move in the tree layer3_node = QgsLayerTreeLayer(layer) layer_tree.addChildNode(layer3_node) self.assertEqual(len(layer_order_changed_spy), 3) layer_tree.removeChildNode(layer1_node) self.assertEqual(len(layer_order_changed_spy), 4)
def get_layers_and_visibility_from_node( root: QgsLayerTree, node: QgsLayerTreeNode) -> List[Tuple[QgsMapLayer, bool]]: layers = [] child: QgsLayerTreeNode for child in node.children(): if root.isGroup(child): # noinspection PyTypeChecker layers += LayerHandler.get_layers_and_visibility_from_node( root, child) else: layer = child.layer() visibility = child.itemVisibilityChecked( ) and node.itemVisibilityChecked() if layer: layers.append((layer, visibility)) return layers
def firstGroupWithoutCustomProperty(group: QgsLayerTreeGroup, _property: str) -> QgsLayerTreeGroup: """ Taken from QgsLayerTreeUtils::firstGroupWithoutCustomProperty :param group: :param _property: :return: """ # if the group is embedded go to the first non-embedded group, at worst the top level item while group.customProperty(_property): if not group.parent(): break if QgsLayerTree.isGroup(group.parent()): group = group.parent() else: dbg_info(group) assert False return group
def findChildren(root: QgsLayerTree, matchString: str, yearLimit: int): """Return a list of groups in the root that match a regex string argument.""" result = [] matchStringParts = matchString.split('/', 1) for child in root.children(): if fnmatch.fnmatch(child.name(), matchStringParts[0]): if isinstance(child, QgsLayerTreeGroup): if child.name().startswith(('1', '2')): if int(child.name()) < yearLimit: result.extend( findChildren(child, matchStringParts[1], yearLimit)) else: print(child.name()) result.extend( findChildren(child, matchStringParts[1], yearLimit)) else: result.append(child) return result
def groupSelected(self): nodes = self.mView.selectedNodes(True) if len(nodes) < 2 or not QgsLayerTree.isGroup(nodes[0].parent()): return parentGroup = nodes[0].parent() insertIdx = parentGroup.children().index(nodes[0]) newGroup = QgsLayerTreeGroup(self.uniqueGroupName(parentGroup)) for node in nodes: newGroup.addChildNode(node.clone()) parentGroup.insertChildNode(insertIdx, newGroup) for node in nodes: group = node.parent() if group != None: group.removeChildNode(node) self.mView.setCurrentIndex( self.mView.layerTreeModel().node2index(newGroup))
def _getPosOfPrevGroup(self, groupName): topLevelGroups = [child.name() for child in self.tocRoot.children() if QgsLayerTree.isGroup(child)] if groupName in topLevelGroups: return topLevelGroups.index(groupName) else: return None
class TemplateSelectorDialog(QDialog): def __init__(self, iface): self.supportedPaperSizes = ['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8'] # ISO A series self.paperSizesPresent = [] self.presetScales = ['200', '500', '1 000', '1 250', '2 500', '5 000', '10 000', '25 000', '50 000', '100 000'] self.iface = iface QDialog.__init__(self) # Set up the user interface from Designer. self.ui = uic.loadUi(ui_file, self) # Set up the list of templates s = QtCore.QSettings() self.identifiable_only = s.value("SelectorTools/ProjectSelector/identifiableOnly", True, type=bool) self.templateFileRoot = s.value("SelectorTools/TemplateSelector/templateRoot", '', type=str) if len(self.templateFileRoot) == 0 or not os.path.isdir(self.templateFileRoot): raise TemplateSelectorException('\'%s\' is not a valid template file root folder.' % self.templateFileRoot) self.populateTemplateTypes() self.populatePoiLayers() self.onPoiLayerChanged() self.onTemplateTypeChanged() self.plugin_dir = os.path.dirname(__file__) # Replacement map self.ui.suitableForComboBox.addItem('<custom>') self.user = os.environ.get('username', '[user]') self.replaceMap = { 'author': "Compiled by {} on [%concat(day($now ),'/',month($now),'/',year($now))%]".format(self.user) } self.ui.autofit_btn.clicked.connect(self.autofit_map) self.ui.suitableForComboBox.currentIndexChanged.connect(self.specify_dpi) self.ui.suitableForComboBox.editTextChanged.connect(self.text_changed) self.ui.poiLayerComboBox.currentIndexChanged.connect(self.onPoiLayerChanged) def specify_dpi(self, idx): if idx == 2: self.ui.suitableForComboBox.setEditable(True) else: self.ui.suitableForComboBox.setEditable(False) def text_changed(self, txt): self.ui.suitableForComboBox.setItemText(self.ui.suitableForComboBox.currentIndex(), txt) def autofit_map(self): canvas = self.iface.mapCanvas() units = canvas.mapUnits() coef = 1 / 0.3048 if units == QgsUnitTypes.DistanceFeet else 1 map_extent = canvas.extent() me_height = map_extent.height() * 1000 / coef me_width = map_extent.width() * 1000 / coef print_layout = self.get_print_layout() map_elements = [item for item in print_layout.items() if isinstance(item, QgsLayoutItemMap)] for idx, item in enumerate(map_elements): size = item.sizeWithUnits() h = size.height() w = size.width() hscale = me_height / h wscale = me_width / w scale = hscale if hscale > wscale else wscale scale_str = '{} (Autofit)'.format(ceil(scale)) scaleCombo = self.ui.scalesGridLayout.itemAtPosition(idx, 3).widget() idx = scaleCombo.findText(scale_str) if idx == -1: scaleCombo.insertItem(0, scale_str) scaleCombo.setCurrentIndex(0) else: scaleCombo.setCurrentIndex(idx) def onPoiLayerChanged(self): # Populate the list of available attribute names self.ui.poiFieldComboBox.blockSignals(True) self.ui.poiFieldComboBox.clear() for layer in self.iface.mapCanvas().layers(): if layer.name() == self.ui.poiLayerComboBox.currentText(): for field in layer.dataProvider().fields().toList(): self.ui.poiFieldComboBox.addItem(field.name()) self.ui.poiFieldComboBox.blockSignals(False) def populatePoiLayers(self): # Called once on init for layer in self.iface.mapCanvas().layers(): if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QgsWkbTypes.PointGeometry: self.ui.poiLayerComboBox.addItem(layer.name()) def populateTemplateTypes(self): self.ui.templateTypeComboBox.blockSignals(True) self.ui.templateTypeComboBox.clear() for entry in os.listdir(self.templateFileRoot): if os.path.isdir(os.path.join(self.templateFileRoot, entry)): for subentry in os.listdir(os.path.join(self.templateFileRoot, entry)): filePath = os.path.join(self.templateFileRoot, entry, subentry) dummy, fileName = os.path.split(filePath) if os.path.isfile(filePath) and \ fileName.lower().endswith('.qpt') and \ fileName[:2] in self.supportedPaperSizes: self.ui.templateTypeComboBox.addItem(entry) break self.ui.templateTypeComboBox.blockSignals(False) def onTemplateTypeChanged(self, newIdx=None): # Determine what paper sizes are available self.ui.sizeComboBox.blockSignals(True) self.ui.sizeComboBox.clear() if self.ui.templateTypeComboBox.count() > 0: self.getPaperSizes() for paperSize in self.paperSizesPresent: self.ui.sizeComboBox.addItem(paperSize) self.loadCopyrights() self.ui.sizeComboBox.blockSignals(False) self.onPaperSizeChanged() def getPaperSizes(self): # Maintain a list of paper sizes available for the given template self.paperSizesPresent = [] templateFolder = os.path.join(self.templateFileRoot, self.ui.templateTypeComboBox.currentText()) for entry in os.listdir(templateFolder): if not os.path.isfile(os.path.join(templateFolder, entry)) or not entry.lower().endswith('.qpt'): continue paperSize = entry[:2] if paperSize in self.supportedPaperSizes and paperSize not in self.paperSizesPresent: self.paperSizesPresent.append(paperSize) self.paperSizesPresent.sort(reverse=True) def onPaperSizeChanged(self, newIdx=None): # Determine what orientations are available self.ui.orientationComboBox.blockSignals(True) self.ui.orientationComboBox.clear() if self.ui.templateTypeComboBox.count() > 0: templateFolder = os.path.join(self.templateFileRoot, self.ui.templateTypeComboBox.currentText()) for entry in os.listdir(templateFolder): if entry.lower().endswith('.qpt') and entry.startswith(self.ui.sizeComboBox.currentText()): if entry[2] == 'P' and self.ui.orientationComboBox.findText('Portrait') == -1: self.ui.orientationComboBox.addItem('Portrait') elif entry[2] == 'L' and self.ui.orientationComboBox.findText('Landscape') == -1: self.ui.orientationComboBox.addItem('Landscape') self.ui.orientationComboBox.blockSignals(False) self.onPaperOrientationChanged() def onPaperOrientationChanged(self, newIdx=None): self.populateScaleChoices() poiEnabled = False if self.ui.orientationComboBox.count() > 0: print_layout = self.get_print_layout() poiEnabled = True if print_layout.itemById('gridref') else False self.ui.poiLayerLabel.setEnabled(poiEnabled) self.ui.poiLayerComboBox.setEnabled(poiEnabled) self.ui.poiFieldLabel.setEnabled(poiEnabled) self.ui.poiFieldComboBox.setEnabled(poiEnabled) # At this point we should know what the path to the .qpt file would be if the user was to hit OK # Check it exists and update the OK button appropriately qptFilePath = self.getQptFilePath() if qptFilePath is None or not os.path.isfile(qptFilePath): self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) else: self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) def populateScaleChoices(self): """ When the template type combo is initialised or changes, update the layout of scales for the various map elements in layout. """ # Clear previous for i in reversed(list(range(self.ui.scalesGridLayout.count()))): item = self.ui.scalesGridLayout.itemAt(i) if type(item) == QSpacerItem: self.ui.scalesGridLayout.removeItem(item) else: item.widget().setParent(None) print_layout = self.get_print_layout() map_elements = [item for item in print_layout.items() if isinstance(item, QgsLayoutItemMap)] i = 0 for elem in map_elements: label_1 = QLabel(elem.displayName()) self.ui.scalesGridLayout.addWidget(label_1, i, 0) spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) self.ui.scalesGridLayout.addItem(spacer, i, 1) label_2 = QLabel('1:') self.ui.scalesGridLayout.addWidget(label_2, i, 2) comboBox = QComboBox() comboBox.setEditable(True) # Add current canvas scale locale.setlocale(locale.LC_ALL, '') currentMapCanvasScale = self.iface.mapCanvas().scale() scaleString = locale.format('%d', currentMapCanvasScale, grouping=True) comboBox.addItem('%s (Current map canvas)' % scaleString) for scale in self.presetScales: comboBox.addItem(str(scale)) self.ui.scalesGridLayout.addWidget(comboBox, i, 3) i += 1 if len(map_elements) == 0: label = QLabel('No layout map elements found. Limited usability.') self.ui.scalesGridLayout.addWidget(label, i, 1) def set_legend_compositions(self, print_layout): non_ident = QgsProject.instance().nonIdentifiableLayers() self.legend_tree_root = QgsLayerTree() layer_nodes = [] for lyr in self.iface.mapCanvas().layers(): if lyr.id() not in non_ident: layer_nodes.append(QgsLayerTreeLayer(lyr)) self.legend_tree_root.insertChildNodes(-1, layer_nodes) # update the model for item in print_layout.items(): if not isinstance(item, QgsLayoutItemLegend): continue legend_model = item.model() legend_model.setRootGroup(self.legend_tree_root) def getQptFilePath(self): try: qptFilePath = os.path.join(self.templateFileRoot, self.ui.templateTypeComboBox.currentText(), self.ui.sizeComboBox.currentText() + self.ui.orientationComboBox.currentText()[ 0] + '.qpt') except IndexError: qptFilePath = None return qptFilePath def loadCopyrights(self): self.ui.copyrightComboBox.clear() templateFolder = os.path.join(self.templateFileRoot, self.ui.templateTypeComboBox.currentText()) copyrightFolder = os.path.join(templateFolder, 'Copyrights') if not os.path.isdir(copyrightFolder): return for entry in os.listdir(copyrightFolder): if entry.lower().endswith('.txt'): entry = entry[:-4] if entry.lower() == 'default': continue self.ui.copyrightComboBox.addItem(entry) defaultFilePath = os.path.join(copyrightFolder, 'default.txt') if os.path.isfile(defaultFilePath): with open(defaultFilePath, 'r') as defaultFile: defautlCopyright = defaultFile.read().strip() if defautlCopyright.lower().endswith('.txt'): defautlCopyright = defautlCopyright[:-4] self.ui.copyrightComboBox.setCurrentIndex(self.ui.copyrightComboBox.findText(defautlCopyright)) def getTemplateFilePath(self): orientationLetter = self.ui.orientationComboBox.currentText() try: orientationLetter = orientationLetter[0] except IndexError: pass qptFilePath = os.path.join(self.templateFileRoot, self.ui.templateTypeComboBox.currentText(), self.ui.sizeComboBox.currentText() + orientationLetter + '.qpt') return qptFilePath def getCopyrightText(self): copyrightFilePath = os.path.join(self.templateFileRoot, self.ui.templateTypeComboBox.currentText(), 'Copyrights', self.ui.copyrightComboBox.currentText() + '.txt') try: with open(copyrightFilePath, 'r') as copyrightFile: copyrightText = copyrightFile.read().strip() except IOError: return '' return copyrightText def openTemplate(self): print_layout = self.get_print_layout() # Load replaceable text self.replaceMap['copyright'] = self.getCopyrightText() self.replaceMap['title'] = self.ui.titleLineEdit.text() # not in examples, is it still supported? self.replaceMap['subtitle'] = self.ui.subtitleLineEdit.text() self.replaceMap['gridref'] = self.getPoiText() for k, v in self.replaceMap.items(): item = print_layout.itemById(k) if item: item.setText(v) # Update images of all maps elements in layout if self.identifiable_only: try: self.set_legend_compositions(print_layout) except AttributeError: msg = 'Filtering by identifiable layers ignored.' self.iface.messageBar().pushMessage('Project and Template Selector: ', msg, level=0) # get map items only map_items = [item for item in print_layout.items() if isinstance(item, QgsLayoutItemMap)] for idx, layout_map in enumerate(map_items): # Get the scale denominator (as a floating point) scaleCombo = self.ui.scalesGridLayout.itemAtPosition(idx, 3).widget() assert scaleCombo != 0 try: scaleDenom = float(scaleCombo.currentText().replace(',', '')) except ValueError: cleanedScaleString = scaleCombo.currentText().split(' (')[0] cleanedScaleString = ''.join(cleanedScaleString.split()) scaleDenom = float(cleanedScaleString) # Set the scale cme = layout_map.extent() canvasEx = self.iface.mapCanvas().extent() p1 = QgsPointXY(canvasEx.center().x() - (cme.width() / 2.0), canvasEx.center().y() - (cme.height() / 2.0)) p2 = QgsPointXY(canvasEx.center().x() + (cme.width() / 2.0), canvasEx.center().y() + (cme.height() / 2.0)) newCme = QgsRectangle(p1, p2) layout_map.setExtent(newCme) layout_map.setScale(scaleDenom) layout_map.refresh() # Set scale cur_idx = self.ui.suitableForComboBox.currentIndex() if cur_idx == 0: # Paper dpi = 300 elif cur_idx == 1: # Electronic dpi = 96 else: res_text = self.ui.suitableForComboBox.currentText() try: dpi = int(res_text) except (TypeError, ValueError): dpi = 96 print_layout.renderContext().setDpi(dpi) self.iface.openLayoutDesigner(print_layout) # All done self.accept() def getPoiText(self): # Return grid references of POIs poiLayer = None for layer in self.iface.mapCanvas().layers(): if layer.name() == self.ui.poiLayerComboBox.currentText(): poiLayer = layer break if poiLayer == None: return 'Failed to find POI layer %s' % self.ui.poiLayerComboBox.currentText() poiString = 'Grid References\n\n' f = QgsFeature() fit = poiLayer.getFeatures() while fit.nextFeature(f): gridRef = xy_to_osgb(f.geometry().centroid().asPoint()[0], f.geometry().centroid().asPoint()[1], 10) coordText = '%s\t%s\n' % (f.attribute(self.ui.poiFieldComboBox.currentText()), gridRef) poiString += coordText return poiString def updateScaleText(self, newText): strippedtext = newText[4:] self.ui.scaleLineEdit.setText(strippedtext) def loadHelpPage(self): helpUrl = 'https://github.com/lutraconsulting/qgis-moor-tools-plugin/blob/master/README.md' QtGui.QDesktopServices.openUrl(QtCore.QUrl(helpUrl)) def get_print_layout(self): template_path = self.getTemplateFilePath() if not os.path.isfile(template_path): msg = 'The requested template {} is not currently available.'.format(template_path) QMessageBox.critical(self.iface.mainWindow(), 'Template Not Found', msg) return # Create a new print layout with name equal to the project title project = QgsProject.instance() layout_manager = project.layoutManager() existing_print_layout = layout_manager.layoutByName(self.ui.titleLineEdit.text()) if existing_print_layout: layout_manager.removeLayout(existing_print_layout) print_layout = QgsPrintLayout(project) print_layout.setName(self.ui.titleLineEdit.text()) layout_manager.addLayout(print_layout) # Load the template file try: tree = ET.parse(template_path) doc = QDomDocument() doc.setContent(ET.tostring(tree.getroot())) except IOError: # problem reading xml template msg = 'The requested template {} could not be read.'.format(template_path) QMessageBox.critical(self.iface.mainWindow(), 'Failed to Read Template', msg) return except: # Unexpected problem msg = 'An unexpected error occurred while reading {}:\n\n{}'.format(template_path, traceback.format_exc()) QMessageBox.critical(self.iface.mainWindow(), 'Failed to Read Template', msg) return if not print_layout.loadFromTemplate(doc, QgsReadWriteContext(), True): msg = 'loadFromTemplate returned False.' QMessageBox.critical(self.iface.mainWindow(), 'Failed to Read Template', msg) return return print_layout
def createContextMenu(self): menu = QMenu() actionAddGroup = QgisHelper.createAction(menu, "&Add GroupLayer", self.addGroup) actionAddVectorLayer = QgisHelper.createAction(menu, "&Add VectorLayer", self.addVectorLayer) actionZoomToGroup = QgisHelper.createAction(menu, "&Zoom To Group", self.zoomToGroup) actionRenameGroupOrLayer = QgisHelper.createAction( menu, "&Rename", self.rename) actionRemoveLayer = QgisHelper.createAction(menu, "&Remove", self.removeLayer) actionGroupSelected = QgisHelper.createAction(menu, "&Group Selected", self.groupSelected) actionZoomToLayer = QgisHelper.createAction(menu, "&Zoom To Layer", self.zoomToLayer) actionOpenAttributetable = QgisHelper.createAction( menu, "&Open Attribute Table", self.openAttriTable) actionSaveAs = QgisHelper.createAction(menu, "&Save as...", self.saveAs) actionLayerProperty = QgisHelper.createAction(menu, "&Property", self.layerProperty) self.actionShowOverview = QgisHelper.createAction( menu, "&Show in overview...", self.showOverview, None, None, None, True) idx = self.mView.currentIndex() node = self.mView.layerTreeModel().index2node(idx) if not idx.isValid(): #global menu menu.addAction(actionAddGroup) menu.addAction(actionAddVectorLayer) # menu.addAction( QgsApplication.getThemeIcon( "/mActionExpandTree.png" ), "&Expand All" , self.mView, "expandAll()" ) # menu.addAction( QgsApplication.getThemeIcon( "/mActionCollapseTree.png" ), "&Collapse All" , self.mView, "collapseAll()" ) elif (node != None): if (QgsLayerTree.isGroup(node)): menu.addAction(actionZoomToGroup) menu.addAction(actionRemoveLayer) # menu.addAction( QgsApplication.getThemeIcon( "/mActionSetCRS.png" ), # tr( "&Set Group CRS" ), QgisApp.instance(), SLOT( legendGroupSetCRS() ) ); menu.addAction(actionRenameGroupOrLayer) if (str(self.mView.selectedNodes(True)) >= 2): menu.addAction(actionGroupSelected) menu.addAction(actionAddGroup) menu.addAction(actionAddVectorLayer) elif (QgsLayerTree.isLayer(node)): menu.addAction(actionZoomToLayer) menu.addAction(actionRemoveLayer) menu.addAction(self.actionShowOverview) if self.mView.currentLayer().type() == QgsMapLayer.VectorLayer: menu.addAction(actionOpenAttributetable) menu.addAction(actionLayerProperty) menu.addAction(actionSaveAs) # menu.addAction( actions.actionShowInOverview( menu ) ) # if layer.type() == QgsMapLayer.RasterLayer : # menu.addAction( "&Zoom to Best Scale (100%)" , QgisApp.instance(), " legendLayerZoomNative() " ) # rasterLayer = qobject_cast<QgsRasterLayer *>( layer ); # if ( rasterLayer && rasterLayer.rastertype() != QgsRasterLayer.Palette ) # menu.addAction( tr( "&Stretch Using Current Extent" ), QgisApp.instance(), SLOT( legendLayerStretchUsingCurrentExtent() ) ); # } # menu.addAction( QgsApplication.getThemeIcon( "/mActionRemoveLayer.svg" ), tr( "&Remove" ), QgisApp.instance(), " removeLayer() "); # // duplicate layer # QAction* duplicateLayersAction = menu.addAction( QgsApplication.getThemeIcon( "/mActionDuplicateLayer.svg" ), tr( "&Duplicate" ), QgisApp.instance(), SLOT( duplicateLayers() ) ); # # // set layer scale visibility # menu.addAction( tr( "&Set Layer Scale Visibility" ), QgisApp.instance(), SLOT( setLayerScaleVisibility() ) ); # # // set layer crs # menu.addAction( QgsApplication.getThemeIcon( "/mActionSetCRS.png" ), tr( "&Set Layer CRS" ), QgisApp.instance(), SLOT( setLayerCRS() ) ); # # // assign layer crs to project # menu.addAction( QgsApplication.getThemeIcon( "/mActionSetProjectCRS.png" ), tr( "Set &Project CRS from Layer" ), QgisApp.instance(), SLOT( setProjectCRSFromLayer() ) ); # # menu.addSeparator(); # # if ( layer && layer.type() == QgsMapLayer.VectorLayer ) # { # QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer *>( layer ); # # QAction *toggleEditingAction = QgisApp.instance().actionToggleEditing(); # QAction *saveLayerEditsAction = QgisApp.instance().actionSaveActiveLayerEdits(); # QAction *allEditsAction = QgisApp.instance().actionAllEdits(); # # // attribute table # menu.addAction( QgsApplication.getThemeIcon( "/mActionOpenTable.png" ), tr( "&Open Attribute Table" ), # QgisApp.instance(), SLOT( attributeTable() ) ); # # // allow editing # int cap = vlayer.dataProvider().capabilities(); # if ( cap & QgsVectorDataProvider.EditingCapabilities ) # { # if ( toggleEditingAction ) # { # menu.addAction( toggleEditingAction ); # toggleEditingAction.setChecked( vlayer.isEditable() ); # } # if ( saveLayerEditsAction && vlayer.isModified() ) # { # menu.addAction( saveLayerEditsAction ); # } # } # # if ( allEditsAction.isEnabled() ) # menu.addAction( allEditsAction ); # # // disable duplication of memory layers # if ( vlayer.storageType() == "Memory storage" && self.mView.selectedLayerNodes().count() == 1 ) # duplicateLayersAction.setEnabled( false ); # # // save as vector file # menu.addAction( tr( "Save As..." ), QgisApp.instance(), SLOT( saveAsFile() ) ); # menu.addAction( tr( "Save As Layer Definition File..." ), QgisApp.instance(), SLOT( saveAsLayerDefinition() ) ); # # if ( !vlayer.isEditable() && vlayer.dataProvider().supportsSubsetString() && vlayer.vectorJoins().isEmpty() ) # menu.addAction( tr( "&Filter..." ), QgisApp.instance(), SLOT( layerSubsetString() ) ); # # menu.addAction( actions.actionShowFeatureCount( menu ) ); # # menu.addSeparator(); # } # else if ( layer && layer.type() == QgsMapLayer.RasterLayer ) # { # menu.addAction( tr( "Save As..." ), QgisApp.instance(), SLOT( saveAsRasterFile() ) ); # menu.addAction( tr( "Save As Layer Definition File..." ), QgisApp.instance(), SLOT( saveAsLayerDefinition() ) ); # } # else if ( layer && layer.type() == QgsMapLayer.PluginLayer && self.mView.selectedLayerNodes().count() == 1 ) # { # // disable duplication of plugin layers # duplicateLayersAction.setEnabled( false ); # } # addCustomLayerActions( menu, layer ); # # if ( layer && QgsProject.instance().layerIsEmbedded( layer.id() ).isEmpty() ) # menu.addAction( tr( "&Properties" ), QgisApp.instance(), SLOT( layerProperties() ) ); # if ( node.parent() != self.mView.layerTreeModel().rootGroup() ): # menu.addAction( actions.actionMakeTopLevel( menu ) ) menu.addAction(actionRenameGroupOrLayer) if (len(self.mView.selectedNodes(True)) >= 2): menu.addAction(actionGroupSelected) # if ( self.mView.selectedLayerNodes().count() == 1 ) # { # QgisApp* app = QgisApp.instance(); # menu.addAction( tr( "Copy Style" ), app, SLOT( copyStyle() ) ); # if ( app.clipboard().hasFormat( QGSCLIPBOARD_STYLE_MIME ) ) # { # menu.addAction( tr( "Paste Style" ), app, SLOT( pasteStyle() ) ); # } # } return menu
def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return node = self.index2node(index) legend_node = self.index2legendNode(index) if legend_node and role == Qt.DecorationRole: pixmap = pixmapForLegendNode(legend_node) if pixmap: return pixmap if not node: return super().data(index, role) if role == Qt.FontRole: f = iface.layerTreeView().font() if node.customProperty("plugins/customTreeIcon/font"): f.fromString(node.customProperty("plugins/customTreeIcon/font")) elif QgsLayerTree.isLayer(node): f = self.layerTreeNodeFont(QgsLayerTree.NodeLayer) elif QgsLayerTree.isGroup(node): f = self.layerTreeNodeFont(QgsLayerTree.NodeGroup) if index == self.currentIndex(): f.setUnderline(not f.underline()) if QgsLayerTree.isLayer(node): _, _, scale = self.legendMapViewData() layer = node.layer() if (not node.isVisible() and (not layer or layer.isSpatial())) or ( layer and not layer.isInScaleRange(scale) ): f.setItalic(not f.italic()) return f if role == Qt.ForegroundRole: color = None if node.customProperty("plugins/customTreeIcon/textColor"): color = QColor(node.customProperty("plugins/customTreeIcon/textColor")) elif QgsLayerTree.isGroup(node): if self.settings.value("group_text_color"): color = QColor(self.settings.value("group_text_color")) else: if self.settings.value("layer_text_color"): color = QColor(self.settings.value("layer_text_color")) if color: if QgsLayerTree.isLayer(node): _, _, scale = self.legendMapViewData() layer = node.layer() if (not node.isVisible() and (not layer or layer.isSpatial())) or ( layer and not layer.isInScaleRange(scale) ): color.setAlpha(128) return color if role == Qt.BackgroundRole: if node.customProperty("plugins/customTreeIcon/backgroundColor"): return QColor( node.customProperty("plugins/customTreeIcon/backgroundColor") ) elif QgsLayerTree.isGroup(node): if self.settings.value("group_background_color"): return QColor(self.settings.value("group_background_color")) else: if self.settings.value("layer_background_color"): return QColor(self.settings.value("layer_background_color")) # Override data for DecorationRole (Icon) if role == Qt.DecorationRole and index.column() == 0: icon = None pixmap = None # If a custom icon was set for this node if node.customProperty("plugins/customTreeIcon/icon"): icon = QIcon(node.customProperty("plugins/customTreeIcon/icon")) # If an icon was set for the node type elif QgsLayerTree.isGroup(node): if self.settings.value("defaulticons/group", ""): icon = QIcon(self.settings.value("defaulticons/group")) else: icon = QIcon(":/images/themes/default/mActionFolder.svg") elif QgsLayerTree.isLayer(node): layer = node.layer() if not layer: return super().data(index, role) if layer.type() == QgsMapLayer.RasterLayer: if self.settings.value("defaulticons/raster", ""): icon = QIcon(self.settings.value("defaulticons/raster")) else: icon = QIcon(":/images/themes/default/mIconRaster.svg") if layer.type() == QgsMapLayer.VectorLayer: if self.testFlag( QgsLayerTreeModel.ShowLegend ) and self.legendEmbeddedInParent(node): size = iface.layerTreeView().iconSize() legend_node = self.legendNodeEmbeddedInParent(node) pixmap = pixmapForLegendNode(legend_node) else: if layer.geometryType() == QgsWkbTypes.PointGeometry: if self.settings.value("defaulticons/point", ""): icon = QIcon(self.settings.value("defaulticons/point")) else: icon = QIcon( ":/images/themes/default/mIconPointLayer.svg" ) elif layer.geometryType() == QgsWkbTypes.LineGeometry: if self.settings.value("defaulticons/line", ""): icon = QIcon(self.settings.value("defaulticons/line")) else: icon = QIcon( ":/images/themes/default/mIconLineLayer.svg" ) elif layer.geometryType() == QgsWkbTypes.PolygonGeometry: if self.settings.value("defaulticons/polygon", ""): icon = QIcon( self.settings.value("defaulticons/polygon") ) else: icon = QIcon( ":/images/themes/default/mIconPolygonLayer.svg" ) elif layer.geometryType() == QgsWkbTypes.NullGeometry: if self.settings.value("defaulticons/nogeometry", ""): icon = QIcon( self.settings.value("defaulticons/nogeometry") ) else: icon = QIcon( ":/images/themes/default/mIconTableLayer.svg" ) try: if layer.type() == QgsMapLayer.MeshLayer: if self.settings.value("defaulticons/mesh", ""): icon = QIcon(self.settings.value("defaulticons/mesh")) else: icon = QIcon(":/images/themes/default/mIconMeshLayer.svg") except AttributeError: pass # Special case: In-edition vector layer. Draw an editing icon over # the custom icon. Adapted from QGIS source code (qgslayertreemodel.cpp) if (pixmap or icon) and QgsLayerTree.isLayer(node): layer = node.layer() if layer and isinstance(layer, QgsVectorLayer) and layer.isEditable(): icon_size = iface.layerTreeView().iconSize().width() if icon_size == -1: icon_size = 16 if not pixmap and icon: pixmap = QPixmap(icon.pixmap(icon_size, icon_size)) painter = QPainter(pixmap) painter.drawPixmap( 0, 0, icon_size, icon_size, QgsApplication.getThemeIcon( ("/mIconEditableEdits.svg") if layer.isModified() else ("/mActionToggleEditing.svg") ).pixmap(icon_size, icon_size), ) painter.end() del painter if pixmap: return pixmap if icon: return icon # call QgsLayerTreeModel implementation return super().data(index, role)
def with_legend_btn_run(self): project = QgsProject.instance() self.checked_layers = [] root = project.layerTreeRoot() for child in root.children(): if child.isVisible() and isinstance(child, QgsLayerTreeLayer): self.checked_layers.append(child.name()) if child.isVisible() and isinstance(child, QgsLayerTreeGroup): self.get_group_layers(child) layersToAdd = [ layer for layer in project.mapLayers().values() if layer.name() in sorted(self.checked_layers, reverse=False) ] layers_names_length = [ len(layer.name()) for layer in project.mapLayers().values() if layer.name() in sorted(self.checked_layers, reverse=False) ] maxlen = max(layers_names_length) root = QgsLayerTree() for layer in layersToAdd: root.addLayer(layer) model = QgsLayerTreeModel(root) lenlen = model.rowCount() view = QgsLayerTreeView() view.setModel(model) view.setFixedHeight(lenlen * 20) view.setFixedWidth(maxlen * 10) legendIm = QImage(QWidget.grab(view)) legendpath = self.plugin_dir + "\\legend.png" legendIm.save(legendpath) legendIm = Image.open(legendpath) #legendIm = legendIm.imageData() legendWidth, legendHeight = legendIm.size main_image = QImage(QWidget.grab(self.iface.mapCanvas())) mainpath = self.plugin_dir + "\\main.png" main_image.save(mainpath) main_image = Image.open(mainpath).convert("RGBA") width, height = main_image.size d = ImageDraw.Draw(main_image) font = ImageFont.truetype("arial.ttf", 16) d.text(((width / 2.5) + len(project.title()), 10), project.title(), fill='black', font=font) if abs(height - width) < 150: sq_fit_size = legendWidth height = legendHeight else: sq_fit_size = width if width > sq_fit_size and height > sq_fit_size: if width > height: height = int((sq_fit_size / width) * height) width = sq_fit_size else: width = int((sq_fit_size / height) * width) height = sq_fit_size main_image = main_image.resize((width, height)) main_image.paste(legendIm, (max(width - legendWidth, 0), height - legendHeight)) finalpath = self.plugin_dir + "\\main.png" main_image.save(finalpath) QApplication.clipboard().setImage(QImage(finalpath)) self.iface.messageBar().pushMessage( 'QCopycanvas', 'Copied map canvas to clipboard with legend', level=Qgis.Success, duration=2)
class TemplateSelectorDialog(QDialog): def __init__(self, iface): self.supportedPaperSizes = [ 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8' ] # ISO A series self.paperSizesPresent = [] self.presetScales = [ '200', '500', '1 000', '1 250', '2 500', '5 000', '10 000', '25 000', '50 000', '100 000' ] self.iface = iface QDialog.__init__(self) # Set up the user interface from Designer. self.ui = uic.loadUi(ui_file, self) # Set up the list of templates s = QtCore.QSettings() self.identifiable_only = s.value( "SelectorTools/ProjectSelector/identifiableOnly", True, type=bool) self.templateFileRoot = s.value( "SelectorTools/TemplateSelector/templateRoot", '', type=str) if len(self.templateFileRoot) == 0 or not os.path.isdir( self.templateFileRoot): raise TemplateSelectorException( '\'%s\' is not a valid template file root folder.' % self.templateFileRoot) self.populateTemplateTypes() self.populatePoiLayers() self.onPoiLayerChanged() self.onTemplateTypeChanged() self.plugin_dir = os.path.dirname(__file__) # Replacement map self.ui.suitableForComboBox.addItem('<custom>') if platform == 'win32': self.username = os.environ.get('username', '<username>') else: self.username = os.environ.get('USER', '<username>') self.replaceMap = { 'username': self.username, 'author': f"Compiled by {self.username} on [%concat(day($now ),'/',month($now),'/',year($now))%]" } self.ui.autofit_btn.clicked.connect(self.autofit_map) self.ui.suitableForComboBox.currentIndexChanged.connect( self.specify_dpi) self.ui.suitableForComboBox.editTextChanged.connect(self.text_changed) self.ui.poiLayerComboBox.currentIndexChanged.connect( self.onPoiLayerChanged) def specify_dpi(self, idx): if idx == 2: self.ui.suitableForComboBox.setEditable(True) else: self.ui.suitableForComboBox.setEditable(False) def text_changed(self, txt): self.ui.suitableForComboBox.setItemText( self.ui.suitableForComboBox.currentIndex(), txt) def autofit_map(self): canvas = self.iface.mapCanvas() units = canvas.mapUnits() coef = 1 / 0.3048 if units == QgsUnitTypes.DistanceFeet else 1 map_extent = canvas.extent() me_height = map_extent.height() * 1000 / coef me_width = map_extent.width() * 1000 / coef print_layout = self.get_print_layout() map_elements = [ item for item in print_layout.items() if isinstance(item, QgsLayoutItemMap) ] for idx, item in enumerate(map_elements): size = item.sizeWithUnits() h = size.height() w = size.width() hscale = me_height / h wscale = me_width / w scale = hscale if hscale > wscale else wscale scale_str = '{} (Autofit)'.format(ceil(scale)) scaleCombo = self.ui.scalesGridLayout.itemAtPosition(idx, 3).widget() idx = scaleCombo.findText(scale_str) if idx == -1: scaleCombo.insertItem(0, scale_str) scaleCombo.setCurrentIndex(0) else: scaleCombo.setCurrentIndex(idx) def onPoiLayerChanged(self): # Populate the list of available attribute names self.ui.poiFieldComboBox.blockSignals(True) self.ui.poiFieldComboBox.clear() for layer in self.iface.mapCanvas().layers(): if layer.name() == self.ui.poiLayerComboBox.currentText(): for field in layer.dataProvider().fields().toList(): self.ui.poiFieldComboBox.addItem(field.name()) self.ui.poiFieldComboBox.blockSignals(False) def populatePoiLayers(self): # Called once on init for layer in self.iface.mapCanvas().layers(): if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType( ) == QgsWkbTypes.PointGeometry: self.ui.poiLayerComboBox.addItem(layer.name()) def populateTemplateTypes(self): self.ui.templateTypeComboBox.blockSignals(True) self.ui.templateTypeComboBox.clear() for entry in os.listdir(self.templateFileRoot): if os.path.isdir(os.path.join(self.templateFileRoot, entry)): for subentry in os.listdir( os.path.join(self.templateFileRoot, entry)): filePath = os.path.join(self.templateFileRoot, entry, subentry) dummy, fileName = os.path.split(filePath) if os.path.isfile(filePath) and \ fileName.lower().endswith('.qpt') and \ fileName[:2] in self.supportedPaperSizes: self.ui.templateTypeComboBox.addItem(entry) break self.ui.templateTypeComboBox.blockSignals(False) def onTemplateTypeChanged(self, newIdx=None): # Determine what paper sizes are available self.ui.sizeComboBox.blockSignals(True) self.ui.sizeComboBox.clear() if self.ui.templateTypeComboBox.count() > 0: self.getPaperSizes() for paperSize in self.paperSizesPresent: self.ui.sizeComboBox.addItem(paperSize) self.loadCopyrights() self.ui.sizeComboBox.blockSignals(False) self.onPaperSizeChanged() def getPaperSizes(self): # Maintain a list of paper sizes available for the given template self.paperSizesPresent = [] templateFolder = os.path.join( self.templateFileRoot, self.ui.templateTypeComboBox.currentText()) for entry in os.listdir(templateFolder): if not os.path.isfile( os.path.join(templateFolder, entry)) or not entry.lower().endswith('.qpt'): continue paperSize = entry[:2] if paperSize in self.supportedPaperSizes and paperSize not in self.paperSizesPresent: self.paperSizesPresent.append(paperSize) self.paperSizesPresent.sort(reverse=True) def onPaperSizeChanged(self, newIdx=None): # Determine what orientations are available self.ui.orientationComboBox.blockSignals(True) self.ui.orientationComboBox.clear() if self.ui.templateTypeComboBox.count() > 0: templateFolder = os.path.join( self.templateFileRoot, self.ui.templateTypeComboBox.currentText()) for entry in os.listdir(templateFolder): if entry.lower().endswith('.qpt') and entry.startswith( self.ui.sizeComboBox.currentText()): if entry[2] == 'P' and self.ui.orientationComboBox.findText( 'Portrait') == -1: self.ui.orientationComboBox.addItem('Portrait') elif entry[ 2] == 'L' and self.ui.orientationComboBox.findText( 'Landscape') == -1: self.ui.orientationComboBox.addItem('Landscape') self.ui.orientationComboBox.blockSignals(False) self.onPaperOrientationChanged() def onPaperOrientationChanged(self, newIdx=None): self.populateScaleChoices() poiEnabled = False if self.ui.orientationComboBox.count() > 0: print_layout = self.get_print_layout() poiEnabled = True if print_layout.itemById('gridref') else False self.ui.poiLayerLabel.setEnabled(poiEnabled) self.ui.poiLayerComboBox.setEnabled(poiEnabled) self.ui.poiFieldLabel.setEnabled(poiEnabled) self.ui.poiFieldComboBox.setEnabled(poiEnabled) # At this point we should know what the path to the .qpt file would be if the user was to hit OK # Check it exists and update the OK button appropriately qptFilePath = self.getQptFilePath() if qptFilePath is None or not os.path.isfile(qptFilePath): self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) else: self.ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) def populateScaleChoices(self): """ When the template type combo is initialised or changes, update the layout of scales for the various map elements in layout. """ # Clear previous for i in reversed(list(range(self.ui.scalesGridLayout.count()))): item = self.ui.scalesGridLayout.itemAt(i) if type(item) == QSpacerItem: self.ui.scalesGridLayout.removeItem(item) else: item.widget().setParent(None) print_layout = self.get_print_layout() map_elements = [ item for item in print_layout.items() if isinstance(item, QgsLayoutItemMap) ] i = 0 for elem in map_elements: label_1 = QLabel(elem.displayName()) self.ui.scalesGridLayout.addWidget(label_1, i, 0) spacer = QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum) self.ui.scalesGridLayout.addItem(spacer, i, 1) label_2 = QLabel('1:') self.ui.scalesGridLayout.addWidget(label_2, i, 2) comboBox = QComboBox() comboBox.setEditable(True) # Add current canvas scale locale.setlocale(locale.LC_ALL, '') currentMapCanvasScale = self.iface.mapCanvas().scale() scaleString = locale.format_string('%d', currentMapCanvasScale, grouping=True) comboBox.addItem(f'{scaleString} (Current map canvas)') for scale in self.presetScales: comboBox.addItem(str(scale)) self.ui.scalesGridLayout.addWidget(comboBox, i, 3) i += 1 if len(map_elements) == 0: label = QLabel('No layout map elements found. Limited usability.') self.ui.scalesGridLayout.addWidget(label, i, 1) def set_legend_compositions(self, print_layout): non_ident = QgsProject.instance().nonIdentifiableLayers() self.legend_tree_root = QgsLayerTree() layer_nodes = [] for lyr in self.iface.mapCanvas().layers(): if lyr.id() not in non_ident: layer_nodes.append(QgsLayerTreeLayer(lyr)) self.legend_tree_root.insertChildNodes(-1, layer_nodes) # update the model for item in print_layout.items(): if not isinstance(item, QgsLayoutItemLegend): continue legend_model = item.model() legend_model.setRootGroup(self.legend_tree_root) def getQptFilePath(self): try: qptFilePath = os.path.join( self.templateFileRoot, self.ui.templateTypeComboBox.currentText(), self.ui.sizeComboBox.currentText() + self.ui.orientationComboBox.currentText()[0] + '.qpt') except IndexError: qptFilePath = None return qptFilePath def loadCopyrights(self): self.ui.copyrightComboBox.clear() templateFolder = os.path.join( self.templateFileRoot, self.ui.templateTypeComboBox.currentText()) copyrightFolder = os.path.join(templateFolder, 'Copyrights') if not os.path.isdir(copyrightFolder): return for entry in os.listdir(copyrightFolder): if entry.lower().endswith('.txt'): entry = entry[:-4] if entry.lower() == 'default': continue self.ui.copyrightComboBox.addItem(entry) defaultFilePath = os.path.join(copyrightFolder, 'default.txt') if os.path.isfile(defaultFilePath): with open(defaultFilePath, 'r', errors='ignore') as defaultFile: defautlCopyright = defaultFile.read().strip() if defautlCopyright.lower().endswith('.txt'): defautlCopyright = defautlCopyright[:-4] self.ui.copyrightComboBox.setCurrentIndex( self.ui.copyrightComboBox.findText(defautlCopyright)) def getTemplateFilePath(self): orientationLetter = self.ui.orientationComboBox.currentText() try: orientationLetter = orientationLetter[0] except IndexError: pass qptFilePath = os.path.join( self.templateFileRoot, self.ui.templateTypeComboBox.currentText(), self.ui.sizeComboBox.currentText() + orientationLetter + '.qpt') return qptFilePath def getCopyrightText(self): copyrightFilePath = os.path.join( self.templateFileRoot, self.ui.templateTypeComboBox.currentText(), 'Copyrights', self.ui.copyrightComboBox.currentText() + '.txt') try: with open(copyrightFilePath, 'r', errors='ignore') as copyrightFile: copyrightText = copyrightFile.read().strip() except IOError: return '' return copyrightText def openTemplate(self): """ Open Layout Designer with the specified print layout template. """ project = QgsProject.instance() layout_manager = project.layoutManager() layout = self.get_print_layout() layout_manager.addLayout(layout) # reload the print layout from the manager, otherwise in some cases it might happen that # the underlying C++ QgsPrintLayout object gets deleted layout_title = self.ui.titleLineEdit.text() print_layout = layout_manager.layoutByName( layout_title if layout_title else "unnamed") # Load replaceable text self.replaceMap['copyright'] = self.getCopyrightText() self.replaceMap['title'] = self.ui.titleLineEdit.text() self.replaceMap['subtitle'] = self.ui.subtitleLineEdit.text() for item in self.get_text_items(print_layout): item_text = item.currentText() for k, v in self.replaceMap.items(): item_text = item_text.replace(f'[{k}]', v) if v else item_text item.setText(item_text) gridref_item = print_layout.itemById('gridref') if gridref_item: gridref_item.setText(self.getPoiText()) # Update images of all maps elements in layout if self.identifiable_only: try: self.set_legend_compositions(print_layout) except AttributeError: msg = 'Filtering by identifiable layers ignored.' self.iface.messageBar().pushMessage( 'Project and Template Selector: ', msg, level=0) # get map items only map_items = [ item for item in print_layout.items() if isinstance(item, QgsLayoutItemMap) ] for idx, layout_map in enumerate(map_items): # Get the scale denominator (as a floating point) scaleCombo = self.ui.scalesGridLayout.itemAtPosition(idx, 3).widget() assert scaleCombo != 0 denom_txt = scaleCombo.currentText() denom_txt = unicodedata.normalize('NFKD', denom_txt) if " (" in denom_txt: denom_txt = denom_txt.split(' (')[0] denom_txt = denom_txt.replace(",", "") denom_txt = "".join(denom_txt.split()) try: scaleDenom = float(denom_txt) except ValueError as e: QMessageBox.critical(self.iface.mainWindow(), 'Invalid scale', repr(e)) return # Set the scale cme = layout_map.extent() canvasEx = self.iface.mapCanvas().extent() p1 = QgsPointXY(canvasEx.center().x() - (cme.width() / 2.0), canvasEx.center().y() - (cme.height() / 2.0)) p2 = QgsPointXY(canvasEx.center().x() + (cme.width() / 2.0), canvasEx.center().y() + (cme.height() / 2.0)) newCme = QgsRectangle(p1, p2) layout_map.setExtent(newCme) layout_map.setScale(scaleDenom) layout_map.refresh() # Set scale cur_idx = self.ui.suitableForComboBox.currentIndex() if cur_idx == 0: # Paper dpi = 300 elif cur_idx == 1: # Electronic dpi = 96 else: res_text = self.ui.suitableForComboBox.currentText() try: dpi = int(res_text) except (TypeError, ValueError): dpi = 96 print_layout.renderContext().setDpi(dpi) layout_designer_interface = self.iface.openLayoutDesigner(print_layout) # Maximize layout window ldi_window = layout_designer_interface.window() ldi_window.showMaximized() # Resizing page and zoom in ldi_parent = layout_designer_interface.parent() resize_button = ldi_parent.findChild(QPushButton, 'mResizePageButton') resize_button.click() zoom_action = ldi_parent.findChild(QAction, 'mActionZoomAll') zoom_action.trigger() # All done self.accept() def getPoiText(self): # Return grid references of POIs poiLayer = None for layer in self.iface.mapCanvas().layers(): if layer.name() == self.ui.poiLayerComboBox.currentText(): poiLayer = layer break if poiLayer == None: return 'Failed to find POI layer {}'.format( self.ui.poiLayerComboBox.currentText()) poiString = 'Grid References\n\n' f = QgsFeature() fit = poiLayer.getFeatures() while fit.nextFeature(f): gridRef = xy_to_osgb(f.geometry().centroid().asPoint()[0], f.geometry().centroid().asPoint()[1], 10) coordText = '{}\t{}\n'.format( f.attribute(self.ui.poiFieldComboBox.currentText()), gridRef) poiString += coordText return poiString def updateScaleText(self, newText): strippedtext = newText[4:] self.ui.scaleLineEdit.setText(strippedtext) def loadHelpPage(self): helpUrl = 'https://github.com/lutraconsulting/qgis-moor-tools-plugin/blob/master/README.md' QtGui.QDesktopServices.openUrl(QtCore.QUrl(helpUrl)) def get_print_layout(self): template_path = self.getTemplateFilePath() if not os.path.isfile(template_path): msg = 'The requested template {} is not currently available.'.format( template_path) QMessageBox.critical(self.iface.mainWindow(), 'Template Not Found', msg) return # Create a new print layout with name equal to the project title project = QgsProject.instance() layout_manager = project.layoutManager() user_layout_title = self.ui.titleLineEdit.text() layout_title = user_layout_title if user_layout_title else "unnamed" existing_print_layout = layout_manager.layoutByName( layout_title) if layout_title else None if existing_print_layout: layout_manager.removeLayout(existing_print_layout) print_layout = QgsPrintLayout(project) # Load the template file try: tree = ET.parse(template_path) doc = QDomDocument() doc.setContent(ET.tostring(tree.getroot())) except IOError: # problem reading xml template msg = 'The requested template {} could not be read.'.format( template_path) QMessageBox.critical(self.iface.mainWindow(), 'Failed to Read Template', msg) return except: # Unexpected problem msg = 'An unexpected error occurred while reading {}:\n\n{}'.format( template_path, traceback.format_exc()) QMessageBox.critical(self.iface.mainWindow(), 'Failed to Read Template', msg) return if not print_layout.loadFromTemplate(doc, QgsReadWriteContext(), True): msg = 'loadFromTemplate returned False.' QMessageBox.critical(self.iface.mainWindow(), 'Failed to Read Template', msg) return print_layout.setName(layout_title) return print_layout @staticmethod def get_text_items(print_layout): print_layout_item_model = print_layout.itemsModel() row_count = print_layout_item_model.rowCount() column_count = print_layout_item_model.columnCount() for r in range(row_count): for c in range(column_count): index = print_layout_item_model.index(r, c) item = print_layout_item_model.itemFromIndex(index) if isinstance(item, QgsLayoutItemLabel): yield item