class AnalysisHandler(QObject):

    """Analysis handler for the dock and the wizard."""

    analysisDone = pyqtSignal(bool)

    # noinspection PyUnresolvedReferences
    def __init__(self, parent):
        """Constructor for the class.

        :param parent: Parent widget i.e. the wizard dialog.
        :type parent: QWidget
        """

        QtCore.QObject.__init__(self)
        self.parent = parent
        # Do not delete this
        self.iface = parent.iface
        self.keyword_io = KeywordIO()
        self.impact_function_manager = ImpactFunctionManager()
        self.extent = Extent(self.iface)
        self.analysis = None

        # Values for settings these get set in read_settings.
        self.run_in_thread_flag = None
        self.zoom_to_impact_flag = None
        self.hide_exposure_flag = None
        self.clip_hard = None
        self.show_intermediate_layers = None
        self.show_rubber_bands = False

        self.last_analysis_rubberband = None
        # This is a rubber band to show what the AOI of the
        # next analysis will be. Also added in 2.1.0
        self.next_analysis_rubberband = None

        self.read_settings()

    def enable_signal_receiver(self):
        """Setup dispatcher for all available signal from Analysis.

        .. note:: Adapted from the dock
        """
        dispatcher.connect(
            self.show_busy,
            signal=BUSY_SIGNAL)

        dispatcher.connect(
            self.hide_busy,
            signal=NOT_BUSY_SIGNAL)

        dispatcher.connect(
            self.completed,
            signal=ANALYSIS_DONE_SIGNAL)

        # noinspection PyArgumentEqualDefault
        dispatcher.connect(
            self.show_dynamic_message,
            signal=DYNAMIC_MESSAGE_SIGNAL)

        # noinspection PyArgumentEqualDefault
        dispatcher.connect(
            self.parent.wvResults.static_message_event,
            signal=STATIC_MESSAGE_SIGNAL,
            sender=dispatcher.Any)

        # noinspection PyArgumentEqualDefault
        dispatcher.connect(
            self.parent.wvResults.error_message_event,
            signal=ERROR_MESSAGE_SIGNAL,
            sender=dispatcher.Any)

    def disable_signal_receiver(self):
        """Remove dispatcher for all available signal from Analysis.

        .. note:: Adapted from the dock
        """
        dispatcher.disconnect(
            self.show_busy,
            signal=BUSY_SIGNAL)

        dispatcher.disconnect(
            self.hide_busy,
            signal=NOT_BUSY_SIGNAL)

        dispatcher.disconnect(
            self.completed,
            signal=ANALYSIS_DONE_SIGNAL)

        dispatcher.disconnect(
            self.show_dynamic_message,
            signal=DYNAMIC_MESSAGE_SIGNAL)

    def show_static_message(self, message):
        """Send a static message to the message viewer.

        Static messages cause any previous content in the MessageViewer to be
        replaced with new content.

        .. note:: Copied from the dock

        :param message: An instance of our rich message class.
        :type message: Message

        """
        dispatcher.send(
            signal=STATIC_MESSAGE_SIGNAL,
            sender=self,
            message=message)

    def show_dynamic_message(self, sender, message):
        """Send a dynamic message to the message viewer.

        Dynamic messages are appended to any existing content in the
        MessageViewer.

        .. note:: Modified from the dock

        :param sender: The object that sent the message.
        :type sender: Object, None

        :param message: An instance of our rich message class.
        :type message: Message

        """
        # TODO Hardcoded step - may overflow, if number of messages increase
        self.parent.pbProgress.setValue(self.parent.pbProgress.value() + 15)
        self.parent.wvResults.dynamic_message_event(sender, message)

    def show_error_message(self, error_message):
        """Send an error message to the message viewer.

        Error messages cause any previous content in the MessageViewer to be
        replaced with new content.

        .. note:: Copied from the dock

        :param error_message: An instance of our rich error message class.
        :type error_message: ErrorMessage
        """
        dispatcher.send(
            signal=ERROR_MESSAGE_SIGNAL,
            sender=self,
            message=error_message)
        self.hide_busy()

    def read_settings(self):
        """Restore settings from QSettings.

        Do this on init and after changing options in the options dialog.
        """

        settings = QSettings()

        flag = bool(settings.value(
            'inasafe/showRubberBands', False, type=bool))
        self.extent.show_rubber_bands = flag
        try:
            extent = settings.value('inasafe/analysis_extent', '', type=str)
            crs = settings.value('inasafe/analysis_extent_crs', '', type=str)
        except TypeError:
            # Any bogus stuff in settings and we just clear them
            extent = ''
            crs = ''

        if extent != '' and crs != '':
            extent = extent_string_to_array(extent)
            try:
                self.extent.user_extent = QgsRectangle(*extent)
                self.extent.user_extent_crs = QgsCoordinateReferenceSystem(crs)
                self.extent.show_user_analysis_extent()
            except TypeError:
                self.extent.user_extent = None
                self.extent.user_extent_crs = None

        flag = settings.value(
            'inasafe/useThreadingFlag', False, type=bool)
        self.run_in_thread_flag = flag

        flag = settings.value(
            'inasafe/setZoomToImpactFlag', True, type=bool)
        self.zoom_to_impact_flag = flag

        # whether exposure layer should be hidden after model completes
        flag = settings.value(
            'inasafe/setHideExposureFlag', False, type=bool)
        self.hide_exposure_flag = flag

        # whether to 'hard clip' layers (e.g. cut buildings in half if they
        # lie partially in the AOI
        self.clip_hard = settings.value('inasafe/clip_hard', False, type=bool)

        # whether to show or not postprocessing generated layers
        self.show_intermediate_layers = settings.value(
            'inasafe/show_intermediate_layers', False, type=bool)

        # whether to show or not dev only options
        self.developer_mode = settings.value(
            'inasafe/developer_mode', False, type=bool)

        # whether to show or not a custom Logo
        self.organisation_logo_path = settings.value(
            'inasafe/organisation_logo_path',
            default_organisation_logo_path(),
            type=str)
        flag = bool(settings.value(
            'inasafe/showOrganisationLogoInDockFlag', True, type=bool))

    def show_busy(self):
        """Lock buttons and enable the busy cursor."""
        self.parent.pbnNext.setEnabled(False)
        self.parent.pbnBack.setEnabled(False)
        self.parent.pbnCancel.setEnabled(False)
        QtGui.qApp.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
        self.parent.repaint()
        QtGui.qApp.processEvents()

    def hide_busy(self):
        """Unlock buttons A helper function to indicate processing is done."""
        self.parent.pbnNext.setEnabled(True)
        self.parent.pbnBack.setEnabled(True)
        self.parent.pbnCancel.setEnabled(True)
        self.parent.repaint()
        QtGui.qApp.restoreOverrideCursor()

    def analysis_error(self, exception, message):
        """A helper to spawn an error and halt processing.

        An exception will be logged, busy status removed and a message
        displayed.

        .. note:: Copied from the dock

        :param message: an ErrorMessage to display
        :type message: ErrorMessage, Message

        :param exception: An exception that was raised
        :type exception: Exception
        """
        self.hide_busy()
        LOGGER.exception(message)
        message = get_error_message(exception, context=message)
        self.show_error_message(message)
        self.analysisDone.emit(False)

    def setup_and_run_analysis(self):
        """Setup and execute the analysis"""
        self.enable_signal_receiver()

        self.show_busy()
        self.init_analysis()
        try:
            self.analysis.setup_analysis()
        except InsufficientOverlapError as e:
            raise e

        self.extent.show_last_analysis_extent(
            self.analysis.clip_parameters[1])

        # Start the analysis
        self.analysis.run_analysis()

        self.disable_signal_receiver()

    def init_analysis(self):
        """Setup analysis to make it ready to work.

        .. note:: Copied or adapted from the dock
        """
        self.analysis = Analysis()
        # Layers
        self.analysis.hazard_layer = self.parent.hazard_layer
        self.analysis.exposure_layer = self.parent.exposure_layer
        self.analysis.aggregation_layer = self.parent.aggregation_layer
        # TODO test if the implement aggregation layer works!

        # noinspection PyTypeChecker
        self.analysis.hazard_keyword = self.keyword_io.read_keywords(
            self.parent.hazard_layer)
        self.analysis.exposure_keyword = self.keyword_io.read_keywords(
            self.parent.exposure_layer)
        # Need to check since aggregation layer is not mandatory
        if self.analysis.aggregation_layer:
            self.analysis.aggregation_keyword = self.keyword_io.read_keywords(
                self.parent.aggregation_layer)

        # Impact Function
        impact_function = self.impact_function_manager.get(
            self.parent.selected_function()['id'])
        impact_function.parameters = self.parent.if_params
        self.analysis.impact_function = impact_function

        # Variables
        self.analysis.clip_hard = self.clip_hard
        self.analysis.show_intermediate_layers = self.show_intermediate_layers
        self.analysis.run_in_thread_flag = self.run_in_thread_flag
        self.analysis.map_canvas = self.iface.mapCanvas()

        # Extent
        if self.parent.rbExtentUser.isChecked():
            self.analysis.user_extent = self.extent.user_extent
        else:
            self.analysis.user_extent = None
        self.analysis.user_extent_crs = self.extent.user_extent_crs
        self.analysis.clip_to_viewport = self.parent.rbExtentScreen.isChecked()

    def completed(self):
        """Slot activated when the process is done.

        .. note:: Adapted from the dock
        """

        # Try to run completion code
        try:
            from datetime import datetime
            LOGGER.debug(datetime.now())
            LOGGER.debug('get engine impact layer')
            LOGGER.debug(self.analysis is None)
            engine_impact_layer = self.analysis.get_impact_layer()

            # Load impact layer into QGIS
            qgis_impact_layer = read_impact_layer(engine_impact_layer)

            report = self.show_results(
                qgis_impact_layer, engine_impact_layer)

        except Exception, e:  # pylint: disable=W0703
            # FIXME (Ole): This branch is not covered by the tests
            self.analysis_error(e, self.tr('Error loading impact layer.'))
        else:
