Skip to content
Snippets Groups Projects
Commit 23db5897 authored by A Nilsen's avatar A Nilsen
Browse files

Upload New File

parent 894a2dcb
Branches
No related tags found
No related merge requests found
# -*- coding: utf-8 -*-
"""
/***************************************************************************
getnib
A QGIS plugin
Plugin "NIB-ortofoto-prosjekt"
Hent alle of-prosjekt i et utsnitt fra Norge i bilder (WMS)
Utsnittet kan være bounding boksen til
- et kartlag i Layers panel
- en fil man laster opp
- aktuelt map canvas
Ressures brukt:
- Plugin Builder for å få riktig oppsett og nødvendige filer
- https://www.qgistutorials.com/en/docs/3/building_a_python_plugin.html
NB! husk å lage og å kopiere over compile.bat (trengs for å kompilere resources.py slik at f.eks. eget icon skal bli synlig)
Se også 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 for å få tilgang til Metadata og se påkrevd format på input-verdier
- installert QGIS plugin "Releod plugin" for å oppdatere endringer gjort i plugin-en
- OSGeo4W-installasjon av QGIS med Qt Designer inkludert mtp. utforming av plugin
- logo hentet fra 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.core import QgsProject, Qgis, QgsMessageLog, QgsRasterLayer, QgsVectorLayer, QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsPointXY
from qgis.gui import QgsMapCanvas
from qgis.utils import iface, showPluginHelp
from urllib import request, parse # for å lese url, gå gjennom streng
import os
# 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):
""" Får å velge fil som skal tjene som bounding box """
filename, _filter = QFileDialog.getOpenFileName(
self.dlg, "Select a file ","", 'geo-files (*.shp *.geojson *.gpkg *.gml *.jpg *.tif);; All files (*.*)')
self.dlg.lineEdit.setText(filename)
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 LAE 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 50 km)
xdist = (ur.x()-ll.x())/1000 # Get lenght in km (xmax-xmin)
ydist = (ur.y()-ll.y())/1000 # Get height in km (ymax-ymin)
if xdist > 50 or ydist > 50:
self.iface.messageBar().pushMessage("Error", "Height or length of bounding box can't be > 50 km",level=Qgis.Critical, duration=3)
ok = False
else:
ok = True # Bounding box is small enough
return ok
""" 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.clicked.connect(self.select_input_file)
self.dlg.toolButton_help.clicked.connect(self.show_help)
# Fetch the currently loaded layers
layers = QgsProject.instance().mapLayers()
# Clear the contents of the combobox from previous runs
self.dlg.comboBox.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.addItems(unique_lyr) # Populate combobox
self.dlg.comboBox.model().sort(0) # Sort layer names (includes filepath) alfabetically
# Clear the contents of the lineEdit from previous runs
self.dlg.lineEdit.clear()
# As default: Remember user input from previous run
""" If this should not be the default, remove comment # from next line """
# self.dlg.checkBoxNib.setChecked(False) # Unchecked
# As default: Set map canvas checkbox to checked
""" If this should not be the default, comment # next line """
self.dlg.checkBox.setChecked(True) # Checked
# 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
""" 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 = '' # Initialise
reset = False
if self.dlg.checkBoxNib.isChecked():
reset = True
# If checked, use the bounding box (= extent of current map canvas)
if self.dlg.checkBox.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
else: # If not checked, use existing layer or open new from file
fname = self.dlg.lineEdit.text() # get the text (path and filename)
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
# No file is chosen
if fname == "": # Use selected layer from combobox
lyr_name = self.dlg.comboBox.currentText() # Get the layer name
if lyr_name != "": # If a layer is present/chosen
selectedLayer = QgsProject.instance().mapLayersByName(lyr_name)[0] # Get this vector layer
# Get the extent of the file (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", "Nothing selected!", level=Qgis.Critical, duration=3)
return # Return from (end) plugin
else: # Use selected file
file = r""+fname+"" # Reads the file
fLayer = QgsVectorLayer(file, lname, "ogr") # If vector fie
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()
# 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()
# 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:
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 active layer's extent
if not root.findGroup("Nib-prosjekt"): # If the group don'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 active layer's bounding box
# (geojson-format x1,y1;x2,y2;x3,y3;...)
# https://stackoverflow.com/questions/50337388/how-to-use-special-character-%c3%a6-%c3%b8-or-%c3%a5-in-a-urllib-request-urlopen-in-python-3
para=parse.quote('{Filter:"ortofototype in (1,2,3,4,8,9,12)",Coordinates:"'+coords+'",InputWKID:'+str(crs_proj_int)+',StopOnCover:false}')
inn = request.urlopen('https://tjenester.norgeibilder.no/rest/projectMetadata.ashx?request='+para).read() #list with of-projects
ut = inn.decode() # Convert from bytes to string
ut = ut.replace('"','') # Remove quotes, i.e. replace " with nothing
a = ut.split("[") # Split string in two at bracket [
b = a[1].split("]") # Split 2nd. substring at each bracket [ gives 2 elements since there is only one occurence of [ (counting starts at 0)
nib_liste = b[0].split(",")
lyr = '' # Initiate
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-prsjekt 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: # 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)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment