def test_windowsDrawingArtifacts(self): """Test that windows rendering does not make artifacts""" # sometimes spurious lines are drawn on the layout myMap = ISMap(IFACE) myMap.setupComposition() myPdfPath = os.path.join(getTempDir(), 'outArtifactsTest.pdf') myMap.setupPrinter(myPdfPath) myPixmap = QtGui.QPixmap(10, 10) myPixmap.fill(QtGui.QColor(250, 250, 250)) myFilename = os.path.join(getTempDir(), 'greyBox') myPixmap.save(myFilename, 'PNG') for i in range(10, 190, 10): myPicture = QgsComposerPicture(myMap.composition) myPicture.setPictureFile(myFilename) myPicture.setFrame(False) myPicture.setItemPosition(i, # x i, # y 10, # width 10) # height myMap.composition.addItem(myPicture) # Same drawing drawn directly as a pixmap myPixmapItem = myMap.composition.addPixmap(myPixmap) myPixmapItem.setOffset(i, i + 20) # Same drawing using our drawPixmap Helper myWidthMM = 1 myMap.drawPixmap(myPixmap, myWidthMM, i, i + 40) myMap.renderPrintout() myUnwantedHash = 'd05e9223d50baf8bb147475aa96d6ba3' myHash = hashForFile(myPdfPath) # when this test no longer matches our broken render hash # we know the issue is fixed myMessage = 'Windows map render still draws with artifacts.' assert myHash != myUnwantedHash, myMessage
def test_addClassToLegend(self): """Test we can add a class to the map legend.""" myLayer, myType = loadLayer('test_shakeimpact.shp') del myType myMap = ISMap(IFACE) myMap.setImpactLayer(myLayer) myMap.legend = None myColour = QtGui.QColor(12, 34, 126) myMap.addClassToLegend(myColour, theMin=None, theMax=None, theCategory=None, theLabel='bar') myMap.addClassToLegend(myColour, theMin=None, theMax=None, theCategory=None, theLabel='foo') myPath = os.path.join(getTempDir(), 'addClassToLegend.png') myMap.legend.save(myPath, 'PNG') # As we have discovered, different versions of Qt and # OS platforms cause different output, so hashes are a list # of 'known good' renders. myExpectedHashes = ['', # win '67c0f45792318298664dd02cc0ac94c3', # ub12.04xiner 'ea0702782c2ed5d950c427fbe1743858', # ub11.10-64 '53e0ba1144e071ad41756595d29bf444', # ub12.04 '0681c3587305074bc9272f456fb4dd09', # ub12.04 xvfb # ub11.04-64 laptop '', ] assertHashesForFile(myExpectedHashes, myPath)
def Xtest_renderTable(self): """Test that html renders nicely. Commented out for now until we work out how to get webkit to do offscreen rendering nicely.""" myFilename = 'test_floodimpact.tif' myLayer, myType = loadLayer(myFilename) CANVAS.refresh() del myType myMessage = 'Layer is not valid: %s' % myFilename assert myLayer.isValid(), myMessage myMap = ISMap(IFACE) myMap.setImpactLayer(myLayer) myPixmap = myMap.renderImpactTable() assert myPixmap is not None myExpectedWidth = 500 myExpectedHeight = 300 myMessage = 'Invalid width - got %s expected %s' % ( myPixmap.width(), myExpectedWidth) assert myPixmap.width() == myExpectedWidth, myMessage myMessage = 'Invalid height - got %s expected %s' % ( myPixmap.height(), myExpectedHeight) assert myPixmap.height() == myExpectedHeight myPath = os.path.join(getTempDir(), 'renderImpactTable.png') myPixmap.save(myPath, 'PNG') myExpectedHash = 'c9164d5c2bb85c6081905456ab827f3e' assertHashForFile(myExpectedHash, myPath)
def test_inasafeMap(self): """Test making a pdf using the ISMap class.""" myLayer, myType = loadLayer('test_shakeimpact.shp') del myType myCanvasLayer = QgsMapCanvasLayer(myLayer) CANVAS.setLayerSet([myCanvasLayer]) myMap = ISMap(IFACE) myRect = QgsRectangle(106.7894, -6.2308, 106.8004, -6.2264) CANVAS.setExtent(myRect) CANVAS.refresh() myMap.setImpactLayer(myLayer) myPath = os.path.join(getTempDir(), 'outCustom.pdf') if os.path.exists(myPath): os.remove(myPath) myMap.makePdf(myPath) assert os.path.exists(myPath) # ,, note:: Template writing is experimental myMap.writeTemplate(os.path.join(getTempDir(), 'template.qpt'))
def test_renderTemplate(self): """Test that load template works""" #Use the template from our resources bundle myInPath = ':/plugins/inasafe/basic.qpt' myLayer, myType = loadLayer('test_shakeimpact.shp') del myType myCanvasLayer = QgsMapCanvasLayer(myLayer) CANVAS.setLayerSet([myCanvasLayer]) myMap = ISMap(IFACE) setJakartaGeoExtent() myMap.setImpactLayer(myLayer) myOutPath = os.path.join(getTempDir(), 'outTemplate.pdf') if os.path.exists(myOutPath): os.remove(myOutPath) myMap.renderTemplate(myInPath, myOutPath) assert os.path.exists(myOutPath)
def test_writeReadKeywordFromUri(self): """Test we can set and get keywords for a non local datasource""" myHandle, myFilename = tempfile.mkstemp('.db', 'keywords_', getTempDir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(myHandle) os.remove(myFilename) myExpectedKeywords = {'category': 'exposure', 'datatype': 'itb', 'subcategory': 'building'} # SQL insert test # On first write schema is empty and there is no matching hash self.keywordIO.setKeywordDbPath(myFilename) self.keywordIO.writeKeywordsForUri(PG_URI, myExpectedKeywords) # SQL Update test # On second write schema is populated and we update matching hash myExpectedKeywords = {'category': 'exposure', 'datatype': 'OSM', # <--note the change here! 'subcategory': 'building'} self.keywordIO.writeKeywordsForUri(PG_URI, myExpectedKeywords) # Test getting all keywords myKeywords = self.keywordIO.readKeywordFromUri(PG_URI) myMessage = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( myKeywords, myExpectedKeywords, myFilename) assert myKeywords == myExpectedKeywords, myMessage # Test getting just a single keyword myKeyword = self.keywordIO.readKeywordFromUri(PG_URI, 'datatype') myExpectedKeyword = 'OSM' myMessage = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( myKeyword, myExpectedKeyword, myFilename) assert myKeyword == myExpectedKeyword, myMessage # Test deleting keywords actually does delete self.keywordIO.deleteKeywordsForUri(PG_URI) try: myKeyword = self.keywordIO.readKeywordFromUri(PG_URI, 'datatype') #if the above didnt cause an exception then bad myMessage = 'Expected a HashNotFoundException to be raised' assert myMessage except HashNotFoundException: #we expect this outcome so good! pass
def addSymbolToLegend(self): """Test we can add a symbol to the legend.""" myLayer, myType = loadLayer('test_floodimpact.tif') del myType myMap = ISMap(IFACE) myMap.setImpactLayer(myLayer) myMap.legend = None mySymbol = QgsSymbol() mySymbol.setColor(QtGui.QColor(12, 34, 56)) myMap.addSymbolToLegend(mySymbol, theMin=0, theMax=2, theCategory=None, theLabel='Foo') myPath = os.path.join(getTempDir(), 'addSymbolToLegend.png') myMap.legend.save(myPath, 'PNG') myExpectedHash = '1234' assertHashForFile(myExpectedHash, myPath)
def test_getRasterLegend(self): """Getting a legend for a raster layer works.""" myLayer, myType = loadLayer('test_floodimpact.tif') del myType myMap = ISMap(IFACE) myMap.setImpactLayer(myLayer) myMap.getRasterLegend() myPath = os.path.join(getTempDir(), 'getRasterLegend.png') myMap.legend.save(myPath, 'PNG') # As we have discovered, different versions of Qt and # OS platforms cause different output, so hashes are a list # of 'known good' renders. myExpectedHashes = ['', # win '9ead6ce0ac789adc65a6f00bd2d1f709', # ub12.04xiner '84bc3d518e3a0504f8dc36dfd620394e', # ub11.10-64 'b68ccc328de852f0c66b8abe43eab3da', # ub12.04 'cd5fb96f6c5926085d251400dd3b4928', # ub12.04 xvfb # ub11.04-64 laptop '', ] assertHashesForFile(myExpectedHashes, myPath)
def test_getVectorLegend(self): """Getting a legend for a vector layer works.""" myLayer, myType = loadLayer('test_shakeimpact.shp') del myType myMap = ISMap(IFACE) myMap.setImpactLayer(myLayer) myMap.getVectorLegend() myPath = os.path.join(getTempDir(), 'getVectorLegend.png') myMap.legend.save(myPath, 'PNG') # As we have discovered, different versions of Qt and # OS platforms cause different output, so hashes are a list # of 'known good' renders. myExpectedHashes = ['', # win 'd0c3071c4babe7db4f9762b311d61184', # ub12.04 xinr 'b94cfd8a10d709ff28466ada425f24c8', # ub11.10-64 '00dc58aa50867de9b617ccfab0d13f21', # ub12.04 'e65853e217a4c9b0c2f303dd2aadb373', # ub12.04 xvfb # ub11.04-64 laptop '', ] assertHashesForFile(myExpectedHashes, myPath)
def extentToKml(theExtent): """A helper to get a little kml doc for an extent so that we can use it with gdal warp for clipping.""" myBottomLeftCorner = '%f,%f' % (theExtent[0], theExtent[1]) myTopLeftCorner = '%f,%f' % (theExtent[0], theExtent[3]) myTopRightCorner = '%f,%f' % (theExtent[2], theExtent[3]) myBottomRightCorner = '%f,%f' % (theExtent[2], theExtent[1]) myKml = ("""<?xml version="1.0" encoding="utf-8" ?> <kml xmlns="http://www.opengis.net/kml/2.2"> <Document> <Folder> <Placemark> <Polygon> <outerBoundaryIs> <LinearRing> <coordinates> %s %s %s %s %s </coordinates> </LinearRing> </outerBoundaryIs> </Polygon> </Placemark> </Folder> </Document> </kml>""" % (myBottomLeftCorner, myTopLeftCorner, myTopRightCorner, myBottomRightCorner, myBottomLeftCorner)) myFilename = tempfile.mkstemp('.kml', 'extent_', getTempDir())[1] myFile = file(myFilename, 'wt') myFile.write(myKml) myFile.close() return myFilename
def _clipVectorLayer(theLayer, theExtent, theExtraKeywords=None): """Clip a Hazard or Exposure layer to the extents of the current view frame. The layer must be a vector layer or an exception will be thrown. The output layer will always be in WGS84/Geographic. Args: * theLayer - a valid QGIS vector layer in EPSG:4326 * theExtent - an array representing the exposure layer extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the coordinates are in EPSG:4326 although currently no checks are made to enforce this. * theExtraKeywords - any additional keywords over and above the original keywords that should be associated with the cliplayer. Returns: Path to the output clipped layer (placed in the system temp dir). Raises: None """ if not theLayer or not theExtent: myMessage = tr('Layer or Extent passed to clip is None.') raise InvalidParameterException(myMessage) if theLayer.type() != QgsMapLayer.VectorLayer: myMessage = tr('Expected a vector layer but received a %s.' % str(theLayer.type())) raise InvalidParameterException(myMessage) myHandle, myFilename = tempfile.mkstemp('.shp', 'clip_', getTempDir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(myHandle) os.remove(myFilename) # Get the clip extents in the layer's native CRS myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) myXForm = QgsCoordinateTransform(myGeoCrs, theLayer.crs()) myRect = QgsRectangle(theExtent[0], theExtent[1], theExtent[2], theExtent[3]) myProjectedExtent = myXForm.transformBoundingBox(myRect) # Get vector layer myProvider = theLayer.dataProvider() if myProvider is None: myMessage = tr('Could not obtain data provider from ' 'layer "%s"' % theLayer.source()) raise Exception(myMessage) # get the layer field list, select by our extent then write to disk # .. todo:: FIXME - for different geometry types we should implement # different clipping behaviour e.g. reject polygons that # intersect the edge of the bbox. Tim myAttributes = myProvider.attributeIndexes() myFetchGeometryFlag = True myUseIntersectFlag = True myProvider.select(myAttributes, myProjectedExtent, myFetchGeometryFlag, myUseIntersectFlag) myFieldList = myProvider.fields() myWriter = QgsVectorFileWriter(myFilename, 'UTF-8', myFieldList, theLayer.wkbType(), myGeoCrs, 'ESRI Shapefile') if myWriter.hasError() != QgsVectorFileWriter.NoError: myMessage = tr('Error when creating shapefile: <br>Filename:' '%s<br>Error: %s' % (myFilename, myWriter.hasError())) raise Exception(myMessage) # Reverse the coordinate xform now so that we can convert # geometries from layer crs to geocrs. myXForm = QgsCoordinateTransform(theLayer.crs(), myGeoCrs) # Retrieve every feature with its geometry and attributes myFeature = QgsFeature() myCount = 0 while myProvider.nextFeature(myFeature): myGeometry = myFeature.geometry() # Loop through the parts adding them to the output file # we ALWAYS write out single part features myGeometryList = explodeMultiPartGeometry(myGeometry) for myPart in myGeometryList: myPart.transform(myXForm) myFeature.setGeometry(myPart) myWriter.addFeature(myFeature) myCount += 1 del myWriter # Flush to disk if myCount < 1: myMessage = tr('No features fall within the clip extents. ' 'Try panning / zooming to an area containing data ' 'and then try to run your analysis again.') raise NoFeaturesInExtentException(myMessage) myKeywordIO = ISKeywordIO() myKeywordIO.copyKeywords(theLayer, myFilename, theExtraKeywords=theExtraKeywords) return myFilename # Filename of created file
def _clipRasterLayer(theLayer, theExtent, theCellSize=None, theExtraKeywords=None): """Clip a Hazard or Exposure raster layer to the extents provided. The layer must be a raster layer or an exception will be thrown. .. note:: The extent *must* be in EPSG:4326. The output layer will always be in WGS84/Geographic. Args: * theLayer - a valid QGIS raster layer in EPSG:4326 * theExtent - an array representing the exposure layer extents in the form [xmin, ymin, xmax, ymax]. It is assumed that the coordinates are in EPSG:4326 although currently no checks are made to enforce this. * theCellSize - cell size (in GeoCRS) which the layer should be resampled to. If not provided for a raster layer (i.e. theCellSize=None), the native raster cell size will be used. Returns: Path to the output clipped layer (placed in the system temp dir). Raises: Exception if input layer is a density layer in projected coordinates - see issue #123 """ if not theLayer or not theExtent: myMessage = tr('Layer or Extent passed to clip is None.') raise InvalidParameterException(myMessage) if theLayer.type() != QgsMapLayer.RasterLayer: myMessage = tr('Expected a raster layer but received a %s.' % str(theLayer.type())) raise InvalidParameterException(myMessage) myWorkingLayer = str(theLayer.source()) # Check for existence of keywords file myKeywordsPath = myWorkingLayer[:-4] + '.keywords' myMessage = tr('Input file to be clipped "%s" does not have the ' 'expected keywords file %s' % (myWorkingLayer, myKeywordsPath)) verify(os.path.isfile(myKeywordsPath), myMessage) # Raise exception if layer is projected and refers to density (issue #123) # FIXME (Ole): Need to deal with it - e.g. by automatically reprojecting # the layer at this point and setting the native resolution accordingly # in its keywords. myKeywords = readKeywordsFromFile(myKeywordsPath) if 'datatype' in myKeywords and myKeywords['datatype'] == 'density': if theLayer.srs().epsg() != 4326: # This layer is not WGS84 geographic myMessage = ('Layer %s represents density but has spatial ' 'reference "%s". Density layers must be given in ' 'WGS84 geographic coordinates, so please reproject ' 'and try again. For more information, see issue ' 'https://github.com/AIFDR/inasafe/issues/123' % (myWorkingLayer, theLayer.srs().toProj4())) raise InvalidProjectionException(myMessage) # We need to provide gdalwarp with a dataset for the clip # because unline gdal_translate, it does not take projwin. myClipKml = extentToKml(theExtent) # Create a filename for the clipped, resampled and reprojected layer myHandle, myFilename = tempfile.mkstemp('.tif', 'clip_', getTempDir()) os.close(myHandle) os.remove(myFilename) # If no cell size is specified, we need to run gdalwarp without # specifying the output pixel size to ensure the raster dims # remain consistent. if theCellSize is None: myCommand = ('gdalwarp -q -t_srs EPSG:4326 -r near ' '-cutline %s -crop_to_cutline -of GTiff ' '"%s" "%s"' % (myClipKml, myWorkingLayer, myFilename)) else: myCommand = ('gdalwarp -q -t_srs EPSG:4326 -r near -tr %f %f ' '-cutline %s -crop_to_cutline -of GTiff ' '"%s" "%s"' % (theCellSize, theCellSize, myClipKml, myWorkingLayer, myFilename)) myExecutablePrefix = '' if sys.platform == 'darwin': # Mac OS X # .. todo:: FIXME - softcode gdal version in this path myExecutablePrefix = ('/Library/Frameworks/GDAL.framework/' 'Versions/1.9/Programs/') myCommand = myExecutablePrefix + myCommand # Now run GDAL warp scottie... try: myResult = call(myCommand, shell=True) del myResult except CalledProcessError, e: myMessage = tr('<p>Error while executing the following shell command:' '</p><pre>%s</pre><p>Error message: %s' % (myCommand, str(e))) # shameless hack - see https://github.com/AIFDR/inasafe/issues/141 if sys.platform == 'darwin': # Mac OS X if 'Errno 4' in str(e): # continue as the error seems to be non critical pass else: raise Exception(myMessage) else: raise Exception(myMessage)