Example #2
0
class AnalysisHandler(QObject):
    """Analysis handler for the dock and the wizard."""

    analysisDone = pyqtSignal(bool)

    # noinspection PyUnresolvedReferences
    def __init__(self, parent):
        """Constructor for the class.

        :param parent: Parent widget i.e. the wizard dialog.
        :type parent: QWidget
        """

        QtCore.QObject.__init__(self)
        self.parent = parent
        # Do not delete this
        self.iface = parent.iface
        self.keyword_io = KeywordIO()

        self.extent = Extent(self.iface)
        self.analysis = None

        # Values for settings these get set in read_settings.
        self.run_in_thread_flag = None
        self.zoom_to_impact_flag = None
        self.hide_exposure_flag = None
        self.clip_hard = None
        self.show_intermediate_layers = None
        self.show_rubber_bands = False

        self.last_analysis_rubberband = None
        # This is a rubber band to show what the AOI of the
        # next analysis will be. Also added in 2.1.0
        self.next_analysis_rubberband = None

        self.read_settings()

    def enable_signal_receiver(self):
        """Setup dispatcher for all available signal from Analysis.

        .. note:: Adapted from the dock
        """
        dispatcher.connect(self.show_busy, signal=BUSY_SIGNAL)

        dispatcher.connect(self.hide_busy, signal=NOT_BUSY_SIGNAL)

        dispatcher.connect(self.completed, signal=ANALYSIS_DONE_SIGNAL)

        # noinspection PyArgumentEqualDefault
        dispatcher.connect(self.show_dynamic_message,
                           signal=DYNAMIC_MESSAGE_SIGNAL)

        # noinspection PyArgumentEqualDefault
        dispatcher.connect(self.parent.wvResults.static_message_event,
                           signal=STATIC_MESSAGE_SIGNAL,
                           sender=dispatcher.Any)

        # noinspection PyArgumentEqualDefault
        dispatcher.connect(self.parent.wvResults.error_message_event,
                           signal=ERROR_MESSAGE_SIGNAL,
                           sender=dispatcher.Any)

    def disable_signal_receiver(self):
        """Remove dispatcher for all available signal from Analysis.

        .. note:: Adapted from the dock
        """
        dispatcher.disconnect(self.show_busy, signal=BUSY_SIGNAL)

        dispatcher.disconnect(self.hide_busy, signal=NOT_BUSY_SIGNAL)

        dispatcher.disconnect(self.completed, signal=ANALYSIS_DONE_SIGNAL)

        dispatcher.disconnect(self.show_dynamic_message,
                              signal=DYNAMIC_MESSAGE_SIGNAL)

    def show_static_message(self, message):
        """Send a static message to the message viewer.

        Static messages cause any previous content in the MessageViewer to be
        replaced with new content.

        .. note:: Copied from the dock

        :param message: An instance of our rich message class.
        :type message: Message

        """
        dispatcher.send(signal=STATIC_MESSAGE_SIGNAL,
                        sender=self,
                        message=message)

    def show_dynamic_message(self, sender, message):
        """Send a dynamic message to the message viewer.

        Dynamic messages are appended to any existing content in the
        MessageViewer.

        .. note:: Modified from the dock

        :param sender: The object that sent the message.
        :type sender: Object, None

        :param message: An instance of our rich message class.
        :type message: Message

        """
        # TODO Hardcoded step - may overflow, if number of messages increase
        self.parent.pbProgress.setValue(self.parent.pbProgress.value() + 15)
        self.parent.wvResults.dynamic_message_event(sender, message)

    def show_error_message(self, error_message):
        """Send an error message to the message viewer.

        Error messages cause any previous content in the MessageViewer to be
        replaced with new content.

        .. note:: Copied from the dock

        :param error_message: An instance of our rich error message class.
        :type error_message: ErrorMessage
        """
        dispatcher.send(signal=ERROR_MESSAGE_SIGNAL,
                        sender=self,
                        message=error_message)
        self.hide_busy()

    def read_settings(self):
        """Restore settings from QSettings.

        Do this on init and after changing options in the options dialog.
        """

        settings = QSettings()

        flag = bool(settings.value('inasafe/showRubberBands', False,
                                   type=bool))
        self.extent.show_rubber_bands = flag
        try:
            extent = settings.value('inasafe/analysis_extent', '', type=str)
            crs = settings.value('inasafe/analysis_extent_crs', '', type=str)
        except TypeError:
            # Any bogus stuff in settings and we just clear them
            extent = ''
            crs = ''

        if extent != '' and crs != '':
            extent = extent_string_to_array(extent)
            try:
                self.extent.user_extent = QgsRectangle(*extent)
                self.extent.user_extent_crs = QgsCoordinateReferenceSystem(crs)
                self.extent.show_user_analysis_extent()
            except TypeError:
                self.extent.user_extent = None
                self.extent.user_extent_crs = None

        flag = settings.value('inasafe/useThreadingFlag', False, type=bool)
        self.run_in_thread_flag = flag

        flag = settings.value('inasafe/setZoomToImpactFlag', True, type=bool)
        self.zoom_to_impact_flag = flag

        # whether exposure layer should be hidden after model completes
        flag = settings.value('inasafe/setHideExposureFlag', False, type=bool)
        self.hide_exposure_flag = flag

        # whether to 'hard clip' layers (e.g. cut buildings in half if they
        # lie partially in the AOI
        self.clip_hard = settings.value('inasafe/clip_hard', False, type=bool)

        # whether to show or not postprocessing generated layers
        self.show_intermediate_layers = settings.value(
            'inasafe/show_intermediate_layers', False, type=bool)

        # whether to show or not dev only options
        self.developer_mode = settings.value('inasafe/developer_mode',
                                             False,
                                             type=bool)

        # whether to show or not a custom Logo
        self.organisation_logo_path = settings.value(
            'inasafe/organisation_logo_path',
            default_organisation_logo_path(),
            type=str)
        flag = bool(
            settings.value('inasafe/showOrganisationLogoInDockFlag',
                           True,
                           type=bool))

    def show_busy(self):
        """Lock buttons and enable the busy cursor."""
        self.parent.pbnNext.setEnabled(False)
        self.parent.pbnBack.setEnabled(False)
        self.parent.pbnCancel.setEnabled(False)
        QtGui.qApp.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
        self.parent.repaint()
        QtGui.qApp.processEvents()

    def hide_busy(self):
        """Unlock buttons A helper function to indicate processing is done."""
        self.parent.pbnNext.setEnabled(True)
        self.parent.pbnBack.setEnabled(True)
        self.parent.pbnCancel.setEnabled(True)
        self.parent.repaint()
        QtGui.qApp.restoreOverrideCursor()

    def analysis_error(self, exception, message):
        """A helper to spawn an error and halt processing.

        An exception will be logged, busy status removed and a message
        displayed.

        .. note:: Copied from the dock

        :param message: an ErrorMessage to display
        :type message: ErrorMessage, Message

        :param exception: An exception that was raised
        :type exception: Exception
        """
        self.hide_busy()
        LOGGER.exception(message)
        message = get_error_message(exception, context=message)
        self.show_error_message(message)
        self.analysisDone.emit(False)

    def setup_and_run_analysis(self):
        """Setup and execute the analysis"""
        self.enable_signal_receiver()

        self.show_busy()
        self.init_analysis()
        try:
            self.analysis.setup_analysis()
        except InsufficientOverlapError as e:
            raise e

        self.extent.show_last_analysis_extent(self.analysis.clip_parameters[1])

        # Start the analysis
        self.analysis.run_analysis()

        self.disable_signal_receiver()

    def init_analysis(self):
        """Setup analysis to make it ready to work.

        .. note:: Copied or adapted from the dock
        """
        self.analysis = Analysis()
        # Layers
        self.analysis.hazard_layer = self.parent.hazard_layer
        self.analysis.exposure_layer = self.parent.exposure_layer
        self.analysis.aggregation_layer = self.parent.aggregation_layer
        # TODO test if the implement aggregation layer works!

        # noinspection PyTypeChecker
        self.analysis.hazard_keyword = self.keyword_io.read_keywords(
            self.parent.hazard_layer)
        self.analysis.exposure_keyword = self.keyword_io.read_keywords(
            self.parent.exposure_layer)
        # Need to check since aggregation layer is not mandatory
        if self.analysis.aggregation_layer:
            self.analysis.aggregation_keyword = self.keyword_io.read_keywords(
                self.parent.aggregation_layer)

        # Impact Function
        self.analysis.impact_function_id = self.parent.selected_function(
        )['id']
        self.analysis.impact_function_parameters = self.parent.if_params

        # Variables
        self.analysis.clip_hard = self.clip_hard
        self.analysis.show_intermediate_layers = self.show_intermediate_layers
        self.analysis.run_in_thread_flag = self.run_in_thread_flag
        self.analysis.map_canvas = self.iface.mapCanvas()

        # Extent
        if self.parent.rbExtentUser.isChecked():
            self.analysis.user_extent = self.extent.user_extent
        else:
            self.analysis.user_extent = None
        self.analysis.user_extent_crs = self.extent.user_extent_crs
        self.analysis.clip_to_viewport = self.parent.rbExtentScreen.isChecked()

    def completed(self):
        """Slot activated when the process is done.

        .. note:: Adapted from the dock
        """

        # Try to run completion code
        try:
            from datetime import datetime
            LOGGER.debug(datetime.now())
            LOGGER.debug('get engine impact layer')
            LOGGER.debug(self.analysis is None)
            engine_impact_layer = self.analysis.get_impact_layer()

            # Load impact layer into QGIS
            qgis_impact_layer = read_impact_layer(engine_impact_layer)

            report = self.show_results(qgis_impact_layer, engine_impact_layer)

        except Exception, e:  # pylint: disable=W0703
            # FIXME (Ole): This branch is not covered by the tests
            self.analysis_error(e, self.tr('Error loading impact layer.'))
        else: