Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
G
get_nib
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Deploy
Releases
Container registry
Model registry
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
A Nilsen
get_nib
Commits
23db5897
Commit
23db5897
authored
3 years ago
by
A Nilsen
Browse files
Options
Downloads
Patches
Plain Diff
Upload New File
parent
894a2dcb
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
get_nib.py
+432
-0
432 additions, 0 deletions
get_nib.py
with
432 additions
and
0 deletions
get_nib.py
0 → 100644
+
432
−
0
View file @
23db5897
# -*- 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
)
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment