示例#1
0
 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 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
示例#3
0
 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
示例#6
0
    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
示例#7
0
    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
示例#9
0
    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)
示例#10
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)
示例#11
0
    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
示例#13
0
    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_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)
示例#15
0
 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
示例#16
0
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
示例#17
0
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
示例#18
0
    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
示例#21
0
    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
示例#22
0
    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