Source code for safe_qgis.safe_interface

"""InaSAFE Disaster risk assessment tool developed by AusAid -
**IS Safe Interface.**

The purpose of the module is to centralise interactions between the gui
package and the underlying InaSAFE packages. This should be the only place
where SAFE modules are imported directly.

Contact : ole.moller.nielsen@gmail.com

.. note:: This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.

"""

__author__ = 'tim@linfiniti.com, ole.moller.nielsen@gmail.com'
__revision__ = '$Format:%H$'
__date__ = '04/04/2012'
__copyright__ = ('Copyright 2012, Australia Indonesia Facility for '
                 'Disaster Reduction')


# Standard modules
import os
import unicodedata
import logging

# SAFE functionality - passed on to QGIS modules
# pylint: disable=W0611
from safe.api import (get_admissible_plugins,
                      get_function_title,
                      get_plugins as safe_get_plugins,
                      read_keywords, bbox_intersection,
                      write_keywords as safe_write_keywords,
                      read_layer as safe_read_layer,
                      buffered_bounding_box,
                      verify as verify_util,
                      VerificationError,
                      InaSAFEError,
                      temp_dir,
                      unique_filename,
                      safe_tr as safeTr,
                      calculate_impact as safe_calculate_impact,
                      BoundingBoxError,
                      ReadLayerError,
                      get_plugins, get_version)

from safe.defaults import DEFAULTS
# pylint: enable=W0611

# InaSAFE GUI specific functionality
from PyQt4.QtCore import QCoreApplication
from safe_qgis.exceptions import (KeywordNotFoundException,
                                  StyleInfoNotFoundException,
                                  InvalidParameterException,
                                  InsufficientOverlapException)

from safe.common.exceptions import BoundingBoxError, ReadLayerError
LOGGER = logging.getLogger('InaSAFE')


[docs]def tr(theText): """We define a tr() alias here since the is_safe_interface implementation below is not a class and does not inherit from QObject. .. note:: see http://tinyurl.com/pyqt-differences Args: theText - string to be translated Returns: Translated version of the given string if available, otherwise the original string. """ myContext = "@default" return QCoreApplication.translate(myContext, theText)
[docs]def verify(theStatement, theMessage=None): """This is just a thin wrapper around safe.api.verify. Args: * theStatement - expression to verify * theMessage - message to display on failure Returns: None Raises: VerificationError """ try: verify_util(theStatement, theMessage) except: raise
[docs]def getOptimalExtent(theHazardGeoExtent, theExposureGeoExtent, theViewportGeoExtent=None): """ A helper function to determine what the optimal extent is. Optimal extent should be considered as the intersection between the three inputs. The inasafe library will perform various checks to ensure that the extent is tenable, includes data from both etc. This is just a thin wrapper around safe.api.bbox_intersection. Typically the result of this function will be used to clip input layers to a commone extent before processing. Args: * theHazardGeoExtent - an array representing the hazard 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. * theExposureGeoExtent - 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. * theViewPortGeoExtent (optional) - an array representing the viewport 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. ..note:: We do minimal checking as the inasafe library takes care of it for us. Returns: An array containing an extent in the form [xmin, ymin, xmax, ymax] e.g.:: [100.03, -1.14, 100.81, -0.73] Raises: Any exceptions raised by the InaSAFE library will be propogated. """ # myMessage = tr('theHazardGeoExtent or theExposureGeoExtent cannot be None.' 'Found: /ntheHazardGeoExtent: %s ' '/ntheExposureGeoExtent: %s' % (theHazardGeoExtent, theExposureGeoExtent)) if (theHazardGeoExtent is None) or (theExposureGeoExtent is None): raise BoundingBoxError(myMessage) # .. note:: The bbox_intersection function below assumes that # all inputs are in EPSG:4326 myOptimalExtent = bbox_intersection(theHazardGeoExtent, theExposureGeoExtent, theViewportGeoExtent) if myOptimalExtent is None: # Bounding boxes did not overlap myMessage = tr('Bounding boxes of hazard data, exposure data ' 'and viewport did not overlap, so no computation was ' 'done. Please make sure you pan to where the data is and ' 'that hazard and exposure data overlaps.') raise InsufficientOverlapException(myMessage) return myOptimalExtent
[docs]def getBufferedExtent(theGeoExtent, theCellSize): """Grow bounding box with one unit of resolution in each direction. Args: * theGeoExtent - Bounding box with format [W, S, E, N] * theCellSize - (resx, resy) Raster resolution in each direction. If resolution is None bbox is returned unchanged. Returns: Adjusted bounding box Raises: Any exceptions are propogated Note: See docstring for underlying function buffered_bounding_box for more details. """ try: return buffered_bounding_box(theGeoExtent, theCellSize) except: raise
[docs]def availableFunctions(theKeywordList=None): """ Query the inasafe engine to see what plugins are available. Args: theKeywordList - an optional parameter which should contain a list of 2 dictionaries (the number of items in the list is not enforced). The dictionaries should be obtained by using readKeywordsFromFile e.g.:: myFile1 = foo.shp myFile2 = bar.asc myKeywords1 = readKeywordsFromFile(myFile1) myKeywords2 = readKeywordsFromFile(myFile2) myList = [myKeywords1, myKeywords2] myFunctions = availableFunctions(myList) Returns: A dictionary of strings where each is a plugin name. .. note:: If theKeywordList is not provided, all available plugins will be returned in the list. Raises: NoFunctionsFoundException if no functions are found. """ try: myDict = get_admissible_plugins(theKeywordList) #if len(myDict) < 1: # myMessage = 'No InaSAFE impact functions could be found' # raise NoFunctionsFoundException(myMessage) return myDict except: raise
[docs]def readKeywordsFromLayer(theLayer, keyword): """Get metadata from the keywords file associated with a layer. .. note:: Requires a inasafe layer instance as parameter. .. seealso:: getKeywordFromPath Args: * theLayer - a InaSAFE layer (vector or raster) * keyword - the metadata keyword to retrieve e.g. 'title' Returns: A string containing the retrieved value for the keyword. Raises: KeywordNotFoundException if the keyword is not recognised. """ myValue = None if theLayer is None: raise InvalidParameterException() try: myValue = theLayer.get_keywords(keyword) except Exception, e: myMessage = tr('Keyword retrieval failed for %s (%s) \n %s' % ( theLayer.get_filename(), keyword, str(e))) raise KeywordNotFoundException(myMessage) if not myValue or myValue == '': myMessage = tr('No value was found for keyword %s in layer %s' % ( theLayer.get_filename(), keyword)) raise KeywordNotFoundException(myMessage) return myValue
[docs]def readKeywordsFromFile(theLayerPath, theKeyword=None): """Get metadata from the keywords file associated with a local file in the file system. .. note:: Requires a str representing a file path instance as parameter As opposed to readKeywordsFromLayer which takes a inasafe file object as parameter. .. seealso:: readKeywordsFromLayer Args: * theLayerPath - a string representing a path to a layer (e.g. '/tmp/foo.shp', '/tmp/foo.tif') * theKeyword - optional - the metadata keyword to retrieve e.g. 'title' Returns: A string containing the retrieved value for the keyword if the keyword argument is specified, otherwise the complete keywords dictionary is returned. Raises: KeywordNotFoundException if the keyword is not recognised. """ # check the source layer path is valid if not os.path.isfile(theLayerPath): myMessage = tr('Cannot get keywords from a non-existent file.' '%s does not exist.' % theLayerPath) raise InvalidParameterException(myMessage) # check there really is a keywords file for this layer myKeywordFilePath = os.path.splitext(theLayerPath)[0] myKeywordFilePath += '.keywords' if not os.path.isfile(myKeywordFilePath): wrappedPath = theLayerPath.replace(os.sep, '<wbr>' + os.sep) myMessage = tr('No keywords file found for %s' % wrappedPath) raise InvalidParameterException(myMessage) # now get the requested keyword using the inasafe library myDictionary = None try: myDictionary = read_keywords(myKeywordFilePath) except Exception, e: myMessage = tr('Keyword retrieval failed for %s (%s) \n %s' % ( myKeywordFilePath, theKeyword, str(e))) raise KeywordNotFoundException(myMessage) # if no keyword was supplied, just return the dict if theKeyword is None: return myDictionary if not theKeyword in myDictionary: myMessage = tr('No value was found in file %s for keyword %s' % ( myKeywordFilePath, theKeyword)) raise KeywordNotFoundException(myMessage) try: myValue = myDictionary[theKeyword] except: raise return myValue
[docs]def writeKeywordsToFile(theFilename, theKeywords): """Thin wrapper around the safe write_keywords function. Args: * thePath - str representing path to layer that must be written. If the file does not end in .keywords, its extension will be stripped off and the basename + .keywords will be used as the file. * theKeywords - a dictionary of keywords to be written Returns: None Raises: Any exceptions are propogated """ myBasename, myExtension = os.path.splitext(theFilename) if 'keywords' not in myExtension: theFilename = myBasename + '.keywords' try: safe_write_keywords(theKeywords, theFilename) except: raise
[docs]def getStyleInfo(theLayer): """Get styleinfo associated with a layer. Args: * theLayer - InaSAFE layer (raster or vector) Returns: A list of dictionaries containing styleinfo info for a layer. Raises: * StyleInfoNotFoundException if the style is not found. * InvalidParameterException if the paramers are not correct. """ if not theLayer: raise InvalidParameterException() if not hasattr(theLayer, 'get_style_info'): myMessage = tr('Argument "%s" was not a valid layer instance' % theLayer) raise StyleInfoNotFoundException(myMessage) try: myValue = theLayer.get_style_info() except Exception, e: myMessage = tr('Styleinfo retrieval failed for %s\n %s' % ( theLayer.get_filename(), str(e))) raise StyleInfoNotFoundException(myMessage) if not myValue or myValue == '': myMessage = tr('No styleInfo was found for layer %s' % ( theLayer.get_filename())) raise StyleInfoNotFoundException(myMessage) return myValue
[docs]def makeAscii(x): """Convert QgsString to ASCII""" x = unicode(x) x = unicodedata.normalize('NFKD', x).encode('ascii', 'ignore') return x
[docs]def readSafeLayer(thePath): """Thin wrapper around the safe read_layer function. Args: thePath - str representing path to layer that must be opened. Returns: A safe readSafeLayer object is returned. Raises: Any exceptions are propogated """ try: return safe_read_layer(makeAscii(thePath)) except: raise
[docs]def getSafeImpactFunctions(theFunction=None): """Thin wrapper around the safe impact_functions function. Args: theFunction - optional str giving a specific plugins name that should be fetched. Returns: A safe impact function is returned Raises: Any exceptions are propogated """ try: return safe_get_plugins(makeAscii(theFunction)) except: raise
[docs]def getFunctionTitle(theFunction): """Thin wrapper around the safe get_function_title. Args: * theFunction - SAFE impact function instance to be used Returns: The title of a safe impact function is returned Raises: Any exceptions are propogated """ # FIXME (Ole): I don't think we have to do try-except-raise. # It would be the same just to call the function # and let Python's normal exception handling # propagate exceptions (just saving some lines and # complexity) try: return get_function_title(theFunction) except: raise
[docs]def calculateSafeImpact(theLayers, theFunction): """Thin wrapper around the safe calculate_impact function. Args: * theLayers - a list of layers to be used. They should be ordered with hazard layer first and exposure layer second. * theFunction - SAFE impact function instance to be used Returns: A safe impact function is returned Raises: Any exceptions are propogated """ try: return safe_calculate_impact(theLayers, theFunction) except: raise