def __init__(self, parent, iface, theDock=None):
        """Constructor for the dialog.

        Args:
           * parent - parent widget of this dialog
           * iface - a Quantum GIS QGisAppInterface instance.
           * theDock - Optional dock widget instance that we can notify of
             changes to the keywords.

        Returns:
           not applicable
        Raises:
           no exceptions explicitly raised
        """
# pylint: enable=W0231
        QtGui.QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle(self.tr('InaSAFE %s Options' % __version__))
        # Save reference to the QGIS interface and parent
        self.iface = iface
        self.parent = parent
        self.dock = theDock
        self.helpDialog = None
        self.keywordIO = ISKeywordIO()
        # Set up things for context help
        myButton = self.buttonBox.button(QtGui.QDialogButtonBox.Help)
        QtCore.QObject.connect(myButton, QtCore.SIGNAL('clicked()'),
                               self.showHelp)
        self.grpNotImplemented.hide()
        self.adjustSize()
        self.restoreState()
 def setUp(self):
     self.keywordIO = ISKeywordIO()
     myUri = QgsDataSourceURI()
     myUri.setDatabase(os.path.join(TESTDATA, 'jk.sqlite'))
     myUri.setDataSource('', 'osm_buildings', 'Geometry')
     self.sqliteLayer = QgsVectorLayer(myUri.uri(), 'OSM Buildings',
                                    'spatialite')
     myHazardPath = os.path.join(HAZDATA, 'Shakemap_Padang_2009.asc')
     self.fileRasterLayer, myType = loadLayer(myHazardPath, DIR=None)
     del myType
     self.fileVectorLayer, myType = loadLayer('Padang_WGS84.shp')
     del myType
     self.expectedSqliteKeywords = {'category': 'exposure',
                                    'datatype': 'OSM',
                                    'subcategory': 'building'}
     self.expectedVectorKeywords = {'category': 'exposure',
                                    'datatype': 'itb',
                                    'subcategory': 'building'}
     self.expectedRasterKeywords = {'category': 'hazard',
                                    'subcategory': 'earthquake',
                                    'unit': 'MMI',
                                    'title': ('An earthquake in Padang '
                                              'like in 2009')}
class ISOptionsDialog(QtGui.QDialog, Ui_ISOptionsDialogBase):
    """Options dialog for the InaSAFE plugin."""
# pylint: disable=W0231
    def __init__(self, parent, iface, theDock=None):
        """Constructor for the dialog.

        Args:
           * parent - parent widget of this dialog
           * iface - a Quantum GIS QGisAppInterface instance.
           * theDock - Optional dock widget instance that we can notify of
             changes to the keywords.

        Returns:
           not applicable
        Raises:
           no exceptions explicitly raised
        """
# pylint: enable=W0231
        QtGui.QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle(self.tr('InaSAFE %s Options' % __version__))
        # Save reference to the QGIS interface and parent
        self.iface = iface
        self.parent = parent
        self.dock = theDock
        self.helpDialog = None
        self.keywordIO = ISKeywordIO()
        # Set up things for context help
        myButton = self.buttonBox.button(QtGui.QDialogButtonBox.Help)
        QtCore.QObject.connect(myButton, QtCore.SIGNAL('clicked()'),
                               self.showHelp)
        self.grpNotImplemented.hide()
        self.adjustSize()
        self.restoreState()

    def restoreState(self):
        """
        Args: Reinstate the options based on the user's stored session info
            None
        Returns:
            None
        Raises:
        """
        mySettings = QtCore.QSettings()
        myFlag = mySettings.value(
                            'inasafe/useThreadingFlag', False).toBool()
        self.cbxUseThread.setChecked(myFlag)

        myFlag = mySettings.value(
                            'inasafe/visibleLayersOnlyFlag', True).toBool()
        self.cbxVisibleLayersOnly.setChecked(myFlag)

        myFlag = mySettings.value(
                            'inasafe/setLayerNameFromTitleFlag', True).toBool()
        self.cbxSetLayerNameFromTitle.setChecked(myFlag)

        myFlag = mySettings.value(
                            'inasafe/setZoomToImpactFlag', True).toBool()
        self.cbxZoomToImpact.setChecked(myFlag)
        # whether exposure layer should be hidden after model completes
        myFlag = mySettings.value(
                            'inasafe/setHideExposureFlag', False).toBool()
        self.cbxHideExposure.setChecked(myFlag)

        myPath = mySettings.value(
                            'inasafe/keywordCachePath',
                            self.keywordIO.defaultKeywordDbPath()).toString()
        self.leKeywordCachePath.setText(myPath)

    def saveState(self):
        """
        Args: Store the options into the user's stored session info
            None
        Returns:
            None
        Raises:
        """
        mySettings = QtCore.QSettings()
        mySettings.setValue('inasafe/useThreadingFlag',
                            self.cbxUseThread.isChecked())
        mySettings.setValue('inasafe/visibleLayersOnlyFlag',
                            self.cbxVisibleLayersOnly.isChecked())
        mySettings.setValue('inasafe/setLayerNameFromTitleFlag',
                            self.cbxSetLayerNameFromTitle.isChecked())
        mySettings.setValue('inasafe/setZoomToImpactFlag',
                            self.cbxZoomToImpact.isChecked())
        mySettings.setValue('inasafe/setHideExposureFlag',
                            self.cbxHideExposure.isChecked())
        mySettings.setValue('inasafe/keywordCachePath',
                            self.leKeywordCachePath.text())

    def showHelp(self):
        """Load the help text for the options gui"""
        if not self.helpDialog:
            self.helpDialog = ISHelp(self.iface.mainWindow(), 'options')
        self.helpDialog.show()

    def accept(self):
        """Method invoked when ok button is clicked
        Args:
            None
        Returns:
            None
        Raises:
        """
        self.saveState()
        self.dock.readSettings()
        self.close()

    @pyqtSignature('')  # prevents actions being handled twice
    def on_toolKeywordCachePath_clicked(self):
        """Autoconnect slot activated when the select cache file tool button is
        clicked,
        Args:
            None
        Returns:
            None
        Raises:
            None
        """
        myFilename = QtGui.QFileDialog.getSaveFileName(self,
                    self.tr('Set keyword cache file'),
                    self.keywordIO.defaultKeywordDbPath(),
                    self.tr('Sqlite DB File (*.db)'))
        self.leKeywordCachePath.setText(myFilename)
    def __init__(self, parent, iface, theDock=None):
        """Constructor for the dialog.
        .. note:: In QtDesigner the advanced editor's predefined keywords
           list should be shown in english always, so when adding entries to
           cboKeyword, be sure to choose :gui:`Properties<<` and untick the
           :gui:`translatable` property.

        Args:
           * parent - parent widget of this dialog
           * iface - a Quantum GIS QGisAppInterface instance.
           * theDock - Optional dock widget instance that we can notify of
             changes to the keywords.

        Returns:
           not applicable
        Raises:
           no exceptions explicitly raised
        """
# pylint: enable=W0231
        QtGui.QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle(self.tr(
                            'InaSAFE %s Keywords Editor' % __version__))
        self.keywordIO = ISKeywordIO()
        # note the keys should remain untranslated as we need to write
        # english to the keywords file. The keys will be written as user data
        # in the combo entries.
        # .. seealso:: http://www.voidspace.org.uk/python/odict.html
        self.standardExposureList = OrderedDict([('population [density]',
                                      self.tr('population [density]')),
                                     ('population [count]',
                                      self.tr('population [count]')),
                                     ('building',
                                      self.tr('building')),
                                     ('building [osm]',
                                      self.tr('building [osm]')),
                                     ('building [sigab]',
                                      self.tr('building [sigab]')),
                                     ('roads',
                                      self.tr('roads'))])
        self.standardHazardList = OrderedDict([('earthquake [MMI]',
                                    self.tr('earthquake [MMI]')),
                                     ('tsunami [m]',
                                      self.tr('tsunami [m]')),
                                     ('tsunami [wet/dry]',
                                      self.tr('tsunami [wet/dry]')),
                                     ('tsunami [feet]',
                                      self.tr('tsunami [feet]')),
                                     ('flood [m]',
                                      self.tr('flood [m]')),
                                     ('flood [wet/dry]',
                                      self.tr('flood [wet/dry]')),
                                     ('flood [feet]', self.tr('flood [feet]')),
                                     ('tephra [kg2/m2',
                                      self.tr('tephra [kg2/m2]'))])
        # Save reference to the QGIS interface and parent
        self.iface = iface
        self.parent = parent
        self.dock = theDock
        # Set up things for context help
        myButton = self.buttonBox.button(QtGui.QDialogButtonBox.Help)
        QtCore.QObject.connect(myButton, QtCore.SIGNAL('clicked()'),
                               self.showHelp)
        self.helpDialog = None
        # set some inital ui state:
        self.pbnAdvanced.setChecked(True)
        self.pbnAdvanced.toggle()
        self.radPredefined.setChecked(True)
        self.adjustSize()
        #myButton = self.buttonBox.button(QtGui.QDialogButtonBox.Ok)
        #myButton.setEnabled(False)
        self.layer = self.iface.activeLayer()
        if self.layer:
            self.loadStateFromKeywords()
class ISKeywordsDialog(QtGui.QDialog, Ui_ISKeywordsDialogBase):
    """Dialog implementation class for the Risk In A Box keywords editor."""
# pylint: disable=W0231
    def __init__(self, parent, iface, theDock=None):
        """Constructor for the dialog.
        .. note:: In QtDesigner the advanced editor's predefined keywords
           list should be shown in english always, so when adding entries to
           cboKeyword, be sure to choose :gui:`Properties<<` and untick the
           :gui:`translatable` property.

        Args:
           * parent - parent widget of this dialog
           * iface - a Quantum GIS QGisAppInterface instance.
           * theDock - Optional dock widget instance that we can notify of
             changes to the keywords.

        Returns:
           not applicable
        Raises:
           no exceptions explicitly raised
        """
