Exemple #1
0
    def testDeferredUpdate(self):
        """ test that map canvas doesn't auto refresh on deferred layer update """
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()

        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        while canvas.isDrawing():
            app.processEvents()

        self.assertTrue(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # add polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))

        # deferred update - so expect that canvas will not been refreshed
        layer.triggerRepaint(True)
        timeout = time.time() + 0.1
        while time.time() < timeout:
            # messy, but only way to check that canvas redraw doesn't occur
            self.assertFalse(canvas.isDrawing())
        # canvas should still be empty
        self.assertTrue(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # refresh canvas
        canvas.refresh()
        while not canvas.isDrawing():
            app.processEvents()
        while canvas.isDrawing():
            app.processEvents()

        # now we expect the canvas check to fail (since they'll be a new polygon rendered over it)
        self.assertFalse(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))
    def testPosition(self):
        """ test that map canvas annotation item syncs position correctly """
        a = QgsTextAnnotation()
        a.setFrameSizeMm(QSizeF(300 / 3.7795275, 200 / 3.7795275))
        a.setFrameOffsetFromReferencePointMm(QPointF(40 / 3.7795275, 50 / 3.7795275))
        a.setMapPosition(QgsPointXY(12, 34))
        a.setMapPositionCrs(QgsCoordinateReferenceSystem(4326))

        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        canvas.show()

        canvas.setExtent(QgsRectangle(10, 30, 20, 35))

        i = QgsMapCanvasAnnotationItem(a, canvas)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        # test that correct initial position is set
        self.assertAlmostEqual(i.pos().x(), 120, 1)
        self.assertAlmostEqual(i.pos().y(), 110, 1)

        # shift annotation map position, check that item is moved
        a.setMapPosition(QgsPointXY(14, 32))
        self.assertAlmostEqual(i.pos().x(), 240, 1)
        self.assertAlmostEqual(i.pos().y(), 230, 1)

        # check relative position
        a.setHasFixedMapPosition(False)
        a.setRelativePosition(QPointF(0.8, 0.4))
        self.assertAlmostEqual(i.pos().x(), 480, 1)
        self.assertAlmostEqual(i.pos().y(), 160, 1)

        # flicking between relative and fixed position
        a.setHasFixedMapPosition(True)
        self.assertAlmostEqual(i.pos().x(), 240, 1)
        self.assertAlmostEqual(i.pos().y(), 230, 1)
        a.setHasFixedMapPosition(False)
        self.assertAlmostEqual(i.pos().x(), 480, 1)
        self.assertAlmostEqual(i.pos().y(), 160, 1)
    def testPosition(self):
        """ test that map canvas annotation item syncs position correctly """
        a = QgsTextAnnotation()
        a.setFrameSize(QSizeF(300, 200))
        a.setFrameOffsetFromReferencePoint(QPointF(40, 50))
        a.setMapPosition(QgsPointXY(12, 34))
        a.setMapPositionCrs(QgsCoordinateReferenceSystem(4326))

        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        canvas.show()

        canvas.setExtent(QgsRectangle(10, 30, 20, 35))

        i = QgsMapCanvasAnnotationItem(a, canvas)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        # test that correct initial position is set
        self.assertAlmostEqual(i.pos().x(), 120, 1)
        self.assertAlmostEqual(i.pos().y(), 110, 1)

        # shift annotation map position, check that item is moved
        a.setMapPosition(QgsPointXY(14, 32))
        self.assertAlmostEqual(i.pos().x(), 240, 1)
        self.assertAlmostEqual(i.pos().y(), 230, 1)

        # check relative position
        a.setHasFixedMapPosition(False)
        a.setRelativePosition(QPointF(0.8, 0.4))
        self.assertAlmostEqual(i.pos().x(), 480, 1)
        self.assertAlmostEqual(i.pos().y(), 160, 1)

        # flicking between relative and fixed position
        a.setHasFixedMapPosition(True)
        self.assertAlmostEqual(i.pos().x(), 240, 1)
        self.assertAlmostEqual(i.pos().y(), 230, 1)
        a.setHasFixedMapPosition(False)
        self.assertAlmostEqual(i.pos().x(), 480, 1)
        self.assertAlmostEqual(i.pos().y(), 160, 1)
Exemple #4
0
    def testDeferredUpdate(self):
        """ test that map canvas doesn't auto refresh on deferred layer update """
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()
        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # add polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))

        # deferred update - so expect that canvas will not been refreshed
        layer.triggerRepaint(True)
        timeout = time.time() + 0.1
        while time.time() < timeout:
            # messy, but only way to check that canvas redraw doesn't occur
            self.assertFalse(canvas.isDrawing())
        # canvas should still be empty
        self.assertTrue(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # refresh canvas
        canvas.refresh()
        canvas.waitWhileRendering()

        # now we expect the canvas check to fail (since they'll be a new polygon rendered over it)
        self.assertFalse(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))
Exemple #5
0
    def testDefaultViewExtentWithCanvas(self):
        p = QgsProject()
        p.setCrs(QgsCoordinateReferenceSystem('EPSG:3857'))

        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()

        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgz".format(tmpDir.path())
        self.assertTrue(p.write(tmpFile))

        QgsProject.instance().read(tmpFile)

        # no default view, extent should not change
        self.assertAlmostEqual(canvas.extent().xMinimum(), 10, 3)
        self.assertAlmostEqual(canvas.extent().yMinimum(), 29.16666, 3)
        self.assertAlmostEqual(canvas.extent().xMaximum(), 20, 3)
        self.assertAlmostEqual(canvas.extent().yMaximum(), 35.833333333, 3)
        self.assertEqual(canvas.mapSettings().destinationCrs().authid(),
                         'EPSG:4326')

        p.viewSettings().setDefaultViewExtent(
            QgsReferencedRectangle(QgsRectangle(1000, 2000, 1500, 2500),
                                   QgsCoordinateReferenceSystem('EPSG:3857')))

        self.assertTrue(p.write(tmpFile))
        QgsProject.instance().read(tmpFile)

        self.assertAlmostEqual(canvas.extent().xMinimum(), 0.0078602, 3)
        self.assertAlmostEqual(canvas.extent().yMinimum(), 0.017966, 3)
        self.assertAlmostEqual(canvas.extent().xMaximum(), 0.01459762, 3)
        self.assertAlmostEqual(canvas.extent().yMaximum(), 0.02245788, 3)
        self.assertEqual(canvas.mapSettings().destinationCrs().authid(),
                         'EPSG:4326')
Exemple #6
0
    def testRefreshOnTimer(self):
        """ test that map canvas refreshes with auto refreshing layers """
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()

        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()
        self.assertTrue(
            self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # add polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))

        # set auto refresh on layer
        layer.setAutoRefreshInterval(100)
        layer.setAutoRefreshEnabled(True)

        timeout = time.time() + 1
        # expect canvas to auto refresh...
        while not canvas.isDrawing():
            app.processEvents()
            self.assertTrue(time.time() < timeout)
        while canvas.isDrawing():
            app.processEvents()
            self.assertTrue(time.time() < timeout)

        # add a polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))
        # wait for canvas auto refresh
        while not canvas.isDrawing():
            app.processEvents()
            self.assertTrue(time.time() < timeout)
        while canvas.isDrawing():
            app.processEvents()
            self.assertTrue(time.time() < timeout)

        # now canvas should look different...
        self.assertFalse(
            self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # switch off auto refresh
        layer.setAutoRefreshEnabled(False)
        timeout = time.time() + 0.5
        while time.time() < timeout:
            # messy, but only way to check that canvas redraw doesn't occur
            self.assertFalse(canvas.isDrawing())
Exemple #7
0
    def testMapTheme(self):
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")
        # add a polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))

        # create a style
        sym1 = QgsFillSymbol.createSimple({'color': '#ffb200'})
        renderer = QgsSingleSymbolRenderer(sym1)
        layer.setRenderer(renderer)

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()

        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))

        # add some styles
        layer.styleManager().addStyleFromLayer('style1')
        sym2 = QgsFillSymbol.createSimple({'color': '#00b2ff'})
        renderer2 = QgsSingleSymbolRenderer(sym2)
        layer.setRenderer(renderer2)
        layer.styleManager().addStyleFromLayer('style2')

        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme2', 'theme2', canvas))

        layer.styleManager().setCurrentStyle('style1')
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))

        # ok, so all good with setting/rendering map styles
        # try setting canvas to a particular theme

        # make some themes...
        theme1 = QgsMapThemeCollection.MapThemeRecord()
        record1 = QgsMapThemeCollection.MapThemeLayerRecord(layer)
        record1.currentStyle = 'style1'
        record1.usingCurrentStyle = True
        theme1.setLayerRecords([record1])

        theme2 = QgsMapThemeCollection.MapThemeRecord()
        record2 = QgsMapThemeCollection.MapThemeLayerRecord(layer)
        record2.currentStyle = 'style2'
        record2.usingCurrentStyle = True
        theme2.setLayerRecords([record2])

        QgsProject.instance().mapThemeCollection().insert('theme1', theme1)
        QgsProject.instance().mapThemeCollection().insert('theme2', theme2)

        canvas.setTheme('theme2')
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme2', 'theme2', canvas))

        canvas.setTheme('theme1')
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))

        # add another layer
        layer2 = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                                "layer2", "memory")
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer2.dataProvider().addFeatures([f]))

        # create a style
        sym1 = QgsFillSymbol.createSimple({'color': '#b2ff00'})
        renderer = QgsSingleSymbolRenderer(sym1)
        layer2.setRenderer(renderer)

        # rerender canvas - should NOT show new layer
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))
        # test again - this time refresh all layers
        canvas.refreshAllLayers()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))

        # add layer 2 to theme1
        record3 = QgsMapThemeCollection.MapThemeLayerRecord(layer2)
        theme1.setLayerRecords([record3])
        QgsProject.instance().mapThemeCollection().update('theme1', theme1)

        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme3', 'theme3', canvas))

        # change the appearance of an active style
        layer2.styleManager().addStyleFromLayer('original')
        layer2.styleManager().addStyleFromLayer('style4')
        record3.currentStyle = 'style4'
        record3.usingCurrentStyle = True
        theme1.setLayerRecords([record3])
        QgsProject.instance().mapThemeCollection().update('theme1', theme1)

        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme3', 'theme3', canvas))

        layer2.styleManager().setCurrentStyle('style4')
        sym3 = QgsFillSymbol.createSimple({'color': '#b200b2'})
        layer2.renderer().setSymbol(sym3)
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))

        # try setting layers while a theme is in place
        canvas.setLayers([layer])
        canvas.refresh()

        # should be no change... setLayers should be ignored if canvas is following a theme!
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))

        # setLayerStyleOverrides while theme is in place
        canvas.setLayerStyleOverrides({layer2.id(): 'original'})
        # should be no change... setLayerStyleOverrides should be ignored if canvas is following a theme!
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))

        # clear theme
        canvas.setTheme('')
        canvas.refresh()
        canvas.waitWhileRendering()
        # should be different - we should now render project layers
        self.assertFalse(self.canvasImageCheck('theme4', 'theme4', canvas))
Exemple #8
0
    def testRefreshOnTimer(self):
        """ test that map canvas refreshes with auto refreshing layers """
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()

        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # add polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))

        # set auto refresh on layer
        layer.setAutoRefreshInterval(100)
        layer.setAutoRefreshEnabled(True)

        timeout = time.time() + 1
        # expect canvas to auto refresh...
        while not canvas.isDrawing():
            app.processEvents()
            self.assertTrue(time.time() < timeout)
        while canvas.isDrawing():
            app.processEvents()
            self.assertTrue(time.time() < timeout)

        # add a polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))
        # wait for canvas auto refresh
        while not canvas.isDrawing():
            app.processEvents()
            self.assertTrue(time.time() < timeout)
        while canvas.isDrawing():
            app.processEvents()
            self.assertTrue(time.time() < timeout)

        # now canvas should look different...
        self.assertFalse(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # switch off auto refresh
        layer.setAutoRefreshEnabled(False)
        timeout = time.time() + 0.5
        while time.time() < timeout:
            # messy, but only way to check that canvas redraw doesn't occur
            self.assertFalse(canvas.isDrawing())
Exemple #9
0
    def testMapTheme(self):
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")
        # add a polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))

        # create a style
        sym1 = QgsFillSymbol.createSimple({'color': '#ffb200'})
        renderer = QgsSingleSymbolRenderer(sym1)
        layer.setRenderer(renderer)

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()

        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))

        # add some styles
        layer.styleManager().addStyleFromLayer('style1')
        sym2 = QgsFillSymbol.createSimple({'color': '#00b2ff'})
        renderer2 = QgsSingleSymbolRenderer(sym2)
        layer.setRenderer(renderer2)
        layer.styleManager().addStyleFromLayer('style2')

        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme2', 'theme2', canvas))

        layer.styleManager().setCurrentStyle('style1')
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))

        # OK, so all good with setting/rendering map styles
        # try setting canvas to a particular theme

        # make some themes...
        theme1 = QgsMapThemeCollection.MapThemeRecord()
        record1 = QgsMapThemeCollection.MapThemeLayerRecord(layer)
        record1.currentStyle = 'style1'
        record1.usingCurrentStyle = True
        theme1.setLayerRecords([record1])

        theme2 = QgsMapThemeCollection.MapThemeRecord()
        record2 = QgsMapThemeCollection.MapThemeLayerRecord(layer)
        record2.currentStyle = 'style2'
        record2.usingCurrentStyle = True
        theme2.setLayerRecords([record2])

        QgsProject.instance().mapThemeCollection().insert('theme1', theme1)
        QgsProject.instance().mapThemeCollection().insert('theme2', theme2)

        canvas.setTheme('theme2')
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme2', 'theme2', canvas))

        canvas.setTheme('theme1')
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))

        # add another layer
        layer2 = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                                "layer2", "memory")
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer2.dataProvider().addFeatures([f]))

        # create a style
        sym1 = QgsFillSymbol.createSimple({'color': '#b2ff00'})
        renderer = QgsSingleSymbolRenderer(sym1)
        layer2.setRenderer(renderer)

        # rerender canvas - should NOT show new layer
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))
        # test again - this time refresh all layers
        canvas.refreshAllLayers()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))

        # add layer 2 to theme1
        record3 = QgsMapThemeCollection.MapThemeLayerRecord(layer2)
        theme1.setLayerRecords([record3])
        QgsProject.instance().mapThemeCollection().update('theme1', theme1)

        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme3', 'theme3', canvas))

        # change the appearance of an active style
        layer2.styleManager().addStyleFromLayer('original')
        layer2.styleManager().addStyleFromLayer('style4')
        record3.currentStyle = 'style4'
        record3.usingCurrentStyle = True
        theme1.setLayerRecords([record3])
        QgsProject.instance().mapThemeCollection().update('theme1', theme1)

        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme3', 'theme3', canvas))

        layer2.styleManager().setCurrentStyle('style4')
        sym3 = QgsFillSymbol.createSimple({'color': '#b200b2'})
        layer2.renderer().setSymbol(sym3)
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))

        # try setting layers while a theme is in place
        canvas.setLayers([layer])
        canvas.refresh()

        # should be no change... setLayers should be ignored if canvas is following a theme!
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))

        # setLayerStyleOverrides while theme is in place
        canvas.setLayerStyleOverrides({layer2.id(): 'original'})
        # should be no change... setLayerStyleOverrides should be ignored if canvas is following a theme!
        canvas.refresh()
        canvas.waitWhileRendering()
        self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))

        # clear theme
        canvas.setTheme('')
        canvas.refresh()
        canvas.waitWhileRendering()
        # should be different - we should now render project layers
        self.assertFalse(self.canvasImageCheck('theme4', 'theme4', canvas))
