예제 #1
0
    def __init__(self, app):
        """
        constructor
        - initialize UI elements
        - connect UI elements to callback            
        """
        super(WidgetResult, self).__init__()
        self.ui = Ui_widgetResult()
        self.ui.setupUi(self)

        # create canvas
        self.canvas = QgsMapCanvas(self.ui.widget_map)
        self.canvas.setGeometry(
            0,  # x
            self.ui.widget_map_menu_l.x() +
            self.ui.widget_map_menu_l.height(),  # y  
            self.ui.widget_map.width() - 2 * UI_PADDING,  # width
            self.ui.widget_map.width() - 2 * UI_PADDING  # height
        )

        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.mapRenderer().setProjectionsEnabled(True)
        self.canvas.mapRenderer().setDestinationCrs(
            QgsCoordinateReferenceSystem(
                4326, QgsCoordinateReferenceSystem.PostgisCrsId))
        self.canvas.zoomNextStatusChanged.connect(self.checkRendering)
        self.canvas.xyCoordinates.connect(self.currentLocation)
        self.registry = QgsMapLayerRegistry.instance()

        self.map_layers = [None] * len(self.LAYER_NAMES)
        self.map_layer_renderer = [None] * len(self.LAYER_NAMES)
        for idx, str_style in enumerate(self.LAYER_STYLES):
            rdoc = QDomDocument("renderer")
            rdoc.setContent(str_style)
            self.map_layer_renderer[idx] = QgsFeatureRendererV2.load(
                rdoc.firstChild().toElement())

        # populate export list
        self.ui.cb_export_format.clear()
        for export_format in self.EXPORT_FORMATS.keys():
            self.ui.cb_export_format.addItem(export_format)

        # style object required for QgsRendererV2PropertiesDialog
        self.style = QgsStyleV2()

        # create the map tools
        self.toolPan = QgsMapToolPan(self.canvas)
        self.toolZoomIn = QgsMapToolZoom(self.canvas, False)  # false = in
        self.toolZoomOut = QgsMapToolZoom(self.canvas, True)  # true = out
        self.toolInfo = QgsMapToolEmitPoint(self.canvas)
        self.toolInfo.canvasClicked.connect(self.showInfo)
        self.canvas.setMapTool(self.toolPan)

        # additional
        self.dlgResultDetail = DialogResult()
        self.dlgResultDetail.setModal(True)

        # set link to application main controller
        self.app = app

        # reset project
        self._project = None

        # default export setting
        self.export_format = ExportTypes.Shapefile

        # connect slots (ui event)
        self.ui.btn_zoom_full.clicked.connect(self.mapZoomFull)
        self.ui.btn_zoom_in.clicked.connect(self.mapZoomIn)
        self.ui.btn_zoom_out.clicked.connect(self.mapZoomOut)
        self.ui.btn_stop.clicked.connect(self.stopRendering)
        self.ui.btn_zoom_layer.clicked.connect(self.mapZoomLayer)
        self.ui.btn_pan.clicked.connect(self.mapPan)
        self.ui.btn_theme.clicked.connect(self.mapEditTheme)
        self.ui.btn_info.clicked.connect(self.mapIdentify)

        self.ui.btn_zoom_to_feature.clicked.connect(self.searchFeature)

        self.ui.cb_export_format.currentIndexChanged[str].connect(
            self.exportFormatChanged)
        self.ui.btn_export.clicked.connect(self.exportData)
        self.ui.btn_export_select_path.clicked.connect(self.selectExportFile)
예제 #2
0
    def __init__(self, app):
        """
        constructor
        - initialize UI elements
        - connect UI elements to callback            
        """
        super(WidgetResult, self).__init__()
        self.ui = Ui_widgetResult()
        self.ui.setupUi(self)
                
        # create canvas
        self.canvas = QgsMapCanvas(self.ui.widget_map)
        self.canvas.setGeometry(
            0,                                                                # x
            self.ui.widget_map_menu_l.x()+self.ui.widget_map_menu_l.height(), # y  
            self.ui.widget_map.width() - 2*UI_PADDING,  # width
            self.ui.widget_map.width() - 2*UI_PADDING   # height
            )
        
        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.mapRenderer().setProjectionsEnabled(True)
        self.canvas.mapRenderer().setDestinationCrs(QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.PostgisCrsId))
        self.canvas.zoomNextStatusChanged.connect(self.checkRendering)
        self.canvas.xyCoordinates.connect(self.currentLocation)
        self.registry = QgsMapLayerRegistry.instance()
        
        self.map_layers = [None] * len(self.LAYER_NAMES)
        self.map_layer_renderer = [None] * len(self.LAYER_NAMES)
        for idx, str_style in enumerate(self.LAYER_STYLES):
            rdoc = QDomDocument("renderer")
            rdoc.setContent(str_style)        
            self.map_layer_renderer[idx] = QgsFeatureRendererV2.load(rdoc.firstChild().toElement())

        # populate export list
        self.ui.cb_export_format.clear()
        for export_format in self.EXPORT_FORMATS.keys():
            self.ui.cb_export_format.addItem(export_format)
                    
        # style object required for QgsRendererV2PropertiesDialog
        self.style = QgsStyleV2()
        
        # create the map tools
        self.toolPan = QgsMapToolPan(self.canvas)
        self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
        self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
        self.toolInfo = QgsMapToolEmitPoint(self.canvas)
        self.toolInfo.canvasClicked.connect(self.showInfo)
        self.canvas.setMapTool(self.toolPan)
        
        # additional         
        self.dlgResultDetail = DialogResult()
        self.dlgResultDetail.setModal(True)

        # set link to application main controller
        self.app = app
        
        # reset project
        self._project = None
        
        # default export setting
        self.export_format = ExportTypes.Shapefile
        
        # connect slots (ui event)
        self.ui.btn_zoom_full.clicked.connect(self.mapZoomFull)
        self.ui.btn_zoom_in.clicked.connect(self.mapZoomIn)
        self.ui.btn_zoom_out.clicked.connect(self.mapZoomOut)
        self.ui.btn_stop.clicked.connect(self.stopRendering)
        self.ui.btn_zoom_layer.clicked.connect(self.mapZoomLayer)        
        self.ui.btn_pan.clicked.connect(self.mapPan)
        self.ui.btn_theme.clicked.connect(self.mapEditTheme)
        self.ui.btn_info.clicked.connect(self.mapIdentify)
        
        self.ui.btn_zoom_to_feature.clicked.connect(self.searchFeature)
        
        self.ui.cb_export_format.currentIndexChanged[str].connect(self.exportFormatChanged)
        self.ui.btn_export.clicked.connect(self.exportData)
        self.ui.btn_export_select_path.clicked.connect(self.selectExportFile)