# pylint: enable=W0231
        QtGui.QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle(self.tr(
                            'InaSAFE %s Keywords Editor' % __version__))
        self.keywordIO = ISKeywordIO()
        # note the keys should remain untranslated as we need to write
        # english to the keywords file. The keys will be written as user data
        # in the combo entries.
        # .. seealso:: http://www.voidspace.org.uk/python/odict.html
        self.standardExposureList = OrderedDict([('population [density]',
                                      self.tr('population [density]')),
                                     ('population [count]',
                                      self.tr('population [count]')),
                                     ('building',
                                      self.tr('building')),
                                     ('building [osm]',
                                      self.tr('building [osm]')),
                                     ('building [sigab]',
                                      self.tr('building [sigab]')),
                                     ('roads',
                                      self.tr('roads'))])
        self.standardHazardList = OrderedDict([('earthquake [MMI]',
                                    self.tr('earthquake [MMI]')),
                                     ('tsunami [m]',
                                      self.tr('tsunami [m]')),
                                     ('tsunami [wet/dry]',
                                      self.tr('tsunami [wet/dry]')),
                                     ('tsunami [feet]',
                                      self.tr('tsunami [feet]')),
                                     ('flood [m]',
                                      self.tr('flood [m]')),
                                     ('flood [wet/dry]',
                                      self.tr('flood [wet/dry]')),
                                     ('flood [feet]', self.tr('flood [feet]')),
                                     ('tephra [kg2/m2',
                                      self.tr('tephra [kg2/m2]'))])
        # Save reference to the QGIS interface and parent
        self.iface = iface
        self.parent = parent
        self.dock = theDock
        # Set up things for context help
        myButton = self.buttonBox.button(QtGui.QDialogButtonBox.Help)
        QtCore.QObject.connect(myButton, QtCore.SIGNAL('clicked()'),
                               self.showHelp)
        self.helpDialog = None
        # set some inital ui state:
        self.pbnAdvanced.setChecked(True)
        self.pbnAdvanced.toggle()
        self.radPredefined.setChecked(True)
        self.adjustSize()
        #myButton = self.buttonBox.button(QtGui.QDialogButtonBox.Ok)
        #myButton.setEnabled(False)
        self.layer = self.iface.activeLayer()
        if self.layer:
            self.loadStateFromKeywords()

    def showHelp(self):
        """Load the help text for the keywords gui"""
        if not self.helpDialog:
            self.helpDialog = ISHelp(self.iface.mainWindow(), 'keywords')
        self.helpDialog.show()

    # prevents actions being handled twice
    @pyqtSignature('bool')
    def on_pbnAdvanced_toggled(self, theFlag):
        """Automatic slot executed when the advanced button is toggled.

        .. note:: some of the behaviour for hiding widgets is done using
           the signal/slot editor in designer, so if you are trying to figure
           out how the interactions work, look there too!

        Args:
           theFlag - boolean indicating the new checked state of the button
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        if theFlag:
            self.pbnAdvanced.setText(self.tr('Hide advanced editor'))
        else:
            self.pbnAdvanced.setText(self.tr('Show advanced editor'))
        self.adjustSize()

    # prevents actions being handled twice
    @pyqtSignature('bool')
    def on_radHazard_toggled(self, theFlag):
        """Automatic slot executed when the hazard radio is toggled.

        Args:
           theFlag - boolean indicating the new checked state of the button
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        if not theFlag:
            return
        self.setCategory('hazard')
        self.updateControlsFromList()

    # prevents actions being handled twice
    @pyqtSignature('bool')
    def on_radExposure_toggled(self, theFlag):
        """Automatic slot executed when the hazard radio is toggled on.

        Args:
           theFlag - boolean indicating the new checked state of the button
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        if not theFlag:
            return
        self.setCategory('exposure')
        self.updateControlsFromList()

    # prevents actions being handled twice
    @pyqtSignature('int')
    def on_cboSubcategory_currentIndexChanged(self, theIndex=None):
        """Automatic slot executed when the subcategory is changed.

        When the user changes the subcategory, we will extract the
        subcategory and dataype or unit (depending on if it is a hazard
        or exposure subcategory) from the [] after the name.

        Args:
           None
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        del theIndex
        myItem = self.cboSubcategory.itemData(
                            self.cboSubcategory.currentIndex()).toString()
        myText = str(myItem)
        if myText == self.tr('Not Set'):
            self.removeItemByKey('subcategory')
            return
        myTokens = myText.split(' ')
        if len(myTokens) < 1:
            self.removeItemByKey('subcategory')
            return
        mySubcategory = myTokens[0]
        self.addListEntry('subcategory', mySubcategory)

        # Some subcategories e.g. roads have no units or datatype
        if len(myTokens) == 1:
            return
        myCategory = self.getValueForKey('category')
        if 'hazard' == myCategory:
            myUnits = myTokens[1].replace('[', '').replace(']', '')
            self.addListEntry('unit', myUnits)
        if 'exposure' == myCategory:
            myDataType = myTokens[1].replace('[', '').replace(']', '')
            self.addListEntry('datatype', myDataType)

    # prevents actions being handled twice
    def setSubcategoryList(self, theEntries, theSelectedItem=None):
        """Helper to populate the subcategory list based on category context.

        Args:

           * theEntries - an OrderedDict of subcategories. The dict entries
             should be ('earthquake', self.tr('earthquake')). See
             http://www.voidspace.org.uk/python/odict.html for info on
             OrderedDict.
           * theSelectedItem - optional parameter indicating which item
             should be selected in the combo. If the selected item is not
             in theList, it will be appended to it.

        Returns:
           None.
        Raises:
           no exceptions explicitly raised.
        """
        # To aoid triggering on_cboSubcategory_currentIndexChanged
        # we block signals from the combo while updating it
        self.cboSubcategory.blockSignals(True)
        self.cboSubcategory.clear()
        if (theSelectedItem is not None and
            theSelectedItem not in theEntries.values() and
            theSelectedItem not in theEntries.keys()):
            # Add it to the OrderedList
            theEntries[theSelectedItem] = theSelectedItem
        myIndex = 0
        mySelectedIndex = 0
        for myKey, myValue in theEntries.iteritems():
            if (myValue == theSelectedItem or
               myKey == theSelectedItem):
                mySelectedIndex = myIndex
            myIndex += 1
            self.cboSubcategory.addItem(myValue, myKey)
        self.cboSubcategory.setCurrentIndex(mySelectedIndex)
        self.cboSubcategory.blockSignals(False)

    # prevents actions being handled twice
    @pyqtSignature('')
    def on_pbnAddToList1_clicked(self):
        """Automatic slot executed when the pbnAddToList1 button is pressed.

        Args:
           None
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""

        myCurrentKey = self.tr(self.cboKeyword.currentText())
        myCurrentValue = self.lePredefinedValue.text()
        self.addListEntry(myCurrentKey, myCurrentValue)
        self.updateControlsFromList()

    # prevents actions being handled twice
    @pyqtSignature('')
    def on_pbnAddToList2_clicked(self):
        """Automatic slot executed when the pbnAddToList2 button is pressed.

        Args:
           None
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""

        myCurrentKey = self.leKey.text()
        myCurrentValue = self.leValue.text()
        if myCurrentKey == 'category' and myCurrentValue == 'hazard':
            self.radHazard.blockSignals(True)
            self.radHazard.setChecked(True)
            self.setSubcategoryList(self.standardHazardList)
            self.radHazard.blockSignals(False)
        elif myCurrentKey == 'category' and myCurrentValue == 'exposure':
            self.radExposure.blockSignals(True)
            self.radExposure.setChecked(True)
            self.setSubcategoryList(self.standardExposureList)
            self.radExposure.blockSignals(False)
        elif myCurrentKey == 'category':
            #.. todo:: notify the user their category is invalid
            pass
        self.addListEntry(myCurrentKey, myCurrentValue)
        self.updateControlsFromList()

    # prevents actions being handled twice
    @pyqtSignature('')
    def on_pbnRemove_clicked(self):
        """Automatic slot executed when the pbnRemove button is pressed.

        It will remove any selected items in the keywords list.

        Args:
           None
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        for myItem in self.lstKeywords.selectedItems():
            self.lstKeywords.takeItem(self.lstKeywords.row(myItem))
        self.updateControlsFromList()

    def addListEntry(self, theKey, theValue):
        """Add an item to the keywords list given its key/value.

        The key and value must both be valid, non empty strings
        or an InvalidKVPException will be raised.

        If an entry with the same key exists, it's value will be
        replaced with theValue.

        It will add the current key/value pair to the list if it is not
        already present. The kvp will also be stored in the data of the
        listwidgetitem as a simple string delimited with a bar ('|').

        Args:

           * theKey - string representing the key part of the key
             value pair (kvp)
           * theValue - string representing the value part of the key
             value pair (kvp)

        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        if theKey is None or theKey == '':
            return
        if theValue is None or theValue == '':
            return

        myMessage = ''
        if ':' in theKey:
            theKey = theKey.replace(':', '.')
            myMessage = self.tr('Colons are not allowed, replaced with "."')
        if ':' in theValue:
            theValue = theValue.replace(':', '.')
            myMessage = self.tr('Colons are not allowed, replaced with "."')
        if myMessage == '':
            self.lblMessage.setText('')
            self.lblMessage.hide()
        else:
            self.lblMessage.setText(myMessage)
            self.lblMessage.show()
        myItem = QtGui.QListWidgetItem(theKey + ':' + theValue)
        # we are going to replace, so remove it if it exists already
        self.removeItemByKey(theKey)
        myData = theKey + '|' + theValue
        myItem.setData(QtCore.Qt.UserRole, myData)
        self.lstKeywords.insertItem(0, myItem)

    def setCategory(self, theCategory):
        """Set the category radio button based on theCategory.

        Args:
           theCategory - a string which must be either 'hazard' or 'exposure'.
        Returns:
           False if the radio button could not be updated
        Raises:
           no exceptions explicitly raised."""
        # convert from QString if needed
        myCategory = str(theCategory)
        if self.getValueForKey('category') == myCategory:
            #nothing to do, go home
            return True
        if myCategory not in ['hazard', 'exposure']:
            # .. todo:: report an error to the user
            return False
        # Special case when category changes, we start on a new slate!

        if myCategory == 'hazard':
            # only cause a toggle if we actually changed the category
            # This will only really be apparent if user manually enters
            # category as a keyword
            self.reset()
            self.radHazard.blockSignals(True)
            self.radHazard.setChecked(True)
            self.radHazard.blockSignals(False)
            self.removeItemByKey('subcategory')
            self.removeItemByKey('datatype')
            self.addListEntry('category', 'hazard')
            myList = self.standardHazardList
            self.setSubcategoryList(myList)

        else:
            self.reset()
            self.radExposure.blockSignals(True)
            self.radExposure.setChecked(True)
            self.radExposure.blockSignals(False)
            self.removeItemByKey('subcategory')
            self.removeItemByKey('unit')
            self.addListEntry('category', 'exposure')
            myList = self.standardExposureList
            self.setSubcategoryList(myList)

        return True

    def reset(self, thePrimaryKeywordsOnlyFlag=True):
        """Reset all controls to a blank state.

        Args:
            thePrimaryKeywordsOnlyFlag - if True (the default), only
            reset Subcategory, datatype and units.
        Returns:
            None
        Raises:
           no exceptions explicitly raised."""

        self.cboSubcategory.clear()
        self.removeItemByKey('subcategory')
        self.removeItemByKey('datatype')
        self.removeItemByKey('unit')
        if not thePrimaryKeywordsOnlyFlag:
            # Clear everything else too
            self.lstKeywords.clear()
            self.leKey.clear()
            self.leValue.clear()
            self.lePredefinedValue.clear()
            self.leTitle.clear()

    def removeItemByKey(self, theKey):
        """Remove an item from the kvp list given its key.

        Args:
            theKey - key of item to be removed.
        Returns:
            None
        Raises:
           no exceptions explicitly raised."""
        for myCounter in range(self.lstKeywords.count()):
            myExistingItem = self.lstKeywords.item(myCounter)
            myText = myExistingItem.text()
            myTokens = myText.split(':')
            if len(myTokens) < 2:
                break
            myKey = myTokens[0]
            if myKey == theKey:
                # remove it since the key is already present
                self.lstKeywords.takeItem(myCounter)
                break

    def removeItemByValue(self, theValue):
        """Remove an item from the kvp list given its key.

        Args:
            theValue - value of item to be removed.
        Returns:
            None
        Raises:
           no exceptions explicitly raised."""
        for myCounter in range(self.lstKeywords.count()):
            myExistingItem = self.lstKeywords.item(myCounter)
            myText = myExistingItem.text()
            myTokens = myText.split(':')
            myValue = myTokens[1]
            if myValue == theValue:
                # remove it since the key is already present
                self.lstKeywords.takeItem(myCounter)
                break

    def getValueForKey(self, theKey):
        """Check if our key list contains a specific key,
        and return its value if present.

        Args:
           theKey- String representing the key to search for
        Returns:
           Value of key if matched otherwise none
        Raises:
           no exceptions explicitly raised."""
        for myCounter in range(self.lstKeywords.count()):
            myExistingItem = self.lstKeywords.item(myCounter)
            myText = myExistingItem.text()
            myTokens = myText.split(':')
            myKey = str(myTokens[0]).strip()
            myValue = str(myTokens[1]).strip()
            if myKey == theKey:
                return myValue
        return None

    def loadStateFromKeywords(self):
        """Set the ui state to match the keywords of the
           currently active layer.

        Args:
           None
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        try:
            myKeywords = self.keywordIO.readKeywords(self.layer)
        except:
            # layer has no keywords file so just start with a blank slate
            # so that subcategory gets populated nicely & we will assume
            # exposure to start with
            myKeywords = {'category': 'exposure'}

        myLayerName = self.layer.name()
        if 'title' not in myKeywords:
            self.leTitle.setText(myLayerName)
        self.lblLayerName.setText(myLayerName)
        #if we have a category key, unpack it first so radio button etc get set
        if 'category' in myKeywords:
            self.setCategory(myKeywords['category'])
            myKeywords.pop('category')

        for myKey in myKeywords.iterkeys():
            self.addListEntry(myKey, myKeywords[myKey])
        # now make the rest of the gui reflect the list entries
        self.updateControlsFromList()

    def updateControlsFromList(self):
        """Set the ui state to match the keywords of the
           currently active layer.

        Args:
           None
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        mySubcategory = self.getValueForKey('subcategory')
        myUnits = self.getValueForKey('unit')
        myType = self.getValueForKey('datatype')
        myTitle = self.getValueForKey('title')
        if myTitle is not None:
            self.leTitle.setText(myTitle)
        elif self.layer is not None:
            myLayerName = self.layer.name()
            self.lblLayerName.setText(myLayerName)
        else:
            self.lblLayerName.setText('')

        if self.radExposure.isChecked():
            if mySubcategory is not None and myType is not None:
                self.setSubcategoryList(self.standardExposureList,
                                     mySubcategory + ' [' + myType + ']')
            elif mySubcategory is not None:
                self.setSubcategoryList(self.standardExposureList,
                                        mySubcategory)
            else:
                self.setSubcategoryList(self.standardExposureList,
                                        self.tr('Not Set'))
        else:
            if mySubcategory is not None and myUnits is not None:
                self.setSubcategoryList(self.standardHazardList,
                                     mySubcategory + ' [' + myUnits + ']')
            elif mySubcategory is not None:
                self.setSubcategoryList(self.standardHazardList,
                                        mySubcategory)
            else:
                self.setSubcategoryList(self.standardHazardList,
                                        self.tr('Not Set'))

    # prevents actions being handled twice
    @pyqtSignature('QString')
    def on_leTitle_textEdited(self, theText):
        """Update the keywords list whenver the user changes the title.
        This slot is not called is the title is changed programmatically.

        Args:
           None
        Returns:
           dict - a dictionary of keyword reflecting the state of the dialog.
        Raises:
           no exceptions explicitly raised."""
        self.addListEntry('title', str(theText))

    def getKeywords(self):
        """Obtain the state of the dialog as a keywords dict

        Args:
           None
        Returns:
           dict - a dictionary of keyword reflecting the state of the dialog.
        Raises:
           no exceptions explicitly raised."""
                #make sure title is listed
        if str(self.leTitle.text()) != '':
            self.addListEntry('title', str(self.leTitle.text()))

        myKeywords = {}
        for myCounter in range(self.lstKeywords.count()):
            myExistingItem = self.lstKeywords.item(myCounter)
            myText = myExistingItem.text()
            myTokens = myText.split(':')
            myKey = str(myTokens[0]).strip()
            myValue = str(myTokens[1]).strip()
            myKeywords[myKey] = myValue
        return myKeywords

    def accept(self):
        """Automatic slot executed when the ok button is pressed.

        It will write out the keywords for the layer that is active.

        Args:
           None
        Returns:
           None.
        Raises:
           no exceptions explicitly raised."""
        myKeywords = self.getKeywords()
        try:
            self.keywordIO.writeKeywords(self.layer, myKeywords)
        except Exception, e:
            QtGui.QMessageBox.warning(self, self.tr('InaSAFE'),
            ((self.tr('An error was encountered when saving the keywords:\n'
                      '%s' % str(getExceptionWithStacktrace(e))))))
        if self.dock is not None:
            self.dock.getLayers()
        self.close()
class ISKeywordIOTest(unittest.TestCase):
    """Tests for reading and writing of raster and vector data
    """

    def setUp(self):
        self.keywordIO = ISKeywordIO()
        myUri = QgsDataSourceURI()
        myUri.setDatabase(os.path.join(TESTDATA, 'jk.sqlite'))
        myUri.setDataSource('', 'osm_buildings', 'Geometry')
        self.sqliteLayer = QgsVectorLayer(myUri.uri(), 'OSM Buildings',
                                       'spatialite')
        myHazardPath = os.path.join(HAZDATA, 'Shakemap_Padang_2009.asc')
        self.fileRasterLayer, myType = loadLayer(myHazardPath, DIR=None)
        del myType
        self.fileVectorLayer, myType = loadLayer('Padang_WGS84.shp')
        del myType
        self.expectedSqliteKeywords = {'category': 'exposure',
                                       'datatype': 'OSM',
                                       'subcategory': 'building'}
        self.expectedVectorKeywords = {'category': 'exposure',
                                       'datatype': 'itb',
                                       'subcategory': 'building'}
        self.expectedRasterKeywords = {'category': 'hazard',
                                       'subcategory': 'earthquake',
                                       'unit': 'MMI',
                                       'title': ('An earthquake in Padang '
                                                 'like in 2009')}

    def tearDown(self):
        pass

    def test_getHashForDatasource(self):
        """Test we can reliably get a hash for a uri"""
        myHash = self.keywordIO.getHashForDatasource(PG_URI)
        myExpectedHash = '7cc153e1b119ca54a91ddb98a56ea95e'
        myMessage = "Got: %s\nExpected: %s" % (myHash, myExpectedHash)
        assert myHash == myExpectedHash, myMessage

    def test_writeReadKeywordFromUri(self):
        """Test we can set and get keywords for a non local datasource"""
        myHandle, myFilename = tempfile.mkstemp('.db', 'keywords_',
                                            getTempDir())

        # Ensure the file is deleted before we try to write to it
        # fixes windows specific issue where you get a message like this
        # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory.
        # This is because mkstemp creates the file handle and leaves
        # the file open.
        os.close(myHandle)
        os.remove(myFilename)
        myExpectedKeywords = {'category': 'exposure',
                              'datatype': 'itb',
                              'subcategory': 'building'}
        # SQL insert test
        # On first write schema is empty and there is no matching hash
        self.keywordIO.setKeywordDbPath(myFilename)
        self.keywordIO.writeKeywordsForUri(PG_URI, myExpectedKeywords)
        # SQL Update test
        # On second write schema is populated and we update matching hash
        myExpectedKeywords = {'category': 'exposure',
                              'datatype': 'OSM',  # <--note the change here!
                              'subcategory': 'building'}
        self.keywordIO.writeKeywordsForUri(PG_URI, myExpectedKeywords)
        # Test getting all keywords
        myKeywords = self.keywordIO.readKeywordFromUri(PG_URI)
        myMessage = 'Got: %s\n\nExpected %s\n\nDB: %s' % (
                    myKeywords, myExpectedKeywords, myFilename)
        assert myKeywords == myExpectedKeywords, myMessage
        # Test getting just a single keyword
        myKeyword = self.keywordIO.readKeywordFromUri(PG_URI, 'datatype')
        myExpectedKeyword = 'OSM'
        myMessage = 'Got: %s\n\nExpected %s\n\nDB: %s' % (
                    myKeyword, myExpectedKeyword, myFilename)
        assert myKeyword == myExpectedKeyword, myMessage
        # Test deleting keywords actually does delete
        self.keywordIO.deleteKeywordsForUri(PG_URI)
        try:
            myKeyword = self.keywordIO.readKeywordFromUri(PG_URI, 'datatype')
            #if the above didnt cause an exception then bad
            myMessage = 'Expected a HashNotFoundException to be raised'
            assert myMessage
        except HashNotFoundException:
            #we expect this outcome so good!
            pass

    def test_areKeywordsFileBased(self):
        """Can we correctly determine if keywords should be written to file or
        to database?"""
        assert not self.keywordIO.areKeywordsFileBased(self.sqliteLayer)
        assert self.keywordIO.areKeywordsFileBased(self.fileRasterLayer)
        assert self.keywordIO.areKeywordsFileBased(self.fileVectorLayer)

    def test_readRasterFileKeywords(self):
        """Can we read raster file keywords using generic readKeywords method
        """
        myKeywords = self.keywordIO.readKeywords(self.fileRasterLayer)
        myExpectedKeywords = self.expectedRasterKeywords
        mySource = self.fileRasterLayer.source()
        myMessage = 'Got: %s\n\nExpected %s\n\nSource: %s' % (
                    myKeywords, myExpectedKeywords, mySource)
        assert myKeywords == myExpectedKeywords, myMessage

    def test_readVectorFileKeywords(self):
        """Can we read vector file keywords with the generic readKeywords
        method """
        myKeywords = self.keywordIO.readKeywords(self.fileVectorLayer)
        myExpectedKeywords = self.expectedVectorKeywords
        mySource = self.fileVectorLayer.source()
        myMessage = 'Got: %s\n\nExpected %s\n\nSource: %s' % (
                    myKeywords, myExpectedKeywords, mySource)
        assert myKeywords == myExpectedKeywords, myMessage

    def test_readDBKeywords(self):
        """Can we read sqlite keywords with the generic readKeywords method
        """
        myLocalPath = os.path.join(os.path.dirname(__file__),
                                   '..', 'jk.sqlite')
        myPath = os.path.join(TESTDATA, 'test_keywords.db')
        self.keywordIO.setKeywordDbPath(myPath)
        # We need to make a local copy of the dataset so
        # that we can use a local path that will hash properly on the
        # database to return us the correct / valid keywords record.
        shutil.copy2(os.path.join(TESTDATA, 'jk.sqlite'), myLocalPath)
        myUri = QgsDataSourceURI()
        # always use relative path!
        myUri.setDatabase('../jk.sqlite')
        myUri.setDataSource('', 'osm_buildings', 'Geometry')
        # create a local version that has the relative url
        mySqliteLayer = QgsVectorLayer(myUri.uri(), 'OSM Buildings',
                                       'spatialite')
        myExpectedSource = ('dbname=\'../jk.sqlite\' table="osm_buildings"'
             ' (Geometry) sql=')
        myMessage = 'Got source: %s\n\nExpected %s\n' % (
                    mySqliteLayer.source, myExpectedSource)
        assert mySqliteLayer.source() == myExpectedSource, myMessage
        myKeywords = self.keywordIO.readKeywords(mySqliteLayer)
        myExpectedKeywords = self.expectedSqliteKeywords
        assert myKeywords == myExpectedKeywords, myMessage
        mySource = self.sqliteLayer.source()
        os.remove(myLocalPath)
        myMessage = 'Got: %s\n\nExpected %s\n\nSource: %s' % (
                    myKeywords, myExpectedKeywords, mySource)
        assert myKeywords == myExpectedKeywords, myMessage
