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')
Esempio n. 2
0
    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')
Esempio n. 4
0
    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
Esempio n. 7
0
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)
Esempio n. 8
0
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)
Esempio n. 9
0
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)