diff --git a/EBVCubeVisualizer.py b/EBVCubeVisualizer.py new file mode 100644 index 0000000000000000000000000000000000000000..922f4f7fdf2c090c1b078e3f42a6dddf572944a4 --- /dev/null +++ b/EBVCubeVisualizer.py @@ -0,0 +1,181 @@ +''' +/**************************************************************************************** +* +* +* +* +* +****************************************************************************************/ + +/**************************************************************************************** +* The 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. +* +* The script is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with this program. +* If not, see http://www.gnu.org/licenses/. +****************************************************************************************/ +''' +#we import the important functions from the pyQt5 library + +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +#import everthing from the netCDF_visualization_funtionality.py file +from .EBVCubeVisualizer_funtionality import * +#we write a class, Plugins is a class +import os + + + +class EBVCubeVisualizer: + """This is a class for the EBVCubeVisualizer Plugin""" + #we set that we need the iface to build something with the class + 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 + """ + #our class builds netCDF_visualization Plugins + #self is a Plugin + #we set the iface as an attribute!! + self.iface = iface + #initialize locale + locale = QSettings().value('locale/userLocale')[0:2] + locale_path = os.path.join( + os.path.dirname(__file__), + 'i18n', + 'EBVCubeVisualizer_{}.qm'.format(locale)) + if os.path.exists(locale_path): + self.translator = QTranslator() + self.translator.load(locale_path) + if qVersion() > '4.3.3': + QCoreApplication.installTranslator(self.translator) + + #decalre instance attributes + self.actions = [] + self.menu = self.tr(u'&EBVCubeVisualizer') + #we want to add a toolbar + self.toolbar = self.iface.addToolBar(u'EBVCubeVisualizer') + self.toolbar.setObjectName(u'EBVCubeVisualizer') + + #print "** INITIALIZING netCDFVisualizer" + + self.pluginIsActive = False + self.dockwidget = None + + 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 + """ + return QCoreApplication.translate('EBVCubeVisualizer', 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: + self.iface.addToolBarIcon(action) + + if add_to_menu: + self.iface.addPluginToMenu( + self.menu, + action) + self.actions.append(action) + return action + + #when we click the plugin in QGIS the plugin will be loaded + def initGui(self): + """Create the menu entries and toolbar icons inside the QGIS GUI.""" + icon_path = ':/plugins/EBVCubeVisualizer/adventure.png' + self.add_action( + icon_path, + text=self.tr(u'EBVCubeVisualizer'), + callback=self.callMask, + parent=self.iface.mainWindow()) + + #when we close the plugin in QGIS the plugin will be unloaded + def unload(self): + """Removes the plugin menu item and icon from QGIS GUI.""" + for action in self.actions: + self.iface.removePluginMenu( + self.tr(u'&EBVCubeVisualizer'), + action) + self.iface.removeToolBarIcon(action) + else : + self.toolbar.removeAction(action) + + #we create a function to call the mask + def callMask(self): + #we create the mask or GUI + self.mask = maskAndFuntionality(self.iface) + #we show the mask + self.mask.show() + diff --git a/EBVCubeVisualizer.ui b/EBVCubeVisualizer.ui new file mode 100644 index 0000000000000000000000000000000000000000..47278a21a8aa69463433a55700536cf6ee93b3db --- /dev/null +++ b/EBVCubeVisualizer.ui @@ -0,0 +1,429 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>netCDF</class> + <widget class="QDialog" name="netCDF"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1273</width> + <height>792</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>1273</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777190</height> + </size> + </property> + <property name="windowTitle"> + <string>EBVCubeVisualizer</string> + </property> + <property name="styleSheet"> + <string notr="true">background-color: rgb(249, 255, 254);</string> + </property> + <property name="modal"> + <bool>false</bool> + </property> + <widget class="QLabel" name="label_6"> + <property name="geometry"> + <rect> + <x>20</x> + <y>10</y> + <width>61</width> + <height>16</height> + </rect> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Load data:</string> + </property> + </widget> + <widget class="QWidget" name="horizontalLayoutWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>40</y> + <width>751</width> + <height>25</height> + </rect> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QToolButton" name="btn_inputFile"> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="text_set"/> + </item> + <item> + <widget class="QPushButton" name="btn_load"> + <property name="text"> + <string>load</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="gridLayoutWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>90</y> + <width>751</width> + <height>651</height> + </rect> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QTabWidget" name="tabWidget"> + <property name="maximumSize"> + <size> + <width>249000</width> + <height>16777215</height> + </size> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>Datasets</string> + </attribute> + <layout class="QFormLayout" name="formLayout"> + <item row="0" column="1"> + <widget class="QTreeWidget" name="tree_data"> + <property name="font"> + <font> + <weight>50</weight> + <bold>false</bold> + </font> + </property> + <property name="styleSheet"> + <string notr="true">alternate-background-color: rgb(205, 233, 236);</string> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAsNeeded</enum> + </property> + <property name="sizeAdjustPolicy"> + <enum>QAbstractScrollArea::AdjustToContents</enum> + </property> + <property name="autoScrollMargin"> + <number>50</number> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="horizontalScrollMode"> + <enum>QAbstractItemView::ScrollPerItem</enum> + </property> + <property name="indentation"> + <number>20</number> + </property> + <attribute name="headerCascadingSectionResizes"> + <bool>true</bool> + </attribute> + <attribute name="headerMinimumSectionSize"> + <number>206</number> + </attribute> + <attribute name="headerDefaultSectionSize"> + <number>300</number> + </attribute> + <attribute name="headerShowSortIndicator" stdset="0"> + <bool>true</bool> + </attribute> + <column> + <property name="text"> + <string>Name</string> + </property> + </column> + <column> + <property name="text"> + <string>Long Name</string> + </property> + </column> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="layoutWidget"> + <property name="geometry"> + <rect> + <x>1010</x> + <y>750</y> + <width>231</width> + <height>25</height> + </rect> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>64</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="btn_closePlugin"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="layoutWidget"> + <property name="geometry"> + <rect> + <x>770</x> + <y>110</y> + <width>491</width> + <height>381</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <item> + <widget class="QTextBrowser" name="text_info"/> + </item> + </layout> + </widget> + <widget class="QWidget" name="layoutWidget"> + <property name="geometry"> + <rect> + <x>30</x> + <y>750</y> + <width>221</width> + <height>25</height> + </rect> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="btn_remove"> + <property name="text"> + <string>Remove all</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btn_remove_sel"> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>56</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <widget class="QFrame" name="load_map"> + <property name="geometry"> + <rect> + <x>770</x> + <y>500</y> + <width>491</width> + <height>241</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Ignored"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="styleSheet"> + <string notr="true">background-color: rgb(198, 225, 221);</string> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <widget class="QWidget" name="verticalLayoutWidget"> + <property name="geometry"> + <rect> + <x>140</x> + <y>210</y> + <width>221</width> + <height>25</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPushButton" name="btn_plot"> + <property name="styleSheet"> + <string notr="true">selection-background-color: rgb(83, 194, 200);</string> + </property> + <property name="text"> + <string>data display</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="formLayoutWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>10</y> + <width>231</width> + <height>189</height> + </rect> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>map data</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Scenario:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="cbox_scenarios"/> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Metric:</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QComboBox" name="cbox_metric"/> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Entity:</string> + </property> + </widget> + </item> + <item row="7" column="1"> + <widget class="QComboBox" name="cbox_entity"/> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Time:</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QComboBox" name="cbox_time"/> + </item> + <item row="5" column="1"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_7"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string> text:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_8"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string> text:</string> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/EBVCubeVisualizer_funtionality.py b/EBVCubeVisualizer_funtionality.py new file mode 100644 index 0000000000000000000000000000000000000000..52b2e00385268924f50c9009b4de4ecb59587a23 --- /dev/null +++ b/EBVCubeVisualizer_funtionality.py @@ -0,0 +1,390 @@ +''' +/**************************************************************************************** +* This is a python script for visualizing netCDF files using PyQt5 and matplotlib +* +* The script is based on the QGIS plugin template by Gispo +* +* +****************************************************************************************/ + +/**************************************************************************************** +* The 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. +* +* The script is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along with this program. +* If not, see http://www.gnu.org/licenses/. +****************************************************************************************/ +''' +#we import the impotant libraries and modules +#always import the libraries and modules at the top of the code +from datetime import datetime +from json import load +from msilib.schema import tables +from PyQt5.QtCore import * +from PyQt5.QtGui import * +from PyQt5.QtWidgets import * +from PyQt5 import uic + +#we want to work with the os module +import os +#to import general tools from QGIS we need the qgis.core module +from qgis.core import * +from qgis.utils import iface + +#for loading the netCDF files we need the netCDF4 module +try: + from pip import main as pipmain +except ImportError: + from pip._internal import main as pipmain + +try: + import netCDF4 as nc +except ImportError: + pipmain(['install', 'netCDF4']) + import netCDF4 as nc + from netCDF4 import Dataset + + +#we need the matplotlib module to plot the data +import matplotlib.pyplot as plt +#we need gdal to work with the raster data +from osgeo import osr, gdal, ogr +#we ned the numpy module to work with the data +import numpy as np + + +#we create the path to the ui file +#Path to the Ordner where the ui file is +ncvPath = os.path.dirname(__file__) #the comand dirname gives the path to the directory where the file is +#path to the ui file +#dosn't matter where the ui file is located in the directory +uiPath = os.path.join(ncvPath, 'EBVCubeVisualizer.ui') + +#TWO CLASES# +# WIDEGT is a class for the GUI +# BASE is a PyQt5 class to insatalize the GUI +WIDGET, BASE = uic.loadUiType(uiPath) + +class maskAndFuntionality (BASE, WIDGET): + """Class for the mask and the funtionality of the netCDFVisualizer Plugin""" + def __init__(self, iface): + #self is GUI and mask + QDialog.__init__(self, iface.mainWindow()) + self.setupUi(self) + #self ist our GUI + #the GUI is built in the running of QGIS in the current session (using the iface) + """ Here is the place for the Clicked Signal""" + self.btn_closePlugin.clicked.connect(self.closePlugin) + self.btn_inputFile.clicked.connect(self.importData) + self.btn_remove.clicked.connect(self.removePath) + self.btn_load.clicked.connect(self.loadNetCDF) + self.btn_load.clicked.connect(self.setMapData) + self.btn_remove_sel.clicked.connect(self.removeSelection) + #here we set the clicked signal for the tree widget + self.tree_data.itemClicked.connect(self.showInfo) + self.btn_plot.clicked.connect(self.displayData) + + """Here is the place for set stzlesheet""" + #self.btn_plot.setStyleSheet("backgrou") + + def closePlugin(self): + """This function closes the plugin""" + #we close the plugin + self.close() + + def importData(self): + """This function imports the netCDF file""" + #we get the path to the netCDF file + path = QFileDialog.getOpenFileName(None,"select netCDF file", filter="*.nc")[0] + #we set the path in the text space + self.text_set.setText(path) + + def removePath(self): + """This function removes the path from the text space""" + self.text_set.clear() + self.tree_data.clear() + self.text_info.clear() + self.cbox_entity.clear() + self.cbox_time.clear() + self.cbox_scenarios.clear() + self.cbox_metric.clear() + + def removeSelection(self): + """this function remove the selection in the tree widget""" + #if the top level item is selected we remove the Item and the children as well text_info, cbox_entity and cbox_time + if self.tree_data.currentItem().parent() == None: + self.tree_data.takeTopLevelItem(self.tree_data.indexOfTopLevelItem(self.tree_data.currentItem())) + self.text_info.clear() + self.cbox_entity.clear() + self.cbox_time.clear() + self.cbox_scenarios.clear() + self.cbox_metric.clear() + #if the child item is selected we don't remove anything + elif self.tree_data.currentItem() == None: + pass + + else: + pass + + + def loadNetCDF(self): + """This function loads the netCDF file and shows the variables, groups and the variables of the gorups in the QTreeWidget""" + #we get the path from the text space + if self.text_set.text()=="": #if the text space is empty + QmessageBox.warning(None, "Warning", "Please select a netCDF file") #we show a warning + + else: #if the text space is not empty + path = self.text_set.text() #we get the path from the text space + ncFile = nc.Dataset(path, 'r', format='NETCDF4') + ncFileName = os.path.basename(path) + ncFileTitle = ncFile.title + #convert file name and file title into a QTreeWidgetItem + top_level = QTreeWidgetItem([ncFileName, ncFileTitle]) + #we get the variables of the netCDf file + ncFileVariablesName = list(ncFile.variables.keys()) + ncFileGroupsName = list(ncFile.groups.keys()) + + + #we set the top of the tree that it is the name od the file + self.tree_data.addTopLevelItem(top_level) + + + #we show the groups of the file in the QTreeWidgetite + for i in range(len(ncFileGroupsName)): + longNameGroups = ncFile.groups[ncFileGroupsName[i]].long_name + child = QTreeWidgetItem([ncFileGroupsName[i], longNameGroups]) + top_level.addChild(child) + + #we get the groups of the groups + ncFileGroupsName2 = list(ncFile.groups[ncFileGroupsName[i]].groups.keys()) + + #we show the groups of the groups in the QTreeWidgetite + for j in range(len(ncFileGroupsName2)): + longNameGroups2 = ncFile.groups[ncFileGroupsName[i]].groups[ncFileGroupsName2[j]].long_name + child2 = QTreeWidgetItem([ncFileGroupsName2[j], longNameGroups2]) + child.addChild(child2) + + #we get the variables of the groups of the groups + ncFileVariablesName2 = list(ncFile.groups[ncFileGroupsName[i]].groups[ncFileGroupsName2[j]].variables.keys()) + + #we show the variables of the groups of the groups in the QTreeWidgetite an set the lon name of the variables + for k in range(len(ncFileVariablesName2)): + longNameVariables2 = ncFile.groups[ncFileGroupsName[i]].groups[ncFileGroupsName2[j]].variables[ncFileVariablesName2[k]].long_name + child3 = QTreeWidgetItem([ncFileVariablesName2[k], longNameVariables2]) + child2.addChild(child3) + + #we get the variables of the groups + ncFileGroupsVariablesName = list(ncFile.groups[ncFileGroupsName[i]].variables.keys()) + + #we show the variables of the groups in the QTreeWidgetite and set the long name of the variables + for j in range(len(ncFileGroupsVariablesName)): + longNameVariables = ncFile.groups[ncFileGroupsName[i]].variables[ncFileGroupsVariablesName[j]].long_name + child4 = QTreeWidgetItem([ncFileGroupsVariablesName[j],longNameVariables]) + child.addChild(child4) + + + #expand all the data + self.tree_data.expandAll() + ncFile.close() #close the file + + def setMapData(self): + """This function sets the entities, time, scenarios and metrics in the QComboBox""" + #we clear the QComboBox + self.cbox_entity.clear() + self.cbox_time.clear() + self.cbox_scenarios.clear() + self.cbox_metric.clear() + + #we get the path from the text space + path = self.text_set.text() + ncFile = nc.Dataset(path, 'r', format='NETCDF4') + + groups = list(ncFile.groups.keys()) #we get the metrics (name of the groups) + groupsOfGroups = list(ncFile.groups[groups[0]].groups.keys()) #we get the scenarios(name of the groups of the groups) + + + #set scenario and metric in the QComboBox + #if there is just groups we set the groups in the cbox_metric + self.cbox_metric.addItems(groups) + self.cbox_scenarios.addItem("not scenarios") + self.cbox_scenarios.setEnabled(False) + + if len(groupsOfGroups)>0: + self.cbox_scenarios.setEnabled(True) + self.cbox_scenarios.clear() + self.cbox_scenarios.addItems(groups) + self.cbox_metric.clear() + self.cbox_metric.addItems(groupsOfGroups) + else: + pass + + #here we are gonna get the entities and the time of the netCDF file and set them into a QComboBox if the top level is clicked + #we get the time of the netCDF file + time = ncFile.variables['time'] + timeUnits = time.units + timeCalendar = time.calendar + time = nc.num2date(time[:], timeUnits, timeCalendar) + + #we set the time into the QComboBox + self.cbox_time.clear() + self.cbox_time.addItems([str(i) for i in time]) + + #we get the entities + self.cbox_entity.clear() + entities = ncFile.variables['entity'] + entityScope = entities.ebv_entity_scope.split(',') + numberOfEntities = len(entities) + #we set the entity_scope and the number of the entity into the QComboBox + #self.cbox_entity.addItems([entityScope[i] + " " + str(i) for i in range(numberOfEntities)]) + + #we set the entities into the QComboBox + self.cbox_entity.addItems(entityScope) + + + #we close the netCDF file + ncFile.close() + + def showInfo(self): + """this function shows first the name of the file and the global attributes and then if a varible is clicked delete the info and add the attributes of the selected variable""" + self.text_info.clear() + #we get the path from the text space + path = self.text_set.text() + ncFile = nc.Dataset(path, 'r', format='NETCDF4') + ncFileName = os.path.basename(path) + ncFileTitle = ncFile.title + ncFileGlobalAttributes = list(ncFile.ncattrs()) + + #when we click on the top level item we show the name of the file, title and the global attributes + if self.tree_data.currentItem().parent() == None: + self.text_info.clear() + self.text_info.append("File name: " + ncFileName) + self.text_info.append("Title: " + ncFileTitle) + self.text_info.append("Global attributes:") + for i in range(len(ncFileGlobalAttributes)): + self.text_info.append("-- " + ncFileGlobalAttributes[i] + ": " + str(ncFile.getncattr(ncFileGlobalAttributes[i]))) + + #when we click on a group we show the name of the group and the attributes of the group and if we click on a variable of the group we show the attributes of the variable + elif self.tree_data.currentItem().parent().parent() == None: + self.text_info.clear() + self.text_info.append("File name: " + ncFileName) + self.text_info.append("Title: " + ncFileTitle) + self.text_info.append("Group name: " + self.tree_data.currentItem().text(0)) + self.text_info.append("Long name: " + self.tree_data.currentItem().text(1)) + self.text_info.append("Attributes:") + for i in range(len(ncFile.groups[self.tree_data.currentItem().text(0)].ncattrs())): + self.text_info.append("-- " + ncFile.groups[self.tree_data.currentItem().text(0)].ncattrs()[i] + ": " + str(ncFile.groups[self.tree_data.currentItem().text(0)].getncattr(ncFile.groups[self.tree_data.currentItem().text(0)].ncattrs()[i]))) + + #when we click on a variable of the group and the attributes of the varibales + else: + self.text_info.append("File name: " + ncFileName) + self.text_info.append("Title: " + ncFileTitle) + self.text_info.append("Variable name: " + self.tree_data.currentItem().text(0)) + self.text_info.append("Long name: " + self.tree_data.currentItem().text(1)) + self.text_info.append("Attributes:") + variableAttributes = list(ncFile.groups[self.tree_data.currentItem().parent().text(0)].variables[self.tree_data.currentItem().text(0)].ncattrs()) + for i in range(len(variableAttributes)): + self.text_info.append("-- " + variableAttributes[i] + ": " + str(ncFile.groups[self.tree_data.currentItem().parent().text(0)].variables[self.tree_data.currentItem().text(0)].getncattr(variableAttributes[i]))) + + #we close the netCDF file + ncFile.close() + + + def displayData(self): + """this fuction get the data of each ebv_cube and add it to the map""" + path = self.text_set.text() #we get the path from the text space + ncFile = nc.Dataset(path, 'r', format='NETCDF4') #we are gonna open the nedCDF file with the netCDF4 library + ncFileName = os.path.basename(path) #We get the name of the netCDF file to show it in the GUI + #get part of the name of the netCDF file + ncFileName = ncFileName.split('_') + ncFileName = ncFileName[0] + '_' + ncFileName[1] + '_' + ncFileName[2] + nameOfRasterLayer = ncFileName + "_entity: " + self.cbox_entity.currentText() + "_time: " + self.cbox_time.currentText() #we get the name of the raster layer + + #time + #we get the time + time = ncFile.variables['time'] + timeUnits = time.units + timeCalendar = time.calendar + time = nc.num2date(time[:], timeUnits, timeCalendar) + time = [str(i) for i in time] #we have to convert the time into a string + max_time = len(time) #we get the length of the time + + #time selected in the QComboBox + timeSelected = self.cbox_time.currentText() + timeIndex = time.index(timeSelected) #we get the index of the time selected + + + #Entity + #we get the entities + entity = ncFile.variables['entity'] + entityScope = entity.ebv_entity_scope.split(',') #we get the name of the entities + + #entity selected in the QComboBox + entitySelected = self.cbox_entity.currentText() + entityIndex = entityScope.index(entitySelected) #we get the index of the entity selected + + + #get the scenarios and the metrics from the interface + #scenarios + scenarioSelected = self.cbox_scenarios.currentText() + scenarioIndex = self.cbox_scenarios.currentIndex() + #metrics + metricSelected = self.cbox_metric.currentText() + metricIndex = self.cbox_metric.currentIndex() + + uri = r'NETCDF:"'+ path + '":' + metricSelected + '/ebv_cube' + + #if there just metrics and no scenarios we have to create a uri with the metric selected in the QComboBox + # if scenarioIndex == 0: + # uri = r'NETCDF:"'+ path + '":' + metricSelected + '/ebv_cube' + # else: + # uri = r'NETCDF:"'+ path + '":'+ scenarioSelected + '/' + metricSelected + '/ebv_cube' + + + + #load the raster layer into the QGIS canvas + rasterLayer = QgsRasterLayer(uri, nameOfRasterLayer, "gdal") + print(rasterLayer.isValid()) + + + #calculate the band number + band = (entityIndex-1)*max_time+ timeIndex + + + #get the min and the max value of the band + dp = rasterLayer.dataProvider() + stats = dp.bandStatistics(band) + min = stats.minimumValue + max = stats.maximumValue + + #build the color ramp + colorRamp = QgsColorRampShader(min, max) + colorRamp.setColorRampType(QgsColorRampShader.Interpolated) + colorRamp.setColorRampItemList([QgsColorRampShader.ColorRampItem(min, QColor(0, 0, 255)), + QgsColorRampShader.ColorRampItem(max, QColor(255, 0, 0))]) + #build the shader + shader = QgsRasterShader() + shader.setRasterShaderFunction(colorRamp) + + #build the renderer + renderer = QgsSingleBandPseudoColorRenderer(rasterLayer.dataProvider(), band, shader) #we have to put the band number + rasterLayer.setRenderer(renderer) + + #add the raster layer to the map + QgsProject.instance().addMapLayer(rasterLayer) + + #close the netCDF file + ncFile.close() + + + + + + + + diff --git a/__init__.py b/__init__.py index d0ed9db93185bc4089e72be1b588c111c7623eb2..5bd9042edd510757992f390bde19dafb25f5d488 100644 --- a/__init__.py +++ b/__init__.py @@ -1,8 +1,8 @@ #the __init__ gives the construction order for the plugin -from .netCDF_visualizer import netCDFVisualizer +from .EBVCubeVisualizer import EBVCubeVisualizer def classFactory(iface): - plugin = netCDFVisualizer(iface) + plugin = EBVCubeVisualizer(iface) return plugin diff --git a/metadata.txt b/metadata.txt index b5c479df16044dab0bec15a111f6c36a86cc7bff..7cfd4d64b869152ddd984bc6354e5af508e903d2 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,5 +1,5 @@ [general] -name=netCDFVisualizer +name=EBVCubeVisualizer email=e.oceguera@gmx.de author=Emmanuel Oceguera-Conchas qgisMinimumVersion=3.0 diff --git a/styles/raster_style.qml b/styles/raster_style.qml new file mode 100644 index 0000000000000000000000000000000000000000..5313c2b9c60c02c73175e383ee41972de2e7a67b --- /dev/null +++ b/styles/raster_style.qml @@ -0,0 +1,91 @@ +<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'> +<qgis maxScale="0" minScale="1e+08" hasScaleBasedVisibilityFlag="0" styleCategories="AllStyleCategories" version="3.24.2-Tisler"> + <flags> + <Identifiable>1</Identifiable> + <Removable>1</Removable> + <Searchable>1</Searchable> + <Private>0</Private> + </flags> + <temporal enabled="0" mode="0" fetchMode="0"> + <fixedRange> + <start></start> + <end></end> + </fixedRange> + </temporal> + <customproperties> + <Option type="Map"> + <Option name="WMSBackgroundLayer" type="bool" value="false"/> + <Option name="WMSPublishDataSourceUrl" type="bool" value="false"/> + <Option name="embeddedWidgets/count" type="int" value="0"/> + <Option name="identify/format" type="QString" value="Value"/> + </Option> + </customproperties> + <pipe-data-defined-properties> + <Option type="Map"> + <Option name="name" type="QString" value=""/> + <Option name="properties"/> + <Option name="type" type="QString" value="collection"/> + </Option> + </pipe-data-defined-properties> + <pipe> + <provider> + <resampling maxOversampling="2" zoomedInResamplingMethod="nearestNeighbour" enabled="false" zoomedOutResamplingMethod="nearestNeighbour"/> + </provider> + <rasterrenderer nodataColor="" type="singlebandpseudocolor" opacity="1" classificationMin="-9.9112749" band="9" alphaBand="-1" classificationMax="19.6030865"> + <rasterTransparency/> + <minMaxOrigin> + <limits>MinMax</limits> + <extent>WholeRaster</extent> + <statAccuracy>Estimated</statAccuracy> + <cumulativeCutLower>0.02</cumulativeCutLower> + <cumulativeCutUpper>0.98</cumulativeCutUpper> + <stdDevFactor>2</stdDevFactor> + </minMaxOrigin> + <rastershader> + <colorrampshader colorRampType="INTERPOLATED" clip="0" maximumValue="19.6030865" classificationMode="1" minimumValue="-9.9112749000000004" labelPrecision="6"> + <colorramp name="[source]" type="gradient"> + <Option type="Map"> + <Option name="color1" type="QString" value="17,116,177,255"/> + <Option name="color2" type="QString" value="189,37,40,255"/> + <Option name="direction" type="QString" value="cw"/> + <Option name="discrete" type="QString" value="0"/> + <Option name="rampType" type="QString" value="gradient"/> + <Option name="spec" type="QString" value="rgb"/> + <Option name="stops" type="QString" value="0.175481;118,185,169,255;rgb;ccw:0.324519;171,221,164,255;rgb;cw:0.496394;243,243,61,255;rgb;cw:0.680288;253,174,97,255;rgb;cw:0.832933;230,84,55,255;rgb;ccw"/> + </Option> + <prop k="color1" v="17,116,177,255"/> + <prop k="color2" v="189,37,40,255"/> + <prop k="direction" v="cw"/> + <prop k="discrete" v="0"/> + <prop k="rampType" v="gradient"/> + <prop k="spec" v="rgb"/> + <prop k="stops" v="0.175481;118,185,169,255;rgb;ccw:0.324519;171,221,164,255;rgb;cw:0.496394;243,243,61,255;rgb;cw:0.680288;253,174,97,255;rgb;cw:0.832933;230,84,55,255;rgb;ccw"/> + </colorramp> + <item color="#1174b1" label="-9.911275" value="-9.911274909973145" alpha="255"/> + <item color="#90cba6" label="-2.532685" value="-2.532684564590454" alpha="255"/> + <item color="#f3f23e" label="4.845906" value="4.845905780792236" alpha="255"/> + <item color="#f2854e" label="12.224496" value="12.224496126174927" alpha="255"/> + <item color="#bd2528" label="19.603086" value="19.603086471557617" alpha="255"/> + <rampLegendSettings direction="0" useContinuousLegend="1" minimumLabel="" orientation="2" suffix="" prefix="" maximumLabel=""> + <numericFormat id="basic"> + <Option type="Map"> + <Option name="decimal_separator" type="QChar" value=""/> + <Option name="decimals" type="int" value="6"/> + <Option name="rounding_type" type="int" value="0"/> + <Option name="show_plus" type="bool" value="false"/> + <Option name="show_thousand_separator" type="bool" value="true"/> + <Option name="show_trailing_zeros" type="bool" value="false"/> + <Option name="thousand_separator" type="QChar" value=""/> + </Option> + </numericFormat> + </rampLegendSettings> + </colorrampshader> + </rastershader> + </rasterrenderer> + <brightnesscontrast brightness="0" contrast="0" gamma="1"/> + <huesaturation colorizeOn="0" grayscaleMode="0" colorizeStrength="100" saturation="0" invertColors="0" colorizeGreen="128" colorizeRed="255" colorizeBlue="128"/> + <rasterresampler maxOversampling="2"/> + <resamplingStage>resamplingFilter</resamplingStage> + </pipe> + <blendMode>0</blendMode> +</qgis>