예제 #3
0
class WidgetResult(Ui_widgetResult, QWidget):
    """
    Widget (Panel) for result review
    """
    ''' buffer around clicked point for point in polygon query '''
    SEARCH_BUFFER = 20.0
    ''' supported export formats '''
    EXPORT_FORMATS = {
        get_ui_string("app.extension.shapefile"):
        ExportTypes.Shapefile,
        #get_ui_string("app.extension.kml"):ExportTypes.KML,
        #get_ui_string("app.extension.nrml"):ExportTypes.NRML,
        get_ui_string("app.extension.csv"):
        ExportTypes.CSV,
    }
    ''' enumeration of Layer to be previewed '''
    EXPOSURE, SURVEY, POP_GRID, FOOTPRINT, ZONES = range(5)
    ''' name for Layer to be previewed '''
    LAYER_NAMES = [
        get_ui_string("widget.result.layer.exposure"),
        get_ui_string("widget.result.layer.survey"),
        get_ui_string("widget.result.layer.popgrid"),
        get_ui_string("widget.result.layer.footprint"),
        get_ui_string("widget.result.layer.zones"),
    ]
    LAYER_STYLES = [
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="fill" name="0"><layer pass="******" class="SimpleLine" locked="0"><prop k="capstyle" v="square"/><prop k="color" v="0,0,0,255"/><prop k="customdash" v="5;2"/><prop k="joinstyle" v="bevel"/><prop k="offset" v="0"/><prop k="penstyle" v="solid"/><prop k="use_custom_dash" v="0"/><prop k="width" v="0.26"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="marker" name="0"><layer pass="******" class="SimpleMarker" locked="0"><prop k="angle" v="0"/><prop k="color" v="0,0,255,255"/><prop k="color_border" v="0,0,255,255"/><prop k="name" v="circle"/><prop k="offset" v="0,0"/><prop k="size" v="2"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="marker" name="0"><layer pass="******" class="SimpleMarker" locked="0"><prop k="angle" v="0"/><prop k="color" v="0,255,0,255"/><prop k="color_border" v="0,255,0,255"/><prop k="name" v="rectangle"/><prop k="offset" v="0,0"/><prop k="size" v="4"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="fill" name="0"><layer pass="******" class="SimpleFill" locked="0"><prop k="color" v="170,250,170,255"/><prop k="color_border" v="0,0,0,255"/><prop k="offset" v="0,0"/><prop k="style" v="solid"/><prop k="style_border" v="solid"/><prop k="width_border" v="0.26"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="fill" name="0"><layer pass="******" class="SimpleFill" locked="0"><prop k="color" v="211,211,158,200"/><prop k="color_border" v="0,0,0,255"/><prop k="offset" v="0,0"/><prop k="style" v="solid"/><prop k="style_border" v="solid"/><prop k="width_border" v="0.26"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
    ]

    # constructor / destructor
    ###############################

    def __init__(self, app):
        """
        constructor
        - initialize UI elements
        - connect UI elements to callback            
        """
        super(WidgetResult, self).__init__()
        self.ui = Ui_widgetResult()
        self.ui.setupUi(self)

        # create canvas
        self.canvas = QgsMapCanvas(self.ui.widget_map)
        self.canvas.setGeometry(
            0,  # x
            self.ui.widget_map_menu_l.x() +
            self.ui.widget_map_menu_l.height(),  # y  
            self.ui.widget_map.width() - 2 * UI_PADDING,  # width
            self.ui.widget_map.width() - 2 * UI_PADDING  # height
        )

        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.mapRenderer().setProjectionsEnabled(True)
        self.canvas.mapRenderer().setDestinationCrs(
            QgsCoordinateReferenceSystem(
                4326, QgsCoordinateReferenceSystem.PostgisCrsId))
        self.canvas.zoomNextStatusChanged.connect(self.checkRendering)
        self.canvas.xyCoordinates.connect(self.currentLocation)
        self.registry = QgsMapLayerRegistry.instance()

        self.map_layers = [None] * len(self.LAYER_NAMES)
        self.map_layer_renderer = [None] * len(self.LAYER_NAMES)
        for idx, str_style in enumerate(self.LAYER_STYLES):
            rdoc = QDomDocument("renderer")
            rdoc.setContent(str_style)
            self.map_layer_renderer[idx] = QgsFeatureRendererV2.load(
                rdoc.firstChild().toElement())

        # populate export list
        self.ui.cb_export_format.clear()
        for export_format in self.EXPORT_FORMATS.keys():
            self.ui.cb_export_format.addItem(export_format)

        # style object required for QgsRendererV2PropertiesDialog
        self.style = QgsStyleV2()

        # create the map tools
        self.toolPan = QgsMapToolPan(self.canvas)
        self.toolZoomIn = QgsMapToolZoom(self.canvas, False)  # false = in
        self.toolZoomOut = QgsMapToolZoom(self.canvas, True)  # true = out
        self.toolInfo = QgsMapToolEmitPoint(self.canvas)
        self.toolInfo.canvasClicked.connect(self.showInfo)
        self.canvas.setMapTool(self.toolPan)

        # additional
        self.dlgResultDetail = DialogResult()
        self.dlgResultDetail.setModal(True)

        # set link to application main controller
        self.app = app

        # reset project
        self._project = None

        # default export setting
        self.export_format = ExportTypes.Shapefile

        # connect slots (ui event)
        self.ui.btn_zoom_full.clicked.connect(self.mapZoomFull)
        self.ui.btn_zoom_in.clicked.connect(self.mapZoomIn)
        self.ui.btn_zoom_out.clicked.connect(self.mapZoomOut)
        self.ui.btn_stop.clicked.connect(self.stopRendering)
        self.ui.btn_zoom_layer.clicked.connect(self.mapZoomLayer)
        self.ui.btn_pan.clicked.connect(self.mapPan)
        self.ui.btn_theme.clicked.connect(self.mapEditTheme)
        self.ui.btn_info.clicked.connect(self.mapIdentify)

        self.ui.btn_zoom_to_feature.clicked.connect(self.searchFeature)

        self.ui.cb_export_format.currentIndexChanged[str].connect(
            self.exportFormatChanged)
        self.ui.btn_export.clicked.connect(self.exportData)
        self.ui.btn_export_select_path.clicked.connect(self.selectExportFile)

    @pyqtSlot(QgsPoint)
    def currentLocation(self, point):
        self.app.updateMapLocation(point.x(), point.y())
        #self.canvas.mouseMoveEvent(mouseEvent)

    # UI event handling calls (Qt slots)
    ###############################
    @pyqtSlot(QObject)
    def resizeEvent(self, event):
        """ handle window resize """
        # find left coordinate for right side panels
        x_right_side = self.width() - self.ui.widget_export.width(
        ) - UI_PADDING
        # adjust right side panels
        self.ui.widget_export.move(x_right_side, self.ui.widget_map.y() + 30)
        self.ui.widget_dq_test.move(
            x_right_side,
            self.ui.widget_export.y() + self.ui.widget_export.height() +
            UI_PADDING)
        # adjust map panel (left side)
        self.ui.widget_map.resize(x_right_side - UI_PADDING,
                                  self.height() - 2 * UI_PADDING)
        # adjust map canvas within the map panel
        map_top = self.ui.widget_map_menu_l.x(
        ) + self.ui.widget_map_menu_l.height() + UI_PADDING
        self.canvas.resize(
            x_right_side - UI_PADDING,  # same width as self.ui.widget_map
            self.ui.widget_map.height() - map_top - 2 * UI_PADDING)  # height
        # adjust map menu
        self.ui.widget_map_menu_r.move(
            self.ui.widget_map.width() -
            self.ui.widget_map_menu_r.width(),  # right align with map panel 
            0)
        # logo
        self.ui.lb_gem_logo.move(self.width() - self.ui.lb_gem_logo.width(),
                                 self.ui.lb_gem_logo.y())

    @logUICall
    @pyqtSlot()
    def mapPan(self):
        """ event handler for btn_pan - pan map """
        self.canvas.unsetMapTool(self.toolInfo)
        self.canvas.setMapTool(self.toolPan)

    @logUICall
    @pyqtSlot()
    def mapZoomIn(self):
        """ event handler for btn_zoom_in - zoom in on map """
        self.canvas.unsetMapTool(self.toolInfo)
        self.canvas.setMapTool(self.toolZoomIn)

    @logUICall
    @pyqtSlot()
    def mapZoomOut(self):
        """ event handler for btn_zoom_out - zoom out on map """
        self.canvas.unsetMapTool(self.toolInfo)
        self.canvas.setMapTool(self.toolZoomOut)

    @logUICall
    @pyqtSlot()
    def mapZoomFull(self):
        """ event handler for btn_zoom_full - zoom to full map """
        self.canvas.zoomToFullExtent()

    def checkRendering(self, changed):
        self.canvas.setRenderFlag(True)

    @logUICall
    @pyqtSlot()
    def stopRendering(self):
        self.canvas.setRenderFlag(False)

    @logUICall
    @pyqtSlot()
    def mapZoomLayer(self):
        self.canvas.unsetMapTool(self.toolInfo)
        cur_layer_name = self.ui.cb_layer_selector.currentText()
        if cur_layer_name.isEmpty():
            return
        self.zoomToLayer(
            self.map_layers[self.LAYER_NAMES.index(cur_layer_name)])

    @logUICall
    @pyqtSlot()
    def mapEditTheme(self):
        """ event handler for btn_edit - identify item on map """
        cur_layer_name = self.ui.cb_layer_selector.currentText()
        if cur_layer_name.isEmpty():
            return
        try:
            cur_layer_idx = self.LAYER_NAMES.index(cur_layer_name)

            # build layer render property Dialog for selected layer
            dlg_render = QDialog()
            dlg_render.setWindowTitle(
                get_ui_string('widget.result.renderer.settings'))
            dlg_render.setModal(True)
            dlg_render.setFixedSize(530, 370)
            dlg_render.renderer = QgsRendererV2PropertiesDialog(
                self.map_layers[cur_layer_idx], self.style, True)
            dlg_render.renderer.setParent(dlg_render)
            dlg_render.renderer.setGeometry(QRect(10, 10, 510, 325))
            dlg_render.buttonBox = QDialogButtonBox(dlg_render)
            dlg_render.buttonBox.setGeometry(QRect(10, 335, 510, 25))
            dlg_render.buttonBox.setStandardButtons(QDialogButtonBox.Cancel
                                                    | QDialogButtonBox.Ok)
            dlg_render.buttonBox.accepted.connect(dlg_render.accept)
            dlg_render.buttonBox.accepted.connect(dlg_render.renderer.onOK)
            dlg_render.buttonBox.rejected.connect(dlg_render.reject)
            dlg_render.setVisible(True)

            # get user input and update renderer
            answer = dlg_render.exec_()
            if answer == QDialog.Accepted:
                self.map_layer_renderer[cur_layer_idx] = None
                self.map_layer_renderer[cur_layer_idx] = self.map_layers[
                    cur_layer_idx].rendererV2().clone()
                self.canvas.refresh()
            dlg_render.destroy()
            del dlg_render
        except Exception as err:
            # thematic is not-critical, allow continue on exception
            logUICall.log(str(err), logUICall.WARNING)

    @logUICall
    @pyqtSlot()
    def searchFeature(self):
        cur_layer_name = self.ui.cb_layer_selector.currentText()
        if cur_layer_name.isEmpty():
            return
        try:
            cur_layer_idx = self.LAYER_NAMES.index(cur_layer_name)
            layer = self.map_layers[cur_layer_idx]
            fields = []
            for fidx in layer.dataProvider().fields():
                fields.append(layer.dataProvider().fields()[fidx].name())
            dlg_search = DialogSearchFeature(fields)
            answer = dlg_search.exec_()
            if answer == QDialog.Accepted:
                extent = self.findFeatureExtentByAttribute(
                    layer, dlg_search.attribute, dlg_search.value)
                if extent is not None:
                    self.zoomToExtent(extent)
                else:
                    logUICall.log(get_ui_string("widget.result.info.notfound"),
                                  logUICall.WARNING)
            dlg_search.destroy()
        except Exception as err:
            # thematic is not-critical, allow continue on exception
            logUICall.log(str(err), logUICall.WARNING)

    @logUICall
    @pyqtSlot()
    def mapIdentify(self):
        """ 
        event handler for btn_info 
        This only enables map querying, method connected to canvasClicked signal does
        the actual point-polygon query     
        """
        self.canvas.setMapTool(self.toolInfo)

    @logUICall
    @pyqtSlot()
    def selectExportFile(self):
        """
        event handler for btn_export_select_path 
        - open save file dialog box to select file name for export 
        """
        filename = QFileDialog.getSaveFileName(
            self, get_ui_string("widget.result.export.file.open"), ".",
            self.ui.cb_export_format.currentText())
        if not filename.isNull():
            self.ui.txt_export_select_path.setText(filename)

    @logUICall
    @pyqtSlot(str)
    def exportFormatChanged(self, selected_val):
        """
        event handler for cb_export_format 
        - update selected file after format change
        """
        self.ui.txt_export_select_path.setText("")
        self.export_format = self.EXPORT_FORMATS[str(selected_val)]

    @logUICall
    @pyqtSlot()
    def exportData(self):
        """ 
        event handler for btn_export
        - do export data 
        """
        export_path = str(self.ui.txt_export_select_path.text())
        if export_path == "":
            logUICall.log(get_ui_string("app.error.path.is.null"),
                          logUICall.WARNING)
            return
        self.app.exportResults(self.export_format, export_path)

    @logUICall
    @pyqtSlot(QPoint, QObject)
    def showInfo(self, point, mouseButton):
        """
        event handler for toolInfo
        @see QGIS tutorial for detail
        point-polygon search on currently selected layer  
        """
        cur_layer_name = self.ui.cb_layer_selector.currentText()
        if cur_layer_name.isEmpty():
            return
        try:
            cur_layer_idx = self.LAYER_NAMES.index(cur_layer_name)
            cur_layer = self.map_layers[cur_layer_idx]

            # if layer is not in same projection as map canvas
            # need to project query point
            if cur_layer.crs() != self.canvas.mapRenderer().destinationCrs():
                transform = QgsCoordinateTransform(
                    self.canvas.mapRenderer().destinationCrs(),
                    cur_layer.crs())
                point = transform.transform(point)

            # do query
            provider = cur_layer.dataProvider()
            provider.rewind()
            feature = QgsFeature()
            colonIndexes = provider.attributeIndexes()

            # search using point as center of rectangle polygon
            search_buffer_x = self.canvas.extent().width(
            ) * self.SEARCH_BUFFER / self.canvas.width()
            search_buffer_y = self.canvas.extent().height(
            ) * self.SEARCH_BUFFER / self.canvas.height()
            provider.select(
                colonIndexes,
                QgsRectangle(point.x() - search_buffer_x,
                             point.y() - search_buffer_y,
                             point.x() + search_buffer_x,
                             point.y() + search_buffer_y), True)
            # get selected and display in result detail dialog box
            selected = []
            while provider.nextFeature(feature):
                # for polygons, only show geometry containing query point
                if cur_layer.geometryType() == QGis.Polygon:
                    if feature.geometry(
                    ) is not None and not feature.geometry().contains(point):
                        continue
                selected.append(feature.attributeMap())

            if len(selected) > 0:
                # display result if exists
                if cur_layer_idx == self.EXPOSURE:
                    self.dlgResultDetail.showExposureData(
                        provider.fields(), selected)
                else:
                    self.dlgResultDetail.showInfoData(provider.fields(),
                                                      selected)
                self.dlgResultDetail.exec_()
            else:
                logUICall.log(get_ui_string("widget.result.info.notfound"),
                              logUICall.WARNING)
        except Exception as err:
            # point-in-polygon search is not critical, continue on error
            logUICall.log(str(err), logUICall.WARNING)

    # public methods
    ###############################
    def set_project(self, project):
        ''' set project to preview. force refresh view on set'''
        self._project = project
        if project is None:
            return
        self.refreshView()
        self.canvas.zoomToFullExtent()
        logUICall.log("Project preview initialized sucessfully",
                      logUICall.INFO)

    def get_project(self):
        return self._project

    # property access to project
    project = property(get_project, set_project)

    def refreshView(self):
        ''' reload all QGIS layers in currently defined project '''
        if self._project is None:
            return

        # display layers if exists
        if self._project.fp_file is not None and exists(self._project.fp_file):
            if self.map_layers[self.FOOTPRINT] is None or self.map_layers[
                    self.FOOTPRINT].source() != self._project.fp_file:
                self.showDataLayer(
                    self.FOOTPRINT,
                    load_shapefile(self._project.fp_file, 'footprint'))
        else:
            self.removeDataLayer(self.FOOTPRINT)

        if self._project.zone_file is not None and exists(
                self._project.zone_file):
            if self.map_layers[self.ZONES] is None or self.map_layers[
                    self.ZONES].source() != self._project.zone_file:
                self.showDataLayer(
                    self.ZONES, load_shapefile(self._project.zone_file,
                                               'zones'))
        else:
            self.removeDataLayer(self.ZONES)

        if self._project.survey_file is not None and exists(
                self._project.survey_file):
            if getattr(self._project, 'survey', None) is None:
                self._project.load_survey()
                self.showDataLayer(self.SURVEY, self._project.survey)
        else:
            self.removeDataLayer(self.SURVEY)

        if self._project.popgrid_file is not None and exists(
                self._project.popgrid_file):
            if getattr(self._project, 'popgrid', None) is None:
                self.showDataLayer(
                    self.POP_GRID,
                    load_shapefile(self._project.popgrid_file, 'popgrid'))
        else:
            self.removeDataLayer(self.POP_GRID)

        # set export options
        for idx, export_format in enumerate(self.EXPORT_FORMATS.values()):
            if export_format == self._project.export_type:
                self.ui.cb_export_format.setCurrentIndex(idx)
        self.ui.txt_export_select_path.setText(self._project.export_path)

        # refreshResult contains refresh call to update all layers currently loaded
        self.refreshResult()

    def refreshResult(self):
        ''' reload result QGIS layer and data quality reports in currently defined project '''
        exposure = getattr(self._project, 'exposure', None)
        if exposure is not None:
            self.showDataLayer(self.EXPOSURE, exposure)
            has_result = True
        else:
            self.removeDataLayer(self.EXPOSURE)
            has_result = False

        if has_result:
            # build quality report
            report_lines = []
            if self._project.operator_options.has_key("proc.extrapolation"):
                proc_option = self._project.operator_options[
                    "proc.extrapolation"]
                if proc_option == ExtrapolateOptions.RandomWalk:
                    proc_method = get_ui_string(
                        "widget.result.dq.method",
                        get_ui_string("dlg.options.ep.random"))
                elif proc_option == ExtrapolateOptions.Fraction:
                    proc_method = get_ui_string(
                        "widget.result.dq.method",
                        get_ui_string("dlg.options.ep.fraction"))
                elif proc_option == ExtrapolateOptions.FractionRounded:
                    proc_method = get_ui_string(
                        "widget.result.dq.method",
                        get_ui_string("dlg.options.ep.fraction.rounded"))
            else:
                proc_method = get_ui_string(
                    "widget.result.dq.method",
                    get_ui_string("dlg.options.ep.random"))
            report_lines.append(proc_method)
            report_lines.append('')

            # total tests
            report_lines.append(
                get_ui_string('widget.result.dq.total_tests',
                              len(self._project.quality_reports.keys())))
            report_lines.append('')

            # detail for each test
            for key, report in self._project.quality_reports.iteritems():
                report_lines.append(
                    get_ui_string('widget.result.dq.tests.%s' % key))
                for title, value in report.iteritems():
                    report_lines.append(
                        get_ui_string(
                            'widget.result.dq.tests.%s.%s' % (key, title),
                            value))
                report_lines.append('')
            self.ui.txt_dq_test_details.setText("\n".join(report_lines))

        self.ui.btn_export.setEnabled(has_result)
        self.ui.widget_dq_test.setVisible(has_result)
        self.ui.txt_export_select_path.setEnabled(has_result)
        self.ui.btn_export_select_path.setEnabled(has_result)
        self.ui.cb_export_format.setEnabled(has_result)

        # this call refresh all layers currently loaded
        self.refreshLayers()

    @logUICall
    def closeResult(self):
        ''' remove from map result QGIS layer and reset quality report display '''
        self.canvas.setLayerSet(
            []
        )  # call necessary to remove all layers to avoid disconnect errors
        self.removeDataLayer(self.EXPOSURE)
        self.refreshLayers()
        self.ui.txt_dq_test_details.setText("")

    @logUICall
    def closeAll(self):
        ''' remove from map all QGIS layer in currently defined project '''
        self.ui.cb_layer_selector.clear()
        try:
            self.canvas.setLayerSet(
                []
            )  # call necessary to remove all layers to avoid disconnect errors
            for i in range(5):
                self.removeDataLayer(i)
            self.ui.txt_dq_test_details.setText("")
            self.refreshLayers()
        except:
            pass  # exception will is thrown when registry is empty

    # internal helper methods
    ###############################
    def showDataLayer(self, index, layer):
        """ display given QGIS layer on map """
        try:
            # add to QGIS registry and refresh view
            if self.map_layers[index] is not None:
                self.removeDataLayer(index)
            self.map_layers[index] = layer
            self.registry.addMapLayer(layer)
            layer.setRendererV2(self.map_layer_renderer[index])
        except:
            pass

    def removeDataLayer(self, index):
        """ remove from map the layer identified with index """
        layer = self.map_layers[index]
        self.map_layers[index] = None
        if layer is not None:
            try:
                self.registry.removeMapLayer(layer.getLayerID(), False)
                del layer
            except:
                pass  # do nothing if it fails. probably already deleted

    def findFeatureExtentByAttribute(self, layer, field, value):
        """ 
        find extent of all objects in QGIS layer matching condition "field=value"         
        """
        fidx = layer_field_index(layer, field)
        if fidx == -1:
            return None
        xmin, xmax, ymin, ymax = 180, -180, 90, -90
        extent = QgsRectangle(xmin, ymin, xmax, ymax)
        need_transform = layer.crs() != self.canvas.mapRenderer(
        ).destinationCrs()
        if need_transform:
            transform = QgsCoordinateTransform(
                layer.crs(),
                self.canvas.mapRenderer().destinationCrs())
        for feature in layer_features(layer):
            if str(value) == feature.attributeMap()[fidx].toString():
                f_extent = feature.geometry().boundingBox()
                if need_transform:
                    f_extent = transform.transform(f_extent)
                xmin = min(f_extent.xMinimum(), xmin)
                xmax = max(f_extent.xMaximum(), xmax)
                ymin = min(f_extent.yMinimum(), ymin)
                ymax = max(f_extent.yMaximum(), ymax)
        extent.set(xmin, ymin, xmax, ymax)
        return extent

    def zoomToLayer(self, layer):
        """ zoom canvas to extent of given layer """
        try:
            lyr_extent = layer.extent()
            if layer.crs() != self.canvas.mapRenderer().destinationCrs():
                transform = QgsCoordinateTransform(
                    layer.crs(),
                    self.canvas.mapRenderer().destinationCrs())
                lyr_extent = transform.transform(lyr_extent)
            self.zoomToExtent(lyr_extent)
        except:
            pass

    def zoomToExtent(self, extent):
        """ zoom canvas to given extent """
        try:
            self.canvas.setExtent(extent)
            self.canvas.zoomByFactor(1.1)
        except:
            self.mapZoomFull()

    def refreshLayers(self):
        """ refresh all layers in canvas """
        # add each layer according to order
        layerSet = []
        self.ui.cb_layer_selector.clear()
        for idx, lyr in enumerate(self.map_layers):
            if lyr is not None:
                layerSet.append(QgsMapCanvasLayer(lyr))
                self.ui.cb_layer_selector.addItem(self.LAYER_NAMES[idx])
        if len(layerSet) > 0:
            self.canvas.setLayerSet(layerSet)