Exemple #10
0
    def test_rendered_item_results_remove_outdated(self):
        """
        Test that outdated results are removed from rendered item result caches
        """
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        canvas.setCachingEnabled(True)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsAnnotationLayer(
            'test',
            QgsAnnotationLayer.LayerOptions(
                QgsProject.instance().transformContext()))
        self.assertTrue(layer.isValid())
        layer2 = QgsAnnotationLayer(
            'test',
            QgsAnnotationLayer.LayerOptions(
                QgsProject.instance().transformContext()))
        self.assertTrue(layer2.isValid())

        item = QgsAnnotationPolygonItem(
            QgsPolygon(
                QgsLineString([
                    QgsPoint(11.5, 13),
                    QgsPoint(12, 13),
                    QgsPoint(12, 13.5),
                    QgsPoint(11.5, 13)
                ])))
        item.setSymbol(
            QgsFillSymbol.createSimple({
                'color': '200,100,100',
                'outline_color': 'black',
                'outline_width': '2'
            }))
        item.setZIndex(1)
        i1_id = layer.addItem(item)

        item = QgsAnnotationLineItem(
            QgsLineString(
                [QgsPoint(11, 13),
                 QgsPoint(12, 13),
                 QgsPoint(12, 15)]))
        item.setZIndex(2)
        i2_id = layer.addItem(item)

        item = QgsAnnotationMarkerItem(QgsPoint(12, 13))
        item.setZIndex(3)
        i3_id = layer2.addItem(item)

        layer.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        layer2.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))

        canvas.setLayers([layer, layer2])
        canvas.setExtent(QgsRectangle(10, 10, 18, 18))
        canvas.show()

        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()

        results = canvas.renderedItemResults()
        self.assertCountEqual([i.itemId() for i in results.renderedItems()],
                              [i1_id, i2_id, i3_id])

        # now try modifying an annotation in the layer -- it will redraw, and we don't want to reuse any previously
        # cached rendered item results for this layer!

        item = QgsAnnotationPolygonItem(
            QgsPolygon(
                QgsLineString([
                    QgsPoint(11.5, 13),
                    QgsPoint(12.5, 13),
                    QgsPoint(12.5, 13.5),
                    QgsPoint(11.5, 13)
                ])))
        item.setZIndex(1)
        layer.replaceItem(i1_id, item)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()

        item = QgsAnnotationMarkerItem(QgsPoint(17, 18))
        item.setZIndex(3)
        layer2.replaceItem(i3_id, item)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()

        results = canvas.renderedItemResults()
        items_in_bounds = results.renderedAnnotationItemsInBounds(
            QgsRectangle(10, 10, 15, 15))
        self.assertCountEqual([i.itemId() for i in items_in_bounds],
                              [i1_id, i2_id])

        items_in_bounds = results.renderedAnnotationItemsInBounds(
            QgsRectangle(15, 15, 20, 20))
        self.assertCountEqual([i.itemId() for i in items_in_bounds], [i3_id])
Exemple #11
0
    def test_rendered_items(self):
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        canvas.setCachingEnabled(True)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsAnnotationLayer(
            'test',
            QgsAnnotationLayer.LayerOptions(
                QgsProject.instance().transformContext()))
        self.assertTrue(layer.isValid())
        layer2 = QgsAnnotationLayer(
            'test',
            QgsAnnotationLayer.LayerOptions(
                QgsProject.instance().transformContext()))
        self.assertTrue(layer2.isValid())

        item = QgsAnnotationPolygonItem(
            QgsPolygon(
                QgsLineString([
                    QgsPoint(11.5, 13),
                    QgsPoint(12, 13),
                    QgsPoint(12, 13.5),
                    QgsPoint(11.5, 13)
                ])))
        item.setSymbol(
            QgsFillSymbol.createSimple({
                'color': '200,100,100',
                'outline_color': 'black',
                'outline_width': '2'
            }))
        item.setZIndex(1)
        i1_id = layer.addItem(item)

        item = QgsAnnotationLineItem(
            QgsLineString(
                [QgsPoint(11, 13),
                 QgsPoint(12, 13),
                 QgsPoint(12, 15)]))
        item.setZIndex(2)
        i2_id = layer.addItem(item)

        item = QgsAnnotationMarkerItem(QgsPoint(12, 13))
        item.setZIndex(3)
        i3_id = layer2.addItem(item)

        layer.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        layer2.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))

        canvas.setLayers([layer, layer2])
        canvas.setExtent(QgsRectangle(10, 10, 18, 18))
        canvas.show()

        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()

        results = canvas.renderedItemResults()
        self.assertCountEqual([i.itemId() for i in results.renderedItems()],
                              [i1_id, i2_id, i3_id])

        # turn off a layer -- the other layer will be rendered direct from the cached version
        canvas.setLayers([layer2])
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()

        results = canvas.renderedItemResults()
        # only layer2 items should be present in results -- but these MUST be present while layer2 is visible in the canvas,
        # even though the most recent canvas redraw used a cached version of layer2 and didn't actually have to redraw the layer
        self.assertEqual([i.itemId() for i in results.renderedItems()],
                         [i3_id])

        # turn layer 1 back on
        canvas.setLayers([layer, layer2])
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()

        results = canvas.renderedItemResults()
        # both layer1 and layer2 items should be present in results -- even though NEITHER of these layers were re-rendered,
        # and instead we used precached renders of both layers
        self.assertCountEqual([i.itemId() for i in results.renderedItems()],
                              [i1_id, i2_id, i3_id])
Exemple #12
0
    def testDistrictBoundaryMatches(self):
        """
        Test retrieving district boundary matches
        """
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")
        f = QgsFeature()
        f.setAttributes(['a'])
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 15, 45)))
        f2 = QgsFeature()
        f2.setAttributes(['a'])
        f2.setGeometry(QgsGeometry.fromRect(QgsRectangle(15, 25, 18, 45)))
        f3 = QgsFeature()
        f3.setAttributes(['b'])
        f3.setGeometry(
            QgsGeometry.fromWkt(
                'Polygon((18 30.01 19 35, 20 30, 19 25, 18 29.99, 20 30, 18 30.01))'
            ))
        success, (f, f2, f3) = layer.dataProvider().addFeatures([f, f2, f3])
        self.assertTrue(success)

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()

        handler = RedistrictHandler(layer, 'fldtxt')
        registry = DistrictRegistry(districts=['a', 'b'])
        tool = InteractiveRedistrictingTool(canvas,
                                            handler,
                                            district_registry=registry)
        # point inside a feature
        self.assertFalse(tool.get_district_boundary_matches(QgsPointXY(10,
                                                                       30)))
        self.assertFalse(
            [f for f in tool.get_target_features_from_matches([])])
        self.assertFalse(tool.get_districts_from_matches([]))
        self.assertFalse(tool.matches_are_valid_for_boundary([]))

        # point directly on boundary
        matches = tool.get_district_boundary_matches(QgsPointXY(15, 30))
        self.assertTrue(matches)
        self.assertCountEqual([match.featureId() for match in matches],
                              [f.id(), f2.id()])
        self.assertCountEqual(
            [f.id() for f in tool.get_target_features_from_matches(matches)],
            [f.id(), f2.id()])
        self.assertCountEqual(tool.get_districts_from_matches(matches), ['a'])
        # not a valid boundary match - both features have same district!
        self.assertFalse(tool.matches_are_valid_for_boundary(matches))

        # point just offset from boundary
        matches = tool.get_district_boundary_matches(QgsPointXY(15.1, 30))
        self.assertTrue(matches)
        self.assertCountEqual([match.featureId() for match in matches],
                              [f.id(), f2.id()])
        self.assertCountEqual(
            [f.id() for f in tool.get_target_features_from_matches(matches)],
            [f.id(), f2.id()])
        self.assertCountEqual(tool.get_districts_from_matches(matches), ['a'])
        self.assertFalse(tool.matches_are_valid_for_boundary(matches))

        # unique matches only
        matches = tool.get_district_boundary_matches(QgsPointXY(18, 30))
        self.assertTrue(matches)
        self.assertCountEqual([match.featureId() for match in matches],
                              [f2.id(), f3.id()])
        self.assertCountEqual(
            [f.id() for f in tool.get_target_features_from_matches(matches)],
            [f2.id(), f3.id()])
        self.assertCountEqual(tool.get_districts_from_matches(matches),
                              ['a', 'b'])
        # valid boundary match - both features have different districts
        self.assertTrue(tool.matches_are_valid_for_boundary(matches))
Exemple #13
0
    def testInteraction(self):  # pylint: disable=too-many-statements
        """
        Test tool interaction
        """
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")
        f = QgsFeature()
        f.setAttributes(['a'])
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 32, 15, 45)))
        f2 = QgsFeature()
        f2.setAttributes(['b'])
        f2.setGeometry(QgsGeometry.fromRect(QgsRectangle(15, 25, 18, 45)))
        success, (f, f2) = layer.dataProvider().addFeatures([f, f2])
        self.assertTrue(success)

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()

        handler = RedistrictHandler(layer, 'fldtxt')
        factory = DecoratorFactory()
        registry = DistrictRegistry(districts=['a', 'b'])
        tool = InteractiveRedistrictingTool(canvas,
                                            handler,
                                            district_registry=registry,
                                            decorator_factory=factory)

        # mouse over a feature's interior
        point = canvas.mapSettings().mapToPixel().transform(20, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertFalse(tool.is_active)
        self.assertFalse(tool.snap_indicator.match().isValid())
        # mouse over a single feature's boundary (not valid district boundary)
        point = canvas.mapSettings().mapToPixel().transform(5, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertFalse(tool.is_active)
        self.assertFalse(tool.snap_indicator.match().isValid())
        # mouse over a two feature's boundary (valid district boundary)
        point = canvas.mapSettings().mapToPixel().transform(15, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertFalse(tool.is_active)
        self.assertTrue(tool.snap_indicator.match().isValid())

        # avoid segfault
        tool.snap_indicator.setMatch(QgsPointLocator.Match())

        # clicks to ignore
        point = canvas.mapSettings().mapToPixel().transform(10, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.MidButton)
        tool.canvasPressEvent(event)
        self.assertFalse(tool.is_active)
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.RightButton)
        tool.canvasPressEvent(event)
        self.assertFalse(tool.is_active)

        # click over bad area
        point = canvas.mapSettings().mapToPixel().transform(10, 30)
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.LeftButton)
        tool.canvasPressEvent(event)
        self.assertFalse(tool.is_active)

        # click over feature area
        layer.startEditing()
        point = canvas.mapSettings().mapToPixel().transform(10, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.LeftButton)
        tool.canvasPressEvent(event)
        self.assertTrue(tool.is_active)
        self.assertEqual(tool.click_point.x(), 10)
        self.assertEqual(tool.click_point.y(), 33)
        self.assertEqual(tool.districts, {'a'})
        self.assertFalse(tool.modified)

        # now move over current feature - should do nothing!
        point = canvas.mapSettings().mapToPixel().transform(10, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertTrue(tool.is_active)
        self.assertFalse(tool.modified)

        # move over other feature
        self.assertEqual(layer.getFeature(f2.id())[0], 'b')
        point = canvas.mapSettings().mapToPixel().transform(16, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertTrue(tool.is_active)
        self.assertEqual(tool.modified, {f2.id()})
        self.assertEqual(tool.current_district, 'a')
        self.assertEqual(layer.getFeature(f2.id())[0], 'a')

        # move over nothing
        point = canvas.mapSettings().mapToPixel().transform(26, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertTrue(tool.is_active)
        self.assertEqual(tool.modified, {f2.id()})

        # left click - commit changes
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.LeftButton)
        tool.canvasPressEvent(event)
        self.assertFalse(tool.is_active)

        layer.rollBack()
        layer.startEditing()

        # add a decorator
        tool.decorator_factory = TestDecoratorFactory()

        # now try with clicks over boundary
        point = canvas.mapSettings().mapToPixel().transform(15, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.LeftButton)
        tool.canvasPressEvent(event)
        self.assertTrue(tool.is_active)
        self.assertEqual(tool.click_point.x(), 15)
        self.assertEqual(tool.click_point.y(), 33)
        self.assertEqual(tool.districts, {'a', 'b'})
        self.assertFalse(tool.modified)

        # move left
        self.assertEqual(layer.getFeature(f.id())[0], 'a')
        self.assertEqual(layer.getFeature(f2.id())[0], 'b')
        point = canvas.mapSettings().mapToPixel().transform(10, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertTrue(tool.is_active)
        self.assertEqual(tool.modified, {f.id()})
        self.assertEqual(tool.current_district, 'b')
        self.assertEqual(layer.getFeature(f.id())[0], 'b')
        self.assertEqual(layer.getFeature(f2.id())[0], 'b')

        # move over nothing
        point = canvas.mapSettings().mapToPixel().transform(26, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertTrue(tool.is_active)
        self.assertEqual(tool.modified, {f.id()})

        # right click - discard changes
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.RightButton)
        tool.canvasPressEvent(event)
        self.assertFalse(tool.is_active)
        self.assertEqual(layer.getFeature(f.id())[0], 'a')
        self.assertEqual(layer.getFeature(f2.id())[0], 'b')

        # try again, move right
        point = canvas.mapSettings().mapToPixel().transform(15, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.LeftButton)
        tool.canvasPressEvent(event)
        self.assertTrue(tool.is_active)
        self.assertEqual(tool.click_point.x(), 15)
        self.assertEqual(tool.click_point.y(), 33)
        self.assertEqual(tool.districts, {'a', 'b'})
        self.assertFalse(tool.modified)
        # move right
        self.assertEqual(layer.getFeature(f.id())[0], 'a')
        self.assertEqual(layer.getFeature(f2.id())[0], 'b')
        point = canvas.mapSettings().mapToPixel().transform(17, 33)
        event = QgsMapMouseEvent(canvas, QEvent.MouseMove,
                                 QPoint(point.x(), point.y()))
        tool.canvasMoveEvent(event)
        self.assertTrue(tool.is_active)
        self.assertEqual(tool.modified, {f2.id()})
        self.assertEqual(tool.current_district, 'a')
        self.assertEqual(layer.getFeature(f.id())[0], 'a')
        self.assertEqual(layer.getFeature(f2.id())[0], 'a')
        event = QgsMapMouseEvent(canvas, QEvent.MouseButtonPress,
                                 QPoint(point.x(), point.y()), Qt.RightButton)
        tool.canvasPressEvent(event)
        self.assertFalse(tool.is_active)
        self.assertEqual(layer.getFeature(f.id())[0], 'a')
        self.assertEqual(layer.getFeature(f2.id())[0], 'b')

        layer.rollBack()
Exemple #14
0
    def testMainAnnotationLayerRendered(self):
        """ test that main annotation layer is rendered above all other layers """
        canvas = QgsMapCanvas()
        canvas.setDestinationCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        canvas.setFrameStyle(0)
        canvas.resize(600, 400)
        self.assertEqual(canvas.width(), 600)
        self.assertEqual(canvas.height(), 400)

        layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
                               "layer", "memory")
        sym3 = QgsFillSymbol.createSimple({'color': '#b200b2'})
        layer.renderer().setSymbol(sym3)

        canvas.setLayers([layer])
        canvas.setExtent(QgsRectangle(10, 30, 20, 35))
        canvas.show()
        # need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
        while not canvas.isDrawing():
            app.processEvents()
        canvas.waitWhileRendering()
        self.assertTrue(
            self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))

        # add polygon to layer
        f = QgsFeature()
        f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
        self.assertTrue(layer.dataProvider().addFeatures([f]))

        # refresh canvas
        canvas.refresh()
        canvas.waitWhileRendering()

        # no annotation yet...
        self.assertFalse(
            self.canvasImageCheck('main_annotation_layer',
                                  'main_annotation_layer',
                                  canvas,
                                  expect_fail=True))

        annotation_layer = QgsProject.instance().mainAnnotationLayer()
        annotation_layer.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        annotation_geom = QgsGeometry.fromRect(QgsRectangle(12, 30, 18, 33))
        annotation = QgsAnnotationPolygonItem(
            annotation_geom.constGet().clone())
        sym3 = QgsFillSymbol.createSimple({
            'color': '#ff0000',
            'outline_style': 'no'
        })
        annotation.setSymbol(sym3)
        annotation_layer.addItem(annotation)

        # refresh canvas
        canvas.refresh()
        canvas.waitWhileRendering()

        # annotation must be rendered over other layers
        self.assertTrue(
            self.canvasImageCheck('main_annotation_layer',
                                  'main_annotation_layer', canvas))
        annotation_layer.clear()
Exemple #15
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)
Exemple #16
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: