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)
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))
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')
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())
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))
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())
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))
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])
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])
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))
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()
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()
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)
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: