# -*- coding: utf-8 -*- """ /*************************************************************************** getnib A QGIS plugin Plugin "NIB-ortofoto-prosjekt" NB! The plugin will ONLY work for those with access to "Norge i bilder ortofoto-prosjekt WMS". NB! The project must be in EPSG 25832, 25833 or 25835. Get one, several or all orthophoto-projects (included "Satellitt" and "Infrarødt") in map canvas from "Norge i bilder prosjekt (WMS)" for one, several or all years available. The bounding box may be - the visible map canvas (default) - a bouding box given by the user in integer km (width x height) - a layer in Layers panel - an uploaded vector og raster file The orthopohoto projectnames may be written to sorted/unsorted text file(s). The maximum size of the bounding box is set to 500 km x 500 km. Resources used: - Plugin Builder to achieve correct configuration and neccessary files: https://www.qgistutorials.com/en/docs/3/building_a_python_plugin.html NB! Rememeber to create and copy compile.bat (neccessary for compiling resources.py to make your own icon visible) - https://gis.stackexchange.com/questions/136861/getting-layer-by-name-in-pyqgis - https://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/cheat_sheet.html# - https://docs.qgis.org/3.16/en/docs/pyqgis_developer_cookbook/plugins/index.html - https://norgeibilder.no/dok/webtjenester.pdf (in Norwegian) to get the metadata and see the required format of input-values - Install QGIS plugin "Releod plugin" to update changes made in the plugin - QGIS installed through OSGeo4W-installation which includes Qt Designer for designing the plugin - Logo downloaded from https://www.flaticon.com Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/ ------------------- begin : 2021-12-22 git sha : $Format:%H$ copyright : (C) 2021 by ban, NIBIO email : ban@nibio.no ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ """ #Default modules from qgis.PyQt.QtCore import QUrl, QSettings, QTranslator, QCoreApplication from qgis.PyQt.QtGui import QIcon, QDesktopServices from qgis.PyQt.QtWidgets import QAction, QFileDialog, QMessageBox #Import additional modules from qgis.PyQt.QtGui import QRegExpValidator, QIntValidator from qgis.PyQt.QtCore import QRegExp from qgis.core import QgsProject, Qgis, QgsMessageLog, QgsRasterLayer, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsPointXY from qgis.gui import QgsMapCanvas from qgis.utils import iface, showPluginHelp from urllib import parse # to parse a string import requests # https://www.geeksforgeeks.org/response-json-python-requests/ https://realpython.com/python-requests/ import os # get the current working directory, access to file / path import datetime # get the current year # Initialize Qt resources from file resources.py from .resources import * # Import the code for the dialog from .get_nib_dialog import getnibDialog import os.path class getnib: """QGIS Plugin Implementation.""" def __init__(self, iface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QSettings().value('locale/userLocale')[0:2] locale_path = os.path.join( self.plugin_dir, 'i18n', 'getnib_{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) QCoreApplication.installTranslator(self.translator) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&NIB of-prosjekt') # Check if plugin was started the first time in current QGIS session # Must be set in initGui() to survive plugin reloads self.first_start = None # noinspection PyMethodMayBeStatic def tr(self, message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('getnib', message) def add_action( self, icon_path, text, callback, enabled_flag=True, add_to_menu=True, add_to_toolbar=True, status_tip=None, whats_this=None, parent=None): """Add a toolbar icon to the toolbar. :param icon_path: Path to the icon for this action. Can be a resource path (e.g. ':/plugins/foo/bar.png') or a normal file system path. :type icon_path: str :param text: Text that should be shown in menu items for this action. :type text: str :param callback: Function to be called when the action is triggered. :type callback: function :param enabled_flag: A flag indicating if the action should be enabled by default. Defaults to True. :type enabled_flag: bool :param add_to_menu: Flag indicating whether the action should also be added to the menu. Defaults to True. :type add_to_menu: bool :param add_to_toolbar: Flag indicating whether the action should also be added to the toolbar. Defaults to True. :type add_to_toolbar: bool :param status_tip: Optional text to show in a popup when mouse pointer hovers over the action. :type status_tip: str :param parent: Parent widget for the new action. Defaults None. :type parent: QWidget :param whats_this: Optional text to show in the status bar when the mouse pointer hovers over the action. :returns: The action that was created. Note that the action is also added to self.actions list. :rtype: QAction """ icon = QIcon(icon_path) action = QAction(icon, text, parent) action.triggered.connect(callback) action.setEnabled(enabled_flag) if status_tip is not None: action.setStatusTip(status_tip) if whats_this is not None: action.setWhatsThis(whats_this) if add_to_toolbar: # Adds plugin icon to Plugins toolbar self.iface.addToolBarIcon(action) if add_to_menu: self.iface.addPluginToMenu( self.menu, action) self.actions.append(action) return action def initGui(self): """Create the menu entries and toolbar icons inside the QGIS GUI.""" icon_path = ':/plugins/get_nib/icon.png' self.add_action( icon_path, text=self.tr(u'Hent of-prosjekt'), callback=self.run, parent=self.iface.mainWindow()) # will be set False in run() self.first_start = True def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu( self.tr(u'&NIB of-prosjekt'), action) self.iface.removeToolBarIcon(action) """ Additional methods """ def select_input_file(self): """ To select a file which will serve as bounding box """ filename, _filter = QFileDialog.getOpenFileName( self.dlg, "Select a file ","", 'geo-files (*.shp *.geojson *.gpkg *.gml *.jpg *.tif);; All files (*.*)') self.dlg.lineEdit_file.setText(filename) self.dlg.radioButton_file.setChecked(True) # In case you choose a file directly without first chosing this option def show_help(self): """ Display application help to the user. """ help_file = 'file:///%s/index.html' % self.plugin_dir # For testing path: # QMessageBox.information(None, 'Help File', help_file) QDesktopServices.openUrl(QUrl(help_file)) def check_bbsize(self, srid, xmin, xmax, ymin, ymax): """ Check length and height of bounding box. Limit is set to 50 km """ sourceCrs = QgsCoordinateReferenceSystem(srid) # Input project or layer crs destCrs = QgsCoordinateReferenceSystem('EPSG:3035') # ETRS89-extended / LAEA Europe transformContext = QgsProject.instance().transformContext() xform = QgsCoordinateTransform(sourceCrs, destCrs, transformContext) # Forward transformation: src -> dest # Computes length and height in LAEA regardless input crs ll = xform.transform(QgsPointXY(xmin,ymin)) # Bounding box's lower left corner ur = xform.transform(QgsPointXY(xmax,ymax)) # Bounding box's upper right corner # Get the maximum height and length of the bounding box (limit is set to 500 km) xdist = (ur.x()-ll.x())/1000 # Get length in km (xmax-xmin) ydist = (ur.y()-ll.y())/1000 # Get height in km (ymax-ymin) if xdist > 500 or ydist > 500: self.iface.messageBar().pushMessage("Error", "Height or length of bounding box can't be > 500 km",level=Qgis.Critical, duration=3) ok = False else: ok = True # Bounding box is small enough return ok ## Bounding box ## # When you change your mind during input (started input of one bb-option and then changes to another bb-option), # e.g. have entered a length or selected a file, but change your mind and click Use current map canvas instead. # Then length and the file should be wiped out. def notUdbb(self): self.dlg.lineEdit_lengthbb.clear() self.dlg.lineEdit_heightbb.clear() self.dlg.lineEdit_lengthbb.setStyleSheet("QLineEdit" "{" "background-color : white" "}") self.dlg.lineEdit_heightbb.setStyleSheet("QLineEdit" "{" "background-color : white" "}") def notFile(self): self.dlg.lineEdit_file.clear() self.dlg.lineEdit_file.setStyleSheet("QLineEdit" "{" "background-color : white" "}") # When "Use current map canvas"-option: length, height and file turns white (if you change your mind) def ucmcbbClicked(self): if self.dlg.radioButton_ucmcbb.isChecked(): self.notUdbb() # if you change option self.notFile() # if you change option # When "User-defined"-option: length turns yellow and is focused def udbbClicked(self): if self.dlg.radioButton_udbb.isChecked(): self.notFile() # if you change option self.dlg.lineEdit_lengthbb.setFocus() self.dlg.lineEdit_lengthbb.setStyleSheet("QLineEdit" "{" "background-color : yellow" "}") # When length entered: length turns white def lengthbbEdited(self): self.dlg.lineEdit_lengthbb.setStyleSheet("QLineEdit" "{" "background-color : white" "}") self.dlg.radioButton_udbb.setChecked(True) # When length finished entered: height turns yellow and is focused def lengthbbFinished(self): self.dlg.lineEdit_heightbb.setFocus() self.dlg.lineEdit_heightbb.setStyleSheet("QLineEdit" "{" "background-color : yellow" "}") # When height edited: height turns white def heightbbEdited(self): self.dlg.lineEdit_heightbb.setStyleSheet("QLineEdit" "{" "background-color : white" "}") # When "layer"-option: def lyrClicked(self): if self.dlg.radioButton_lyr.isChecked(): self.notUdbb() # if you change option self.notFile() # if you change option # When selected lyr is changed def lyrActivated(self): self.dlg.radioButton_lyr.setChecked(True) # When "file"-option: file turns yellow and is focused def fileClicked(self): if self.dlg.radioButton_file.isChecked(): if self.dlg.lineEdit_file.text() == '': self.dlg.lineEdit_file.setStyleSheet("QLineEdit" "{" "background-color : yellow" "}") self.notUdbb() # if you change option # When file selected: file lineedit turns white def fileSelected(self): self.dlg.lineEdit_file.setStyleSheet("QLineEdit" "{" "background-color : white" "}") ## Years ## # When "All years"-option: start-year turns white (if you change your mind) def allyearsClicked(self): if self.dlg.radioButton_allyears.isChecked(): self.dlg.lineEdit_startyear.setStyleSheet("QLineEdit" "{" "background-color : white" "}") self.dlg.lineEdit_startyear.clear() self.dlg.lineEdit_endyear.setStyleSheet("QLineEdit" "{" "background-color : white" "}") self.dlg.lineEdit_endyear.clear() # When "Between years"-option: start-year turns yellow and is focused def btwyearsClicked(self): if self.dlg.radioButton_btwyears.isChecked(): self.dlg.lineEdit_startyear.setFocus() self.dlg.lineEdit_startyear.setStyleSheet("QLineEdit" "{" "background-color : yellow" "}") # When start-year entered: start-year turns white def startEdited(self): self.dlg.lineEdit_startyear.setStyleSheet("QLineEdit" "{" "background-color : white" "}") # Due to "All years"-option is set to default: self.dlg.radioButton_btwyears.setChecked(True) # Check # When start-year finished entered: end-year turns yellow and is focused def startFinished(self): self.dlg.lineEdit_endyear.setFocus() self.dlg.lineEdit_endyear.setStyleSheet("QLineEdit" "{" "background-color : yellow" "}") # When end-year edited: end-year turns white def endEdited(self): self.dlg.lineEdit_endyear.setStyleSheet("QLineEdit" "{" "background-color : white" "}") """ end additional methods """ def run(self): """Run method that performs all the real work""" # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started if self.first_start == True: self.first_start = False self.dlg = getnibDialog() """ Additional code """ #connect the select_input_file method to the clicked signal of the push button widget self.dlg.pushButton_file.clicked.connect(self.select_input_file) self.dlg.toolButton_help.clicked.connect(self.show_help) self.dlg.radioButton_ucmcbb.toggled.connect(self.ucmcbbClicked) # When current map canvas bb is chosen self.dlg.radioButton_udbb.toggled.connect(self.udbbClicked) # When User-defined bb is chosen self.dlg.lineEdit_lengthbb.textEdited.connect(self.lengthbbEdited) # Whenever length is edited self.dlg.lineEdit_lengthbb.editingFinished.connect(self.lengthbbFinished) # When you press ‘Enter’ or length loses focus self.dlg.lineEdit_heightbb.textEdited.connect(self.heightbbEdited) # Whenever height is edited self.dlg.radioButton_lyr.toggled.connect(self.lyrClicked) # When lyr bb is chosen self.dlg.comboBox_lyr.activated.connect(self.lyrActivated) # Whenever a lyr item is chosen self.dlg.radioButton_file.toggled.connect(self.fileClicked) # When file bb is chosen self.dlg.lineEdit_file.textChanged.connect(self.fileSelected) # Whenever file is selected self.dlg.radioButton_allyears.toggled.connect(self.allyearsClicked) # When All years is chosen self.dlg.radioButton_btwyears.toggled.connect(self.btwyearsClicked) # When Between years is chosen self.dlg.lineEdit_startyear.textEdited.connect(self.startEdited) # Whenever the start year is edited self.dlg.lineEdit_startyear.editingFinished.connect(self.startFinished) # When you press ‘Enter’ or start loses focus self.dlg.lineEdit_endyear.textEdited.connect(self.endEdited) # Whenever end year is edited # As default: Remember user input from previous run """ If this should not be the default, remove comment # from next line """ # self.dlg.checkBox_resetNib.setChecked(False) # Unchecked # As default: Set orthophoto-projects checkbox checked """ If this should not be the default, remove comment # from next line """ self.dlg.checkBox_resetNib.setChecked(True) # Unchecked # As default: Set map canvas checkbox to checked """ If this should not be the default, comment # next line """ self.dlg.radioButton_ucmcbb.setChecked(True) # Checked # Clear the contents of the lineEdit_lengthbb from previous runs self.dlg.lineEdit_lengthbb.clear() # Clear the contents of the lineEdit_heightbb from previous runs self.dlg.lineEdit_heightbb.clear() # Clear the contents of the lineEdit_start (year) from previous runs self.dlg.lineEdit_startyear.clear() # Clear the contents of the lineEdit_end (year) from previous runs self.dlg.lineEdit_endyear.clear() # Clear the contents of the lineEdit_file (select file) from previous runs self.dlg.lineEdit_file.clear() self.dlg.radioButton_file.setChecked(False) # Unchecked # Fetch the currently loaded layers layers = QgsProject.instance().mapLayers() # Clear the contents of the combobox from previous runs self.dlg.comboBox_lyr.clear() # Populate the combobox with names of all the loaded layers layer_list = [] unique_lyr = [] for layer in layers.values(): item = layer.name() layer_list.append(item) # Includes duplicates unique_lyr = list(set(layer_list)) # No duplicates self.dlg.comboBox_lyr.addItems(unique_lyr) # Populate combobox self.dlg.comboBox_lyr.model().sort(0) # Sort layer names (includes filepath) alphabetically # Length and height: allowing only 1-500 due to max. 500 km x 500 km l = self.dlg.lineEdit_lengthbb l.setValidator(QRegExpValidator(QRegExp("[1-9]|[1-9][0-9]|[1-4][0-9][0-9]|500"))) h = self.dlg.lineEdit_heightbb h.setValidator(QRegExpValidator(QRegExp("[1-9]|[1-9][0-9]|[1-4][0-9][0-9]|500"))) # As default: Set all years radiobutton to checked """ If this should not be the default, comment # next line """ self.dlg.radioButton_allyears.setChecked(True) # Checked # Start- and end-year: must be 1900-current year currentDate = datetime.date.today() cy = currentDate.year # get current year, e.g. 2023 (position 0,1,2,3) # cy = 2042 # used for testing the regexp cy = str(cy) # Convert number to string d3 = cy[2] # Get the third digit (in position 2) d4 = cy[3] # Get the fourth digit (in position 3) # # Debugging # self.iface.messageBar().pushMessage("Info", "Two last digits in year are: "+d3+', '+d4, level=Qgis.Info, duration=3) # Verifying the entered years start = self.dlg.lineEdit_startyear start.setValidator(QRegExpValidator(QRegExp("19[0-9][0-9]|20[0-"+str(int(d3)-1)+"][0-9]|20["+d3+"][0-"+d4+"]"))) end = self.dlg.lineEdit_endyear end.setValidator(QRegExpValidator(QRegExp("19[0-9][0-9]|20[0-"+str(int(d3)-1)+"][0-9]|20["+d3+"][0-"+d4+"]"))) # Get projects epsg-code # e.g. crs_proj_str = iface.mapCanvas().mapSettings().destinationCrs().authid() # EPSG:25832 crs_proj_int = int(crs_proj_str[5:]) # 25832 epsg_list = [25832, 25833, 25835] if not crs_proj_int in epsg_list: self.iface.messageBar().pushMessage("Error", "Project must be in UTM 32, 33 or 35", level=Qgis.Critical, duration=3) return # Return from (end) plugin """ end additional code """ # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result: # Do something useful here - delete the line containing pass and # substitute with your code. # pass """" Additional code""" selectedLayer = '' # Initialize reset = False of = False sat = False ir = False if self.dlg.checkBox_resetNib.isChecked(): # Empty the Nib-prosjekt group reset = True if self.dlg.checkBox_of_proj.isChecked(): # Orthphoto-projects is checked of = True if self.dlg.checkBox_sat_proj.isChecked(): # Satellite-projects is checked sat = True if self.dlg.checkBox_ir_proj.isChecked(): # Infrared-projects is checked ir = True # If checked, use current map canvas as bounding box (= extent of) if self.dlg.radioButton_ucmcbb.isChecked(): # Get the extent of current map canvas (coordinates in the project's crs) e = iface.mapCanvas().extent() xmin = e.xMinimum() xmax = e.xMaximum() ymin = e.yMinimum() ymax = e.yMaximum() ok = self.check_bbsize(crs_proj_str, xmin, xmax, ymin, ymax) # Check if bb is small enough # If checked, use the length and height from map canvas centre point elif self.dlg.radioButton_udbb.isChecked(): l = self.dlg.lineEdit_lengthbb.text() h = self.dlg.lineEdit_heightbb.text() if l == "" or h == "": self.iface.messageBar().pushMessage("Error", "Length and/or height are/is missing!", level=Qgis.Critical, duration=3) return # Return from (end) plugin else: # Length and height (in km) requires projected coordinated. Ensures this by converting the coordinates to LAEA e = iface.mapCanvas().extent().center() # Debugging # self.iface.messageBar().pushMessage("Info", "Centre x, y: " + str(e.x()) +", "+ str(e.y())+", "+crs_proj_str, level=Qgis.Info, duration=5) # Computes corner coordinates in LAEA regardless input crs xmin = e.x() - int(l)*1000/2 xmax = e.x() + int(l)*1000/2 ymin = e.y() - int(h)*1000/2 ymax = e.y() + int(h)*1000/2 """ Check length and height of bounding box. Limit is set to 500 km """ ok = self.check_bbsize(crs_proj_str, xmin, xmax, ymin, ymax) # Check if bb is small enough else: # A layer or a file is selcted as bounding box if self.dlg.radioButton_lyr.isChecked(): # Layer-option checked lyr_name = self.dlg.comboBox_lyr.currentText() # Get the layer name if lyr_name != "": # If a layer is present/chosen # self.iface.messageBar().pushMessage("Info", "Chosen layer: "+lyr_name, level=Qgis.Info, duration=3) selectedLayer = QgsProject.instance().mapLayersByName(lyr_name)[0] # Get this vector layer # Get the extent of the layer (coordinates in the selected layer's crs) e = selectedLayer.extent() else: # If no file or layer is selcted, throw an error mesage and end plugin self.iface.messageBar().pushMessage("Error", "No layer selected!", level=Qgis.Critical, duration=3) return # Return from (end) plugin elif self.dlg.radioButton_file.isChecked(): # File-option checked fname = self.dlg.lineEdit_file.text() # Get the text (path and filename) if fname != "": lname = os.path.splitext(os.path.basename(fname))[0] #get only the filename without the extension, will be used as layer name in the Layers panel in QGIS # self.iface.messageBar().pushMessage("Info", "Chosen file: "+lname, level=Qgis.Info, duration=3) file = r""+fname+"" # Reads the file fLayer = QgsVectorLayer(file, lname, "ogr") # If vector file if not fLayer.isValid(): # If not valid vector laayer fLayer = QgsRasterLayer(file, lname, "gdal") # If raster file if not fLayer.isValid(): # If not valid raster layer either, show error message and end plugin self.iface.messageBar().pushMessage("Error", "Not a valid geo-file: "+ str(lname),level=Qgis.Critical, duration=3) return # Return from (end) plugin QgsProject.instance().addMapLayer(fLayer, True) # Add the layer and show it (True) selectedLayer = QgsProject.instance().mapLayersByName(lname)[0] # Get this layer # Get the extent of the active layer (coordinates in the selected file's crs) e = selectedLayer.extent() else: self.iface.messageBar().pushMessage("Error", "No file selected!", level=Qgis.Critical, duration=3) return # Return from (end) plugin # Neither layer nor file is selected else: self.iface.messageBar().pushMessage("Error", "No extent", level=Qgis.Critical, duration=5) # Activate (select) the selected layer in the combobox or in the lineEdit iface.setActiveLayer(selectedLayer) # Zoom to activated layer iface.zoomToActiveLayer() # Get selected layer's epsg:code crs_lyr = selectedLayer.crs() # Example: crs_lyr_str = crs_lyr.authid() # EPSG:4258 try: crs_lyr_int = int(crs_lyr_str[5:]) # 4258 except: # In case a non-geo-layer (e.g. csv-file) is selected from the combobox self.iface.messageBar().pushMessage("Error", "Layer is missing crs", level=Qgis.Critical, duration=3) layers = [] # Empty layers to get a fresh start when rerunning the plugin return # Return from (end) plugin # Get the extent of the active layer (coordinates in file or layer crs) xmin = e.xMinimum() xmax = e.xMaximum() ymin = e.yMinimum() ymax = e.yMaximum() # coords = "%f,%f;%f,%f;%f,%f;%f,%f" %(xmin,ymin,xmin,ymax,xmax,ymax,xmax,ymin) # self.iface.messageBar().pushMessage("Info", "extent"+str(coords), level=Qgis.Info, duration=3) # If selected file or layer and project have different crs, the layer's # bounding box coordinates must be transformed into the project's crs if crs_lyr_int != crs_proj_int: # Selected file or layer and project have NOT the same crs sourceCrs = QgsCoordinateReferenceSystem(crs_lyr_str) destCrs = QgsCoordinateReferenceSystem(crs_proj_str) transformContext = QgsProject.instance().transformContext() xform = QgsCoordinateTransform(sourceCrs, destCrs, transformContext) # forward transformation: src -> dest pt1 = xform.transform(QgsPointXY(xmin,ymin)) pt2 = xform.transform(QgsPointXY(xmax,ymax)) xmin = pt1.x() xmax = pt2.x() ymin = pt1.y() ymax = pt2.y() # Check if bb is small enough - input crs and transformed bb coordinates are in project crs ok = self.check_bbsize(crs_proj_str, xmin, xmax, ymin, ymax) else: # Selected file or layer and project have the same crs # Check if bb is small enough - input crs and bb coordinates are in layer crs = project crs ok = self.check_bbsize(crs_lyr_str, xmin, xmax, ymin, ymax) if ok: # If bb small enough, get the corner coordinates (to be used in url-request) # Set bounding box corner coordinates as geojson (x1,y1;x2,y2;...) coords = "%f,%f;%f,%f;%f,%f;%f,%f" %(xmin,ymin,xmin,ymax,xmax,ymax,xmax,ymin) else: # If bb too large return # Return from (end) plugin # Accessing layers' tree root root = QgsProject.instance().layerTreeRoot() # If reset box is checked, group "Nib-prosjekt" is deleted before recreated # and filled with layers if reset: # The reset box is checked group = root.findGroup('Nib-prosjekt') # Find the group root.removeChildNode(group) # Remove the group # Recreate Nib-prosjekt group # Add a layer group to be used for all orthophoto-projects within the bounding box if not root.findGroup("Nib-prosjekt"): # If the group doesn't exist, group = root.addGroup('Nib-prosjekt') # add the group and name it "Nib-prosjekt" # group.setExpanded(False) # Collapse the layer group # Load Norge i bilder-project based on the bounding box (geojson-format x1,y1;x2,y2;x3,y3;...) # https://norgeibilder.no/dok/webtjenester.pdf # 1 = Ortofoto 10, 2 = Ortofoto 20, 3 = Ortofoto 50, 4 = Ortofoto N50, 5 = Ortofoto Skog, 6 = Satellittbilde, 7 = Infrarødt, 8 = Rektifiserte flybilder, 9 = Ortofoto if of and not sat and not ir: param ='{Filter:"ortofototype in (1,2,3,4,5,8,9)",Coordinates:"'+coords+'",InputWKID:'+str(crs_proj_int)+',StopOnCover:false}' if of and sat and not ir: param = '{Filter:"ortofototype in (1,2,3,4,5,6,8,9)",Coordinates:"'+coords+'",InputWKID:'+str(crs_proj_int)+',StopOnCover:false}' if of and sat and ir: param = '{Filter:"ortofototype in (1,2,3,4,5,6,7,8,9)",Coordinates:"'+coords+'",InputWKID:'+str(crs_proj_int)+',StopOnCover:false}' if sat and not of and not ir: param = '{Filter:"ortofototype in (6)",Coordinates:"'+coords+'",InputWKID:'+str(crs_proj_int)+',StopOnCover:false}' if sat and ir and not of: param ='{Filter:"ortofototype in (6,7)",Coordinates:"'+coords+'",InputWKID:'+str(crs_proj_int)+',StopOnCover:false}' if ir and not sat and not of: param ='{Filter:"ortofototype in (7)",Coordinates:"'+coords+'",InputWKID:'+str(crs_proj_int)+',StopOnCover:false}' # para=parse.quote('{Filter:"ortofototype in (1,2,3,4,5,6,7,8,9)",Coordinates:"'+coords+'",InputWKID:'+str(crs_proj_int)+',StopOnCover:false}') para=parse.quote(param) js = requests.get('https://tjenester.norgeibilder.no/rest/projectMetadata.ashx?request='+para).json() #list with current of-project on json-format nib_liste = js['ProjectList'] # Debugging # print(js) # {Success:true,ErrorMessage:null,ProjectList:[Sør-Varanger 2019,Sør-Varanger veg 2016,Sør-Varanger 2013,Sør-Varanger 2011],ProjectMetadata:null} # print('nib_liste =',nib_liste) # nib_liste = ['Sør-Varanger 2019', 'Sør-Varanger veg 2016', 'Sør-Varanger 2013', 'Sør-Varanger 2011'] # self.iface.messageBar().pushMessage("Info", "All OF: "+ str(nib_liste),level=Qgis.Info, duration=3) nib_liste_years = [] # Initialize # Ensure 4 digits in start and end-year and start year < end year. if self.dlg.radioButton_btwyears.isChecked(): # Get the extent of current map canvas (coordinates in the project's crs) start = self.dlg.lineEdit_startyear.text() end = self.dlg.lineEdit_endyear.text() if int(start) <= int(end): if len(start) == 4 and len(end) == 4: # Creates a new list with only the OF-proejcts from between the start- and end-year. years = [str(y) for y in range(int(start),int(end)+1,1)] # We have an original list with all nib-projects (nib_liste) # We make a new list selecting projects from nib_liste from the wanted time period for year in years: for n in (nib_liste): if year in n.split(): nib_liste_years.append(n) nib_liste = nib_liste_years else: self.iface.messageBar().pushMessage("Warning", "year must have 4 digits",level=Qgis.Warning, duration=3) return else: self.iface.messageBar().pushMessage("Warning", "end-year must be >= start-year",level=Qgis.Warning, duration=3) return lyr = '' # Initialize for nibprosjwms in nib_liste: # Loads WMS (raster layer) for each of-project within the bounding box in question urlWithParams = 'contextualWMSLegend=0&crs=EPSG:'+str(crs_proj_int)+'&dpiMode=7&featureCount=10&format=image/png&layers='+nibprosjwms+'&styles&url=https://wms.geonorge.no/skwms1/wms.nib-prosjekter' rlayer = QgsRasterLayer(urlWithParams, nibprosjwms, 'wms') # Get the raster layer if rlayer.isValid(): # Valid raster layer layers = QgsProject.instance().mapLayersByName(nibprosjwms) # Check if loaded layer already exists if layers: lyr = layers[0] tree_layer = root.findLayer(lyr.id()) if tree_layer: # True if layer exists, otherwise False layer_parent = tree_layer.parent() if str(layer_parent.name()) == 'Nib-prosjekt': # If layer exists in group Nib-prosjekt, it will not be added self.iface.messageBar().pushMessage("Info", "WMS layer exists: "+ str(nibprosjwms),level=Qgis.Info, duration=1) continue # Return the control to the beginning of the for-loop else: # The layer exists but not in the Nib-prosjekt group QgsProject.instance().addMapLayer(rlayer, False) # Add the raster layer without showing it (False) mygroup = root.findGroup("Nib-prosjekt") # Get the group named "Nib-prosjekt" mygroup.addLayer(rlayer) # Add the layer to this group # Uncheck the raster layer QgsProject.instance().layerTreeRoot().findLayer(rlayer.id()).setItemVisibilityChecked(False) self.iface.messageBar().pushMessage("Success", "WMS reloaded", level=Qgis.Success, duration=3) else: # The layer does not exist, thus it will be addedd QgsProject.instance().addMapLayer(rlayer, False) # Add the raster layer without showing it (False) mygroup = root.findGroup("Nib-prosjekt") # Get the group named "Nib-prosjekt" mygroup.addLayer(rlayer) # Add the layer to this group # Uncheck the raster layer QgsProject.instance().layerTreeRoot().findLayer(rlayer.id()).setItemVisibilityChecked(False) self.iface.messageBar().pushMessage("Success", "WMS added in Nib-prosjekt", level=Qgis.Success, duration=3) else: # If not valid raster layer, an error message will appear self.iface.messageBar().pushMessage("Warning", "Can't load: "+ str(nibprosjwms),level=Qgis.Warning, duration=3) # Current working directory will be set as destination folder for project lists cwd = os.getcwd() # Get current working directory # self.iface.messageBar().pushMessage("Info", "Folder is:" +str(cwd), level=Qgis.Info, duration=3) if self.dlg.checkBox_sort.isChecked(): nib_liste = sorted(nib_liste) # Sort the nib_liste alphabetically if self.dlg.checkBox_savelist.isChecked(): if self.dlg.radioButton_btwyears.isChecked(): for y in years: # Year-list is created above and contain all years from start to end year # Delete files if existing fn = str(cwd)+"\\OF_"+str(y)+".txt" if os.path.isfile(fn): os.remove(fn) for n in nib_liste: for year in years: if year in n.split(): if y == year: with open(str(cwd)+'\\OF_'+str(year)+'.txt','a',encoding='utf-8') as f: # a = append to textfile f.write(str(n)+'\n') # Write the OF-project(s) for the specific year to the textfile else: # Delete file if existing fn = str(cwd)+"\\OF_all.txt" if os.path.isfile(fn): os.remove(fn) with open(str(cwd)+'\\OF_all.txt','w',encoding='utf-8') as f: # w = write - will overwrite any existing content https://www.w3schools.com/python/python_file_write.asp for n in nib_liste: f.write(str(n)+'\n') # Write all OF-projects to the textfile # Show message self.iface.messageBar().pushMessage("Success", "List with Nib-projects saved to file", level=Qgis.Success, duration=3)