예제 #4
0
class WidgetResult(Ui_widgetResult, QWidget):
    """
    Widget (Panel) for result review
    """
    
    ''' buffer around clicked point for point in polygon query ''' 
    SEARCH_BUFFER = 20.0
    ''' supported export formats '''
    EXPORT_FORMATS = {
        get_ui_string("app.extension.shapefile"):ExportTypes.Shapefile,
        #get_ui_string("app.extension.kml"):ExportTypes.KML,
        #get_ui_string("app.extension.nrml"):ExportTypes.NRML,
        get_ui_string("app.extension.csv"):ExportTypes.CSV,
    };
    ''' enumeration of Layer to be previewed '''
    EXPOSURE, SURVEY, POP_GRID, FOOTPRINT, ZONES = range(5);
    ''' name for Layer to be previewed '''
    LAYER_NAMES = [
        get_ui_string("widget.result.layer.exposure"),        
        get_ui_string("widget.result.layer.survey"),
        get_ui_string("widget.result.layer.popgrid"),
        get_ui_string("widget.result.layer.footprint"),        
        get_ui_string("widget.result.layer.zones"),
    ];    
    LAYER_STYLES = [
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="fill" name="0"><layer pass="******" class="SimpleLine" locked="0"><prop k="capstyle" v="square"/><prop k="color" v="0,0,0,255"/><prop k="customdash" v="5;2"/><prop k="joinstyle" v="bevel"/><prop k="offset" v="0"/><prop k="penstyle" v="solid"/><prop k="use_custom_dash" v="0"/><prop k="width" v="0.26"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="marker" name="0"><layer pass="******" class="SimpleMarker" locked="0"><prop k="angle" v="0"/><prop k="color" v="0,0,255,255"/><prop k="color_border" v="0,0,255,255"/><prop k="name" v="circle"/><prop k="offset" v="0,0"/><prop k="size" v="2"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="marker" name="0"><layer pass="******" class="SimpleMarker" locked="0"><prop k="angle" v="0"/><prop k="color" v="0,255,0,255"/><prop k="color_border" v="0,255,0,255"/><prop k="name" v="rectangle"/><prop k="offset" v="0,0"/><prop k="size" v="4"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="fill" name="0"><layer pass="******" class="SimpleFill" locked="0"><prop k="color" v="170,250,170,255"/><prop k="color_border" v="0,0,0,255"/><prop k="offset" v="0,0"/><prop k="style" v="solid"/><prop k="style_border" v="solid"/><prop k="width_border" v="0.26"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
        '<!DOCTYPE renderer><renderer-v2 symbollevels="0" type="singleSymbol"><symbols><symbol outputUnit="MM" alpha="1" type="fill" name="0"><layer pass="******" class="SimpleFill" locked="0"><prop k="color" v="211,211,158,200"/><prop k="color_border" v="0,0,0,255"/><prop k="offset" v="0,0"/><prop k="style" v="solid"/><prop k="style_border" v="solid"/><prop k="width_border" v="0.26"/></layer></symbol></symbols><rotation field=""/><sizescale field=""/></renderer-v2>',
    ]
    
    # constructor / destructor
    ###############################
    
    def __init__(self, app):
        """
        constructor
        - initialize UI elements
        - connect UI elements to callback            
        """
        super(WidgetResult, self).__init__()
        self.ui = Ui_widgetResult()
        self.ui.setupUi(self)
                
        # create canvas
        self.canvas = QgsMapCanvas(self.ui.widget_map)
        self.canvas.setGeometry(
            0,                                                                # x
            self.ui.widget_map_menu_l.x()+self.ui.widget_map_menu_l.height(), # y  
            self.ui.widget_map.width() - 2*UI_PADDING,  # width
            self.ui.widget_map.width() - 2*UI_PADDING   # height
            )
        
        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.mapRenderer().setProjectionsEnabled(True)
        self.canvas.mapRenderer().setDestinationCrs(QgsCoordinateReferenceSystem(4326, QgsCoordinateReferenceSystem.PostgisCrsId))
        self.canvas.zoomNextStatusChanged.connect(self.checkRendering)
        self.canvas.xyCoordinates.connect(self.currentLocation)
        self.registry = QgsMapLayerRegistry.instance()
        
        self.map_layers = [None] * len(self.LAYER_NAMES)
        self.map_layer_renderer = [None] * len(self.LAYER_NAMES)
        for idx, str_style in enumerate(self.LAYER_STYLES):
            rdoc = QDomDocument("renderer")
            rdoc.setContent(str_style)        
            self.map_layer_renderer[idx] = QgsFeatureRendererV2.load(rdoc.firstChild().toElement())

        # populate export list
        self.ui.cb_export_format.clear()
        for export_format in self.EXPORT_FORMATS.keys():
            self.ui.cb_export_format.addItem(export_format)
                    
        # style object required for QgsRendererV2PropertiesDialog
        self.style = QgsStyleV2()
        
        # create the map tools
        self.toolPan = QgsMapToolPan(self.canvas)
        self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
        self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
        self.toolInfo = QgsMapToolEmitPoint(self.canvas)
        self.toolInfo.canvasClicked.connect(self.showInfo)
        self.canvas.setMapTool(self.toolPan)
        
        # additional         
        self.dlgResultDetail = DialogResult()
        self.dlgResultDetail.setModal(True)

        # set link to application main controller
        self.app = app
        
        # reset project
        self._project = None
        
        # default export setting
        self.export_format = ExportTypes.Shapefile
        
        # connect slots (ui event)
        self.ui.btn_zoom_full.clicked.connect(self.mapZoomFull)
        self.ui.btn_zoom_in.clicked.connect(self.mapZoomIn)
        self.ui.btn_zoom_out.clicked.connect(self.mapZoomOut)
        self.ui.btn_stop.clicked.connect(self.stopRendering)
        self.ui.btn_zoom_layer.clicked.connect(self.mapZoomLayer)        
        self.ui.btn_pan.clicked.connect(self.mapPan)
        self.ui.btn_theme.clicked.connect(self.mapEditTheme)
        self.ui.btn_info.clicked.connect(self.mapIdentify)
        
        self.ui.btn_zoom_to_feature.clicked.connect(self.searchFeature)
        
        self.ui.cb_export_format.currentIndexChanged[str].connect(self.exportFormatChanged)
        self.ui.btn_export.clicked.connect(self.exportData)
        self.ui.btn_export_select_path.clicked.connect(self.selectExportFile)

    @pyqtSlot(QgsPoint)
    def currentLocation(self, point):
        self.app.updateMapLocation(point.x(),point.y())
        #self.canvas.mouseMoveEvent(mouseEvent)
        

    # UI event handling calls (Qt slots)
    ###############################
    @pyqtSlot(QObject)
    def resizeEvent(self, event):
        """ handle window resize """ 
        # find left coordinate for right side panels
        x_right_side = self.width()-self.ui.widget_export.width()-UI_PADDING
        # adjust right side panels
        self.ui.widget_export.move(x_right_side, self.ui.widget_map.y()+30)
        self.ui.widget_dq_test.move(x_right_side, self.ui.widget_export.y()+self.ui.widget_export.height()+UI_PADDING)
        # adjust map panel (left side)        
        self.ui.widget_map.resize(x_right_side-UI_PADDING, self.height()-2*UI_PADDING)
        # adjust map canvas within the map panel        
        map_top = self.ui.widget_map_menu_l.x()+self.ui.widget_map_menu_l.height()+UI_PADDING        
        self.canvas.resize(
            x_right_side-UI_PADDING,                            # same width as self.ui.widget_map
            self.ui.widget_map.height()-map_top-2*UI_PADDING)   # height        
        # adjust map menu
        self.ui.widget_map_menu_r.move(
            self.ui.widget_map.width()-self.ui.widget_map_menu_r.width(),   # right align with map panel 
            0)
        # logo
        self.ui.lb_gem_logo.move(self.width()-self.ui.lb_gem_logo.width(), self.ui.lb_gem_logo.y())

    @logUICall
    @pyqtSlot()
    def mapPan(self):
        """ event handler for btn_pan - pan map """
        self.canvas.unsetMapTool(self.toolInfo)
        self.canvas.setMapTool(self.toolPan)

    @logUICall
    @pyqtSlot()
    def mapZoomIn(self):
        """ event handler for btn_zoom_in - zoom in on map """
        self.canvas.unsetMapTool(self.toolInfo)
        self.canvas.setMapTool(self.toolZoomIn)

    @logUICall
    @pyqtSlot()
    def mapZoomOut(self):
        """ event handler for btn_zoom_out - zoom out on map """
        self.canvas.unsetMapTool(self.toolInfo)
        self.canvas.setMapTool(self.toolZoomOut)

    @logUICall
    @pyqtSlot()
    def mapZoomFull(self):
        """ event handler for btn_zoom_full - zoom to full map """
        self.canvas.zoomToFullExtent()

    def checkRendering(self, changed):
        self.canvas.setRenderFlag(True)
        
    @logUICall
    @pyqtSlot()
    def stopRendering(self):        
        self.canvas.setRenderFlag(False)

    @logUICall
    @pyqtSlot()
    def mapZoomLayer(self):
        self.canvas.unsetMapTool(self.toolInfo)
        cur_layer_name = self.ui.cb_layer_selector.currentText()
        if cur_layer_name.isEmpty():
            return
        self.zoomToLayer(self.map_layers[self.LAYER_NAMES.index(cur_layer_name)])
        
    @logUICall
    @pyqtSlot()
    def mapEditTheme(self):
        """ event handler for btn_edit - identify item on map """
        cur_layer_name = self.ui.cb_layer_selector.currentText()
        if cur_layer_name.isEmpty():
            return
        try:
            cur_layer_idx = self.LAYER_NAMES.index(cur_layer_name)
            
            # build layer render property Dialog for selected layer  
            dlg_render = QDialog()
            dlg_render.setWindowTitle(get_ui_string('widget.result.renderer.settings'))
            dlg_render.setModal(True)
            dlg_render.setFixedSize(530, 370)
            dlg_render.renderer = QgsRendererV2PropertiesDialog(self.map_layers[cur_layer_idx], self.style, True)
            dlg_render.renderer.setParent(dlg_render)        
            dlg_render.renderer.setGeometry(QRect(10, 10, 510, 325))
            dlg_render.buttonBox = QDialogButtonBox(dlg_render)
            dlg_render.buttonBox.setGeometry(QRect(10, 335, 510, 25))
            dlg_render.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
            dlg_render.buttonBox.accepted.connect(dlg_render.accept)
            dlg_render.buttonBox.accepted.connect(dlg_render.renderer.onOK)
            dlg_render.buttonBox.rejected.connect(dlg_render.reject)
            dlg_render.setVisible(True)

            # get user input and update renderer
            answer = dlg_render.exec_()
            if answer == QDialog.Accepted:
                self.map_layer_renderer[cur_layer_idx] = None
                self.map_layer_renderer[cur_layer_idx] = self.map_layers[cur_layer_idx].rendererV2().clone()             
                self.canvas.refresh()
            dlg_render.destroy()
            del dlg_render
        except Exception as err:
            # thematic is not-critical, allow continue on exception
            logUICall.log(str(err), logUICall.WARNING)

    @logUICall
    @pyqtSlot()
    def searchFeature(self):        
        cur_layer_name = self.ui.cb_layer_selector.currentText()
        if cur_layer_name.isEmpty():
            return
        try:
            cur_layer_idx = self.LAYER_NAMES.index(cur_layer_name)            
            layer = self.map_layers[cur_layer_idx]
            fields = []
            for fidx in layer.dataProvider().fields():
                fields.append(layer.dataProvider().fields()[fidx].name())
            dlg_search = DialogSearchFeature(fields)           
            answer = dlg_search.exec_()
            if answer == QDialog.Accepted:
                extent = self.findFeatureExtentByAttribute(layer, dlg_search.attribute, dlg_search.value)
                if extent is not None:
                    self.zoomToExtent(extent)
                else:
                    logUICall.log(get_ui_string("widget.result.info.notfound"), logUICall.WARNING)
            dlg_search.destroy()
        except Exception as err:
            # thematic is not-critical, allow continue on exception
            logUICall.log(str(err), logUICall.WARNING)
            
    @logUICall
    @pyqtSlot()
    def mapIdentify(self):
        """ 
        event handler for btn_info 
        This only enables map querying, method connected to canvasClicked signal does
        the actual point-polygon query     
        """
        self.canvas.setMapTool(self.toolInfo)

    @logUICall
    @pyqtSlot()
    def selectExportFile(self):
        """
        event handler for btn_export_select_path 
        - open save file dialog box to select file name for export 
        """
        filename = QFileDialog.getSaveFileName(self,
                                               get_ui_string("widget.result.export.file.open"),
                                               ".", 
                                               self.ui.cb_export_format.currentText())
        if not filename.isNull():
            self.ui.txt_export_select_path.setText(filename) 
                    
    @logUICall
    @pyqtSlot(str)
    def exportFormatChanged(self, selected_val):
        """
        event handler for cb_export_format 
        - update selected file after format change
        """
        self.ui.txt_export_select_path.setText("")
        self.export_format = self.EXPORT_FORMATS[str(selected_val)]
        
    @logUICall
    @pyqtSlot()
    def exportData(self):
        """ 
        event handler for btn_export
        - do export data 
        """
        export_path = str(self.ui.txt_export_select_path.text())
        if export_path == "":
            logUICall.log(get_ui_string("app.error.path.is.null"), logUICall.WARNING)
            return
        self.app.exportResults(self.export_format, export_path)

        
    @logUICall
    @pyqtSlot(QPoint, QObject)
    def showInfo(self, point, mouseButton):
        """
        event handler for toolInfo
        @see QGIS tutorial for detail
        point-polygon search on currently selected layer  
        """
        cur_layer_name = self.ui.cb_layer_selector.currentText()
        if cur_layer_name.isEmpty():
            return
        try:
            cur_layer_idx = self.LAYER_NAMES.index(cur_layer_name)
            cur_layer = self.map_layers[cur_layer_idx]
            
            # if layer is not in same projection as map canvas
            # need to project query point
            if cur_layer.crs() != self.canvas.mapRenderer().destinationCrs():
                transform = QgsCoordinateTransform(self.canvas.mapRenderer().destinationCrs(), cur_layer.crs())
                point = transform.transform(point)
            
            # do query
            provider = cur_layer.dataProvider() 
            provider.rewind()
            feature = QgsFeature()
            colonIndexes = provider.attributeIndexes()
        
            # search using point as center of rectangle polygon
            search_buffer_x = self.canvas.extent().width() * self.SEARCH_BUFFER / self.canvas.width()
            search_buffer_y = self.canvas.extent().height() * self.SEARCH_BUFFER / self.canvas.height()
            provider.select(colonIndexes,
                            QgsRectangle(point.x()-search_buffer_x,
                                         point.y()-search_buffer_y,
                                         point.x()+search_buffer_x,
                                         point.y()+search_buffer_y),
                            True)
            # get selected and display in result detail dialog box 
            selected = []        
            while provider.nextFeature(feature):            
                # for polygons, only show geometry containing query point            
                if cur_layer.geometryType() == QGis.Polygon:                
                    if feature.geometry() is not None and not feature.geometry().contains (point):
                        continue
                selected.append(feature.attributeMap())

            if len(selected)>0:
                # display result if exists
                if cur_layer_idx == self.EXPOSURE:
                    self.dlgResultDetail.showExposureData(provider.fields(), selected)                    
                else:
                    self.dlgResultDetail.showInfoData(provider.fields(), selected)
                self.dlgResultDetail.exec_()
            else:
                logUICall.log(get_ui_string("widget.result.info.notfound"), logUICall.WARNING)
        except Exception as err:
            # point-in-polygon search is not critical, continue on error 
            logUICall.log(str(err), logUICall.WARNING)
        
    # public methods
    ###############################
    def set_project(self, project):
        ''' set project to preview. force refresh view on set'''        
        self._project = project
        if project is None:
            return
        self.refreshView()
        self.canvas.zoomToFullExtent()
        logUICall.log("Project preview initialized sucessfully", logUICall.INFO)
        
    def get_project(self):
        return self._project
    
    # property access to project
    project = property(get_project, set_project)
    
    def refreshView(self):
        ''' reload all QGIS layers in currently defined project '''
        if self._project is None:
            return
        
        # display layers if exists                
        if self._project.fp_file is not None and exists(self._project.fp_file):
            if self.map_layers[self.FOOTPRINT] is None or self.map_layers[self.FOOTPRINT].source() != self._project.fp_file:                            
                self.showDataLayer(self.FOOTPRINT, load_shapefile(self._project.fp_file, 'footprint'))
        else:            
            self.removeDataLayer(self.FOOTPRINT)
        
        if self._project.zone_file is not None and exists(self._project.zone_file):
            if self.map_layers[self.ZONES] is None or self.map_layers[self.ZONES].source() != self._project.zone_file:
                self.showDataLayer(self.ZONES, load_shapefile(self._project.zone_file, 'zones'))
        else:            
            self.removeDataLayer(self.ZONES)
            
        if self._project.survey_file is not None and exists(self._project.survey_file):
            if getattr(self._project, 'survey', None) is None:
                self._project.load_survey()
                self.showDataLayer(self.SURVEY, self._project.survey)
        else:            
            self.removeDataLayer(self.SURVEY)
        
        if self._project.popgrid_file is not None and exists(self._project.popgrid_file):
            if getattr(self._project, 'popgrid', None) is None:
                self.showDataLayer(self.POP_GRID, load_shapefile(self._project.popgrid_file, 'popgrid'))
        else:            
            self.removeDataLayer(self.POP_GRID)
        
        # set export options
        for idx, export_format in enumerate(self.EXPORT_FORMATS.values()):
            if export_format == self._project.export_type:
                self.ui.cb_export_format.setCurrentIndex(idx)
        self.ui.txt_export_select_path.setText(self._project.export_path)
        
        # refreshResult contains refresh call to update all layers currently loaded
        self.refreshResult()

    def refreshResult(self):
        ''' reload result QGIS layer and data quality reports in currently defined project '''
        exposure = getattr(self._project, 'exposure', None)        
        if exposure is not None:
            self.showDataLayer(self.EXPOSURE, exposure)
            has_result = True            
        else:
            self.removeDataLayer(self.EXPOSURE)
            has_result = False
        
        if has_result:
            # build quality report 
            report_lines = []
            if self._project.operator_options.has_key("proc.extrapolation"):
                proc_option = self._project.operator_options["proc.extrapolation"]
                if proc_option == ExtrapolateOptions.RandomWalk:
                    proc_method = get_ui_string("widget.result.dq.method", get_ui_string("dlg.options.ep.random"))
                elif proc_option == ExtrapolateOptions.Fraction:
                    proc_method = get_ui_string("widget.result.dq.method", get_ui_string("dlg.options.ep.fraction"))
                elif proc_option == ExtrapolateOptions.FractionRounded:
                    proc_method = get_ui_string("widget.result.dq.method", get_ui_string("dlg.options.ep.fraction.rounded"))
            else:
                proc_method = get_ui_string("widget.result.dq.method", get_ui_string("dlg.options.ep.random")) 
            report_lines.append(proc_method)
            report_lines.append('')
            
            # total tests
            report_lines.append(get_ui_string('widget.result.dq.total_tests', len(self._project.quality_reports.keys())))
            report_lines.append('')
            
            # detail for each test
            for key, report in self._project.quality_reports.iteritems():
                report_lines.append(get_ui_string('widget.result.dq.tests.%s' % key))            
                for title, value in report.iteritems():
                    report_lines.append( get_ui_string('widget.result.dq.tests.%s.%s' % (key, title), value) )
                report_lines.append('')                    
            self.ui.txt_dq_test_details.setText("\n".join(report_lines))


        self.ui.btn_export.setEnabled(has_result)
        self.ui.widget_dq_test.setVisible(has_result)
        self.ui.txt_export_select_path.setEnabled(has_result)
        self.ui.btn_export_select_path.setEnabled(has_result)
        self.ui.cb_export_format.setEnabled(has_result)        

        # this call refresh all layers currently loaded        
        self.refreshLayers()      

    @logUICall
    def closeResult(self):
        ''' remove from map result QGIS layer and reset quality report display '''
        self.canvas.setLayerSet([]) # call necessary to remove all layers to avoid disconnect errors  
        self.removeDataLayer(self.EXPOSURE)
        self.refreshLayers()
        self.ui.txt_dq_test_details.setText("")
        
    @logUICall
    def closeAll(self):
        ''' remove from map all QGIS layer in currently defined project '''
        self.ui.cb_layer_selector.clear()
        try:
            self.canvas.setLayerSet([]) # call necessary to remove all layers to avoid disconnect errors 
            for i in range(5):
                self.removeDataLayer(i)            
            self.ui.txt_dq_test_details.setText("")
            self.refreshLayers()
        except:            
            pass    # exception will is thrown when registry is empty
    
    # internal helper methods
    ###############################
    def showDataLayer(self, index, layer):
        """ display given QGIS layer on map """
        try:
            # add to QGIS registry and refresh view
            if self.map_layers[index] is not None:
                self.removeDataLayer(index)
            self.map_layers[index] = layer
            self.registry.addMapLayer(layer)
            layer.setRendererV2(self.map_layer_renderer[index])            
        except:
            pass

    def removeDataLayer(self, index):
        """ remove from map the layer identified with index """
        layer = self.map_layers[index]
        self.map_layers[index] = None    
        if layer is not None:                
            try:
                self.registry.removeMapLayer(layer.getLayerID(), False)
                del layer      
            except:
                pass # do nothing if it fails. probably already deleted

    def findFeatureExtentByAttribute(self, layer, field, value):
        """ 
        find extent of all objects in QGIS layer matching condition "field=value"         
        """
        fidx = layer_field_index(layer, field)
        if fidx == -1:
            return None
        xmin, xmax, ymin, ymax = 180, -180, 90, -90
        extent = QgsRectangle(xmin, ymin, xmax, ymax)
        need_transform = layer.crs() != self.canvas.mapRenderer().destinationCrs()
        if need_transform:
            transform = QgsCoordinateTransform(layer.crs(), self.canvas.mapRenderer().destinationCrs())
        for feature in layer_features(layer):
            if str(value) == feature.attributeMap()[fidx].toString():
                f_extent = feature.geometry().boundingBox()
                if need_transform:
                    f_extent = transform.transform(f_extent)
                xmin = min(f_extent.xMinimum(), xmin)
                xmax = max(f_extent.xMaximum(), xmax)
                ymin = min(f_extent.yMinimum(), ymin)
                ymax = max(f_extent.yMaximum(), ymax)
        extent.set (xmin, ymin, xmax, ymax)
        return extent

    def zoomToLayer(self, layer):
        """ zoom canvas to extent of given layer """
        try:
            lyr_extent = layer.extent()            
            if layer.crs() != self.canvas.mapRenderer().destinationCrs():
                transform = QgsCoordinateTransform(layer.crs(), self.canvas.mapRenderer().destinationCrs())
                lyr_extent = transform.transform(lyr_extent)
            self.zoomToExtent(lyr_extent)
        except:
            pass
    
    def zoomToExtent(self, extent):
        """ zoom canvas to given extent """
        try:
            self.canvas.setExtent(extent)
            self.canvas.zoomByFactor(1.1)
        except:
            self.mapZoomFull()
    
    def refreshLayers(self):
        """ refresh all layers in canvas """
        # add each layer according to order
        layerSet = []
        self.ui.cb_layer_selector.clear()
        for idx, lyr in enumerate(self.map_layers):
            if lyr is not None:
                layerSet.append(QgsMapCanvasLayer(lyr))
                self.ui.cb_layer_selector.addItem(self.LAYER_NAMES[idx])
        if len(layerSet) > 0: