def testGettersSetters(self): """ test combobox getters/setters """ l = create_layer() w = QgsFieldComboBox() w.setLayer(l) self.assertEqual(w.layer(), l) w.setField('fldint') self.assertEqual(w.currentField(), 'fldint')
def testGettersSetters(self): """ test combobox getters/setters """ l = create_layer() w = QgsFieldComboBox() w.setLayer(l) self.assertEqual(w.layer(), l) w.setField('fldint') self.assertEqual(w.currentField(), 'fldint')
def testFilter(self): """ test setting field with filter """ l = create_layer() w = QgsFieldComboBox() w.setLayer(l) w.setFilters(QgsFieldProxyModel.Int) self.assertEqual(w.layer(), l) w.setField('fldint') self.assertEqual(w.currentField(), 'fldint')
def testFilter(self): """ test setting field with filter """ l = create_layer() w = QgsFieldComboBox() w.setLayer(l) w.setFilters(QgsFieldProxyModel.Int) self.assertEqual(w.layer(), l) w.setField('fldint') self.assertEqual(w.currentField(), 'fldint')
def testGettersSetters(self): """ test combobox getters/setters """ l = create_layer() w = QgsFieldComboBox() w.setLayer(l) self.assertEqual(w.layer(), l) w.setField('fldint') self.assertEqual(w.currentField(), 'fldint') fields = QgsFields() fields.append(QgsField('test1', QVariant.String)) fields.append(QgsField('test2', QVariant.String)) w.setFields(fields) self.assertIsNone(w.layer()) self.assertEqual(w.fields(), fields)
class BpejPuCaWidget(PuCaWidget): """A widget for 'BPEJ' analysis.""" def _build_widgets(self): """Builds own widgets.""" self.lastBpejLayer = None self.bpejMapLayerComboBox = QgsMapLayerComboBox(self) self.bpejMapLayerComboBox.setObjectName(u'bpejMapLayerComboBox') self.bpejMapLayerComboBox.setFilters( QgsMapLayerProxyModel.PolygonLayer) self.bpejMapLayerComboBox.activated.connect(self._set_last_bpej_layer) QgsMapLayerRegistry.instance().layersAdded.connect( self._rollback_bpej_layer) QgsMapLayerRegistry.instance().layersRemoved.connect( self._reset_bpej_layer) self.set_bpej_layer(self.lastBpejLayer) self.vBoxLayout.addWidget(self.bpejMapLayerComboBox) self.bpejFieldComboBox = QgsFieldComboBox(self) self.bpejFieldComboBox.setObjectName(u'bpejFieldComboBox') self.bpejFieldComboBox.setLayer( self.bpejMapLayerComboBox.currentLayer()) self.vBoxLayout.addWidget(self.bpejFieldComboBox) self.bpejMapLayerComboBox.layerChanged.connect( self.bpejFieldComboBox.setLayer) def set_bpej_layer(self, bpejLayer, lastBpejLayer=True): """Sets the BPEJ layer in the bpejMapLayerComboBox. Args: bpejLayer (QgsVectorLayer): A reference to the BPEJ layer. lastBpejLayer (bool): True to set self.lastBpejLayer, False otherwise. """ if lastBpejLayer: self.lastBpejLayer = bpejLayer self.bpejMapLayerComboBox.setLayer(bpejLayer) def _set_last_bpej_layer(self): """Sets the lastBpejLayer. Sets the lastBpejLayer according to the current layer in the bpejMapLayerComboBox. """ bpejLayer = self.bpejMapLayerComboBox.currentLayer() if bpejLayer != self.lastBpejLayer: self.lastBpejLayer = bpejLayer def _reset_bpej_layer(self): """Resets the BPEJ layer.""" layers = self.iface.legendInterface().layers() if self.lastBpejLayer not in layers: self.set_bpej_layer(None) def _rollback_bpej_layer(self): """Rolls the BPEJ layer back.""" if self.lastBpejLayer == None: self.set_bpej_layer(self.lastBpejLayer, False) else: self.lastBpejLayer = self.bpejMapLayerComboBox.currentLayer() def execute(self, layer): """Executes the analysis. Args: layer (QgsVectorLayer): A reference to the active layer. """ try: editing = self.dW.check_editing() bpejLayer = self.bpejMapLayerComboBox.currentLayer() if bpejLayer == None: self.pW.set_text_statusbar.emit(u'Žádná vrstva BPEJ.', 10, True) return if bpejLayer.featureCount() == 0: self.pW.set_text_statusbar.emit( u'Vrstva BPEJ neobsahuje žádný prvek.', 10, True) return bpejLayerCrs = bpejLayer.crs().authid() layerCrs = layer.crs().authid() if bpejLayerCrs != layerCrs: self.pW.set_text_statusbar.emit( u'Aktivní vrstva a vrstva BPEJ nemají stejný ' u'souřadnicový systém.', 10, True) return bpejField = self.bpejFieldComboBox.currentField() if bpejField == u'': self.pW.set_text_statusbar.emit( u'Není vybrán sloupec ceny.', 10, True) return self.pW.set_text_statusbar.emit( u'Provádím analýzu - oceňování podle BPEJ...', 0, False) layer.removeSelection() bpejLayer.removeSelection() editedBpejField = self._edit_bpej_field(bpejField, layer) unionFilePath = processing.runalg( 'qgis:union', layer, bpejLayer, None)['OUTPUT'] unionLayer = QgsVectorLayer(unionFilePath, 'unionLayer', 'ogr') expression = QgsExpression( "\"{}\" is null " "or " "\"{}\" is null " "or " "\"{}\" is null " "or " "\"{}\" != 2" .format( editedBpejField, self.dW.defaultMajorParNumberColumnName[:10], self.dW.puCategoryColumnName[:10], self.dW.puCategoryColumnName[:10])) self.dW.delete_features_by_expression(unionLayer, expression) if unionLayer.featureCount() != 0: multiToSingleFilePath = processing.runalg( 'qgis:multiparttosingleparts', unionLayer, None)['OUTPUT'] multiToSingleLayer = QgsVectorLayer( multiToSingleFilePath, 'multiToSingleLayer', 'ogr') bpejCodePrices = self._get_bpej_code_prices() rowidColumnName = self.dW.rowidColumnName prices, missingBpejCodes, bpejCodeAreasPrices = \ self._calculate_feature_prices( rowidColumnName, multiToSingleLayer, editedBpejField, bpejCodePrices) priceFieldName = self.dW.puPriceColumnName priceFieldId = layer.fieldNameIndex(priceFieldName) bpejCodeAreaPricesFieldName = \ self.dW.puBpejCodeAreaPricesColumnName bpejCodeAreaPricesFielId = layer.fieldNameIndex( bpejCodeAreaPricesFieldName) layer.startEditing() layer.updateFields() features = layer.getFeatures() for feature in features: rowid = feature.attribute(rowidColumnName) id = feature.id() price = prices[rowid] if price != 0: layer.changeAttributeValue(id, priceFieldId, price) bpejCodeAreaPrices = bpejCodeAreasPrices[rowid] bpejCodeAreaPricesStr = self._get_bpej_string( bpejCodeAreaPrices) layer.changeAttributeValue( id, bpejCodeAreaPricesFielId, bpejCodeAreaPricesStr) layer.commitChanges() if editing: self.iface.actionToggleEditing() if len(missingBpejCodes) != 0: fields = bpejLayer.pendingFields() for field in fields: if field.name() == bpejField: bpejFieldTypeName = field.typeName() break if bpejFieldTypeName.lower() == u'string': missingBpejCodesStr = \ '\'' + '\', \''.join(missingBpejCodes) + '\'' else: missingBpejCodesStr = ', '.join(missingBpejCodes) expression = QgsExpression( "\"{}\" in ({})".format(bpejField, missingBpejCodesStr)) self.dW.select_features_by_expression(bpejLayer, expression) featureCount = bpejLayer.selectedFeatureCount() duration = 15 if featureCount == 1: self.iface.messageBar().pushMessage( u'BPEJ kód vybraného prvku ve vrstvě BPEJ ' u'nebyl nalezen.', QgsMessageBar.WARNING, duration) elif featureCount > 1: self.iface.messageBar().pushMessage( u'BPEJ kódy vybraných prvků ve vrstvě BPEJ ' u'nebyly nalezeny.', QgsMessageBar.WARNING, duration) self.pW.set_text_statusbar.emit( u'Analýza oceňování podle BPEJ úspěšně dokončena.', 20, False) except self.dW.puError: QgsApplication.processEvents() except: QgsApplication.processEvents() currentCheckAnalysisName = \ self.pW.checkAnalysisComboBox.currentText() self.dW.display_error_messages( self.pW, u'Error executing "{}".'.format(currentCheckAnalysisName), u'Chyba při provádění "{}".'.format(currentCheckAnalysisName)) def _edit_bpej_field(self, bpejField, layer): """Edits BPEJ field name according to the layer fields. Args: bpejField (str): A name of the BPEJ field. layer (QgsVectorLayer): A reference to the active layer. Returns: str: An edited BPEJ field name """ bpejField = bpejField[:10] parFields = layer.pendingFields() for field in parFields: if bpejField.lower() == field.name().lower(): if len(bpejField) <= 8: bpejField = bpejField + '_2' break elif len(bpejField) == 9: bpejField = bpejField + '_' break elif len(bpejField) == 10: bpejField = bpejField[:8] + '_1' break return bpejField def _get_bpej_code_prices(self): """Returns BPEJ code prices. Returns: dict: A dictionary with BPEJ codes as keys (int) and prices as values (float). """ formatTimeStr = '%d.%m.%Y' bpejDir = QDir(self.pluginDir.path() + u'/data/bpej') bpejBaseName = u'SC_BPEJ' bpejZipFileName = bpejBaseName + u'.zip' bpejZipFilePath = bpejDir.filePath(bpejZipFileName) bpejCsvFileName = bpejBaseName + u'.csv' bpejCsvFilePath = bpejDir.filePath(bpejCsvFileName) upToDate = self._check_bpej_csv(bpejCsvFilePath, formatTimeStr) if not upToDate: testInternetUrl, bpejZipUrl = self._get_url() self._download_bpej_csv( testInternetUrl, bpejZipUrl, bpejZipFilePath, bpejCsvFileName) bpejCodePrices = self._read_bpej_csv(bpejCsvFilePath, formatTimeStr) return bpejCodePrices def _check_bpej_csv(self, bpejCsvFilePath, formatTimeStr): """Checks if the BPEJ CSV file is up-to-date. Args: bpejCsvFilePath (str): A full path to the BPEJ CSV file. formatTimeStr (str): A string for time formatting. Returns: bool: True when the BPEJ CSV file is up-to-date, False otherwise. """ modificationEpochTime = os.path.getmtime(bpejCsvFilePath) modificationDateTime = datetime.fromtimestamp(modificationEpochTime) todayDateTime = datetime.now() bpejTodayDateTime = todayDateTime.replace( hour=03, minute=06, second=0, microsecond=0) if modificationDateTime > bpejTodayDateTime: return True else: return False def _get_url(self): """Returns URL. Returns an URL for testing the internet connection and an URL of the BPEJ ZIP file. Returns: str: An URL for testing the internet connection. str: An URL of the BPEJ ZIP file. """ config = RawConfigParser() config.read(self.pluginDir.filePath(u'puplugin.cfg')) testInternetUrl = config.get('BPEJ', 'testinterneturl') bpejZipUrl = config.get('BPEJ', 'bpejzipurl') return testInternetUrl, bpejZipUrl def _download_bpej_csv( self, testInternetUrl, bpejZipUrl, bpejZipFilePath, bpejCsvFileName): """Downloads BPEJ CSV file and unzips it. Args: testInternetUrl (str): An URL for testing the internet connection. bpejZipUrl (str): An URL of the BPEJ ZIP file. bpejZipFilePath (str): A full path to the BPEJ ZIP file. bpejCsvFileName (str): A name of the BPEJ CSV file. Raises: dw.puError: When a connection to the CUZK website failed. """ try: testInternetConnection = urllib.urlopen(testInternetUrl) except: return else: testInternetConnection.close() try: testBpejConnection = urllib.urlopen(bpejZipUrl) except: raise self.dW.puError( self.dW, self.pW, u'A Connection to "{}" failed.'.format(bpejZipUrl), u'Nepodařilo se připojit k "{}"'.format(bpejZipUrl)) else: testBpejConnection.close() urllib.urlretrieve(bpejZipUrl, bpejZipFilePath) self._unzip_bpej_zip(bpejZipFilePath, bpejCsvFileName) os.remove(bpejZipFilePath) def _unzip_bpej_zip(self, bpejZipFilePath, bpejCsvFileName): """Unzips BPEJ ZIP file into the same directory. Args: bpejZipFilePath (str): A full path to the BPEJ ZIP file. bpejCsvFileName (str): A name of the BPEJ CSV file. """ fileInfo = QFileInfo(bpejZipFilePath) bpejDir = fileInfo.absolutePath() bpejZip = zipfile.ZipFile(bpejZipFilePath, 'r') bpejZipContent = bpejZip.namelist() if len(bpejZipContent) != 1: bpejZip.close() raise self.dW.puError( self.dW, self.pW, u'The structure of the BPEJ ZIP file has changed. ' u'The BPEJ ZIP file contains more than one file.', u'Struktura stahovaného BPEJ ZIP souboru se změnila.') bpejZipFirstMember = bpejZipContent[0] bpejZip.extract(bpejZipFirstMember, bpejDir) bpejZip.close() if bpejZipFirstMember != bpejCsvFileName: bpejDir = QDir(bpejDir) bpejZipFirstMemberFilePath = bpejDir.filePath(bpejZipFirstMember) bpejCsvFilePath = bpejDir.filePath(bpejCsvFileName) os.rename(bpejZipFirstMemberFilePath, bpejCsvFilePath) def _read_bpej_csv(self, bpejCsvFilePath, formatTimeStr): """Reads the BPEJ CSV file. Args: bpejCsvFilePath (str): A full path to the BPEJ CSV file. formatTimeStr (str): A string for time formatting. Returns: dict: A dictionary with BPEJ codes as keys (int) and prices as values (float). """ with open(bpejCsvFilePath, 'rb') as bpejCsvFile: bpejCsvReader = csv.reader(bpejCsvFile, delimiter=';') columnNames = bpejCsvReader.next() codeColumnIndex = columnNames.index('KOD') priceColumnIndex = columnNames.index('CENA') validFromColumnIndex = columnNames.index('PLATNOST_OD') validToColumnIndex = columnNames.index('PLATNOST_DO') todayDate = datetime.now().date() bpejCodePrices = {} for row in bpejCsvReader: if len(row) == 0: break validFromDateStr = row[validFromColumnIndex] validFromDate = datetime.strptime( validFromDateStr, formatTimeStr).date() validToDateStr = row[validToColumnIndex] if validToDateStr == '': validToDate = todayDate else: validToDate = datetime.strptime( validToDateStr, formatTimeStr).date() if validFromDate <= todayDate <= validToDate: code = int(row[codeColumnIndex]) price = row[priceColumnIndex] bpejCodePrices[code] = float(price) return bpejCodePrices def _calculate_feature_prices( self, rowidColumnName, multiToSingleLayer, bpejField, bpejCodePrices): """Calculates feature prices. Args: rowidColumnName (str): A name of rowid column. multiToSingleLayer (QgsVectorLayer): A reference to the single features layer. bpejField (str): A name of the BPEJ field. bpejCodePrices (dict): A dictionary with BPEJ codes as keys (int) and prices as values (float). Returns: defaultdict: A defaultdict with rowids as keys (long) and prices as values (float). set: A set of BPEJ codes that are not in BPEJ SCV file. defaultdict: A defaultdict with rowids as keys (long) and defaultdicts as values. defaultdict: A defaultdict with BPEJ codes (without dots) as keys (str) and defaultdicts as values. defaultdict: A defaultdict with area and prices as keys (str) and their values as values (float). """ prices = defaultdict(float) bpejCodeAreasPrices = defaultdict( lambda : defaultdict(lambda : defaultdict(float))) missingBpejCodes = set() features = multiToSingleLayer.getFeatures() for feature in features: rowid = feature.attribute(rowidColumnName) bpejCode = str(feature.attribute(bpejField)) geometry = feature.geometry() editedBpejCode = int(bpejCode.replace('.', '')) if editedBpejCode in bpejCodePrices: bpejPrice = bpejCodePrices[editedBpejCode] else: bpejPrice = 0.0 missingBpejCodes.add(bpejCode) if geometry != None: area = geometry.area() price = bpejPrice*area bpejCodeAreasPrices[rowid][editedBpejCode]['bpejPrice'] += \ bpejPrice bpejCodeAreasPrices[rowid][editedBpejCode]['area'] += area for rowid, bpejCode in bpejCodeAreasPrices.items(): for editedBpejCode, values in bpejCode.items(): values['roundedArea'] = round(values['area']) values['price'] = values['roundedArea']*values['bpejPrice'] prices[rowid] += values['price'] return (prices, missingBpejCodes, bpejCodeAreasPrices) def _get_bpej_string(self, bpejCodeAreaPrices): """Returns a BPEJ string. Args: bpejCodeAreaPrices (defaultdict): A defaultdict with BPEJ codes (without dots) as keys (str) and defaultdicts as values. defaultdict: A defaultdict with area and prices as keys (str) and their values as values (float). Returns: str: A string that contains information about BPEJ. The string consists of strings '<BPEJ code>-<BPEJ price>-<rounded Area>-<price>' for each BPEJ code in the input defaultdict separated by ', '. """ bpejCodeAreaPricesStr = '' for bpejCode, values in bpejCodeAreaPrices.items(): bpejCodeAreaPricesStr += str(bpejCode) bpejCodeAreaPricesStr += '-' bpejCodeAreaPricesStr += str(values['bpejPrice']) bpejCodeAreaPricesStr += '-' bpejCodeAreaPricesStr += str(int(values['roundedArea'])) bpejCodeAreaPricesStr += '-' bpejCodeAreaPricesStr += str(values['price']) bpejCodeAreaPricesStr += ', ' bpejCodeAreaPricesStr = \ bpejCodeAreaPricesStr.strip(', ') return bpejCodeAreaPricesStr
class UAVPreparer: """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', 'UAVPreparer_{}.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) # Declare instance attributes self.actions = [] self.menu = self.tr(u'&UAV Preparer') # TODO: We are going to let the user set this up in a future iteration self.toolbar = self.iface.addToolBar(u'UAVPreparer') self.toolbar.setObjectName(u'UAVPreparer') # Declare variables self.outputfile = 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('UAVPreparer', 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 """ # Create the dialog (after translation) and keep reference self.dlg = UAVPreparerDialog() 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.toolbar.addAction(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/UAVPreparer/icon.png' self.add_action( icon_path, text=self.tr(self.tr(u'Unmanned Aerial Vehicle Preparer')), callback=self.run, parent=self.iface.mainWindow()) # Access the raster layer self.layerComboManagerDSM = QgsMapLayerComboBox(self.dlg.widgetDSM) self.layerComboManagerDSM.setFilters(QgsMapLayerProxyModel.RasterLayer) self.layerComboManagerDSM.setFixedWidth(175) self.layerComboManagerDSM.setCurrentIndex(-1) # Access the vector layer and an attribute field self.layerComboManagerPoint = QgsMapLayerComboBox(self.dlg.widgetPoint) self.layerComboManagerPoint.setCurrentIndex(-1) self.layerComboManagerPoint.setFilters(QgsMapLayerProxyModel.PointLayer) self.layerComboManagerPoint.setFixedWidth(175) self.layerComboManagerPointField = QgsFieldComboBox(self.dlg.widgetField) self.layerComboManagerPointField.setFilters(QgsFieldProxyModel.Numeric) self.layerComboManagerPoint.layerChanged.connect(self.layerComboManagerPointField.setLayer) # Set up of file save dialog self.fileDialog = QFileDialog() self.dlg.selectButton.clicked.connect(self.savefile) # Set up for the Help button self.dlg.helpButton.clicked.connect(self.help) # Set up for the Run button self.dlg.runButton.clicked.connect(self.start_progress) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu( self.tr(u'&UAV Preparer'), action) self.iface.removeToolBarIcon(action) # remove the toolbar del self.toolbar def run(self): """Run method that performs all the real work""" # 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 def savefile(self): self.outputfile = self.fileDialog.getSaveFileName(None, "Save File As:", None, "Text Files (*.txt)") self.dlg.textOutput.setText(self.outputfile) def help(self): url = "https://github.com/nilswallenberg/UAVPreparer" webbrowser.open_new_tab(url) def start_progress(self): if not self.outputfile: QMessageBox.critical(None, "Error", "Specify an output file") return # Acquiring geodata and attributes dsm_layer = self.layerComboManagerDSM.currentLayer() if dsm_layer is None: QMessageBox.critical(None, "Error", "No valid raster layer is selected") return else: provider = dsm_layer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) point_layer = self.layerComboManagerPoint.currentLayer() if point_layer is None: QMessageBox.critical(None, "Error", "No valid vector point layer is selected") return else: vlayer = QgsVectorLayer(point_layer.source(), "point", "ogr") point_field = self.layerComboManagerPointField.currentField() idx = vlayer.fieldNameIndex(point_field) if idx == -1: QMessageBox.critical(None, "Error", "An attribute with unique fields must be selected") return ### main code ### # set radius rSquare = int(self.dlg.spinBox.value()) # half picture size # finding ID column idx = vlayer.fieldNameIndex(point_field) numfeat = vlayer.featureCount() result = np.zeros([numfeat, 4]) self.dlg.progressBar.setRange(0, numfeat) counter = 0 for feature in vlayer.getFeatures(): self.dlg.progressBar.setValue (counter + 1) geom = feature.geometry() pp = geom.asPoint() x = pp[0] y = pp[1] gdalclipdsm = "gdalwarp -dstnodata -9999 -q -overwrite -te " + str(x - rSquare) + " " + str(y - rSquare) + \ " " + str(x + rSquare) + " " + str(y + rSquare) + " -of GTiff " + filepath_dsm + " " + self.plugin_dir + "/clipdsm.tif" #QMessageBox.critical(None, "Bla", gdalclipdsm) # call the gdal function si = subprocess.STARTUPINFO() # used to suppress cmd window si.dwFlags |= subprocess.STARTF_USESHOWWINDOW subprocess.call(gdalclipdsm, startupinfo=si) #subprocess.call(gdalclipdsm) dataset = gdal.Open(self.plugin_dir + "/clipdsm.tif") dsm_array = dataset.ReadAsArray().astype(np.float) result[counter, 0] = int(feature.attributes()[idx]) result[counter, 1] = np.mean(dsm_array) result[counter, 2] = np.max(dsm_array) result[counter, 3] = np.min(dsm_array) counter += 1 numformat = "%d " + "%6.2f " * 3 headertext = "id mean max min" np.savetxt(self.outputfile, result, fmt=numformat, header=headertext, comments="", delimiter="/t") self.iface.messageBar().pushMessage("UAV Preparer. Operation successful!", level=QgsMessageBar.INFO, duration=5)
class DSMGenerator: """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', 'DSMGenerator_{}.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) # Create the dialog (after translation) and keep reference self.dlg = DSMGeneratorDialog() # Declare instance attributes self.actions = [] self.menu = self.tr(u'&DSM Generator') # TODO: We are going to let the user set this up in a future iteration # self.toolbar = self.iface.addToolBar(u'DSMGenerator') # self.toolbar.setObjectName(u'DSMGenerator') # Declare variables self.OSMoutputfile = None self.DSMoutputfile = None if not (os.path.isdir(self.plugin_dir + '/temp')): os.mkdir(self.plugin_dir + '/temp') # Access the raster layer self.layerComboManagerDEM = QgsMapLayerComboBox(self.dlg.widgetRaster) self.layerComboManagerDEM.setFilters(QgsMapLayerProxyModel.RasterLayer) self.layerComboManagerDEM.setFixedWidth(175) self.layerComboManagerDEM.setCurrentIndex(-1) # Access the vector layer and an attribute field self.layerComboManagerPolygon = QgsMapLayerComboBox( self.dlg.widgetPolygon) self.layerComboManagerPolygon.setCurrentIndex(-1) self.layerComboManagerPolygon.setFilters( QgsMapLayerProxyModel.PolygonLayer) self.layerComboManagerPolygon.setFixedWidth(175) self.layerComboManagerPolygonField = QgsFieldComboBox( self.dlg.widgetField) self.layerComboManagerPolygonField.setFilters( QgsFieldProxyModel.Numeric) self.layerComboManagerPolygonField.setFixedWidth(150) self.layerComboManagerPolygon.layerChanged.connect( self.layerComboManagerPolygonField.setLayer) # Set up of DSM file save dialog self.DSMfileDialog = QFileDialog() self.dlg.saveButton.clicked.connect(self.savedsmfile) # Set up of OSM polygon file save dialog self.OSMfileDialog = QFileDialog() self.dlg.savePolygon.clicked.connect(self.saveosmfile) # Set up for the Help button self.dlg.helpButton.clicked.connect(self.help) # Set up for the Close button self.dlg.closeButton.clicked.connect(self.resetPlugin) # Set up for the Run button self.dlg.runButton.clicked.connect(self.start_progress) # Set up extent self.dlg.canvasButton.toggled.connect(self.checkbox_canvas) # self.dlg.layerButton.toggled.connect(self.checkbox_layer) self.layerComboManagerExtent = QgsMapLayerComboBox( self.dlg.widgetLayerExtent) self.layerComboManagerExtent.setCurrentIndex(-1) self.layerComboManagerExtent.layerChanged.connect(self.checkbox_layer) self.layerComboManagerExtent.setFixedWidth(175) # noinspection PyMethodMayBeStatic def tr(self, message): return QCoreApplication.translate('DSMGenerator', 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): 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.toolbar.addAction(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/DSMGenerator/icon.png' self.add_action(icon_path, text=self.tr(u'DSM Generator'), callback=self.run, parent=self.iface.mainWindow()) def savedsmfile(self): self.DSMoutputfile = self.DSMfileDialog.getSaveFileName( None, "Save File As:", None, "Raster Files (*.tif)") self.dlg.DSMtextOutput.setText(self.DSMoutputfile) def saveosmfile(self): self.OSMoutputfile = self.OSMfileDialog.getSaveFileName( None, "Save File As:", None, "Shapefiles (*.shp)") self.dlg.OSMtextOutput.setText(self.OSMoutputfile) def checkbox_canvas(self): extent = self.iface.mapCanvas().extent() self.dlg.lineEditNorth.setText(str(extent.yMaximum())) self.dlg.lineEditSouth.setText(str(extent.yMinimum())) self.dlg.lineEditWest.setText(str(extent.xMinimum())) self.dlg.lineEditEast.setText(str(extent.xMaximum())) def checkbox_layer(self): dem_layer_extent = self.layerComboManagerExtent.currentLayer() if dem_layer_extent: extent = dem_layer_extent.extent() self.dlg.lineEditNorth.setText(str(extent.yMaximum())) self.dlg.lineEditSouth.setText(str(extent.yMinimum())) self.dlg.lineEditWest.setText(str(extent.xMinimum())) self.dlg.lineEditEast.setText(str(extent.xMaximum())) # Help button def help(self): url = "http://umep-docs.readthedocs.io/en/latest/pre-processor/Spatial%20Data%20DSM%20Generator.html" webbrowser.open_new_tab(url) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for action in self.actions: self.iface.removePluginMenu(self.tr(u'&DSM Generator'), action) # self.iface.removeToolBarIcon(action) # remove the toolbar # del self.toolbar def run(self): self.dlg.show() self.dlg.exec_() def start_progress(self): import datetime start = datetime.datetime.now() # Check OS and dep if sys.platform == 'darwin': gdal_os_dep = '/Library/Frameworks/GDAL.framework/Versions/Current/Programs/' else: gdal_os_dep = '' if self.dlg.canvasButton.isChecked(): # Map Canvas extentCanvasCRS = self.iface.mapCanvas() srs = extentCanvasCRS.mapSettings().destinationCrs() crs = str(srs.authid()) # old_crs = osr.SpatialReference() # old_crs.ImportFromEPSG(int(crs[5:])) can_crs = QgsCoordinateReferenceSystem(int(crs[5:])) # can_wkt = extentCanvasCRS.mapRenderer().destinationCrs().toWkt() # can_crs = osr.SpatialReference() # can_crs.ImportFromWkt(can_wkt) # Raster Layer dem_layer = self.layerComboManagerDEM.currentLayer() dem_prov = dem_layer.dataProvider() dem_path = str(dem_prov.dataSourceUri()) dem_raster = gdal.Open(dem_path) projdsm = osr.SpatialReference(wkt=dem_raster.GetProjection()) projdsm.AutoIdentifyEPSG() projdsmepsg = int(projdsm.GetAttrValue('AUTHORITY', 1)) dem_crs = QgsCoordinateReferenceSystem(projdsmepsg) # dem_wkt = dem_raster.GetProjection() # dem_crs = osr.SpatialReference() # dem_crs.ImportFromWkt(dem_wkt) if can_crs != dem_crs: extentCanvas = self.iface.mapCanvas().extent() extentDEM = dem_layer.extent() transformExt = QgsCoordinateTransform(can_crs, dem_crs) # transformExt = osr.CoordinateTransformation(can_crs, dem_crs) canminx = extentCanvas.xMinimum() canmaxx = extentCanvas.xMaximum() canminy = extentCanvas.yMinimum() canmaxy = extentCanvas.yMaximum() canxymin = transformExt.TransformPoint(canminx, canminy) canxymax = transformExt.TransformPoint(canmaxx, canmaxy) extDiffminx = canxymin[0] - extentDEM.xMinimum( ) # If smaller than zero = warning extDiffminy = canxymin[1] - extentDEM.yMinimum( ) # If smaller than zero = warning extDiffmaxx = canxymax[0] - extentDEM.xMaximum( ) # If larger than zero = warning extDiffmaxy = canxymax[0] - extentDEM.yMaximum( ) # If larger than zero = warning if extDiffminx < 0 or extDiffminy < 0 or extDiffmaxx > 0 or extDiffmaxy > 0: QMessageBox.warning( None, "Warning! Extent of map canvas is larger than raster extent.", "Change to an extent equal to or smaller than the raster extent." ) return # Extent self.yMax = self.dlg.lineEditNorth.text() self.yMin = self.dlg.lineEditSouth.text() self.xMin = self.dlg.lineEditWest.text() self.xMax = self.dlg.lineEditEast.text() if not self.DSMoutputfile: QMessageBox.critical(None, "Error", "Specify a raster output file") return if self.dlg.checkBoxPolygon.isChecked() and not self.OSMoutputfile: QMessageBox.critical(None, "Error", "Specify an output file for OSM data") return # Acquiring geodata and attributes dem_layer = self.layerComboManagerDEM.currentLayer() if dem_layer is None: QMessageBox.critical(None, "Error", "No valid raster layer is selected") return else: provider = dem_layer.dataProvider() filepath_dem = str(provider.dataSourceUri()) demRaster = gdal.Open(filepath_dem) dem_layer_crs = osr.SpatialReference() dem_layer_crs.ImportFromWkt(demRaster.GetProjection()) self.dem_layer_unit = dem_layer_crs.GetAttrValue("UNIT") posUnits = [ 'metre', 'US survey foot', 'meter', 'm', 'ft', 'feet', 'foot', 'ftUS', 'International foot' ] # Possible units if not self.dem_layer_unit in posUnits: QMessageBox.critical( None, "Error", "Raster projection is not in metre or foot. Please reproject.") return polygon_layer = self.layerComboManagerPolygon.currentLayer() osm_layer = self.dlg.checkBoxOSM.isChecked() if polygon_layer is None and osm_layer is False: QMessageBox.critical(None, "Error", "No valid building height layer is selected") return elif polygon_layer: vlayer = QgsVectorLayer(polygon_layer.source(), "buildings", "ogr") fileInfo = QFileInfo(polygon_layer.source()) polygon_ln = fileInfo.baseName() polygon_field = self.layerComboManagerPolygonField.currentField() idx = vlayer.fieldNameIndex(polygon_field) flname = vlayer.attributeDisplayName(idx) if idx == -1: QMessageBox.critical( None, "Error", "An attribute with unique fields must be selected") return ### main code ### self.dlg.progressBar.setRange(0, 5) self.dlg.progressBar.setValue(1) if self.dlg.checkBoxOSM.isChecked(): # TODO replace osr.CoordinateTransformation with QgsCoordinateTransform dem_original = gdal.Open(filepath_dem) dem_wkt = dem_original.GetProjection() ras_crs = osr.SpatialReference() ras_crs.ImportFromWkt(dem_wkt) rasEPSG = ras_crs.GetAttrValue("PROJCS|AUTHORITY", 1) if self.dlg.layerButton.isChecked(): old_crs = ras_crs elif self.dlg.canvasButton.isChecked(): canvasCRS = self.iface.mapCanvas() outputWkt = canvasCRS.mapRenderer().destinationCrs().toWkt() old_crs = osr.SpatialReference() old_crs.ImportFromWkt(outputWkt) wgs84_wkt = """ GEOGCS["WGS 84", DATUM["WGS_1984", SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]], AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich",0, AUTHORITY["EPSG","8901"]], UNIT["degree",0.01745329251994328, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4326"]]""" new_crs = osr.SpatialReference() new_crs.ImportFromWkt(wgs84_wkt) transform = osr.CoordinateTransformation(old_crs, new_crs) minx = float(self.xMin) miny = float(self.yMin) maxx = float(self.xMax) maxy = float(self.yMax) lonlatmin = transform.TransformPoint(minx, miny) lonlatmax = transform.TransformPoint(maxx, maxy) if ras_crs != old_crs: rasTrans = osr.CoordinateTransformation(old_crs, ras_crs) raslonlatmin = rasTrans.TransformPoint(float(self.xMin), float(self.yMin)) raslonlatmax = rasTrans.TransformPoint(float(self.xMax), float(self.yMax)) #else: #raslonlatmin = [float(self.xMin), float(self.yMin)] #raslonlatmax = [float(self.xMax), float(self.yMax)] self.xMin = raslonlatmin[0] self.yMin = raslonlatmin[1] self.xMax = raslonlatmax[0] self.yMax = raslonlatmax[1] # Make data queries to overpass-api urlStr = 'http://overpass-api.de/api/map?bbox=' + str( lonlatmin[0]) + ',' + str(lonlatmin[1]) + ',' + str( lonlatmax[0]) + ',' + str(lonlatmax[1]) osmXml = urllib.urlopen(urlStr).read() #print urlStr # Make OSM building file osmPath = self.plugin_dir + '/temp/OSM_building.osm' osmFile = open(osmPath, 'w') osmFile.write(osmXml) if os.fstat(osmFile.fileno()).st_size < 1: urlStr = 'http://api.openstreetmap.org/api/0.6/map?bbox=' + str( lonlatmin[0]) + ',' + str(lonlatmin[1]) + ',' + str( lonlatmax[0]) + ',' + str(lonlatmax[1]) osmXml = urllib.urlopen(urlStr).read() osmFile.write(osmXml) #print 'Open Street Map' if os.fstat(osmFile.fileno()).st_size < 1: QMessageBox.critical(None, "Error", "No OSM data available") return osmFile.close() outputshp = self.plugin_dir + '/temp/' osmToShape = gdal_os_dep + 'ogr2ogr --config OSM_CONFIG_FILE "' + self.plugin_dir + '/osmconf.ini" -skipfailures -t_srs EPSG:' + str( rasEPSG ) + ' -overwrite -nlt POLYGON -f "ESRI Shapefile" "' + outputshp + '" "' + osmPath + '"' if sys.platform == 'win32': si = subprocess.STARTUPINFO() si.dwFlags |= subprocess.STARTF_USESHOWWINDOW subprocess.call(osmToShape, startupinfo=si) else: os.system(osmToShape) driver = ogr.GetDriverByName('ESRI Shapefile') driver.DeleteDataSource(outputshp + 'lines.shp') driver.DeleteDataSource(outputshp + 'multilinestrings.shp') driver.DeleteDataSource(outputshp + 'other_relations.shp') driver.DeleteDataSource(outputshp + 'points.shp') osmPolygonPath = outputshp + 'multipolygons.shp' vlayer = QgsVectorLayer(osmPolygonPath, 'multipolygons', 'ogr') polygon_layer = vlayer fileInfo = QFileInfo(polygon_layer.source()) polygon_ln = fileInfo.baseName() def renameField(srcLayer, oldFieldName, newFieldName): ds = gdal.OpenEx(srcLayer.source(), gdal.OF_VECTOR | gdal.OF_UPDATE) ds.ExecuteSQL('ALTER TABLE {} RENAME COLUMN {} TO {}'.format( srcLayer.name(), oldFieldName, newFieldName)) srcLayer.reload() vlayer.startEditing() renameField(vlayer, 'building_l', 'bld_levels') renameField(vlayer, 'building_h', 'bld_hght') renameField(vlayer, 'building_c', 'bld_colour') renameField(vlayer, 'building_m', 'bld_materi') renameField(vlayer, 'building_u', 'bld_use') vlayer.commitChanges() vlayer.startEditing() vlayer.dataProvider().addAttributes( [QgsField('bld_height', QVariant.Double, 'double', 3, 2)]) vlayer.updateFields() bld_lvl = vlayer.fieldNameIndex('bld_levels') hght = vlayer.fieldNameIndex('height') bld_hght = vlayer.fieldNameIndex('bld_hght') bld_height = vlayer.fieldNameIndex('bld_height') bldLvlHght = float(self.dlg.doubleSpinBoxBldLvl.value()) illegal_chars = string.ascii_letters + "!#$%&'*+^_`|~:" + " " counterNone = 0 counter = 0 #counterWeird = 0 for feature in vlayer.getFeatures(): if feature[hght]: try: #feature[bld_height] = float(re.sub("[^0-9]", ".", str(feature[hght]))) feature[bld_height] = float( str(feature[hght]).translate(None, illegal_chars)) except: counterNone += 1 elif feature[bld_hght]: try: #feature[bld_height] = float(re.sub("[^0-9]", ".", str(feature[bld_hght]))) feature[bld_height] = float( str(feature[bld_hght]).translate( None, illegal_chars)) except: counterNone += 1 elif feature[bld_lvl]: try: #feature[bld_height] = float(re.sub("[^0-9]", "", str(feature[bld_lvl])))*bldLvlHght feature[bld_height] = float( str(feature[bld_lvl]).translate( None, illegal_chars)) * bldLvlHght except: counterNone += 1 else: counterNone += 1 vlayer.updateFeature(feature) counter += 1 vlayer.commitChanges() flname = vlayer.attributeDisplayName(bld_height) counterDiff = counter - counterNone # Zonal statistics vlayer.startEditing() zoneStat = QgsZonalStatistics(vlayer, filepath_dem, "stat_", 1, QgsZonalStatistics.Mean) zoneStat.calculateStatistics(None) vlayer.dataProvider().addAttributes( [QgsField('height_asl', QVariant.Double)]) vlayer.updateFields() e = QgsExpression('stat_mean + ' + flname) e.prepare(vlayer.pendingFields()) idx = vlayer.fieldNameIndex('height_asl') for f in vlayer.getFeatures(): f[idx] = e.evaluate(f) vlayer.updateFeature(f) vlayer.commitChanges() vlayer.startEditing() idx2 = vlayer.fieldNameIndex('stat_mean') vlayer.dataProvider().deleteAttributes([idx2]) vlayer.updateFields() vlayer.commitChanges() self.dlg.progressBar.setValue(2) # Convert polygon layer to raster # Define pixel_size and NoData value of new raster pixel_size = self.dlg.spinBox.value() # half picture size # Create the destination data source gdalrasterize = gdal_os_dep + 'gdal_rasterize -a ' + 'height_asl' + ' -te ' + str(self.xMin) + ' ' + str(self.yMin) + ' ' + str(self.xMax) + ' ' + str(self.yMax) +\ ' -tr ' + str(pixel_size) + ' ' + str(pixel_size) + ' -l "' + str(polygon_ln) + '" "' \ + str(polygon_layer.source()) + '" "' + self.plugin_dir + '/temp/clipdsm.tif"' # gdalclipdem = gdal_os_dep + 'gdalwarp -dstnodata -9999 -q -overwrite -te ' + str(self.xMin) + ' ' + str(self.yMin) + ' ' + str(self.xMax) + ' ' + str(self.yMax) +\ # ' -tr ' + str(pixel_size) + ' ' + str(pixel_size) + \ # ' -of GTiff ' + '"' + filepath_dem + '" "' + self.plugin_dir + '/temp/clipdem.tif"' # Rasterize if sys.platform == 'win32': si = subprocess.STARTUPINFO() si.dwFlags |= subprocess.STARTF_USESHOWWINDOW subprocess.call(gdalrasterize, startupinfo=si) # subprocess.call(gdalclipdem, startupinfo=si) gdal.Warp(self.plugin_dir + '/temp/clipdem.tif', filepath_dem, xRes=pixel_size, yRes=pixel_size) else: os.system(gdalrasterize) # os.system(gdalclipdem) gdal.Warp(self.plugin_dir + '/temp/clipdem.tif', filepath_dem, xRes=pixel_size, yRes=pixel_size) # Remove gdalwarp with gdal.Translate # bigraster = gdal.Open(filepath_dem) # bbox = (self.xMin, self.yMax, self.xMax, self.yMin) # gdal.Translate(self.plugin_dir + '/data/clipdem.tif', bigraster, projWin=bbox) self.dlg.progressBar.setValue(3) # Adding DSM to DEM # Read DEM dem_raster = gdal.Open(self.plugin_dir + '/temp/clipdem.tif') dem_array = np.array(dem_raster.ReadAsArray().astype(np.float)) dsm_raster = gdal.Open(self.plugin_dir + '/temp/clipdsm.tif') dsm_array = np.array(dsm_raster.ReadAsArray().astype(np.float)) indx = dsm_array.shape for ix in range(0, int(indx[0])): for iy in range(0, int(indx[1])): if int(dsm_array[ix, iy]) == 0: dsm_array[ix, iy] = dem_array[ix, iy] if self.dlg.checkBoxPolygon.isChecked(): vlayer.startEditing() idxHght = vlayer.fieldNameIndex('height_asl') idxBld = vlayer.fieldNameIndex('building') features = vlayer.getFeatures() #for f in vlayer.getFeatures(): for f in features: geom = f.geometry() posUnitsMetre = ['metre', 'meter', 'm'] # Possible metre units posUnitsFt = [ 'US survey foot', 'ft', 'feet', 'foot', 'ftUS', 'International foot' ] # Possible foot units if self.dem_layer_unit in posUnitsMetre: sqUnit = 1 elif self.dem_layer_unit in posUnitsFt: sqUnit = 10.76 if int(geom.area()) > 50000 * sqUnit: vlayer.deleteFeature(f.id()) #if not f[idxHght]: #vlayer.deleteFeature(f.id()) #elif not f[idxBld]: #vlayer.deleteFeature(f.id()) vlayer.updateFields() vlayer.commitChanges() QgsVectorFileWriter.writeAsVectorFormat(vlayer, str(self.OSMoutputfile), "UTF-8", None, "ESRI Shapefile") else: vlayer.startEditing() idx3 = vlayer.fieldNameIndex('height_asl') vlayer.dataProvider().deleteAttributes([idx3]) vlayer.updateFields() vlayer.commitChanges() self.dlg.progressBar.setValue(4) # Save raster def saveraster( gdal_data, filename, raster ): # gdal_data = raster extent, filename = output filename, raster = numpy array (raster to be saved) rows = gdal_data.RasterYSize cols = gdal_data.RasterXSize outDs = gdal.GetDriverByName("GTiff").Create( filename, cols, rows, int(1), gdal.GDT_Float32) outBand = outDs.GetRasterBand(1) # write the data outBand.WriteArray(raster, 0, 0) # flush data to disk, set the NoData value and calculate stats outBand.FlushCache() outBand.SetNoDataValue(-9999) # georeference the image and set the projection outDs.SetGeoTransform(gdal_data.GetGeoTransform()) outDs.SetProjection(gdal_data.GetProjection()) saveraster(dsm_raster, self.DSMoutputfile, dsm_array) # Load result into canvas rlayer = self.iface.addRasterLayer(self.DSMoutputfile) # Trigger a repaint if hasattr(rlayer, "setCacheImage"): rlayer.setCacheImage(None) rlayer.triggerRepaint() self.dlg.progressBar.setValue(5) #runTime = datetime.datetime.now() - start if self.dlg.checkBoxOSM.isChecked(): QMessageBox.information( self.dlg, 'DSM Generator', 'Operation successful! ' + str(counterDiff) + ' building polygons out of ' + str(counter) + ' contained height values.') #self.iface.messageBar().pushMessage("DSM Generator. Operation successful! " + str(counterDiff) + " buildings out of " + str(counter) + " contained height values.", level=QgsMessageBar.INFO, duration=5) else: #self.iface.messageBar().pushMessage("DSM Generator. Operation successful!", level=QgsMessageBar.INFO, duration=5) QMessageBox.information(self.dlg, 'DSM Generator', 'Operation successful!') self.resetPlugin() #print "finished run: %s\n\n" % (datetime.datetime.now() - start) def resetPlugin(self): # Reset plugin self.dlg.canvasButton.setAutoExclusive(False) self.dlg.canvasButton.setChecked(False) self.dlg.layerButton.setAutoExclusive(False) self.dlg.layerButton.setChecked(False) self.dlg.checkBoxOSM.setCheckState(0) self.dlg.checkBoxPolygon.setCheckState(0) # Extent self.layerComboManagerExtent.setCurrentIndex(-1) self.dlg.lineEditNorth.setText("") self.dlg.lineEditSouth.setText("") self.dlg.lineEditWest.setText("") self.dlg.lineEditEast.setText("") # Output boxes self.dlg.OSMtextOutput.setText("") self.dlg.DSMtextOutput.setText("") # Input raster self.layerComboManagerDEM.setCurrentIndex(-1) # Input polygon self.layerComboManagerPolygon.setCurrentIndex(-1) # Progress bar self.dlg.progressBar.setValue(0) # Spin boxes self.dlg.spinBox.setValue(2) self.dlg.doubleSpinBoxBldLvl.setValue(2.5)
class UAVPreparer: """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', 'UAVPreparer_{}.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'&UAV Preparer') # 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 # Declare variables self.outputfile = 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('UAVPreparer', 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/uav_preparer/icon.png' self.add_action(icon_path, text=self.tr(u'UAV Preparer'), 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'&UAV Preparer'), action) self.iface.removeToolBarIcon(action) 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 = UAVPreparerDialog() # Access the raster layer self.layerComboManagerDSM = QgsMapLayerComboBox(self.dlg.widgetDSM) self.layerComboManagerDSM.setFilters(QgsMapLayerProxyModel.RasterLayer) self.layerComboManagerDSM.setFixedWidth(175) self.layerComboManagerDSM.setCurrentIndex(-1) # Access the vector layer and an attribute field self.layerComboManagerPoint = QgsMapLayerComboBox( self.dlg.widgetPointLayer) self.layerComboManagerPoint.setCurrentIndex(-1) self.layerComboManagerPoint.setFilters( QgsMapLayerProxyModel.PointLayer) self.layerComboManagerPoint.setFixedWidth(175) self.layerComboManagerPointField = QgsFieldComboBox( self.dlg.widgetField) self.layerComboManagerPointField.setFilters(QgsFieldProxyModel.Numeric) self.layerComboManagerPoint.layerChanged.connect( self.layerComboManagerPointField.setLayer) # Set up of file save dialog self.fileDialog = QFileDialog() self.dlg.pushButtonSave.clicked.connect(self.savefile) # Set up for the Help button self.dlg.helpButton.clicked.connect(self.help) # Set up for the Run button self.dlg.runButton.clicked.connect(self.start_progress) # 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 def savefile(self): self.outputfile = self.fileDialog.getSaveFileName( None, "Save File As:", None, "Text Files (*.txt)") self.dlg.textOutput.setText(self.outputfile[0]) def help(self): url = "https://github.com/biglimp/UAVPreparer" webbrowser.open_new_tab(url) def start_progress(self): if not self.outputfile: QMessageBox.critical(None, "Error", "Specify an output file") return # Acquiring geodata and attributes dsm_layer = self.layerComboManagerDSM.currentLayer() if dsm_layer is None: QMessageBox.critical(None, "Error", "No valid raster layer is selected") return else: provider = dsm_layer.dataProvider() filepath_dsm = str(provider.dataSourceUri()) point_layer = self.layerComboManagerPoint.currentLayer() if point_layer is None: QMessageBox.critical(None, "Error", "No valid vector point layer is selected") return else: vlayer = QgsVectorLayer(point_layer.source(), "polygon", "ogr") point_field = self.layerComboManagerPointField.currentField() idx = vlayer.fields().indexFromName(point_field) if idx == -1: QMessageBox.critical( None, "Error", "An attribute with unique fields must be selected") return ### main code ### # set radius r = 100 # half picture size numfeat = vlayer.featureCount() result = np.zeros([numfeat, 4]) # load big raster bigraster = gdal.Open(filepath_dsm) filepath_tempdsm = self.plugin_dir + '/clipdsm.tif' self.dlg.progressBar.setRange(0, numfeat) i = 0 for f in vlayer.getFeatures(): self.dlg.progressBar.setValue(i + 1) # get the coordinate for the point y = f.geometry().centroid().asPoint().y() x = f.geometry().centroid().asPoint().x() bbox = (x - r, y + r, x + r, y - r) gdal.Translate(filepath_tempdsm, bigraster, projWin=bbox) data = gdal.Open(filepath_tempdsm) mat = np.array(data.ReadAsArray()) result[i, 0] = int(f.attributes()[idx]) result[i, 1] = np.mean(mat) result[i, 2] = np.max(mat) result[i, 3] = np.min(mat) i = i + 1 # Saving to file numformat = '%d ' + '%6.2f ' * 3 headertext = 'id mean max min' np.savetxt(self.outputfile[0], result, fmt=numformat, delimiter=' ', header=headertext) self.iface.messageBar().pushMessage( 'UAV Preparer. Operation successful!', level=Qgis.Success, duration=5)