Exemple #7
0
def _clipVectorLayer(theLayer, theExtent,
                     theExtraKeywords=None):
    """Clip a Hazard or Exposure layer to the
    extents of the current view frame. The layer must be a
    vector layer or an exception will be thrown.

    The output layer will always be in WGS84/Geographic.

    Args:

        * theLayer - a valid QGIS vector layer in EPSG:4326
        * theExtent -  an array representing the exposure layer
           extents in the form [xmin, ymin, xmax, ymax]. It is assumed
           that the coordinates are in EPSG:4326 although currently
           no checks are made to enforce this.
        * theExtraKeywords - any additional keywords over and above the
          original keywords that should be associated with the cliplayer.

    Returns:
        Path to the output clipped layer (placed in the
        system temp dir).

    Raises:
       None

    """
    if not theLayer or not theExtent:
        myMessage = tr('Layer or Extent passed to clip is None.')
        raise InvalidParameterException(myMessage)

    if theLayer.type() != QgsMapLayer.VectorLayer:
        myMessage = tr('Expected a vector layer but received a %s.' %
                str(theLayer.type()))
        raise InvalidParameterException(myMessage)

    myHandle, myFilename = tempfile.mkstemp('.shp', 'clip_',
                                            getTempDir())

    # Ensure the file is deleted before we try to write to it
    # fixes windows specific issue where you get a message like this
    # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory.
    # This is because mkstemp creates the file handle and leaves
    # the file open.
    os.close(myHandle)
    os.remove(myFilename)

    # Get the clip extents in the layer's native CRS
    myGeoCrs = QgsCoordinateReferenceSystem()
    myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId)
    myXForm = QgsCoordinateTransform(myGeoCrs, theLayer.crs())
    myRect = QgsRectangle(theExtent[0], theExtent[1],
                          theExtent[2], theExtent[3])
    myProjectedExtent = myXForm.transformBoundingBox(myRect)

    # Get vector layer
    myProvider = theLayer.dataProvider()
    if myProvider is None:
        myMessage = tr('Could not obtain data provider from '
               'layer "%s"' % theLayer.source())
        raise Exception(myMessage)

    # get the layer field list, select by our extent then write to disk
    # .. todo:: FIXME - for different geometry types we should implement
    #    different clipping behaviour e.g. reject polygons that
    #    intersect the edge of the bbox. Tim
    myAttributes = myProvider.attributeIndexes()
    myFetchGeometryFlag = True
    myUseIntersectFlag = True
    myProvider.select(myAttributes,
                      myProjectedExtent,
                      myFetchGeometryFlag,
                      myUseIntersectFlag)

    myFieldList = myProvider.fields()

    myWriter = QgsVectorFileWriter(myFilename,
                                   'UTF-8',
                                   myFieldList,
                                   theLayer.wkbType(),
                                   myGeoCrs,
                                   'ESRI Shapefile')
    if myWriter.hasError() != QgsVectorFileWriter.NoError:
        myMessage = tr('Error when creating shapefile: <br>Filename:'
               '%s<br>Error: %s' %
            (myFilename, myWriter.hasError()))
        raise Exception(myMessage)

    # Reverse the coordinate xform now so that we can convert
    # geometries from layer crs to geocrs.
    myXForm = QgsCoordinateTransform(theLayer.crs(), myGeoCrs)
    # Retrieve every feature with its geometry and attributes
    myFeature = QgsFeature()
    myCount = 0
    while myProvider.nextFeature(myFeature):
        myGeometry = myFeature.geometry()
        # Loop through the parts adding them to the output file
        # we ALWAYS write out single part features
        myGeometryList = explodeMultiPartGeometry(myGeometry)
        for myPart in myGeometryList:
            myPart.transform(myXForm)
            myFeature.setGeometry(myPart)
            myWriter.addFeature(myFeature)
        myCount += 1
    del myWriter  # Flush to disk

    if myCount < 1:
        myMessage = tr('No features fall within the clip extents. '
                       'Try panning / zooming to an area containing data '
                       'and then try to run your analysis again.')
        raise NoFeaturesInExtentException(myMessage)

    myKeywordIO = ISKeywordIO()
    myKeywordIO.copyKeywords(theLayer, myFilename,
                  theExtraKeywords=theExtraKeywords)

    return myFilename  # Filename of created file
