get_nib.py 40.21 KiB
# -*- 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
param = ''
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)