Exemple #8
0
    except CalledProcessError, e:
        myMessage = tr('<p>Error while executing the following shell command:'
                     '</p><pre>%s</pre><p>Error message: %s'
                     % (myCommand, str(e)))
        # shameless hack - see https://github.com/AIFDR/inasafe/issues/141
        if sys.platform == 'darwin':  # Mac OS X
            if 'Errno 4' in str(e):
                # continue as the error seems to be non critical
                pass
            else:
                raise Exception(myMessage)
        else:
            raise Exception(myMessage)

    # .. todo:: Check the result of the shell call is ok
    myKeywordIO = ISKeywordIO()
    myKeywordIO.copyKeywords(theLayer, myFilename,
                             theExtraKeywords=theExtraKeywords)
    return myFilename  # Filename of created file


def extentToKml(theExtent):
    """A helper to get a little kml doc for an extent so that
    we can use it with gdal warp for clipping."""

    myBottomLeftCorner = '%f,%f' % (theExtent[0], theExtent[1])
    myTopLeftCorner = '%f,%f' % (theExtent[0], theExtent[3])
    myTopRightCorner = '%f,%f' % (theExtent[2], theExtent[3])
    myBottomRightCorner = '%f,%f' % (theExtent[2], theExtent[1])
    myKml = ("""<?xml version="1.0" encoding="utf-8" ?>
<kml xmlns="http://www.opengis.net/kml/2.2">