def show_user_analysis_extent(self): """Update the rubber band showing the user defined analysis extent. Primary purpose of this slot is to draw a rubber band of where the analysis will be carried out based on valid intersection between layers and the user's preferred analysis area. This slot is called on pan, zoom, layer visibility changes and when the user updates the defined extent. .. versionadded:: 2.2.0 """ self.hide_user_analysis_extent() extent = self.user_extent source_crs = self.user_extent_crs try: extent = self.validate_rectangle(extent) except InvalidGeometryError: # keep existing user extent without updating it return # make sure the extent is in the same crs as the canvas destination_crs = self.iface.mapCanvas().mapSettings().destinationCrs() transform = QgsCoordinateTransform(source_crs, destination_crs) extent = transform.transformBoundingBox(extent) if self.show_rubber_bands: # Draw in blue self.user_analysis_rubberband = self._draw_rubberband( extent, QColor(0, 0, 255, 100))
def writeTmpLayer(layer, restrictToExtent, iface, extent): fields = layer.fields() usedFields = [] for count, field in enumerate(fields): fieldIndex = fields.indexFromName(unicode(field.name())) editorWidget = layer.editorWidgetSetup(fieldIndex).type() addField = False try: if layer.renderer().classAttribute() == field.name(): addField = True except: pass if layer.customProperty("labeling/fieldName") == field.name(): addField = True if (editorWidget != 'Hidden'): addField = True if addField: usedFields.append(count) uri = TYPE_MAP[layer.wkbType()] crs = layer.crs() if crs.isValid(): uri += '?crs=' + crs.authid() for field in usedFields: fieldIndex = layer.fields().indexFromName(unicode( layer.fields().field(field).name())) editorWidget = layer.editorWidgetSetup(fieldIndex).type() fieldType = layer.fields().field(field).type() fieldName = layer.fields().field(field).name() if (editorWidget == 'Hidden'): fieldName = "q2wHide_" + fieldName fieldType = "double" if (fieldType == QVariant.Double or fieldType == QVariant.Int) else ("string") uri += '&field=' + unicode(fieldName) + ":" + fieldType newlayer = QgsVectorLayer(uri, layer.name(), 'memory') writer = newlayer.dataProvider() outFeat = QgsFeature() if restrictToExtent and extent == "Canvas extent": canvas = iface.mapCanvas() extent = canvas.extent() canvasCRS = canvas.mapSettings().destinationCrs() layerCRS = layer.crs() try: transform = QgsCoordinateTransform(canvasCRS, layerCRS, QgsProject.instance()) except: transform = QgsCoordinateTransform(canvasCRS, layerCRS) projectedExtent = transform.transformBoundingBox(extent) request = QgsFeatureRequest(projectedExtent) request.setFlags(QgsFeatureRequest.ExactIntersect) features = layer.getFeatures(request) else: features = layer.getFeatures() for feature in features: if feature.geometry() is not None: outFeat.setGeometry(feature.geometry()) attrs = [feature[f] for f in usedFields] if attrs: outFeat.setAttributes(attrs) writer.addFeatures([outFeat]) return newlayer
def rasterScript(layer, safeLayerName): out_raster = 'data/' + safeLayerName + '.png' pt2 = layer.extent() crsSrc = layer.crs() crsDest = QgsCoordinateReferenceSystem(4326) try: xform = QgsCoordinateTransform(crsSrc, crsDest, QgsProject.instance()) except: xform = QgsCoordinateTransform(crsSrc, crsDest) pt3 = xform.transformBoundingBox(pt2) bbox_canvas2 = [pt3.yMinimum(), pt3.yMaximum(), pt3.xMinimum(), pt3.xMaximum()] bounds = '[[' + unicode(pt3.yMinimum()) + ',' bounds += unicode(pt3.xMinimum()) + '],[' bounds += unicode(pt3.yMaximum()) + ',' bounds += unicode(pt3.xMaximum()) + ']]' raster = """ var img_{safeLayerName} = '{out_raster}'; var img_bounds_{safeLayerName} = {bounds}; var overlay_{safeLayerName} = """.format(safeLayerName=safeLayerName, out_raster=out_raster, bounds=bounds) raster += "new L.imageOverlay(img_" raster += """{safeLayerName}, img_bounds_{safeLayerName}); bounds_group.addLayer(overlay_{safeLayerName});""".format( safeLayerName=safeLayerName) return raster
def load_layer_features(self, point=None, layers=None): # TODO Move this logic into the viewer and let it track it's position if point is None and self.marker.map_pos is None: return if point is None: point = self.marker.map_pos area, units = self.distancearea() rect = search_area(units, area, point) if layers is None: layers = self.visible_layers() for layer in layers: transform = self.coordinatetransform(layer) # Transform the rect source = self.canvas.mapRenderer().destinationCrs() dest = layer.crs() recttransform = QgsCoordinateTransform(source, dest) rect = recttransform.transformBoundingBox(rect) features = list(get_features_in_area(layer, rect, transform, self.canvas.mapSettings())) geomtype = layer.geometryType() layerdata = dict(id=layer.id(), geomtype=QGis.vectorGeometryType(geomtype)) self.viewer.load_features(layerdata, features)
def extent_to_geoarray(extent, source_crs): """Convert the supplied extent to geographic and return as as array. :param extent: QgsRectangle to be transformed to geocrs. :type extent: :param source_crs: QgsCoordinateReferenceSystem representing the original extent's CRS. :type source_crs: :returns: Transformed extents in EPSG:4326 in the form [xmin, ymin, xmax, ymax] """ myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) myXForm = QgsCoordinateTransform( source_crs, myGeoCrs) # Get the clip area in the layer's crs myTransformedExtent = myXForm.transformBoundingBox(extent) myGeoExtent = [myTransformedExtent.xMinimum(), myTransformedExtent.yMinimum(), myTransformedExtent.xMaximum(), myTransformedExtent.yMaximum()] return myGeoExtent
def extent_to_geo_array(extent, source_crs, dest_crs=None): """Convert the supplied extent to geographic and return as an array. :param extent: Rectangle defining a spatial extent in any CRS. :type extent: QgsRectangle :param source_crs: Coordinate system used for extent. :type source_crs: QgsCoordinateReferenceSystem :returns: a list in the form [xmin, ymin, xmax, ymax] where all coordinates provided are in Geographic / EPSG:4326. :rtype: list """ if dest_crs is None: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) else: geo_crs = dest_crs transform = QgsCoordinateTransform(source_crs, geo_crs) # Get the clip area in the layer's crs transformed_extent = transform.transformBoundingBox(extent) geo_extent = [ transformed_extent.xMinimum(), transformed_extent.yMinimum(), transformed_extent.xMaximum(), transformed_extent.yMaximum()] return geo_extent
def extentToGeoArray(theExtent, theSourceCrs): """Convert the supplied extent to geographic and return as as array. Args: theExtent: QgsRectangle to be transformed to geocrs. theSourceCrs: QgsCoordinateReferenceSystem representing the original extent's CRS. Returns: list: Transformed extents in EPSG:4326 in the form [xmin, ymin, xmix, ymax] Raises: None """ myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) myXForm = QgsCoordinateTransform( theSourceCrs, myGeoCrs) # Get the clip area in the layer's crs myTransformedExtent = myXForm.transformBoundingBox(theExtent) myGeoExtent = [myTransformedExtent.xMinimum(), myTransformedExtent.yMinimum(), myTransformedExtent.xMaximum(), myTransformedExtent.yMaximum()] return myGeoExtent
def extent_to_geo_array(extent, source_crs): """Convert the supplied extent to geographic and return as an array. :param extent: Rectangle defining a spatial extent in any CRS. :type extent: QgsRectangle :param source_crs: Coordinate system used for extent. :type source_crs: QgsCoordinateReferenceSystem :returns: a list in the form [xmin, ymin, xmax, ymax] where all coordinates provided are in Geographic / EPSG:4326. :rtype: list """ myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) myXForm = QgsCoordinateTransform(source_crs, myGeoCrs) # Get the clip area in the layer's crs myTransformedExtent = myXForm.transformBoundingBox(extent) myGeoExtent = [myTransformedExtent.xMinimum(), myTransformedExtent.yMinimum(), myTransformedExtent.xMaximum(), myTransformedExtent.yMaximum()] return myGeoExtent
def bounds(iface, useCanvas, layers, matchCRS): if useCanvas: canvas = iface.mapCanvas() canvasCrs = canvas.mapSettings().destinationCrs() if not matchCRS: epsg3857 = QgsCoordinateReferenceSystem("EPSG:3857") try: transform = QgsCoordinateTransform(canvasCrs, epsg3857, QgsProject.instance()) except: transform = QgsCoordinateTransform(canvasCrs, epsg3857) try: extent = transform.transformBoundingBox(canvas.extent()) except QgsCsException: extent = QgsRectangle(-20026376.39, -20048966.10, 20026376.39, 20048966.10) else: extent = canvas.extent() else: extent = None for layer in layers: if not matchCRS: epsg3857 = QgsCoordinateReferenceSystem("EPSG:3857") try: transform = QgsCoordinateTransform(layer.crs(), epsg3857, QgsProject.instance()) except: transform = QgsCoordinateTransform(layer.crs(), epsg3857) try: layerExtent = transform.transformBoundingBox( layer.extent()) except QgsCsException: layerExtent = QgsRectangle(-20026376.39, -20048966.10, 20026376.39, 20048966.10) else: layerExtent = layer.extent() if extent is None: extent = layerExtent else: extent.combineExtentWith(layerExtent) if extent is None: extent = QgsRectangle(-20026376.39, -20048966.10, 20026376.39, 20048966.10) return "[%f, %f, %f, %f]" % (extent.xMinimum(), extent.yMinimum(), extent.xMaximum(), extent.yMaximum())
def get_wgs84_resolution(layer): """Return resolution of raster layer in EPSG:4326. If input layer is already in EPSG:4326, simply return the resolution If not, work it out based on EPSG:4326 representations of its extent. :param layer: Raster layer :type layer: QgsRasterLayer or QgsMapLayer :returns: The resolution of the given layer in the form of (res_x, res_y) :rtype: tuple """ msg = tr( 'Input layer to get_wgs84_resolution must be a raster layer. ' 'I got: %s' % str(layer.type())[1:-1]) if not layer.type() == QgsMapLayer.RasterLayer: raise RuntimeError(msg) if layer.crs().authid() == 'EPSG:4326': cell_size = ( layer.rasterUnitsPerPixelX(), layer.rasterUnitsPerPixelY()) else: # Otherwise, work it out based on EPSG:4326 representations of # its extent # Reproject extent to EPSG:4326 geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) transform = QgsCoordinateTransform(layer.crs(), geo_crs) extent = layer.extent() projected_extent = transform.transformBoundingBox(extent) # Estimate resolution x columns = layer.width() width = abs( projected_extent.xMaximum() - projected_extent.xMinimum()) cell_size_x = width / columns # Estimate resolution y rows = layer.height() height = abs( projected_extent.yMaximum() - projected_extent.yMinimum()) cell_size_y = height / rows cell_size = (cell_size_x, cell_size_y) return cell_size
def testTransformBoundingBox(self): """Test that we can transform a rectangular bbox from utm56s to LonLat""" myExtent = QgsRectangle(242270, 6043737, 246330, 6045897) myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) myUtmCrs = QgsCoordinateReferenceSystem() myUtmCrs.createFromId(32756, QgsCoordinateReferenceSystem.EpsgCrsId) myXForm = QgsCoordinateTransform(myUtmCrs, myGeoCrs) myProjectedExtent = myXForm.transformBoundingBox(myExtent) myExpectedExtent = ('150.1509239873580270,-35.7176936443908772 : ' '150.1964384662953194,-35.6971885216629090') myMessage = ('Expected:\n%s\nGot:\n%s\n' % ( myExpectedExtent, myProjectedExtent.toString())) self.assertEquals(myExpectedExtent, myProjectedExtent.toString(), myMessage)
def project_extent(): """ Calculate the project's extent from each layer's extent. Can't assume on-the-fly transformation is on, so we convert each layer's extent to 4326, then combine it. """ extent = None for layer in list(QgsProject.instance().mapLayers().values()): transform = QgsCoordinateTransform(layer.crs(), WGS84, QgsProject.instance()) layer_extent = transform.transformBoundingBox(layer.extent()) if extent: extent.combineExtentWith(layer_extent) else: extent = layer_extent return extent_to_geojson(extent)
def testTransformBoundingBox(self): """Test that we can transform a rectangular bbox from utm56s to LonLat""" myExtent = QgsRectangle(242270, 6043737, 246330, 6045897) myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) myUtmCrs = QgsCoordinateReferenceSystem() myUtmCrs.createFromId(32756, QgsCoordinateReferenceSystem.EpsgCrsId) myXForm = QgsCoordinateTransform(myUtmCrs, myGeoCrs) myProjectedExtent = myXForm.transformBoundingBox(myExtent) myExpectedExtent = "150.1509239873580270,-35.7176936443908772 : " "150.1964384662953194,-35.6971885216629090" myExpectedValues = [150.1509239873580270, -35.7176936443908772, 150.1964384662953194, -35.6971885216629090] myMessage = "Expected:\n%s\nGot:\n%s\n" % (myExpectedExtent, myProjectedExtent.toString()) self.assertAlmostEqual(myExpectedValues[0], myProjectedExtent.xMinimum(), msg=myMessage) self.assertAlmostEqual(myExpectedValues[1], myProjectedExtent.yMinimum(), msg=myMessage) self.assertAlmostEqual(myExpectedValues[2], myProjectedExtent.xMaximum(), msg=myMessage) self.assertAlmostEqual(myExpectedValues[3], myProjectedExtent.yMaximum(), msg=myMessage)
def _geo_extent_to_canvas_crs(self, extent): """Transform a bounding box into the CRS of the canvas. :param extent: An extent in geographic coordinates. :type extent: QgsRectangle :returns: The extent in CRS of the canvas. :rtype: QgsRectangle """ # make sure the extent is in the same crs as the canvas destination_crs = self.iface.mapCanvas().mapRenderer().destinationCrs() source_crs = QgsCoordinateReferenceSystem() source_crs.createFromSrid(4326) transform = QgsCoordinateTransform(source_crs, destination_crs) extent = transform.transformBoundingBox(extent) return extent
def dissolvePolygonsOnCanvas(writer, layer): """dissolve polygons of the layer and clip the dissolution with base extent""" settings = writer.settings baseExtent = settings.baseExtent baseExtentGeom = baseExtent.geometry() rotation = baseExtent.rotation() transform = QgsCoordinateTransform(layer.crs(), settings.crs) combi = None request = QgsFeatureRequest() request.setFilterRect(transform.transformBoundingBox(baseExtent.boundingBox(), QgsCoordinateTransform.ReverseTransform)) for f in layer.getFeatures(request): geometry = f.geometry() if geometry is None: logMessage("null geometry skipped") continue # coordinate transformation - layer crs to project crs geom = QgsGeometry(geometry) if geom.transform(transform) != 0: logMessage("Failed to transform geometry") continue # check if geometry intersects with the base extent (rotated rect) if rotation and not baseExtentGeom.intersects(geom): continue if combi: combi = combi.combine(geom) else: combi = geom # clip geom with slightly smaller extent than base extent # to make sure that the clipped polygon stays within the base extent geom = combi.intersection(baseExtent.clone().scale(0.999999).geometry()) if geom is None: return None # check if geometry is empty if geom.isGeosEmpty(): logMessage("empty geometry") return None return geom
def getWGS84resolution(layer): """Return resolution of raster layer in EPSG:4326. If input layer is already in EPSG:4326, simply return the resolution If not, work it out based on EPSG:4326 representations of its extent. :param layer: Raster layer :type layer: QgsRasterLayer :returns: The resolution of the given layer. :rtype: float """ msg = tr( 'Input layer to getWGS84resolution must be a raster layer. ' 'I got: %s' % str(layer.type())[1:-1]) if not layer.type() == QgsMapLayer.RasterLayer: raise RuntimeError(msg) if layer.crs().authid() == 'EPSG:4326': # If it is already in EPSG:4326, simply use the native resolution if qgis_version() > 10800: myCellSize = layer.rasterUnitsPerPixelX() else: myCellSize = layer.rasterUnitsPerPixel() else: # Otherwise, work it out based on EPSG:4326 representations of # its extent # Reproject extent to EPSG:4326 myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) myXForm = QgsCoordinateTransform(layer.crs(), myGeoCrs) myExtent = layer.extent() myProjectedExtent = myXForm.transformBoundingBox(myExtent) # Estimate cell size myColumns = layer.width() myGeoWidth = abs(myProjectedExtent.xMaximum() - myProjectedExtent.xMinimum()) myCellSize = myGeoWidth / myColumns return myCellSize
def reproj(qgsgeom, crs_in, crs_out): """Simple reprojector.. qgsgeom -> QgsGeometry() object crs_in -> input CRS in EPSG (e.g. 3857) crs_out -> output CRS in EPSG (e.g. 4326) """ x_crs_type = QgsCoordinateReferenceSystem.EpsgCrsId x_crs_in = QgsCoordinateReferenceSystem(crs_in, x_crs_type) x_crs_out = QgsCoordinateReferenceSystem(crs_out, x_crs_type) x = QgsCoordinateTransform(x_crs_in, x_crs_out) # Spefic case for bbox.. if type(qgsgeom) == QgsRectangle: return x.transformBoundingBox(qgsgeom) # This is the default.. return x.transform(qgsgeom)
def getRaster(iface, layer, layerName, layerAttr, minResolution, maxResolution, matchCRS): crsSrc = layer.crs() projectCRS = iface.mapCanvas().mapSettings().destinationCrs() if not (matchCRS and crsSrc == projectCRS): mapCRS = "EPSG:3857" crsDest = QgsCoordinateReferenceSystem(3857) try: xform = QgsCoordinateTransform(crsSrc, crsDest, QgsProject.instance()) except: xform = QgsCoordinateTransform(crsSrc, crsDest) extentRep = xform.transformBoundingBox(layer.extent()) else: mapCRS = projectCRS.authid() extentRep = layer.extent() sExtent = "[%f, %f, %f, %f]" % (extentRep.xMinimum(), extentRep.yMinimum(), extentRep.xMaximum(), extentRep.yMaximum()) return '''var lyr_%(n)s = new ol.layer.Image({ opacity: 1, title: "%(name)s", %(minRes)s %(maxRes)s source: new ol.source.ImageStatic({ url: "./layers/%(n)s.png", attributions: '%(layerAttr)s', projection: '%(mapCRS)s', alwaysInRange: true, imageExtent: %(extent)s }) });''' % {"n": layerName, "extent": sExtent, "name": layer.name(), "minRes": minResolution, "maxRes": maxResolution, "mapCRS": mapCRS, "layerAttr": layerAttr}
def getWGS84resolution(theLayer): """Return resolution of raster layer in EPSG:4326 Input theLayer: Raster layer Output resolution. If input layer is already in EPSG:4326, simply return the resolution If not, work it out based on EPSG:4326 representations of its extent """ msg = tr("Input layer to getWGS84resolution must be a raster layer. " "I got: %s" % str(theLayer.type())[1:-1]) if not theLayer.type() == QgsMapLayer.RasterLayer: raise RuntimeError(msg) if theLayer.crs().authid() == "EPSG:4326": # If it is already in EPSG:4326, simply use the native resolution myCellSize = theLayer.rasterUnitsPerPixel() else: # Otherwise, work it out based on EPSG:4326 representations of # its extent # Reproject extent to EPSG:4326 myGeoCrs = QgsCoordinateReferenceSystem() myGeoCrs.createFromId(4326, QgsCoordinateReferenceSystem.EpsgCrsId) myXForm = QgsCoordinateTransform(theLayer.crs(), myGeoCrs) myExtent = theLayer.extent() myProjectedExtent = myXForm.transformBoundingBox(myExtent) # Estimate cellsize myColumns = theLayer.width() myGeoWidth = abs(myProjectedExtent.xMaximum() - myProjectedExtent.xMinimum()) myCellSize = myGeoWidth / myColumns return myCellSize
def exportRaster(layer, count, layersFolder, feedback, iface, matchCRS): feedback.showFeedback("Exporting %s to PNG..." % layer.name()) name_ts = (safeName(layer.name()) + unicode(count) + unicode(int(time.time()))) # We need to create a new file to export style piped_file = os.path.join(tempfile.gettempdir(), name_ts + '_piped.tif') piped_extent = layer.extent() piped_width = layer.height() piped_height = layer.width() piped_crs = layer.crs() piped_renderer = layer.renderer() piped_provider = layer.dataProvider() pipe = QgsRasterPipe() pipe.set(piped_provider.clone()) pipe.set(piped_renderer.clone()) file_writer = QgsRasterFileWriter(piped_file) file_writer.writeRaster(pipe, piped_height, -1, piped_extent, piped_crs) # Export layer as PNG out_raster = os.path.join( layersFolder, safeName(layer.name()) + "_" + unicode(count) + ".png") projectCRS = iface.mapCanvas().mapSettings().destinationCrs() if not (matchCRS and layer.crs() == projectCRS): # Extent of the layer in EPSG:3857 crsSrc = layer.crs() crsDest = QgsCoordinateReferenceSystem(3857) try: xform = QgsCoordinateTransform(crsSrc, crsDest, QgsProject.instance()) except: xform = QgsCoordinateTransform(crsSrc, crsDest) extentRep = xform.transformBoundingBox(layer.extent()) extentRepNew = ','.join([ unicode(extentRep.xMinimum()), unicode(extentRep.xMaximum()), unicode(extentRep.yMinimum()), unicode(extentRep.yMaximum()) ]) # Reproject in 3857 piped_3857 = os.path.join(tempfile.gettempdir(), name_ts + '_piped_3857.tif') qgis_version = Qgis.QGIS_VERSION old_stdout = sys.stdout sys.stdout = mystdout = StringIO() try: processing.algorithmHelp("gdal:warpreproject") except: pass sys.stdout = old_stdout params = { "INPUT": piped_file, "SOURCE_CRS": layer.crs().authid(), "TARGET_CRS": "EPSG:3857", "NODATA": 0, "TARGET_RESOLUTION": 0, "RESAMPLING": 2, "TARGET_EXTENT": extentRepNew, "EXT_CRS": "EPSG:3857", "TARGET_EXTENT_CRS": "EPSG:3857", "DATA_TYPE": 0, "COMPRESS": 4, "JPEGCOMPRESSION": 75, "ZLEVEL": 6, "PREDICTOR": 1, "TILED": False, "BIGTIFF": 0, "TFW": False, "MULTITHREADING": False, "COPY_SUBDATASETS": False, "EXTRA": "", "OUTPUT": piped_3857 } warpArgs = {} lines = mystdout.getvalue() for count, line in enumerate(lines.split("\n")): if count != 0 and ":" in line: try: k = line.split(":")[0] warpArgs[k] = params[k] except: pass try: processing.run("gdal:warpreproject", warpArgs) except: shutil.copyfile(piped_file, piped_3857) try: processing.run( "gdal:translate", { "INPUT": piped_3857, "OUTSIZE": 100, "OUTSIZE_PERC": True, "NODATA": 0, "EXPAND": 0, "TARGET_CRS": "", "PROJWIN": extentRepNew, "SDS": False, "DATA_TYPE": 0, "COMPRESS": 4, "JPEGCOMPRESSION": 75, "ZLEVEL": 6, "PREDICTOR": 1, "TILED": False, "BIGTIFF": 0, "TFW": False, "COPY_SUBDATASETS": False, "OPTIONS": "", "OUTPUT": out_raster }) except: shutil.copyfile(piped_3857, out_raster) else: srcExtent = ','.join([ unicode(piped_extent.xMinimum()), unicode(piped_extent.xMaximum()), unicode(piped_extent.yMinimum()), unicode(piped_extent.yMaximum()) ]) processing.run( "gdal:translate", { "INPUT": piped_file, "OUTSIZE": 100, "OUTSIZE_PERC": True, "NODATA": 0, "EXPAND": 0, "TARGET_CRS": "", "PROJWIN": srcExtent, "SDS": False, "DATA_TYPE": 0, "COMPRESS": 4, "JPEGCOMPRESSION": 75, "ZLEVEL": 6, "PREDICTOR": 1, "TILED": False, "BIGTIFF": 0, "TFW": False, "COPY_SUBDATASETS": False, "OPTIONS": "", "OUTPUT": out_raster })
def testReprojectionErrorsWhileRendering(self): # WKT of a polygon which causes reprojection errors while rendering # (apologies for the ridiculously complex wkt, but I can't find a way to reproduce with simplifiction) wkt = 'MultiPolygon (((16.93392988400009358 42.77094147300012139, 16.88493899800005238 42.72939687700012712, ' \ '16.80298912900011032 42.76349518400014915, 16.85816491000014139 42.78400299700011544, ' \ '16.93392988400009358 42.77094147300012139)),((17.38200931100010393 42.79783763200002511, ' \ '17.65894616000011297 42.74298737200008702, 17.74887129000009622 42.69456614800010641, ' \ '17.32374108200008322 42.79083893400003547, 17.38200931100010393 42.79783763200002511)),' \ '((16.768565300000148 42.97223541900014254, 17.03207441500009622 42.98261139500014849, ' \ '17.13184655000009116 42.96954987200014386, 17.20020592500009116 42.92177969000012183, ' \ '16.85141035200010151 42.90070221600008438, 16.65544681100004709 42.92625560099999404, ' \ '16.70679772200014668 42.96954987200014386, 16.63168379000003938 42.98261139500014849, ' \ '16.768565300000148 42.97223541900014254)),((17.05567467500011958 43.02895742400001211, ' \ '17.24024498800011429 43.02277252800014651, 17.74146569100011561 42.83926015800001608, ' \ '17.70736738400009358 42.88703034100014122, 17.65334906206413734 42.8909283361407887, ' \ '17.70158573400010482 42.91950022500007833, 17.81175988700005064 42.909862570000044, ' \ '17.85847538200005147 42.81697418200012351, 18.22413781700009849 42.62807098500009317, ' \ '18.43735477700010961 42.55921213800017711, 18.4371480710000526 42.4934022020000981, ' \ '18.49642988400009358 42.41632721600008438, 18.23894290500010129 42.55906810100005089, ' \ '18.21753991000014139 42.6201032570001388, 18.07601972700010151 42.65131256700003348, ' \ '18.0432235040000819 42.70205312700007028, 17.90162194100014403 42.75189850500014188, ' \ '17.8928328790000819 42.79083893400003547, 17.72095787900005348 42.8262393250000315, ' \ '17.7618921230000808 42.77871328300012976, 17.74870853000004445 42.77204010600017625, ' \ '17.21387780000011958 42.98261139500014849, 17.04615319100011561 42.9950625670000619, ' \ '17.00163821700004974 43.05149974200010377, 17.05567467500011958 43.02895742400001211)),' \ '((16.19467207100007045 43.07440827000000638, 16.254893425000148 43.06854889500006323, ' \ '16.08716881600014403 43.01146067900008063, 16.04883873800011429 43.06517161700004692, ' \ '16.19467207100007045 43.07440827000000638)),((16.56275475400011032 43.22898997600010773, ' \ '16.65951582100009887 43.21596914300012315, 16.72771243600001867 43.16461823100003414, ' \ '17.19336998800014271 43.12726471600016964, 16.67017662900013875 43.12547435099999404, ' \ '16.37159264400014536 43.19550202000006323, 16.49642988400006516 43.21808502800014651, ' \ '16.58326256600014403 43.18866608300005794, 16.52051842500006273 43.22898997600010773, ' \ '16.56275475400011032 43.22898997600010773)),((16.80681399800010922 43.34247467700005529, ' \ '16.89234459700011826 43.31220123900006058, 16.84620201900008851 43.27338288000005662, ' \ '16.62826582100012729 43.26373932500008834, 16.50074303500014139 43.28424713700003679, ' \ '16.42188561300008587 43.31757233300011478, 16.40577233200011165 43.33270905200011214, ' \ '16.45346113400009358 43.35317617400009738, 16.42628014400008851 43.39411041900011412, ' \ '16.44703209700008983 43.39484284100014122, 16.80681399800010922 43.34247467700005529)),' \ '((16.29818769600012729 43.40363190300011809, 16.30274498800008587 43.38727448100009099, ' \ '16.39144941500012465 43.34638092700005529, 16.348643425000148 43.33869049700003018, ' \ '16.20045006600014403 43.40704987200003018, 16.29818769600012729 43.40363190300011809)),' \ '((16.33415774800010922 43.50153229400014254, 16.3752547540000819 43.49017975500008504, ' \ '16.21143639400008851 43.49005768400009231, 16.26441491000014139 43.51288483300011478, ' \ '16.33415774800010922 43.50153229400014254)),((15.67888431100004709 43.64801666900014254, ' \ '15.74040774800010922 43.62750885600009099, 15.67204837300002396 43.63743724200010377, ' \ '15.60377037900013875 43.67470937700007028, 15.67888431100004709 43.64801666900014254)),' \ '((15.36736087300005238 43.79010651200015047, 15.39568118600007551 43.7724063170000619, ' \ '15.22779381600014403 43.87445709800014981, 15.24073326900014536 43.88076406500009341, ' \ '15.36736087300005238 43.79010651200015047)),((15.44271894600009887 43.89907461100013109, ' \ '15.35865319100014403 43.91937897300014981, 15.26124108200011165 44.01105377800003282, ' \ '15.38404381600008719 43.9701602230000077, 15.44271894600009887 43.89907461100013109)),' \ '((15.22575931100010393 44.06622955900014915, 15.25440514400008851 44.01788971600014122, ' \ '15.12183678500014139 44.09223053600005926, 15.06251061300008587 44.16193268400012073, ' \ '15.22575931100010393 44.06622955900014915)),((14.83545983200014007 44.15102773600013109, ' \ '14.85726972700010151 44.15204498900000374, 14.86915123800014271 44.14052969000006499, ' \ '14.83521569100008719 44.14166901200009363, 14.81983483200014007 44.15302155199999845, ' \ '14.82243899800005238 44.16868724200004692, 14.83545983200014007 44.15102773600013109)),' \ '((14.98511803500011297 44.09096914300012315, 15.21680748800008587 43.91278717700008372, ' \ '15.13331139400011693 43.92121002800003282, 15.19450931100004709 43.87262604400017096, ' \ '15.10661868600007551 43.92544179900015422, 14.84961998800014271 44.17560455900014915, ' \ '14.98511803500011297 44.09096914300012315)),((14.765961134000122 44.26504140800015819, ' \ '14.74854576900014536 44.26166413000014188, 14.73959394600012729 44.28017812700015554, ' \ '14.79167728000007287 44.27252838700003679, 14.765961134000122 44.26504140800015819)),' \ '((14.66138756600011561 44.30866120000014519, 14.6407983730000808 44.31183502800003282, ' \ '14.59506269600007045 44.34711334800006455, 14.643565300000148 44.32575104400011412, ' \ '14.66138756600011561 44.30866120000014519)),((14.81120853000004445 44.35004303600000242, ' \ '14.75619550900009358 44.36399974200004692, 14.76343834700008983 44.41535065300017493, ' \ '14.80323326900008851 44.40550364800004957, 14.81120853000004445 44.35004303600000242)),' \ '((14.27116946700002131 44.61253489800004957, 14.23259524800005238 44.62604401200012205, ' \ '14.2657983730000808 44.67951080900003547, 14.28044681100007551 44.67755768400009231, ' \ '14.27116946700002131 44.61253489800004957)),((14.84522545700008322 44.60053131700011875, ' \ '14.93824303500014139 44.59414297100001079, 15.07553144600007045 44.48407623900006058, ' \ '14.91114342500011958 44.54547760600014783, 15.04802493600004709 44.43943919500001982, ' \ '15.09669030000009116 44.41518789300000947, 15.04151451900014536 44.47662995000008834, ' \ '15.25440514400008851 44.34003327000000638, 15.165049675000148 44.36737702000006323, ' \ '15.22022545700008322 44.3127302100001117, 15.13086998800008587 44.33258698100003414, ' \ '15.17237389400014536 44.29913971600016964, 15.12875410200007309 44.31199778900018771, ' \ '15.08920332100009887 44.37421295800000109, 15.11719811300014271 44.38719310099999404, ' \ '15.04900149800010922 44.39468008000015686, 14.89747155000009116 44.49091217699999845, ' \ '14.91863040500010129 44.50454336100013109, 14.87696373800011429 44.55975983300005794, ' \ '14.73365319100008719 44.70319245000014519, 14.84522545700008322 44.60053131700011875)),' \ '((14.41000410200010151 44.60097890800001608, 14.52662194100011561 44.50372955900012073, ' \ '14.53435306100010393 44.48407623900006058, 14.42261803500008455 44.57387929900009738, ' \ '14.36304772200014668 44.57343170800000109, 14.38257897200014668 44.60325755399999537, ' \ '14.33578535200007309 44.71678294500010509, 14.39747155000009116 44.6856143250000315, ' \ '14.41000410200010151 44.60097890800001608)),((14.75326582100007045 44.84585195500012844, ' \ '14.74048912900011032 44.82050202000000638, 14.82243899800005238 44.77142975500005662, ' \ '14.84961998800014271 44.70319245000014519, 14.65788821700004974 44.79877350500014188, ' \ '14.7268172540000819 44.79877350500014188, 14.6858016290000819 44.8471540390000456, ' \ '14.75326582100007045 44.84585195500012844)),((14.47103925900006516 44.95392487200003018, ' \ '14.45191491000008455 44.79877350500014188, 14.47217858200011165 44.7079531920000619, ' \ '14.53435306100010393 44.63426341400010244, 14.51335696700007816 44.618841864000089, ' \ '14.42790774800005238 44.65656159100014122, 14.29420006600008719 44.9086367860001161, ' \ '14.30152428500011297 44.94342682500014519, 14.38738040500004445 44.90900299700003018, ' \ '14.39031009200004974 44.96039459800012139, 14.41138756600008719 44.95636627800014651, ' \ '14.27849368600004709 45.1133487000000315, 14.29957116000014139 45.16233958499999801, ' \ '14.35621178500014139 45.16925690300008966, 14.387705925000148 45.03904857000013351, ' \ '14.47103925900006516 44.95392487200003018)),((14.56332441500012465 45.24974192900008063, ' \ '14.62378991000011297 45.17548248900006058, 14.59742272200011826 45.16644928600005926, ' \ '14.66529381600011561 45.16181061400011743, 14.66529381600011561 45.08734772300006455, ' \ '14.74048912900011032 45.07306549700014386, 14.81495201900008851 44.97748444200009033, ' \ '14.70639082100009887 44.9467227230000077, 14.62891686300014271 44.97817617400004053, ' \ '14.62086022200008983 45.04559967700011214, 14.61695397200008983 45.02464427300007799, ' \ '14.51050866000014139 45.03217194200011875, 14.43873131600014403 45.07050202000006323, ' \ '14.4670516290000819 45.12409088700015047, 14.53012129000009622 45.13483307500014519, ' \ '14.53435306100010393 45.23753489800002114, 14.56332441500012465 45.24974192900008063)),' \ '((16.36947066200013978 46.54057118800012915, 16.63767134600004738 46.47447703100009164, ' \ '16.75508020000012266 46.38187286400001597, 16.83765913900006694 46.38187286400001597, ' \ '16.88923221800007468 46.29216257800014489, 17.05294315600005461 46.15346303300005104, ' \ '17.20859257000006437 46.11656606000003933, 17.27587528500004055 46.01202463800002818, ' \ '17.31680301900004793 45.99765859000002877, 17.29013798000011093 45.98463612900009423, ' \ '17.40620324700006449 45.94365671800015605, 17.59110152100009827 45.93621531200012953, ' \ '17.65652388500006964 45.84541982000014571, 17.80917606600013414 45.81441396100005647, ' \ '17.85806197100004056 45.77172922800004073, 18.21121870900006456 45.78537180600012846, ' \ '18.40438521300006869 45.74180857400001798, 18.57347049900010916 45.81668772400014689, ' \ '18.6556360270001278 45.90758656800015558, 18.7755253500000947 45.88283355700004051, ' \ '18.90130578600007993 45.93120269800006383, 18.87288374800004931 45.89523590100002082, ' \ '18.90699019400011593 45.86795074500018643, 18.85531376100007606 45.85735707600009903, ' \ '18.84497847500006174 45.8157058720000947, 18.96848514800012708 45.66873809800016204, ' \ '18.90357954900008508 45.57308502200005762, 18.94171675700005153 45.53892689999999277, ' \ '19.01809452300011571 45.56740061400002162, 19.10625451700005328 45.51164174500017623, ' \ '19.00961958800010621 45.49867095900005154, 19.00300500400010151 45.45536611000007099, ' \ '19.03742150900006891 45.42229319300010104, 18.97592655400006834 45.39495636000008005, ' \ '19.09199182100007874 45.34999786400005917, 19.12475467900009107 45.29811472600006539, ' \ '19.36308638500014467 45.24824696900010679, 19.40783817500010855 45.20313344400013023, ' \ '19.39068160000005037 45.16933705700016333, 19.22593713300008744 45.16194732700016345, ' \ '19.12186079900010327 45.195795390000157, 19.13767378700009658 45.14603098600004216, ' \ '19.04486291500009543 45.13724599300006446, 19.08227665200013234 45.08494944300004192, ' \ '19.0872375890000967 44.97710072800013847, 19.13167932100006396 44.95317454000003465, ' \ '19.06667036900009293 44.90568389900012392, 18.99142948400006503 44.9149339800001286, ' \ '19.01582076000008215 44.86563466400004074, 18.88962691200009658 44.86119049100013001, ' \ '18.78338016700013213 44.91374542300012251, 18.79175174900009893 45.00154368100008639, ' \ '18.73831831900008638 45.0159097290000858, 18.68405806500004473 45.08479441400000098, ' \ '18.64871138500012648 45.06267689999999959, 18.61667199700013953 45.09766184500010411, ' \ '18.54959598800010667 45.09476796500011631, 18.51703983500007666 45.05585561200003042, ' \ '18.23788374800011525 45.15745147700012296, 18.15365116400005263 45.0975584930001645, ' \ '18.00347945100011771 45.1493382780000303, 17.83573775200005684 45.0644338990000648, ' \ '17.68473921700012852 45.1639627080000281, 17.48185754400009273 45.11440500900012296, ' \ '17.49622359200009214 45.1416901650001563, 17.44775109900012922 45.13430043600014585, ' \ '17.44330692500011537 45.16205068000009248, 17.38243208800008688 45.1396231090000839, ' \ '17.26895064300006766 45.18954254200015441, 17.24548954300007608 45.15538442000017483, ' \ '17.18709517400012032 45.14856313100001728, 17.0363033440001459 45.23047027600007652, ' \ '17.00829471800011561 45.21615590500009318, 17.00829471800011561 45.24416453100009505, ' \ '16.94731652900014751 45.23568959600000028, 16.9243721930001243 45.28452382500016427, ' \ '16.81171757000004163 45.18122263700009, 16.52894413300009546 45.22225372400005483, ' \ '16.38921106000003647 45.11683380099999852, 16.31624393700010955 45.00123362300008978, ' \ '16.12152714000009723 45.09616322900008356, 16.02044803900011516 45.213933818000001, ' \ '15.79234826700013627 45.18980092400012438, 15.76361617000014803 44.97555043600003444, ' \ '15.7308533120001357 44.92723297200008403, 15.77343469200010873 44.84501576800015243, ' \ '15.71607385200013596 44.80320953400008932, 15.72847619600008784 44.76910308800002269, ' \ '15.80568078600006743 44.69665273000013883, 15.88877648900006534 44.72424794500012979, ' \ '15.96897831200004703 44.63924021400013942, 16.02830285600006732 44.62471913700009907, ' \ '16.04473596200011798 44.58937245700018082, 16.00608199000004106 44.54100331600012908, ' \ '16.11646285000011858 44.52146962500013672, 16.15966434700004584 44.41610138000002905, ' \ '16.13827030500004867 44.37760243800015303, 16.20286584400008678 44.35977406800010669, ' \ '16.18756962000011868 44.28241444999999032, 16.21578495300011014 44.20815541600011045, ' \ '16.32688928200008149 44.08237498000012522, 16.50103885900011846 43.99271637000008184, ' \ '16.67859908100004418 43.8406843060001421, 16.71260217300007866 43.77151540100005889, ' \ '17.03051558500007445 43.54847991900005866, 17.27050093600007585 43.46321380700000248, ' \ '17.28993127500007176 43.3034302780000786, 17.44206669100009321 43.15243174300015028, ' \ '17.6284119050001209 43.04657257100008394, 17.66272505700004558 42.96569895500012137, ' \ '17.63450972400008254 42.950402731000068, 17.51563561300008587 42.95888906500012183, ' \ '17.47087649800005238 43.01341380400010905, 17.50196373800014271 43.03099192900005221, ' \ '17.43360436300014271 43.01740143400009231, 17.46021569100011561 43.03099192900005221, ' \ '17.42611738400009358 43.06517161700004692, 17.4045516290000819 43.05149974200010377, ' \ '17.31625410200012993 43.12726471600016964, 17.11394290500004445 43.21320221600008438, ' \ '16.88062584700011826 43.40595123900006058, 16.62582441500009622 43.44904205900009231, ' \ '16.52466881600011561 43.51080963700009363, 16.39144941500012465 43.51080963700009363, ' \ '16.47339928500008455 43.5381533870001789, 16.43384850400013875 43.54975006700000506, ' \ '16.11768639400008851 43.52448151200003679, 16.17237389400014536 43.4896914730000077, ' \ '16.11312910200004467 43.47890859600009605, 15.95948326900011693 43.50397370000008834, ' \ '15.987315300000148 43.54490794500010509, 15.92530358200011165 43.55857982000004824, ' \ '15.91895592500009116 43.62872955900012073, 15.96631920700011165 43.64118073100003414, ' \ '15.90479576900014536 43.64801666900014254, 15.95297285200010151 43.65086497599999404, ' \ '15.95045006600008719 43.68854401200015047, 15.70630944100008719 43.76341380400005221, ' \ '15.6174422540000819 43.82550690300017493, 15.66309655000009116 43.81297435099999404, ' \ '15.67888431100004709 43.81928131700011875, 15.45508873800014271 43.92804596600014122, ' \ '15.14454186300011429 44.19546133000015686, 15.15219160200012993 44.23529694200014717, ' \ '15.11036217500011958 44.26434967700011214, 15.14063561300011429 44.28245677300013483, ' \ '15.17660566500009622 44.24994538000005662, 15.20777428500008455 44.27277252800014651, ' \ '15.19809004000012465 44.30166250200007028, 15.295258009000122 44.25067780199999845, ' \ '15.30274498800008587 44.29913971600016964, 15.26124108200011165 44.33258698100003414, ' \ '15.42448978000001603 44.26797109600006763, 15.52865644600009887 44.27179596600008438, ' \ '15.30795332100009887 44.35439687700007028, 15.00733483200014007 44.56972890800012976, ' \ '14.883799675000148 44.7236188820001388, 14.883799675000148 44.86147695500012844, 14.92164147200008983 ' \ '44.95880768400009231, 14.85279381600011561 45.09365469000000815, 14.65788821700004974 ' \ '45.19660065300017493, 14.57081139400008851 45.29364655200011214, 14.31153405000009116 ' \ '45.34398021000005485, 14.23259524800005238 45.14935944200000506, 14.17937259200007816 ' \ '45.13450755400005221, 14.19312584700008983 45.10561758000012844, 14.14389082100007045 ' \ '45.05939362200003018, 14.151377800000148 44.97748444200009033, 14.06885826900014536 ' \ '44.94953034100014122, 14.08383222700007309 44.9863955750000315, 14.04029381600014403 ' \ '45.03896719000015025, 14.0756942070000548 44.98371002800003282, 14.02051842500011958 ' \ '44.90110911700004692, 13.97266686300011429 44.90110911700004692, 13.99301191500009622 ' \ '44.88129303600014453, 13.97266686300011429 44.82664622599999404, 14.00001061300008587 ' \ '44.81305573100003414, 13.89014733200011165 44.83348216400010244, 13.91797936300014271 ' \ '44.77826569200009033, 13.90316816500009622 44.77240631700014717, 13.89698326900011693 ' \ '44.81305573100003414, 13.78711998800014271 44.87506745000008834, 13.84229576900008851 ' \ '44.88812897300006455, 13.79460696700010658 44.89496491100008768, 13.77409915500007287 ' \ '44.96381256700014717, 13.6232202480000808 45.07306549700014386, 13.61255944100014403 ' \ '45.11786530199999845, 13.72624759200004974 45.13450755400005221, 13.5959578790000819 ' \ '45.14541250200001343, 13.57545006600011561 45.26487864800007799, 13.60271243600001867 ' \ '45.28534577000012007, 13.57545006600011561 45.30646393400006389, 13.60954837300005238 ' \ '45.32013580900017757, 13.54127037900013875 45.34613678600005926, 13.50709069100014403 ' \ '45.51190827000000638, 13.62901778100007277 45.45898346000016943, 13.75929406800014476 ' \ '45.46316925100011019, 13.88900191200011136 45.42363678000005223, 13.98263960800005634 ' \ '45.47531321200001742, 13.97189091000012695 45.5142255660000643, 14.09291711400010172 ' \ '45.47391794800002174, 14.21869755100007637 45.49717234400004884, 14.37279667100006009 ' \ '45.47784535800009564, 14.4689148350000778 45.52559438100014688, 14.49857710800012001 ' \ '45.59618438800005435, 14.58094934100009255 45.66780792200010808, 14.66848921700008646 ' \ '45.53396596300005683, 14.79716353300005949 45.46518463200006011, 14.88160282300009385 ' \ '45.46978383400001178, 14.9226339110000481 45.51494903600017494, 15.13926151500010064 ' \ '45.43004465799999991, 15.32519331800011742 45.45283396399999276, 15.36136682100004691 ' \ '45.48203114900003641, 15.29666792800006192 45.52295888300012905, 15.2685559480001416 ' \ '45.60166208900012919, 15.37376916500011248 45.64021270800010655, 15.25501672300006817 ' \ '45.72346344000011698, 15.42906294700014769 45.77529490200011253, 15.45128381300008868 ' \ '45.81513743100013869, 15.67607629400006886 45.84169911700014666, 15.65943648300003588 ' \ '45.88882802400014782, 15.69798710100010908 46.0362092080000167, 15.58988000500005455 ' \ '46.11351715100001059, 15.62284956800010605 46.19170359400006021, 16.01920780400010358 ' \ '46.29882883700007312, 16.05961877400008575 46.33231516600015709, 16.0579651280001201 ' \ '46.37753204400003426, 16.2756262620000598 46.37316538500006402, 16.23490523300009158 ' \ '46.4933389280001137, 16.36947066200013978 46.54057118800012915))) ' geom = QgsGeometry.fromWkt(wkt) f = QgsFeature() f.setGeometry(geom) image = QImage(200, 200, QImage.Format_RGB32) painter = QPainter() ms = QgsMapSettings() crs = QgsCoordinateReferenceSystem.fromProj4('+proj=ortho +lat_0=36.5 +lon_0=-118.8 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs') self.assertTrue(crs.isValid()) ms.setDestinationCrs(crs) ms.setExtent(QgsRectangle(1374999.8, 3912610.7, 4724462.5, 6505499.6)) ms.setOutputSize(image.size()) context = QgsRenderContext.fromMapSettings(ms) context.setPainter(painter) context.setScaleFactor(96 / 25.4) # 96 DPI ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem('epsg:4326'), crs, QgsProject.instance()) self.assertTrue(ct.isValid()) context.setCoordinateTransform(ct) context.setExtent(ct.transformBoundingBox(ms.extent(), QgsCoordinateTransform.ReverseTransform)) fill_symbol = QgsFillSymbol.createSimple({'color': '#ffffff', 'outline_color': '#ffffff', 'outline_width': '10'}) painter.begin(image) try: image.fill(QColor(0, 0, 0)) fill_symbol.startRender(context) fill_symbol.renderFeature(f, context) fill_symbol.stopRender(context) finally: painter.end() assert self.imageCheck('Reprojection errors polygon', 'reprojection_errors_polygon', image) #also test linestring linestring = QgsGeometry(geom.constGet().boundary()) f.setGeometry(linestring) line_symbol = QgsLineSymbol.createSimple({'color': '#ffffff', 'outline_width': '10'}) image = QImage(200, 200, QImage.Format_RGB32) painter.begin(image) try: image.fill(QColor(0, 0, 0)) line_symbol.startRender(context) line_symbol.renderFeature(f, context) line_symbol.stopRender(context) finally: painter.end() assert self.imageCheck('Reprojection errors linestring', 'reprojection_errors_linestring', image)
def writeTmpLayer(layer, restrictToExtent, iface, extent): if layer.wkbType() == QgsWkbTypes.NoGeometry: return fields = layer.fields() usedFields = [] for count, field in enumerate(fields): fieldIndex = fields.indexFromName(field.name()) editorWidget = layer.editorWidgetSetup(fieldIndex).type() addField = False try: if layer.renderer().classAttribute() == field.name(): addField = True except Exception: pass if layer.customProperty("labeling/fieldName") == field.name(): addField = True if (editorWidget != 'Hidden'): addField = True if addField: usedFields.append(count) uri = TYPE_MAP[layer.wkbType()] crs = layer.crs() if crs.isValid(): uri += '?crs=' + crs.authid() for field in usedFields: fieldIndex = layer.fields().indexFromName( layer.fields().field(field).name()) editorWidget = layer.editorWidgetSetup(fieldIndex).type() fieldType = layer.fields().field(field).type() fieldName = layer.fields().field(field).name() if (editorWidget == 'Hidden'): fieldName = "q2wHide_" + fieldName if fieldType == QVariant.Double or fieldType == QVariant.Int: fieldType = "double" else: fieldType = "string" uri += '&field=' + fieldName + ":" + fieldType newlayer = QgsVectorLayer(uri, layer.name(), 'memory') writer = newlayer.dataProvider() outFeat = QgsFeature() if restrictToExtent and extent == "Canvas extent": canvas = iface.mapCanvas() extent = canvas.extent() canvasCRS = canvas.mapSettings().destinationCrs() layerCRS = layer.crs() try: transform = QgsCoordinateTransform(canvasCRS, layerCRS, QgsProject.instance()) except Exception: transform = QgsCoordinateTransform(canvasCRS, layerCRS) projectedExtent = transform.transformBoundingBox(extent) request = QgsFeatureRequest(projectedExtent) request.setFlags(QgsFeatureRequest.ExactIntersect) features = layer.getFeatures(request) else: features = layer.getFeatures() for feature in features: if feature.geometry() is not None: outFeat.setGeometry(feature.geometry()) attrs = [feature[f] for f in usedFields] if attrs: outFeat.setAttributes(attrs) writer.addFeatures([outFeat]) return newlayer
def _cellsize(layer): ext = layer.extent() if layer.crs() != crs: transform = QgsCoordinateTransform(layer.crs(), crs, context.project()) ext = transform.transformBoundingBox(ext) return (ext.xMaximum() - ext.xMinimum()) / layer.width()
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) join_source = self.parameterAsSource(parameters, self.JOIN, context) join_fields = self.parameterAsFields(parameters, self.JOIN_FIELDS, context) discard_nomatch = self.parameterAsBool(parameters, self.DISCARD_NONMATCHING, context) summaries = [self.statistics[i][0] for i in sorted(self.parameterAsEnums(parameters, self.SUMMARIES, context))] if not summaries: # none selected, so use all summaries = [s[0] for s in self.statistics] source_fields = source.fields() fields_to_join = QgsFields() join_field_indexes = [] if not join_fields: # no fields selected, use all join_fields = [join_source.fields().at(i).name() for i in range(len(join_source.fields()))] def addFieldKeepType(original, stat): """ Adds a field to the output, keeping the same data type as the original """ field = QgsField(original) field.setName(field.name() + '_' + stat) fields_to_join.append(field) def addField(original, stat, type): """ Adds a field to the output, with a specified type """ field = QgsField(original) field.setName(field.name() + '_' + stat) field.setType(type) if type == QVariant.Double: field.setLength(20) field.setPrecision(6) fields_to_join.append(field) numeric_fields = ( ('count', QVariant.Int, 'count'), ('unique', QVariant.Int, 'variety'), ('min', QVariant.Double, 'min'), ('max', QVariant.Double, 'max'), ('range', QVariant.Double, 'range'), ('sum', QVariant.Double, 'sum'), ('mean', QVariant.Double, 'mean'), ('median', QVariant.Double, 'median'), ('stddev', QVariant.Double, 'stDev'), ('minority', QVariant.Double, 'minority'), ('majority', QVariant.Double, 'majority'), ('q1', QVariant.Double, 'firstQuartile'), ('q3', QVariant.Double, 'thirdQuartile'), ('iqr', QVariant.Double, 'interQuartileRange') ) datetime_fields = ( ('count', QVariant.Int, 'count'), ('unique', QVariant.Int, 'countDistinct'), ('empty', QVariant.Int, 'countMissing'), ('filled', QVariant.Int), ('min', None), ('max', None) ) string_fields = ( ('count', QVariant.Int, 'count'), ('unique', QVariant.Int, 'countDistinct'), ('empty', QVariant.Int, 'countMissing'), ('filled', QVariant.Int), ('min', None, 'min'), ('max', None, 'max'), ('min_length', QVariant.Int, 'minLength'), ('max_length', QVariant.Int, 'maxLength'), ('mean_length', QVariant.Double, 'meanLength') ) field_types = [] for f in join_fields: idx = join_source.fields().lookupField(f) if idx >= 0: join_field_indexes.append(idx) join_field = join_source.fields().at(idx) if join_field.isNumeric(): field_types.append('numeric') field_list = numeric_fields elif join_field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime): field_types.append('datetime') field_list = datetime_fields else: field_types.append('string') field_list = string_fields for f in field_list: if f[0] in summaries: if f[1] is not None: addField(join_field, f[0], f[1]) else: addFieldKeepType(join_field, f[0]) out_fields = QgsProcessingUtils.combineFields(source_fields, fields_to_join) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, out_fields, source.wkbType(), source.sourceCrs()) # do the join predicates = [self.predicates[i][0] for i in self.parameterAsEnums(parameters, self.PREDICATE, context)] features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 # bounding box transform bbox_transform = QgsCoordinateTransform(source.sourceCrs(), join_source.sourceCrs(), context.project()) for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): if not discard_nomatch: # ensure consistent count of attributes - otherwise non matching # features will have incorrect attribute length # and provider may reject them attrs = f.attributes() if len(attrs) < len(out_fields): attrs += [NULL] * (len(out_fields) - len(attrs)) f.setAttributes(attrs) sink.addFeature(f, QgsFeatureSink.FastInsert) continue bbox = bbox_transform.transformBoundingBox(f.geometry().boundingBox()) engine = None values = [] request = QgsFeatureRequest().setFilterRect(bbox).setSubsetOfAttributes(join_field_indexes).setDestinationCrs(source.sourceCrs(), context.transformContext()) for test_feat in join_source.getFeatures(request): if feedback.isCanceled(): break join_attributes = [] for a in join_field_indexes: join_attributes.append(test_feat.attributes()[a]) if engine is None: engine = QgsGeometry.createGeometryEngine(f.geometry().constGet()) engine.prepareGeometry() for predicate in predicates: if getattr(engine, predicate)(test_feat.geometry().constGet()): values.append(join_attributes) break feedback.setProgress(int(current * total)) if len(values) == 0: if discard_nomatch: continue else: # ensure consistent count of attributes - otherwise non matching # features will have incorrect attribute length # and provider may reject them attrs = f.attributes() if len(attrs) < len(out_fields): attrs += [NULL] * (len(out_fields) - len(attrs)) f.setAttributes(attrs) sink.addFeature(f, QgsFeatureSink.FastInsert) else: attrs = f.attributes() for i in range(len(join_field_indexes)): attribute_values = [v[i] for v in values] field_type = field_types[i] if field_type == 'numeric': stat = QgsStatisticalSummary() for v in attribute_values: stat.addVariant(v) stat.finalize() for s in numeric_fields: if s[0] in summaries: attrs.append(getattr(stat, s[2])()) elif field_type == 'datetime': stat = QgsDateTimeStatisticalSummary() stat.calculate(attribute_values) for s in datetime_fields: if s[0] in summaries: if s[0] == 'filled': attrs.append(stat.count() - stat.countMissing()) elif s[0] == 'min': attrs.append(stat.statistic(QgsDateTimeStatisticalSummary.Min)) elif s[0] == 'max': attrs.append(stat.statistic(QgsDateTimeStatisticalSummary.Max)) else: attrs.append(getattr(stat, s[2])()) else: stat = QgsStringStatisticalSummary() for v in attribute_values: if v == NULL: stat.addString('') else: stat.addString(str(v)) stat.finalize() for s in string_fields: if s[0] in summaries: if s[0] == 'filled': attrs.append(stat.count() - stat.countMissing()) else: attrs.append(getattr(stat, s[2])()) f.setAttributes(attrs) sink.addFeature(f, QgsFeatureSink.FastInsert) return {self.OUTPUT: dest_id}
def _clip_vector_layer( layer, extent, extra_keywords=None, explode_flag=True, hard_clip_flag=False, explode_attribute=None): """Clip a Hazard or Exposure layer to the extents provided. The layer must be a vector layer or an exception will be thrown. The output layer will always be in WGS84/Geographic. :param layer: A valid QGIS vector or raster layer :type layer: :param extent: Either 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. or: A QgsGeometry of type polygon. **Polygon clipping is currently only supported for vector datasets.** :type extent: list(float, float, float, float) :param extra_keywords: Optional keywords dictionary to be added to output layer. :type extra_keywords: dict :param explode_flag: A bool specifying whether multipart features should be 'exploded' into singleparts. **This parameter is ignored for raster layer clipping.** :type explode_flag: bool :param hard_clip_flag: A bool specifying whether line and polygon features that extend beyond the extents should be clipped such that they are reduced in size to the part of the geometry that intersects the extent only. Default is False. **This parameter is ignored for raster layer clipping.** :type hard_clip_flag: bool :param explode_attribute: A str specifying to which attribute #1, #2 and so on will be added in case of explode_flag being true. The attribute is modified only if there are at least 2 parts. :type explode_attribute: str :returns: Clipped layer (placed in the system temp dir). The output layer will be reprojected to EPSG:4326 if needed. :rtype: QgsVectorLayer """ if not layer or not extent: myMessage = tr('Layer or Extent passed to clip is None.') raise InvalidParameterError(myMessage) if layer.type() != QgsMapLayer.VectorLayer: myMessage = tr('Expected a vector layer but received a %s.' % str(layer.type())) raise InvalidParameterError(myMessage) #myHandle, myFilename = tempfile.mkstemp('.sqlite', 'clip_', # temp_dir()) myHandle, myFilename = tempfile.mkstemp('.shp', 'clip_', temp_dir()) # 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.createFromSrid(4326) myXForm = QgsCoordinateTransform(myGeoCrs, layer.crs()) myAllowedClipTypes = [QGis.WKBPolygon, QGis.WKBPolygon25D] if type(extent) is list: myRect = QgsRectangle( extent[0], extent[1], extent[2], extent[3]) # noinspection PyCallByClass myClipPolygon = QgsGeometry.fromRect(myRect) elif (type(extent) is QgsGeometry and extent.wkbType in myAllowedClipTypes): myRect = extent.boundingBox().toRectF() myClipPolygon = extent else: raise InvalidClipGeometryError( tr( 'Clip geometry must be an extent or a single part' 'polygon based geometry.')) myProjectedExtent = myXForm.transformBoundingBox(myRect) # Get vector layer myProvider = layer.dataProvider() if myProvider is None: myMessage = tr('Could not obtain data provider from ' 'layer "%s"' % layer.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 myRequest = QgsFeatureRequest() if not myProjectedExtent.isEmpty(): myRequest.setFilterRect(myProjectedExtent) myRequest.setFlags(QgsFeatureRequest.ExactIntersect) myFieldList = myProvider.fields() myWriter = QgsVectorFileWriter( myFilename, 'UTF-8', myFieldList, layer.wkbType(), myGeoCrs, #'SQLite') # FIXME (Ole): This works but is far too slow '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(layer.crs(), myGeoCrs) # Retrieve every feature with its geometry and attributes myCount = 0 myHasMultipart = False for myFeature in myProvider.getFeatures(myRequest): myGeometry = myFeature.geometry() # Loop through the parts adding them to the output file # we write out single part features unless explode_flag is False if explode_flag: myGeometryList = explode_multipart_geometry(myGeometry) else: myGeometryList = [myGeometry] for myPartIndex, myPart in enumerate(myGeometryList): myPart.transform(myXForm) if hard_clip_flag: # Remove any dangling bits so only intersecting area is # kept. myPart = clip_geometry(myClipPolygon, myPart) if myPart is None: continue myFeature.setGeometry(myPart) # There are multiple parts and we want to show it in the # explode_attribute if myPartIndex > 0 and explode_attribute is not None: myHasMultipart = True 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. If hazard and exposure data doesn\'t overlap at all, it ' 'is not possible to do an analysis. Another possibility is that ' 'the layers do overlap but because they may have different ' 'spatial references, they appear to be disjointed. If this is the ' 'case, try to turn on reproject on-the-fly in QGIS.') raise NoFeaturesInExtentError(myMessage) myKeywordIO = KeywordIO() if extra_keywords is None: extra_keywords = {} extra_keywords['had multipart polygon'] = myHasMultipart myKeywordIO.copy_keywords( layer, myFilename, extra_keywords=extra_keywords) myBaseName = '%s clipped' % layer.name() myLayer = QgsVectorLayer(myFilename, myBaseName, 'ogr') return myLayer
def print_map(self, mode='pdf'): """Open impact report dialog that used define report options. :param mode: Mode for report - defaults to PDF. :type mode: """ # Check if selected layer is valid impact_layer = self.iface.activeLayer() if impact_layer is None: # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning( self.parent, self.tr('InaSAFE'), self.tr('Please select a valid impact layer before trying to ' 'print.')) return # Open Impact Report Dialog print_dialog = ImpactReportDialog(self.iface) print_dialog.button_ok = QtGui.QPushButton(self.tr('OK')) print_dialog.button_box.addButton(print_dialog.button_ok, QtGui.QDialogButtonBox.ActionRole) # noinspection PyUnresolvedReferences print_dialog.button_ok.clicked.connect(print_dialog.accept) print_dialog.button_save_pdf.hide() print_dialog.button_open_composer.hide() if not print_dialog.exec_() == QtGui.QDialog.Accepted: # noinspection PyTypeChecker self.show_dynamic_message( self, m.Message(m.Heading(self.tr('Map Creator'), **WARNING_STYLE), m.Text(self.tr('Report generation cancelled!')))) return # Get the extent of the map for report use_full_extent = print_dialog.analysis_extent_radio.isChecked() if use_full_extent: map_crs = self.iface.mapCanvas().mapRenderer().destinationCrs() layer_crs = self.iface.activeLayer().crs() layer_extent = self.iface.activeLayer().extent() if map_crs != layer_crs: # noinspection PyCallingNonCallable transform = QgsCoordinateTransform(layer_crs, map_crs) layer_extent = transform.transformBoundingBox(layer_extent) area_extent = layer_extent else: area_extent = self.iface.mapCanvas().extent() # Get selected template path to use if print_dialog.default_template_radio.isChecked(): template_path = print_dialog.template_combo.itemData( print_dialog.template_combo.currentIndex()) else: template_path = print_dialog.template_path.text() if not os.path.exists(template_path): # noinspection PyCallByClass,PyTypeChecker,PyArgumentList QtGui.QMessageBox.warning( self.parent, self.tr('InaSAFE'), self.tr('Please select a valid template before printing. ' 'The template you choose does not exist.')) return # Instantiate and prepare Report # noinspection PyTypeChecker self.show_dynamic_message( self, m.Message( m.Heading(self.tr('Map Creator'), **PROGRESS_UPDATE_STYLE), m.Text(self.tr('Preparing map and report')))) impact_report = ImpactReport(self.iface, template_path, impact_layer) impact_report.extent = area_extent # Get other setting settings = QSettings() logo_path = settings.value('inasafe/organisation_logo_path', '', type=str) impact_report.organisation_logo = logo_path disclaimer_text = settings.value('inasafe/reportDisclaimer', '', type=str) impact_report.disclaimer = disclaimer_text north_arrow_path = settings.value('inasafe/north_arrow_path', '', type=str) impact_report.north_arrow = north_arrow_path template_warning_verbose = bool( settings.value('inasafe/template_warning_verbose', True, type=bool)) # Check if there's missing elements needed in the template component_ids = [ 'safe-logo', 'north-arrow', 'organisation-logo', 'impact-map', 'impact-legend' ] impact_report.component_ids = component_ids if template_warning_verbose and \ len(impact_report.missing_elements) != 0: title = self.tr('Template is missing some elements') question = self.tr( 'The composer template you are printing to is missing ' 'these elements: %s. Do you still want to continue') % ( ', '.join(impact_report.missing_elements)) # noinspection PyCallByClass,PyTypeChecker answer = QtGui.QMessageBox.question( self.parent, title, question, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) if answer == QtGui.QMessageBox.No: return create_pdf_flag = bool(mode == 'pdf') self.show_busy() if create_pdf_flag: self.print_map_to_pdf(impact_report) else: self.open_map_in_composer(impact_report) self.hide_busy()
class TilesXYZAlgorithmBase(QgisAlgorithm): EXTENT = 'EXTENT' ZOOM_MIN = 'ZOOM_MIN' ZOOM_MAX = 'ZOOM_MAX' DPI = 'DPI' BACKGROUND_COLOR = 'BACKGROUND_COLOR' TILE_FORMAT = 'TILE_FORMAT' QUALITY = 'QUALITY' METATILESIZE = 'METATILESIZE' def initAlgorithm(self, config=None): self.addParameter( QgsProcessingParameterExtent(self.EXTENT, self.tr('Extent'))) self.addParameter( QgsProcessingParameterNumber(self.ZOOM_MIN, self.tr('Minimum zoom'), minValue=0, maxValue=25, defaultValue=12)) self.addParameter( QgsProcessingParameterNumber(self.ZOOM_MAX, self.tr('Maximum zoom'), minValue=0, maxValue=25, defaultValue=12)) self.addParameter( QgsProcessingParameterNumber(self.DPI, self.tr('DPI'), minValue=48, maxValue=600, defaultValue=96)) self.addParameter( QgsProcessingParameterColor(self.BACKGROUND_COLOR, self.tr('Background color'), defaultValue=QColor(Qt.transparent), optional=True)) self.formats = ['PNG', 'JPG'] self.addParameter( QgsProcessingParameterEnum(self.TILE_FORMAT, self.tr('Tile format'), self.formats, defaultValue=0)) self.addParameter( QgsProcessingParameterNumber(self.QUALITY, self.tr('Quality (JPG only)'), minValue=1, maxValue=100, defaultValue=75)) self.addParameter( QgsProcessingParameterNumber(self.METATILESIZE, self.tr('Metatile size'), minValue=1, maxValue=20, defaultValue=4)) self.thread_nr_re = re.compile('[0-9]+$') # thread number regex def prepareAlgorithm(self, parameters, context, feedback): project = context.project() visible_layers = [ item.layer() for item in project.layerTreeRoot().findLayers() if item.isVisible() ] self.layers = [ l for l in project.layerTreeRoot().layerOrder() if l in visible_layers ] return True def renderSingleMetatile(self, metatile): if self.feedback.isCanceled(): return # Haven't found a better way to break than to make all the new threads return instantly if "Dummy" in threading.current_thread().name or len( self.settingsDictionary) == 1: # single thread testing threadSpecificSettings = list(self.settingsDictionary.values())[0] else: thread_nr = self.thread_nr_re.search( threading.current_thread().name)[0] # terminating number only threadSpecificSettings = self.settingsDictionary[thread_nr] size = QSize(self.tile_width * metatile.rows(), self.tile_height * metatile.columns()) extent = QgsRectangle(*metatile.extent()) threadSpecificSettings.setExtent( self.wgs_to_dest.transformBoundingBox(extent)) threadSpecificSettings.setOutputSize(size) #Append MapSettings scope in order to update map variables (e.g @map_scale) with new extent data exp_context = threadSpecificSettings.expressionContext() exp_context.appendScope( QgsExpressionContextUtils.mapSettingsScope(threadSpecificSettings)) threadSpecificSettings.setExpressionContext(exp_context) image = QImage(size, QImage.Format_ARGB32_Premultiplied) image.fill(self.color) dpm = threadSpecificSettings.outputDpi() / 25.4 * 1000 image.setDotsPerMeterX(dpm) image.setDotsPerMeterY(dpm) painter = QPainter(image) job = QgsMapRendererCustomPainterJob(threadSpecificSettings, painter) job.renderSynchronously() painter.end() # For analysing metatiles (labels, etc.) # metatile_dir = os.path.join(output_dir, str(zoom)) # os.makedirs(metatile_dir, exist_ok=True) # image.save(os.path.join(metatile_dir, 'metatile_%s.png' % i)) for r, c, tile in metatile.tiles: tileImage = image.copy(self.tile_width * r, self.tile_height * c, self.tile_width, self.tile_height) self.writer.write_tile(tile, tileImage) # to stop thread sync issues with self.progressThreadLock: self.progress += 1 self.feedback.setProgress(100 * (self.progress / self.totalMetatiles)) def generate(self, writer, parameters, context, feedback): self.feedback = feedback feedback.setProgress(1) extent = self.parameterAsExtent(parameters, self.EXTENT, context) self.min_zoom = self.parameterAsInt(parameters, self.ZOOM_MIN, context) self.max_zoom = self.parameterAsInt(parameters, self.ZOOM_MAX, context) dpi = self.parameterAsInt(parameters, self.DPI, context) self.color = self.parameterAsColor(parameters, self.BACKGROUND_COLOR, context) self.tile_format = self.formats[self.parameterAsEnum( parameters, self.TILE_FORMAT, context)] self.quality = self.parameterAsInt(parameters, self.QUALITY, context) self.metatilesize = self.parameterAsInt(parameters, self.METATILESIZE, context) self.maxThreads = int( ProcessingConfig.getSetting(ProcessingConfig.MAX_THREADS)) try: self.tile_width = self.parameterAsInt(parameters, self.TILE_WIDTH, context) self.tile_height = self.parameterAsInt(parameters, self.TILE_HEIGHT, context) except AttributeError: self.tile_width = 256 self.tile_height = 256 wgs_crs = QgsCoordinateReferenceSystem('EPSG:4326') dest_crs = QgsCoordinateReferenceSystem('EPSG:3857') project = context.project() self.src_to_wgs = QgsCoordinateTransform(project.crs(), wgs_crs, context.transformContext()) self.wgs_to_dest = QgsCoordinateTransform(wgs_crs, dest_crs, context.transformContext()) # without re-writing, we need a different settings for each thread to stop async errors # naming doesn't always line up, but the last number does self.settingsDictionary = { str(i): QgsMapSettings() for i in range(self.maxThreads) } for thread in self.settingsDictionary: self.settingsDictionary[thread].setOutputImageFormat( QImage.Format_ARGB32_Premultiplied) self.settingsDictionary[thread].setDestinationCrs(dest_crs) self.settingsDictionary[thread].setLayers(self.layers) self.settingsDictionary[thread].setOutputDpi(dpi) if self.tile_format == 'PNG': self.settingsDictionary[thread].setBackgroundColor(self.color) # disable partial labels (they would be cut at the edge of tiles) labeling_engine_settings = self.settingsDictionary[ thread].labelingEngineSettings() labeling_engine_settings.setFlag( QgsLabelingEngineSettings.UsePartialCandidates, False) self.settingsDictionary[thread].setLabelingEngineSettings( labeling_engine_settings) # Transfer context scopes to MapSettings self.settingsDictionary[thread].setExpressionContext( context.expressionContext()) self.wgs_extent = self.src_to_wgs.transformBoundingBox(extent) self.wgs_extent = [ self.wgs_extent.xMinimum(), self.wgs_extent.yMinimum(), self.wgs_extent.xMaximum(), self.wgs_extent.yMaximum() ] metatiles_by_zoom = {} self.totalMetatiles = 0 allMetatiles = [] for zoom in range(self.min_zoom, self.max_zoom + 1): metatiles = get_metatiles(self.wgs_extent, zoom, self.metatilesize) metatiles_by_zoom[zoom] = metatiles allMetatiles += metatiles self.totalMetatiles += len(metatiles) lab_buffer_px = 100 self.progress = 0 tile_params = { 'format': self.tile_format, 'quality': self.quality, 'width': self.tile_width, 'height': self.tile_height, 'min_zoom': self.min_zoom, 'max_zoom': self.max_zoom, 'extent': self.wgs_extent, } writer.set_parameters(tile_params) self.writer = writer self.progressThreadLock = threading.Lock() if self.maxThreads > 1: feedback.pushConsoleInfo( self.tr('Using {max_threads} CPU Threads:').format( max_threads=self.maxThreads)) for zoom in range(self.min_zoom, self.max_zoom + 1): feedback.pushConsoleInfo( self.tr('Generating tiles for zoom level: {zoom}').format( zoom=zoom)) with ThreadPoolExecutor( max_workers=self.maxThreads) as threadPool: threadPool.map(self.renderSingleMetatile, metatiles_by_zoom[zoom]) else: feedback.pushConsoleInfo(self.tr('Using 1 CPU Thread:')) for zoom in range(self.min_zoom, self.max_zoom + 1): feedback.pushConsoleInfo( self.tr('Generating tiles for zoom level: {zoom}').format( zoom=zoom)) for i, metatile in enumerate(metatiles_by_zoom[zoom]): self.renderSingleMetatile(metatile) writer.close() def checkParameterValues(self, parameters, context): min_zoom = self.parameterAsInt(parameters, self.ZOOM_MIN, context) max_zoom = self.parameterAsInt(parameters, self.ZOOM_MAX, context) if max_zoom < min_zoom: return False, self.tr('Invalid zoom levels range.') return super().checkParameterValues(parameters, context)
def __init__(self, iface, parent=None, extent=None, crs=None): """Constructor for the dialog. :param iface: A Quantum GIS QGisAppInterface instance. :type iface: QGisAppInterface :param parent: Parent widget of this dialog :type parent: QWidget :param extent: Extent of the user's preferred analysis area. :type extent: QgsRectangle :param crs: Coordinate reference system for user defined analysis extent. :type crs: QgsCoordinateReferenceSystem """ QDialog.__init__(self, parent) self.setupUi(self) self.iface = iface self.parent = parent self.canvas = iface.mapCanvas() self.previous_map_tool = None # Prepare the map tool self.tool = RectangleMapTool(self.canvas) self.previous_map_tool = self.canvas.mapTool() if extent is None and crs is None: # Use the current map canvas extents as a starting point self.tool.set_rectangle(self.canvas.extent()) else: # Ensure supplied extent is in current canvas crs transform = QgsCoordinateTransform( crs, self.canvas.mapRenderer().destinationCrs()) transformed_extent = transform.transformBoundingBox(extent) self.tool.set_rectangle(transformed_extent) self._populate_coordinates() # Observe inputs for changes self.x_minimum.valueChanged.connect(self._coordinates_changed) self.y_minimum.valueChanged.connect(self._coordinates_changed) self.x_maximum.valueChanged.connect(self._coordinates_changed) self.y_maximum.valueChanged.connect(self._coordinates_changed) # Draw the rubberband self._coordinates_changed() # Wire up button events self.capture_button.clicked.connect(self.start_capture) # Handle cancel cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel) cancel_button.clicked.connect(self.reject) # Make sure to reshow this dialog when rectangle is captured self.tool.rectangle_created.connect(self.stop_capture) # Setup ok button self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok) self.ok_button.clicked.connect(self.accept) # Set up context help self.help_button = self.button_box.button(QtGui.QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) self.main_stacked_widget.setCurrentIndex(1) # Reset / Clear button clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset) clear_button.setText(self.tr('Clear')) clear_button.clicked.connect(self.clear) # Populate the bookmarks list and connect the combobox self._populate_bookmarks_list() self.bookmarks_list.currentIndexChanged.connect( self.bookmarks_index_changed) # Reinstate the last used radio button settings = QSettings() mode = settings.value('inasafe/analysis_extents_mode', 'HazardExposureView') if mode == 'HazardExposureView': self.hazard_exposure_view_extent.setChecked(True) elif mode == 'HazardExposure': self.hazard_exposure_only.setChecked(True) elif mode == 'HazardExposureBookmark': self.hazard_exposure_bookmark.setChecked(True) elif mode == 'HazardExposureBoundingBox': self.hazard_exposure_user_extent.setChecked(True) show_warnings = settings.value('inasafe/show_extent_warnings', True, type=bool) if show_warnings: self.show_warnings.setChecked(True) else: self.show_warnings.setChecked(False) show_confirmations = settings.value( 'inasafe/show_extent_confirmations', True, type=bool) if show_confirmations: self.show_confirmations.setChecked(True) else: self.show_confirmations.setChecked(False)
def processAlgorithm(self, parameters, context, feedback): source = self.parameterAsSource(parameters, self.INPUT, context) join_source = self.parameterAsSource(parameters, self.JOIN, context) join_fields = self.parameterAsFields(parameters, self.JOIN_FIELDS, context) discard_nomatch = self.parameterAsBool(parameters, self.DISCARD_NONMATCHING, context) summaries = [ self.statistics[i][0] for i in sorted( self.parameterAsEnums(parameters, self.SUMMARIES, context)) ] if not summaries: # none selected, so use all summaries = [s[0] for s in self.statistics] source_fields = source.fields() fields_to_join = QgsFields() join_field_indexes = [] if not join_fields: # no fields selected, use all join_fields = [ join_source.fields().at(i).name() for i in range(len(join_source.fields())) ] def addFieldKeepType(original, stat): """ Adds a field to the output, keeping the same data type as the original """ field = QgsField(original) field.setName(field.name() + '_' + stat) fields_to_join.append(field) def addField(original, stat, type): """ Adds a field to the output, with a specified type """ field = QgsField(original) field.setName(field.name() + '_' + stat) field.setType(type) if type == QVariant.Double: field.setLength(20) field.setPrecision(6) fields_to_join.append(field) numeric_fields = (('count', QVariant.Int, 'count'), ('unique', QVariant.Int, 'variety'), ('min', QVariant.Double, 'min'), ('max', QVariant.Double, 'max'), ('range', QVariant.Double, 'range'), ('sum', QVariant.Double, 'sum'), ('mean', QVariant.Double, 'mean'), ('median', QVariant.Double, 'median'), ('stddev', QVariant.Double, 'stDev'), ('minority', QVariant.Double, 'minority'), ('majority', QVariant.Double, 'majority'), ('q1', QVariant.Double, 'firstQuartile'), ('q3', QVariant.Double, 'thirdQuartile'), ('iqr', QVariant.Double, 'interQuartileRange')) datetime_fields = (('count', QVariant.Int, 'count'), ('unique', QVariant.Int, 'countDistinct'), ('empty', QVariant.Int, 'countMissing'), ('filled', QVariant.Int), ('min', None), ('max', None)) string_fields = (('count', QVariant.Int, 'count'), ('unique', QVariant.Int, 'countDistinct'), ('empty', QVariant.Int, 'countMissing'), ('filled', QVariant.Int), ('min', None, 'min'), ('max', None, 'max'), ('min_length', QVariant.Int, 'minLength'), ('max_length', QVariant.Int, 'maxLength'), ('mean_length', QVariant.Double, 'meanLength')) field_types = [] for f in join_fields: idx = join_source.fields().lookupField(f) if idx >= 0: join_field_indexes.append(idx) join_field = join_source.fields().at(idx) if join_field.isNumeric(): field_types.append('numeric') field_list = numeric_fields elif join_field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime): field_types.append('datetime') field_list = datetime_fields else: field_types.append('string') field_list = string_fields for f in field_list: if f[0] in summaries: if f[1] is not None: addField(join_field, f[0], f[1]) else: addFieldKeepType(join_field, f[0]) out_fields = QgsProcessingUtils.combineFields(source_fields, fields_to_join) (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, out_fields, source.wkbType(), source.sourceCrs()) # do the join predicates = [ self.predicates[i][0] for i in self.parameterAsEnums(parameters, self.PREDICATE, context) ] features = source.getFeatures() total = 100.0 / source.featureCount() if source.featureCount() else 0 # bounding box transform bbox_transform = QgsCoordinateTransform(source.sourceCrs(), join_source.sourceCrs()) for current, f in enumerate(features): if feedback.isCanceled(): break if not f.hasGeometry(): if not discard_nomatch: sink.addFeature(f, QgsFeatureSink.FastInsert) continue bbox = bbox_transform.transformBoundingBox( f.geometry().boundingBox()) engine = None values = [] request = QgsFeatureRequest().setFilterRect( bbox).setSubsetOfAttributes( join_field_indexes).setDestinationCrs(source.sourceCrs()) for test_feat in join_source.getFeatures(request): if feedback.isCanceled(): break join_attributes = [] for a in join_field_indexes: join_attributes.append(test_feat.attributes()[a]) if engine is None: engine = QgsGeometry.createGeometryEngine( f.geometry().constGet()) engine.prepareGeometry() for predicate in predicates: if getattr(engine, predicate)(test_feat.geometry().constGet()): values.append(join_attributes) break feedback.setProgress(int(current * total)) if len(values) == 0: if discard_nomatch: continue else: sink.addFeature(f, QgsFeatureSink.FastInsert) else: attrs = f.attributes() for i in range(len(join_field_indexes)): attribute_values = [v[i] for v in values] field_type = field_types[i] if field_type == 'numeric': stat = QgsStatisticalSummary() for v in attribute_values: stat.addVariant(v) stat.finalize() for s in numeric_fields: if s[0] in summaries: attrs.append(getattr(stat, s[2])()) elif field_type == 'datetime': stat = QgsDateTimeStatisticalSummary() stat.calculate(attribute_values) for s in datetime_fields: if s[0] in summaries: if s[0] == 'filled': attrs.append(stat.count() - stat.countMissing()) elif s[0] == 'min': attrs.append( stat.statistic( QgsDateTimeStatisticalSummary.Min)) elif s[0] == 'max': attrs.append( stat.statistic( QgsDateTimeStatisticalSummary.Max)) else: attrs.append(getattr(stat, s[2])()) else: stat = QgsStringStatisticalSummary() for v in attribute_values: if v == NULL: stat.addString('') else: stat.addString(str(v)) stat.finalize() for s in string_fields: if s[0] in summaries: if s[0] == 'filled': attrs.append(stat.count() - stat.countMissing()) else: attrs.append(getattr(stat, s[2])()) f.setAttributes(attrs) sink.addFeature(f, QgsFeatureSink.FastInsert) return {self.OUTPUT: dest_id}
def testReprojectionErrorsWhileRendering(self): # WKT of a polygon which causes reprojection errors while rendering # (apologies for the ridiculously complex wkt, but I can't find a way to reproduce with simplifiction) wkt = 'MultiPolygon (((16.93392988400009358 42.77094147300012139, 16.88493899800005238 42.72939687700012712, ' \ '16.80298912900011032 42.76349518400014915, 16.85816491000014139 42.78400299700011544, ' \ '16.93392988400009358 42.77094147300012139)),((17.38200931100010393 42.79783763200002511, ' \ '17.65894616000011297 42.74298737200008702, 17.74887129000009622 42.69456614800010641, ' \ '17.32374108200008322 42.79083893400003547, 17.38200931100010393 42.79783763200002511)),' \ '((16.768565300000148 42.97223541900014254, 17.03207441500009622 42.98261139500014849, ' \ '17.13184655000009116 42.96954987200014386, 17.20020592500009116 42.92177969000012183, ' \ '16.85141035200010151 42.90070221600008438, 16.65544681100004709 42.92625560099999404, ' \ '16.70679772200014668 42.96954987200014386, 16.63168379000003938 42.98261139500014849, ' \ '16.768565300000148 42.97223541900014254)),((17.05567467500011958 43.02895742400001211, ' \ '17.24024498800011429 43.02277252800014651, 17.74146569100011561 42.83926015800001608, ' \ '17.70736738400009358 42.88703034100014122, 17.65334906206413734 42.8909283361407887, ' \ '17.70158573400010482 42.91950022500007833, 17.81175988700005064 42.909862570000044, ' \ '17.85847538200005147 42.81697418200012351, 18.22413781700009849 42.62807098500009317, ' \ '18.43735477700010961 42.55921213800017711, 18.4371480710000526 42.4934022020000981, ' \ '18.49642988400009358 42.41632721600008438, 18.23894290500010129 42.55906810100005089, ' \ '18.21753991000014139 42.6201032570001388, 18.07601972700010151 42.65131256700003348, ' \ '18.0432235040000819 42.70205312700007028, 17.90162194100014403 42.75189850500014188, ' \ '17.8928328790000819 42.79083893400003547, 17.72095787900005348 42.8262393250000315, ' \ '17.7618921230000808 42.77871328300012976, 17.74870853000004445 42.77204010600017625, ' \ '17.21387780000011958 42.98261139500014849, 17.04615319100011561 42.9950625670000619, ' \ '17.00163821700004974 43.05149974200010377, 17.05567467500011958 43.02895742400001211)),' \ '((16.19467207100007045 43.07440827000000638, 16.254893425000148 43.06854889500006323, ' \ '16.08716881600014403 43.01146067900008063, 16.04883873800011429 43.06517161700004692, ' \ '16.19467207100007045 43.07440827000000638)),((16.56275475400011032 43.22898997600010773, ' \ '16.65951582100009887 43.21596914300012315, 16.72771243600001867 43.16461823100003414, ' \ '17.19336998800014271 43.12726471600016964, 16.67017662900013875 43.12547435099999404, ' \ '16.37159264400014536 43.19550202000006323, 16.49642988400006516 43.21808502800014651, ' \ '16.58326256600014403 43.18866608300005794, 16.52051842500006273 43.22898997600010773, ' \ '16.56275475400011032 43.22898997600010773)),((16.80681399800010922 43.34247467700005529, ' \ '16.89234459700011826 43.31220123900006058, 16.84620201900008851 43.27338288000005662, ' \ '16.62826582100012729 43.26373932500008834, 16.50074303500014139 43.28424713700003679, ' \ '16.42188561300008587 43.31757233300011478, 16.40577233200011165 43.33270905200011214, ' \ '16.45346113400009358 43.35317617400009738, 16.42628014400008851 43.39411041900011412, ' \ '16.44703209700008983 43.39484284100014122, 16.80681399800010922 43.34247467700005529)),' \ '((16.29818769600012729 43.40363190300011809, 16.30274498800008587 43.38727448100009099, ' \ '16.39144941500012465 43.34638092700005529, 16.348643425000148 43.33869049700003018, ' \ '16.20045006600014403 43.40704987200003018, 16.29818769600012729 43.40363190300011809)),' \ '((16.33415774800010922 43.50153229400014254, 16.3752547540000819 43.49017975500008504, ' \ '16.21143639400008851 43.49005768400009231, 16.26441491000014139 43.51288483300011478, ' \ '16.33415774800010922 43.50153229400014254)),((15.67888431100004709 43.64801666900014254, ' \ '15.74040774800010922 43.62750885600009099, 15.67204837300002396 43.63743724200010377, ' \ '15.60377037900013875 43.67470937700007028, 15.67888431100004709 43.64801666900014254)),' \ '((15.36736087300005238 43.79010651200015047, 15.39568118600007551 43.7724063170000619, ' \ '15.22779381600014403 43.87445709800014981, 15.24073326900014536 43.88076406500009341, ' \ '15.36736087300005238 43.79010651200015047)),((15.44271894600009887 43.89907461100013109, ' \ '15.35865319100014403 43.91937897300014981, 15.26124108200011165 44.01105377800003282, ' \ '15.38404381600008719 43.9701602230000077, 15.44271894600009887 43.89907461100013109)),' \ '((15.22575931100010393 44.06622955900014915, 15.25440514400008851 44.01788971600014122, ' \ '15.12183678500014139 44.09223053600005926, 15.06251061300008587 44.16193268400012073, ' \ '15.22575931100010393 44.06622955900014915)),((14.83545983200014007 44.15102773600013109, ' \ '14.85726972700010151 44.15204498900000374, 14.86915123800014271 44.14052969000006499, ' \ '14.83521569100008719 44.14166901200009363, 14.81983483200014007 44.15302155199999845, ' \ '14.82243899800005238 44.16868724200004692, 14.83545983200014007 44.15102773600013109)),' \ '((14.98511803500011297 44.09096914300012315, 15.21680748800008587 43.91278717700008372, ' \ '15.13331139400011693 43.92121002800003282, 15.19450931100004709 43.87262604400017096, ' \ '15.10661868600007551 43.92544179900015422, 14.84961998800014271 44.17560455900014915, ' \ '14.98511803500011297 44.09096914300012315)),((14.765961134000122 44.26504140800015819, ' \ '14.74854576900014536 44.26166413000014188, 14.73959394600012729 44.28017812700015554, ' \ '14.79167728000007287 44.27252838700003679, 14.765961134000122 44.26504140800015819)),' \ '((14.66138756600011561 44.30866120000014519, 14.6407983730000808 44.31183502800003282, ' \ '14.59506269600007045 44.34711334800006455, 14.643565300000148 44.32575104400011412, ' \ '14.66138756600011561 44.30866120000014519)),((14.81120853000004445 44.35004303600000242, ' \ '14.75619550900009358 44.36399974200004692, 14.76343834700008983 44.41535065300017493, ' \ '14.80323326900008851 44.40550364800004957, 14.81120853000004445 44.35004303600000242)),' \ '((14.27116946700002131 44.61253489800004957, 14.23259524800005238 44.62604401200012205, ' \ '14.2657983730000808 44.67951080900003547, 14.28044681100007551 44.67755768400009231, ' \ '14.27116946700002131 44.61253489800004957)),((14.84522545700008322 44.60053131700011875, ' \ '14.93824303500014139 44.59414297100001079, 15.07553144600007045 44.48407623900006058, ' \ '14.91114342500011958 44.54547760600014783, 15.04802493600004709 44.43943919500001982, ' \ '15.09669030000009116 44.41518789300000947, 15.04151451900014536 44.47662995000008834, ' \ '15.25440514400008851 44.34003327000000638, 15.165049675000148 44.36737702000006323, ' \ '15.22022545700008322 44.3127302100001117, 15.13086998800008587 44.33258698100003414, ' \ '15.17237389400014536 44.29913971600016964, 15.12875410200007309 44.31199778900018771, ' \ '15.08920332100009887 44.37421295800000109, 15.11719811300014271 44.38719310099999404, ' \ '15.04900149800010922 44.39468008000015686, 14.89747155000009116 44.49091217699999845, ' \ '14.91863040500010129 44.50454336100013109, 14.87696373800011429 44.55975983300005794, ' \ '14.73365319100008719 44.70319245000014519, 14.84522545700008322 44.60053131700011875)),' \ '((14.41000410200010151 44.60097890800001608, 14.52662194100011561 44.50372955900012073, ' \ '14.53435306100010393 44.48407623900006058, 14.42261803500008455 44.57387929900009738, ' \ '14.36304772200014668 44.57343170800000109, 14.38257897200014668 44.60325755399999537, ' \ '14.33578535200007309 44.71678294500010509, 14.39747155000009116 44.6856143250000315, ' \ '14.41000410200010151 44.60097890800001608)),((14.75326582100007045 44.84585195500012844, ' \ '14.74048912900011032 44.82050202000000638, 14.82243899800005238 44.77142975500005662, ' \ '14.84961998800014271 44.70319245000014519, 14.65788821700004974 44.79877350500014188, ' \ '14.7268172540000819 44.79877350500014188, 14.6858016290000819 44.8471540390000456, ' \ '14.75326582100007045 44.84585195500012844)),((14.47103925900006516 44.95392487200003018, ' \ '14.45191491000008455 44.79877350500014188, 14.47217858200011165 44.7079531920000619, ' \ '14.53435306100010393 44.63426341400010244, 14.51335696700007816 44.618841864000089, ' \ '14.42790774800005238 44.65656159100014122, 14.29420006600008719 44.9086367860001161, ' \ '14.30152428500011297 44.94342682500014519, 14.38738040500004445 44.90900299700003018, ' \ '14.39031009200004974 44.96039459800012139, 14.41138756600008719 44.95636627800014651, ' \ '14.27849368600004709 45.1133487000000315, 14.29957116000014139 45.16233958499999801, ' \ '14.35621178500014139 45.16925690300008966, 14.387705925000148 45.03904857000013351, ' \ '14.47103925900006516 44.95392487200003018)),((14.56332441500012465 45.24974192900008063, ' \ '14.62378991000011297 45.17548248900006058, 14.59742272200011826 45.16644928600005926, ' \ '14.66529381600011561 45.16181061400011743, 14.66529381600011561 45.08734772300006455, ' \ '14.74048912900011032 45.07306549700014386, 14.81495201900008851 44.97748444200009033, ' \ '14.70639082100009887 44.9467227230000077, 14.62891686300014271 44.97817617400004053, ' \ '14.62086022200008983 45.04559967700011214, 14.61695397200008983 45.02464427300007799, ' \ '14.51050866000014139 45.03217194200011875, 14.43873131600014403 45.07050202000006323, ' \ '14.4670516290000819 45.12409088700015047, 14.53012129000009622 45.13483307500014519, ' \ '14.53435306100010393 45.23753489800002114, 14.56332441500012465 45.24974192900008063)),' \ '((16.36947066200013978 46.54057118800012915, 16.63767134600004738 46.47447703100009164, ' \ '16.75508020000012266 46.38187286400001597, 16.83765913900006694 46.38187286400001597, ' \ '16.88923221800007468 46.29216257800014489, 17.05294315600005461 46.15346303300005104, ' \ '17.20859257000006437 46.11656606000003933, 17.27587528500004055 46.01202463800002818, ' \ '17.31680301900004793 45.99765859000002877, 17.29013798000011093 45.98463612900009423, ' \ '17.40620324700006449 45.94365671800015605, 17.59110152100009827 45.93621531200012953, ' \ '17.65652388500006964 45.84541982000014571, 17.80917606600013414 45.81441396100005647, ' \ '17.85806197100004056 45.77172922800004073, 18.21121870900006456 45.78537180600012846, ' \ '18.40438521300006869 45.74180857400001798, 18.57347049900010916 45.81668772400014689, ' \ '18.6556360270001278 45.90758656800015558, 18.7755253500000947 45.88283355700004051, ' \ '18.90130578600007993 45.93120269800006383, 18.87288374800004931 45.89523590100002082, ' \ '18.90699019400011593 45.86795074500018643, 18.85531376100007606 45.85735707600009903, ' \ '18.84497847500006174 45.8157058720000947, 18.96848514800012708 45.66873809800016204, ' \ '18.90357954900008508 45.57308502200005762, 18.94171675700005153 45.53892689999999277, ' \ '19.01809452300011571 45.56740061400002162, 19.10625451700005328 45.51164174500017623, ' \ '19.00961958800010621 45.49867095900005154, 19.00300500400010151 45.45536611000007099, ' \ '19.03742150900006891 45.42229319300010104, 18.97592655400006834 45.39495636000008005, ' \ '19.09199182100007874 45.34999786400005917, 19.12475467900009107 45.29811472600006539, ' \ '19.36308638500014467 45.24824696900010679, 19.40783817500010855 45.20313344400013023, ' \ '19.39068160000005037 45.16933705700016333, 19.22593713300008744 45.16194732700016345, ' \ '19.12186079900010327 45.195795390000157, 19.13767378700009658 45.14603098600004216, ' \ '19.04486291500009543 45.13724599300006446, 19.08227665200013234 45.08494944300004192, ' \ '19.0872375890000967 44.97710072800013847, 19.13167932100006396 44.95317454000003465, ' \ '19.06667036900009293 44.90568389900012392, 18.99142948400006503 44.9149339800001286, ' \ '19.01582076000008215 44.86563466400004074, 18.88962691200009658 44.86119049100013001, ' \ '18.78338016700013213 44.91374542300012251, 18.79175174900009893 45.00154368100008639, ' \ '18.73831831900008638 45.0159097290000858, 18.68405806500004473 45.08479441400000098, ' \ '18.64871138500012648 45.06267689999999959, 18.61667199700013953 45.09766184500010411, ' \ '18.54959598800010667 45.09476796500011631, 18.51703983500007666 45.05585561200003042, ' \ '18.23788374800011525 45.15745147700012296, 18.15365116400005263 45.0975584930001645, ' \ '18.00347945100011771 45.1493382780000303, 17.83573775200005684 45.0644338990000648, ' \ '17.68473921700012852 45.1639627080000281, 17.48185754400009273 45.11440500900012296, ' \ '17.49622359200009214 45.1416901650001563, 17.44775109900012922 45.13430043600014585, ' \ '17.44330692500011537 45.16205068000009248, 17.38243208800008688 45.1396231090000839, ' \ '17.26895064300006766 45.18954254200015441, 17.24548954300007608 45.15538442000017483, ' \ '17.18709517400012032 45.14856313100001728, 17.0363033440001459 45.23047027600007652, ' \ '17.00829471800011561 45.21615590500009318, 17.00829471800011561 45.24416453100009505, ' \ '16.94731652900014751 45.23568959600000028, 16.9243721930001243 45.28452382500016427, ' \ '16.81171757000004163 45.18122263700009, 16.52894413300009546 45.22225372400005483, ' \ '16.38921106000003647 45.11683380099999852, 16.31624393700010955 45.00123362300008978, ' \ '16.12152714000009723 45.09616322900008356, 16.02044803900011516 45.213933818000001, ' \ '15.79234826700013627 45.18980092400012438, 15.76361617000014803 44.97555043600003444, ' \ '15.7308533120001357 44.92723297200008403, 15.77343469200010873 44.84501576800015243, ' \ '15.71607385200013596 44.80320953400008932, 15.72847619600008784 44.76910308800002269, ' \ '15.80568078600006743 44.69665273000013883, 15.88877648900006534 44.72424794500012979, ' \ '15.96897831200004703 44.63924021400013942, 16.02830285600006732 44.62471913700009907, ' \ '16.04473596200011798 44.58937245700018082, 16.00608199000004106 44.54100331600012908, ' \ '16.11646285000011858 44.52146962500013672, 16.15966434700004584 44.41610138000002905, ' \ '16.13827030500004867 44.37760243800015303, 16.20286584400008678 44.35977406800010669, ' \ '16.18756962000011868 44.28241444999999032, 16.21578495300011014 44.20815541600011045, ' \ '16.32688928200008149 44.08237498000012522, 16.50103885900011846 43.99271637000008184, ' \ '16.67859908100004418 43.8406843060001421, 16.71260217300007866 43.77151540100005889, ' \ '17.03051558500007445 43.54847991900005866, 17.27050093600007585 43.46321380700000248, ' \ '17.28993127500007176 43.3034302780000786, 17.44206669100009321 43.15243174300015028, ' \ '17.6284119050001209 43.04657257100008394, 17.66272505700004558 42.96569895500012137, ' \ '17.63450972400008254 42.950402731000068, 17.51563561300008587 42.95888906500012183, ' \ '17.47087649800005238 43.01341380400010905, 17.50196373800014271 43.03099192900005221, ' \ '17.43360436300014271 43.01740143400009231, 17.46021569100011561 43.03099192900005221, ' \ '17.42611738400009358 43.06517161700004692, 17.4045516290000819 43.05149974200010377, ' \ '17.31625410200012993 43.12726471600016964, 17.11394290500004445 43.21320221600008438, ' \ '16.88062584700011826 43.40595123900006058, 16.62582441500009622 43.44904205900009231, ' \ '16.52466881600011561 43.51080963700009363, 16.39144941500012465 43.51080963700009363, ' \ '16.47339928500008455 43.5381533870001789, 16.43384850400013875 43.54975006700000506, ' \ '16.11768639400008851 43.52448151200003679, 16.17237389400014536 43.4896914730000077, ' \ '16.11312910200004467 43.47890859600009605, 15.95948326900011693 43.50397370000008834, ' \ '15.987315300000148 43.54490794500010509, 15.92530358200011165 43.55857982000004824, ' \ '15.91895592500009116 43.62872955900012073, 15.96631920700011165 43.64118073100003414, ' \ '15.90479576900014536 43.64801666900014254, 15.95297285200010151 43.65086497599999404, ' \ '15.95045006600008719 43.68854401200015047, 15.70630944100008719 43.76341380400005221, ' \ '15.6174422540000819 43.82550690300017493, 15.66309655000009116 43.81297435099999404, ' \ '15.67888431100004709 43.81928131700011875, 15.45508873800014271 43.92804596600014122, ' \ '15.14454186300011429 44.19546133000015686, 15.15219160200012993 44.23529694200014717, ' \ '15.11036217500011958 44.26434967700011214, 15.14063561300011429 44.28245677300013483, ' \ '15.17660566500009622 44.24994538000005662, 15.20777428500008455 44.27277252800014651, ' \ '15.19809004000012465 44.30166250200007028, 15.295258009000122 44.25067780199999845, ' \ '15.30274498800008587 44.29913971600016964, 15.26124108200011165 44.33258698100003414, ' \ '15.42448978000001603 44.26797109600006763, 15.52865644600009887 44.27179596600008438, ' \ '15.30795332100009887 44.35439687700007028, 15.00733483200014007 44.56972890800012976, ' \ '14.883799675000148 44.7236188820001388, 14.883799675000148 44.86147695500012844, 14.92164147200008983 ' \ '44.95880768400009231, 14.85279381600011561 45.09365469000000815, 14.65788821700004974 ' \ '45.19660065300017493, 14.57081139400008851 45.29364655200011214, 14.31153405000009116 ' \ '45.34398021000005485, 14.23259524800005238 45.14935944200000506, 14.17937259200007816 ' \ '45.13450755400005221, 14.19312584700008983 45.10561758000012844, 14.14389082100007045 ' \ '45.05939362200003018, 14.151377800000148 44.97748444200009033, 14.06885826900014536 ' \ '44.94953034100014122, 14.08383222700007309 44.9863955750000315, 14.04029381600014403 ' \ '45.03896719000015025, 14.0756942070000548 44.98371002800003282, 14.02051842500011958 ' \ '44.90110911700004692, 13.97266686300011429 44.90110911700004692, 13.99301191500009622 ' \ '44.88129303600014453, 13.97266686300011429 44.82664622599999404, 14.00001061300008587 ' \ '44.81305573100003414, 13.89014733200011165 44.83348216400010244, 13.91797936300014271 ' \ '44.77826569200009033, 13.90316816500009622 44.77240631700014717, 13.89698326900011693 ' \ '44.81305573100003414, 13.78711998800014271 44.87506745000008834, 13.84229576900008851 ' \ '44.88812897300006455, 13.79460696700010658 44.89496491100008768, 13.77409915500007287 ' \ '44.96381256700014717, 13.6232202480000808 45.07306549700014386, 13.61255944100014403 ' \ '45.11786530199999845, 13.72624759200004974 45.13450755400005221, 13.5959578790000819 ' \ '45.14541250200001343, 13.57545006600011561 45.26487864800007799, 13.60271243600001867 ' \ '45.28534577000012007, 13.57545006600011561 45.30646393400006389, 13.60954837300005238 ' \ '45.32013580900017757, 13.54127037900013875 45.34613678600005926, 13.50709069100014403 ' \ '45.51190827000000638, 13.62901778100007277 45.45898346000016943, 13.75929406800014476 ' \ '45.46316925100011019, 13.88900191200011136 45.42363678000005223, 13.98263960800005634 ' \ '45.47531321200001742, 13.97189091000012695 45.5142255660000643, 14.09291711400010172 ' \ '45.47391794800002174, 14.21869755100007637 45.49717234400004884, 14.37279667100006009 ' \ '45.47784535800009564, 14.4689148350000778 45.52559438100014688, 14.49857710800012001 ' \ '45.59618438800005435, 14.58094934100009255 45.66780792200010808, 14.66848921700008646 ' \ '45.53396596300005683, 14.79716353300005949 45.46518463200006011, 14.88160282300009385 ' \ '45.46978383400001178, 14.9226339110000481 45.51494903600017494, 15.13926151500010064 ' \ '45.43004465799999991, 15.32519331800011742 45.45283396399999276, 15.36136682100004691 ' \ '45.48203114900003641, 15.29666792800006192 45.52295888300012905, 15.2685559480001416 ' \ '45.60166208900012919, 15.37376916500011248 45.64021270800010655, 15.25501672300006817 ' \ '45.72346344000011698, 15.42906294700014769 45.77529490200011253, 15.45128381300008868 ' \ '45.81513743100013869, 15.67607629400006886 45.84169911700014666, 15.65943648300003588 ' \ '45.88882802400014782, 15.69798710100010908 46.0362092080000167, 15.58988000500005455 ' \ '46.11351715100001059, 15.62284956800010605 46.19170359400006021, 16.01920780400010358 ' \ '46.29882883700007312, 16.05961877400008575 46.33231516600015709, 16.0579651280001201 ' \ '46.37753204400003426, 16.2756262620000598 46.37316538500006402, 16.23490523300009158 ' \ '46.4933389280001137, 16.36947066200013978 46.54057118800012915))) ' geom = QgsGeometry.fromWkt(wkt) f = QgsFeature() f.setGeometry(geom) image = QImage(200, 200, QImage.Format_RGB32) painter = QPainter() ms = QgsMapSettings() crs = QgsCoordinateReferenceSystem.fromProj4('+proj=ortho +lat_0=36.5 +lon_0=-118.8 +x_0=0 +y_0=0 +a=6371000 +b=6371000 +units=m +no_defs') self.assertTrue(crs.isValid()) ms.setDestinationCrs(crs) ms.setExtent(QgsRectangle(1374999.8, 3912610.7, 4724462.5, 6505499.6)) ms.setOutputSize(image.size()) context = QgsRenderContext.fromMapSettings(ms) context.setPainter(painter) context.setScaleFactor(96 / 25.4) # 96 DPI ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem('epsg:4326'), crs, QgsProject.instance()) self.assertTrue(ct.isValid()) context.setCoordinateTransform(ct) context.setExtent(ct.transformBoundingBox(ms.extent(), QgsCoordinateTransform.ReverseTransform)) fill_symbol = QgsFillSymbol.createSimple({'color': '#ffffff', 'outline_color': '#ffffff', 'outline_width': '10'}) painter.begin(image) try: image.fill(QColor(0, 0, 0)) fill_symbol.startRender(context) fill_symbol.renderFeature(f, context) fill_symbol.stopRender(context) finally: painter.end() assert self.imageCheck('Reprojection errors polygon', 'reprojection_errors_polygon', image) #also test linestring linestring = QgsGeometry(geom.constGet().boundary()) f.setGeometry(linestring) line_symbol = QgsLineSymbol.createSimple({'color': '#ffffff', 'outline_width': '10'}) image = QImage(200, 200, QImage.Format_RGB32) painter.begin(image) try: image.fill(QColor(0, 0, 0)) line_symbol.startRender(context) line_symbol.renderFeature(f, context) line_symbol.stopRender(context) finally: painter.end() assert self.imageCheck('Reprojection errors linestring', 'reprojection_errors_linestring', image)
def _cellsize(layer): ext = layer.extent() if layer.crs() != crs: transform = QgsCoordinateTransform(layer.crs(), crs, context.project()) ext = transform.transformBoundingBox(ext) return (ext.xMaximum() - ext.xMinimum()) / layer.width()
def processAlgorithm(self, parameters, context, feedback): expression = self.parameterAsString(parameters, self.EXPRESSION, context) layers = self.parameterAsLayerList(parameters, self.LAYERS, context) layersDict = {} if layers: layersDict = {lyr.source(): lyr for lyr in layers} crs = self.parameterAsCrs(parameters, self.CRS, context) if crs is None or not crs.isValid(): if not layers: raise QgsProcessingException( self.tr("No reference layer selected nor CRS provided")) else: crs = list(layersDict.values())[0].crs() bbox = self.parameterAsExtent(parameters, self.EXTENT, context) if bbox.isNull() and not layers: raise QgsProcessingException( self.tr("No reference layer selected nor extent box provided")) if not bbox.isNull(): bboxCrs = self.parameterAsExtentCrs(parameters, self.EXTENT, context) if bboxCrs != crs: transform = QgsCoordinateTransform(bboxCrs, crs, context.project()) bbox = transform.transformBoundingBox(bbox) if bbox.isNull() and layers: bbox = QgsProcessingUtils.combineLayerExtents(layers, crs, context) cellsize = self.parameterAsDouble(parameters, self.CELLSIZE, context) if cellsize == 0 and not layers: raise QgsProcessingException( self.tr( "No reference layer selected nor cellsize value provided")) def _cellsize(layer): ext = layer.extent() if layer.crs() != crs: transform = QgsCoordinateTransform(layer.crs(), crs, context.project()) ext = transform.transformBoundingBox(ext) return (ext.xMaximum() - ext.xMinimum()) / layer.width() if cellsize == 0: cellsize = min([_cellsize(lyr) for lyr in layersDict.values()]) # check for layers available in the model layersDictCopy = layersDict.copy( ) # need a shallow copy because next calls invalidate iterator for lyr in layersDictCopy.values(): expression = self.mappedNameToLayer(lyr, expression, layersDict, context) # check for layers available in the project for lyr in QgsProcessingUtils.compatibleRasterLayers( context.project()): expression = self.mappedNameToLayer(lyr, expression, layersDict, context) # create the list of layers to be passed as inputs to RasterCalculaltor # at this phase expression has been modified to match available layers # in the current scope entries = [] for name, lyr in layersDict.items(): for n in range(lyr.bandCount()): ref = '{:s}@{:d}'.format(name, n + 1) if ref in expression: entry = QgsRasterCalculatorEntry() entry.ref = ref entry.raster = lyr entry.bandNumber = n + 1 entries.append(entry) # Append any missing entry from the current project for entry in QgsRasterCalculatorEntry.rasterEntries(): if not [e for e in entries if e.ref == entry.ref]: entries.append(entry) output = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) width = round((bbox.xMaximum() - bbox.xMinimum()) / cellsize) height = round((bbox.yMaximum() - bbox.yMinimum()) / cellsize) driverName = GdalUtils.getFormatShortNameFromFilename(output) calc = QgsRasterCalculator(expression, output, driverName, bbox, crs, width, height, entries, context.transformContext()) res = calc.processCalculation(feedback) if res == QgsRasterCalculator.ParserError: raise QgsProcessingException(self.tr("Error parsing formula")) return {self.OUTPUT: output}
def _clip_vector_layer(layer, extent, extra_keywords=None, explode_flag=True, hard_clip_flag=False, explode_attribute=None): """Clip a Hazard or Exposure layer to the extents provided. The layer must be a vector layer or an exception will be thrown. The output layer will always be in WGS84/Geographic. :param layer: A valid QGIS vector or raster layer :type layer: :param extent: Either 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. or: A QgsGeometry of type polygon. **Polygon clipping is currently only supported for vector datasets.** :type extent: list(float, float, float, float) :param extra_keywords: Optional keywords dictionary to be added to output layer. :type extra_keywords: dict :param explode_flag: A bool specifying whether multipart features should be 'exploded' into singleparts. **This parameter is ignored for raster layer clipping.** :type explode_flag: bool :param hard_clip_flag: A bool specifying whether line and polygon features that extend beyond the extents should be clipped such that they are reduced in size to the part of the geometry that intersects the extent only. Default is False. **This parameter is ignored for raster layer clipping.** :type hard_clip_flag: bool :param explode_attribute: A str specifying to which attribute #1, #2 and so on will be added in case of explode_flag being true. The attribute is modified only if there are at least 2 parts. :type explode_attribute: str :returns: Clipped layer (placed in the system temp dir). The output layer will be reprojected to EPSG:4326 if needed. :rtype: QgsVectorLayer """ if not layer or not extent: message = tr('Layer or Extent passed to clip is None.') raise InvalidParameterError(message) if layer.type() != QgsMapLayer.VectorLayer: message = tr('Expected a vector layer but received a %s.' % str(layer.type())) raise InvalidParameterError(message) # handle, file_name = tempfile.mkstemp('.sqlite', 'clip_', # temp_dir()) handle, file_name = tempfile.mkstemp('.shp', 'clip_', temp_dir()) # 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(handle) os.remove(file_name) # Get the clip extents in the layer's native CRS geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) transform = QgsCoordinateTransform(geo_crs, layer.crs()) allowed_clip_values = [QGis.WKBPolygon, QGis.WKBPolygon25D] if isinstance(extent, list): rectangle = QgsRectangle(extent[0], extent[1], extent[2], extent[3]) # noinspection PyCallByClass # noinspection PyTypeChecker polygon = QgsGeometry.fromRect(rectangle) elif (isinstance(extent, QgsGeometry) and extent.wkbType in allowed_clip_values): rectangle = extent.boundingBox().toRectF() polygon = extent else: raise InvalidClipGeometryError( tr('Clip geometry must be an extent or a single part' 'polygon based geometry.')) projected_extent = transform.transformBoundingBox(rectangle) # Get vector layer provider = layer.dataProvider() if provider is None: message = tr('Could not obtain data provider from ' 'layer "%s"' % layer.source()) raise Exception(message) # 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 request = QgsFeatureRequest() if not projected_extent.isEmpty(): request.setFilterRect(projected_extent) request.setFlags(QgsFeatureRequest.ExactIntersect) field_list = provider.fields() writer = QgsVectorFileWriter( file_name, None, field_list, layer.wkbType(), geo_crs, # 'SQLite') # FIXME (Ole): This works but is far too slow 'ESRI Shapefile') if writer.hasError() != QgsVectorFileWriter.NoError: message = tr('Error when creating shapefile: <br>Filename:' '%s<br>Error: %s' % (file_name, writer.hasError())) raise Exception(message) # Reverse the coordinate xform now so that we can convert # geometries from layer crs to geocrs. transform = QgsCoordinateTransform(layer.crs(), geo_crs) # Retrieve every feature with its geometry and attributes count = 0 has_multipart = False for feature in provider.getFeatures(request): geometry = feature.geometry() # Loop through the parts adding them to the output file # we write out single part features unless explode_flag is False if explode_flag: geometry_list = explode_multipart_geometry(geometry) else: geometry_list = [geometry] for part_index, part in enumerate(geometry_list): part.transform(transform) if hard_clip_flag: # Remove any dangling bits so only intersecting area is # kept. part = clip_geometry(polygon, part) if part is None: continue feature.setGeometry(part) # There are multiple parts and we want to show it in the # explode_attribute if part_index > 0 and explode_attribute is not None: has_multipart = True writer.addFeature(feature) count += 1 del writer # Flush to disk if count < 1: message = tr( 'No features fall within the clip extents. Try panning / zooming ' 'to an area containing data and then try to run your analysis ' 'again. If hazard and exposure data doesn\'t overlap at all, it ' 'is not possible to do an analysis. Another possibility is that ' 'the layers do overlap but because they may have different ' 'spatial references, they appear to be disjointed. If this is the ' 'case, try to turn on reproject on-the-fly in QGIS.') raise NoFeaturesInExtentError(message) keyword_io = KeywordIO() if extra_keywords is None: extra_keywords = {} extra_keywords[multipart_polygon_key] = has_multipart keyword_io.copy_keywords(layer, file_name, extra_keywords=extra_keywords) base_name = '%s clipped' % layer.name() layer = QgsVectorLayer(file_name, base_name, 'ogr') return layer
def processAlgorithm(self, parameters, context, feedback): feedback.setProgress(1) extent = self.parameterAsExtent(parameters, self.EXTENT, context) min_zoom = self.parameterAsInt(parameters, self.ZOOM_MIN, context) max_zoom = self.parameterAsInt(parameters, self.ZOOM_MAX, context) dpi = self.parameterAsInt(parameters, self.DPI, context) tile_format = self.formats[self.parameterAsEnum( parameters, self.TILE_FORMAT, context)] output_format = self.outputs[self.parameterAsEnum( parameters, self.OUTPUT_FORMAT, context)] if output_format == 'Directory': output_dir = self.parameterAsString(parameters, self.OUTPUT_DIRECTORY, context) if not output_dir: raise QgsProcessingException( self.tr('You need to specify output directory.')) else: # MBTiles output_file = self.parameterAsString(parameters, self.OUTPUT_FILE, context) if not output_file: raise QgsProcessingException( self.tr('You need to specify output filename.')) tile_width = 256 tile_height = 256 wgs_crs = QgsCoordinateReferenceSystem('EPSG:4326') dest_crs = QgsCoordinateReferenceSystem('EPSG:3857') project = context.project() src_to_wgs = QgsCoordinateTransform(project.crs(), wgs_crs, context.transformContext()) wgs_to_dest = QgsCoordinateTransform(wgs_crs, dest_crs, context.transformContext()) settings = QgsMapSettings() settings.setOutputImageFormat(QImage.Format_ARGB32_Premultiplied) settings.setDestinationCrs(dest_crs) settings.setLayers(self.layers) settings.setOutputDpi(dpi) wgs_extent = src_to_wgs.transformBoundingBox(extent) wgs_extent = [ wgs_extent.xMinimum(), wgs_extent.yMinimum(), wgs_extent.xMaximum(), wgs_extent.yMaximum() ] metatiles_by_zoom = {} metatiles_count = 0 for zoom in range(min_zoom, max_zoom + 1): metatiles = get_metatiles(wgs_extent, zoom, 4) metatiles_by_zoom[zoom] = metatiles metatiles_count += len(metatiles) lab_buffer_px = 100 progress = 0 tile_params = { 'format': tile_format, 'quality': 75, 'width': tile_width, 'height': tile_height } if output_format == 'Directory': writer = DirectoryWriter(output_dir, tile_params) else: writer = MBTilesWriter(output_file, tile_params, wgs_extent, min_zoom, max_zoom) for zoom in range(min_zoom, max_zoom + 1): feedback.pushConsoleInfo('Generating tiles for zoom level: %s' % zoom) for i, metatile in enumerate(metatiles_by_zoom[zoom]): size = QSize(tile_width * metatile.rows(), tile_height * metatile.columns()) extent = QgsRectangle(*metatile.extent()) settings.setExtent(wgs_to_dest.transformBoundingBox(extent)) settings.setOutputSize(size) label_area = QgsRectangle(settings.extent()) lab_buffer = label_area.width() * (lab_buffer_px / size.width()) label_area.set(label_area.xMinimum() + lab_buffer, label_area.yMinimum() + lab_buffer, label_area.xMaximum() - lab_buffer, label_area.yMaximum() - lab_buffer) settings.setLabelBoundaryGeometry( QgsGeometry.fromRect(label_area)) image = QImage(size, QImage.Format_ARGB32_Premultiplied) image.fill(Qt.transparent) dpm = settings.outputDpi() / 25.4 * 1000 image.setDotsPerMeterX(dpm) image.setDotsPerMeterY(dpm) painter = QPainter(image) job = QgsMapRendererCustomPainterJob(settings, painter) job.renderSynchronously() painter.end() # For analysing metatiles (labels, etc.) # metatile_dir = os.path.join(output_dir, str(zoom)) # os.makedirs(metatile_dir, exist_ok=True) # image.save(os.path.join(metatile_dir, 'metatile_%s.png' % i)) for r, c, tile in metatile.tiles: tile_img = image.copy(tile_width * r, tile_height * c, tile_width, tile_height) writer.writeTile(tile, tile_img) progress += 1 feedback.setProgress(100 * (progress / metatiles_count)) writer.close() results = {} if output_format == 'Directory': results['OUTPUT_DIRECTORY'] = output_dir else: # MBTiles results['OUTPUT_FILE'] = output_file return results
def _clipVectorLayer(theLayer, theExtent, extraKeywords=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. Returns: Path to the output clipped layer (placed in the system temp dir). Raises: None """ if not theLayer or not theExtent: msg = tr('Layer or Extent passed to clip is None.') raise InvalidParameterException(msg) if theLayer.type() != QgsMapLayer.VectorLayer: msg = tr('Expected a vector layer but received a %s.' % str(theLayer.type())) raise InvalidParameterException(msg) 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: msg = tr('Could not obtain data provider from ' 'layer "%s"' % theLayer.source()) raise Exception(msg) # 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: msg = tr('Error when creating shapefile: <br>Filename:' '%s<br>Error: %s' % (myFilename, myWriter.hasError())) raise Exception(msg) # 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() myGeometry.transform(myXForm) myFeature.setGeometry(myGeometry) 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) copyKeywords(theLayer.source(), myFilename, extraKeywords=extraKeywords) return myFilename # Filename of created file
def __init__(self, iface, parent=None, extent=None, crs=None): """Constructor for the dialog. :param iface: A Quantum GIS QgisAppInterface instance. :type iface: QgisAppInterface :param parent: Parent widget of this dialog. :type parent: QWidget :param extent: Extent of the user's preferred analysis area. :type extent: QgsRectangle :param crs: CRS for user defined analysis extent. :type crs: QgsCoordinateReferenceSystem """ QDialog.__init__(self, parent) self.setupUi(self) icon = resources_path('img', 'icons', 'set-extents-tool.svg') self.setWindowIcon(QIcon(icon)) self.iface = iface self.parent = parent self.canvas = iface.mapCanvas() self.previous_map_tool = None # Prepare the map tool self.tool = RectangleMapTool(self.canvas) self.previous_map_tool = self.canvas.mapTool() if extent is None: # Use the current map canvas extents as a starting point self.tool.set_rectangle(self.canvas.extent()) else: if isinstance(extent, QgsGeometry): # In InaSAFE V4, the extent is a QgsGeometry. # This like a hack to transform a geometry to a rectangle. extent = wkt_to_rectangle(extent.asWkt()) # Ensure supplied extent is in current canvas crs transform = QgsCoordinateTransform( crs, self.canvas.mapSettings().destinationCrs(), QgsProject.instance()) transformed_extent = transform.transformBoundingBox(extent) self.tool.set_rectangle(transformed_extent) self._populate_coordinates() # Observe inputs for changes self.x_minimum.valueChanged.connect(self._coordinates_changed) self.y_minimum.valueChanged.connect(self._coordinates_changed) self.x_maximum.valueChanged.connect(self._coordinates_changed) self.y_maximum.valueChanged.connect(self._coordinates_changed) # Draw the rubberband self._coordinates_changed() # Wire up button events self.capture_button.clicked.connect(self.start_capture) # Handle cancel cancel_button = self.button_box.button( QtWidgets.QDialogButtonBox.Cancel) cancel_button.clicked.connect(self.reject) # Make sure to reshow this dialog when rectangle is captured self.tool.rectangle_created.connect(self.stop_capture) # Setup ok button self.ok_button = self.button_box.button(QtWidgets.QDialogButtonBox.Ok) self.ok_button.clicked.connect(self.accept) # Set up context help self.help_button = self.button_box.button( QtWidgets.QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) self.main_stacked_widget.setCurrentIndex(1) # Reset / Clear button clear_button = self.button_box.button(QtWidgets.QDialogButtonBox.Reset) clear_button.setText(self.tr('Clear')) clear_button.clicked.connect(self.clear) # Populate the bookmarks list and connect the combobox self._populate_bookmarks_list() self.bookmarks_list.currentIndexChanged.connect( self.bookmarks_index_changed) # Reinstate the last used radio button mode = setting('analysis_extents_mode', HAZARD_EXPOSURE_VIEW) if mode == HAZARD_EXPOSURE_VIEW: self.hazard_exposure_view_extent.setChecked(True) elif mode == EXPOSURE: self.exposure_only.setChecked(True) elif mode == HAZARD_EXPOSURE: self.hazard_exposure_only.setChecked(True) elif mode == HAZARD_EXPOSURE_BOOKMARK: self.hazard_exposure_bookmark.setChecked(True) elif mode == HAZARD_EXPOSURE_BOUNDINGBOX: self.hazard_exposure_user_extent.setChecked(True) self.show_warnings.setChecked( setting('show_extent_warnings', True, bool)) self.show_confirmations.setChecked( setting('show_extent_confirmations', True, bool))
def __init__(self, iface, parent=None, extent=None, crs=None): """Constructor for the dialog. :param iface: A Quantum GIS QGisAppInterface instance. :type iface: QGisAppInterface :param parent: Parent widget of this dialog :type parent: QWidget :param extent: Extent of the user's preferred analysis area. :type extent: QgsRectangle :param crs: Coordinate reference system for user defined analysis extent. :type crs: QgsCoordinateReferenceSystem """ QDialog.__init__(self, parent) self.setupUi(self) self.iface = iface self.parent = parent self.canvas = iface.mapCanvas() self.previous_map_tool = None # Prepare the map tool self.tool = RectangleMapTool(self.canvas) self.previous_map_tool = self.canvas.mapTool() if extent is None and crs is None: # Use the current map canvas extents as a starting point self.tool.set_rectangle(self.canvas.extent()) else: # Ensure supplied extent is in current canvas crs transform = QgsCoordinateTransform( crs, self.canvas.mapRenderer().destinationCrs()) transformed_extent = transform.transformBoundingBox(extent) self.tool.set_rectangle(transformed_extent) self._populate_coordinates() # Observe inputs for changes self.x_minimum.valueChanged.connect(self._coordinates_changed) self.y_minimum.valueChanged.connect(self._coordinates_changed) self.x_maximum.valueChanged.connect(self._coordinates_changed) self.y_maximum.valueChanged.connect(self._coordinates_changed) # Draw the rubberband self._coordinates_changed() # Wire up button events self.capture_button.clicked.connect(self.start_capture) # Handle cancel cancel_button = self.button_box.button(QtGui.QDialogButtonBox.Cancel) cancel_button.clicked.connect(self.reject) # Make sure to reshow this dialog when rectangle is captured self.tool.rectangle_created.connect(self.stop_capture) # Setup ok button self.ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok) self.ok_button.clicked.connect(self.accept) # Set up context help self.help_button = self.button_box.button(QtGui.QDialogButtonBox.Help) # Allow toggling the help button self.help_button.setCheckable(True) self.help_button.toggled.connect(self.help_toggled) self.main_stacked_widget.setCurrentIndex(1) # Reset / Clear button clear_button = self.button_box.button(QtGui.QDialogButtonBox.Reset) clear_button.setText(self.tr('Clear')) clear_button.clicked.connect(self.clear) # Populate the bookmarks list and connect the combobox self._populate_bookmarks_list() self.bookmarks_list.currentIndexChanged.connect( self.bookmarks_index_changed) # Reinstate the last used radio button settings = QSettings() mode = settings.value( 'inasafe/analysis_extents_mode', 'HazardExposureView') if mode == 'HazardExposureView': self.hazard_exposure_view_extent.setChecked(True) elif mode == 'HazardExposure': self.hazard_exposure_only.setChecked(True) elif mode == 'HazardExposureBookmark': self.hazard_exposure_bookmark.setChecked(True) elif mode == 'HazardExposureBoundingBox': self.hazard_exposure_user_extent.setChecked(True) show_warnings = settings.value( 'inasafe/show_extent_warnings', True, type=bool) if show_warnings: self.show_warnings.setChecked(True) else: self.show_warnings.setChecked(False) show_confirmations = settings.value( 'inasafe/show_extent_confirmations', True, type=bool) if show_confirmations: self.show_confirmations.setChecked(True) else: self.show_confirmations.setChecked(False)
def build_layer_table(layer_list=None, only_raster_boundingbox=True): """Build a table of layer properties. Can be used in conjunction with selecting layers to exclude from mapcomboboxes Layer_list: default None if None then it will build the table from all layers in the QGIS project otherwise it will use the list. only_raster_boundingbox: default False create a bounding box from the raster data ie removing nodata from polygon. This will slow it down if large numbers of rasters are present. """ dest_crs = QgsProject.instance().crs() gdf_layers = gpd.GeoDataFrame(columns=[ 'layer', 'layer_name', 'layer_id', 'layer_type', 'source', 'format', 'epsg', 'crs_name', 'is_projected', 'extent', 'provider', 'geometry' ], geometry='geometry', crs=dest_crs.authid()) # pd.DataFrame() if layer_list is None or len(layer_list) == 0: layermap = QgsProject.instance().mapLayers().values() else: layermap = layer_list new_rows = [] for layer in layermap: if layer.type() not in [ QgsMapLayer.VectorLayer, QgsMapLayer.RasterLayer ]: continue if layer.providerType() not in ['ogr', 'gdal', 'delimitedtext']: continue if layer.type() == QgsMapLayer.VectorLayer: format = layer.dataProvider().storageType() else: format = None if layer.crs().isValid() and layer.crs().authid() == '': # Try and convert older style coordinates systems # were correctly definied in QGIS 2 as GDA94 / MGA zone 54 # but get interpreted in QGIS 3 as Unknown CRS: BOUNDCRS[SOURCECRS[PROJCRS["GDA94 / MGA zone 54",..... layer_crs = QgsCoordinateReferenceSystem() if not layer_crs.createFromProj(layer.crs().toWkt()): #print('Could not match a coordinate system for {}'.format(layer.id())) layer_crs = layer.crs() # could apply to the layer, but what if it's wrong.... #layer.setCrs(layer_crs) else: layer_crs = layer.crs() # project the bounding box extents to be the same as the qgis project. if layer_crs.authid() != dest_crs.authid(): transform = QgsCoordinateTransform(layer_crs, dest_crs, QgsProject.instance()) prj_ext = transform.transformBoundingBox(layer.extent()) else: prj_ext = layer.extent() row_dict = { 'layer': layer, 'layer_name': layer.name(), 'layer_id': layer.id(), 'layer_type': layerTypes[layer.type()], 'format': format, 'source': get_layer_source(layer), 'epsg': layer_crs.authid(), 'crs_name': layer_crs.description(), 'is_projected': not layer_crs.isGeographic(), 'provider': layer.providerType(), 'geometry': wkt.loads(prj_ext.asWktPolygon()) } # 'extent': prj_ext.asWktPolygon(), if layer.type() == QgsMapLayer.RasterLayer: pixel_size = get_pixel_size(layer) if not only_raster_boundingbox: with rasterio.open(get_layer_source(layer)) as src: msk = src.dataset_mask() # 0 = nodata 255=valid rast_shapes = rasterio.features.shapes( np.ma.masked_equal(np.where(msk > 0, 1, 0), 0), transform=src.transform) try: results = ({ 'properties': { 'raster_val': v }, 'geometry': s } for i, (s, v) in enumerate(rast_shapes)) geoms = list(results) gpd_rPoly = gpd.GeoDataFrame.from_features( geoms, crs=layer.crs().authid()) dest_crs.authid().replace('epgs:', '') gpd_rPoly.to_crs(dest_crs.authid().replace('epgs:', ''), inplace=True) row_dict.update({'geometry': gpd_rPoly.unary_union}) del gpd_rPoly, results, msk, rast_shapes except: pass row_dict.update({ 'bandcount': layer.bandCount(), 'datatype': dataTypes[layer.dataProvider().dataType(1)], 'pixel_size': pixel_size[0], 'pixel_text': '{} {}'.format(*pixel_size), }) new_rows.append(row_dict) # gdf_layers = gpd.GeoDataFrame(new_rows, geometry='extent') if len(new_rows) == 0: return gdf_layers # for pandas 0.23.4 add sort=False to prevent row and column orders to change. try: gdf_layers = gdf_layers.append(new_rows, ignore_index=True, sort=False) except: gdf_layers = gdf_layers.append(new_rows, ignore_index=True) #df_layers.set_geometry('geometry') return gdf_layers
def processAlgorithm(self, parameters, context, feedback): expression = self.parameterAsString(parameters, self.EXPRESSION, context) layers = self.parameterAsLayerList(parameters, self.LAYERS, context) layersDict = {} if layers: layersDict = {os.path.basename(lyr.source().split(".")[0]): lyr for lyr in layers} crs = self.parameterAsCrs(parameters, self.CRS, context) if not layers and not crs.isValid(): raise QgsProcessingException(self.tr("No reference layer selected nor CRS provided")) if not crs.isValid() and layers: crs = list(layersDict.values())[0].crs() bbox = self.parameterAsExtent(parameters, self.EXTENT, context) if not layers and bbox.isNull(): raise QgsProcessingException(self.tr("No reference layer selected nor extent box provided")) if not bbox.isNull(): bboxCrs = self.parameterAsExtentCrs(parameters, self.EXTENT, context) if bboxCrs != crs: transform = QgsCoordinateTransform(bboxCrs, crs, context.project()) bbox = transform.transformBoundingBox(bbox) if bbox.isNull() and layers: bbox = QgsProcessingUtils.combineLayerExtents(layers, crs) cellsize = self.parameterAsDouble(parameters, self.CELLSIZE, context) if not layers and cellsize == 0: raise QgsProcessingException(self.tr("No reference layer selected nor cellsize value provided")) def _cellsize(layer): ext = layer.extent() if layer.crs() != crs: transform = QgsCoordinateTransform(layer.crs(), crs, context.project()) ext = transform.transformBoundingBox(ext) return (ext.xMaximum() - ext.xMinimum()) / layer.width() if cellsize == 0: cellsize = min([_cellsize(lyr) for lyr in layersDict.values()]) for lyr in QgsProcessingUtils.compatibleRasterLayers(context.project()): name = lyr.name() if (name + "@") in expression: layersDict[name] = lyr entries = [] for name, lyr in layersDict.items(): for n in range(lyr.bandCount()): ref = '{:s}@{:d}'.format(name, n + 1) if ref in expression: entry = QgsRasterCalculatorEntry() entry.ref = ref entry.raster = lyr entry.bandNumber = n + 1 entries.append(entry) output = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) width = math.floor((bbox.xMaximum() - bbox.xMinimum()) / cellsize) height = math.floor((bbox.yMaximum() - bbox.yMinimum()) / cellsize) driverName = GdalUtils.getFormatShortNameFromFilename(output) calc = QgsRasterCalculator(expression, output, driverName, bbox, crs, width, height, entries) res = calc.processCalculation(feedback) if res == QgsRasterCalculator.ParserError: raise QgsProcessingException(self.tr("Error parsing formula")) return {self.OUTPUT: output}
def processAlgorithm(self, parameters, context, feedback): feedback.setProgress(1) extent = self.parameterAsExtent(parameters, self.EXTENT, context) min_zoom = self.parameterAsInt(parameters, self.ZOOM_MIN, context) max_zoom = self.parameterAsInt(parameters, self.ZOOM_MAX, context) dpi = self.parameterAsInt(parameters, self.DPI, context) tile_format = self.formats[self.parameterAsEnum(parameters, self.TILE_FORMAT, context)] output_format = self.outputs[self.parameterAsEnum(parameters, self.OUTPUT_FORMAT, context)] if output_format == 'Directory': output_dir = self.parameterAsString(parameters, self.OUTPUT_DIRECTORY, context) if not output_dir: raise QgsProcessingException(self.tr('You need to specify output directory.')) else: # MBTiles output_file = self.parameterAsString(parameters, self.OUTPUT_FILE, context) if not output_file: raise QgsProcessingException(self.tr('You need to specify output filename.')) tile_width = 256 tile_height = 256 wgs_crs = QgsCoordinateReferenceSystem('EPSG:4326') dest_crs = QgsCoordinateReferenceSystem('EPSG:3857') project = context.project() src_to_wgs = QgsCoordinateTransform(project.crs(), wgs_crs, context.transformContext()) wgs_to_dest = QgsCoordinateTransform(wgs_crs, dest_crs, context.transformContext()) settings = QgsMapSettings() settings.setOutputImageFormat(QImage.Format_ARGB32_Premultiplied) settings.setDestinationCrs(dest_crs) settings.setLayers(self.layers) settings.setOutputDpi(dpi) wgs_extent = src_to_wgs.transformBoundingBox(extent) wgs_extent = [wgs_extent.xMinimum(), wgs_extent.yMinimum(), wgs_extent.xMaximum(), wgs_extent.yMaximum()] metatiles_by_zoom = {} metatiles_count = 0 for zoom in range(min_zoom, max_zoom + 1): metatiles = get_metatiles(wgs_extent, zoom, 4) metatiles_by_zoom[zoom] = metatiles metatiles_count += len(metatiles) lab_buffer_px = 100 progress = 0 tile_params = { 'format': tile_format, 'quality': 75, 'width': tile_width, 'height': tile_height } if output_format == 'Directory': writer = DirectoryWriter(output_dir, tile_params) else: writer = MBTilesWriter(output_file, tile_params, wgs_extent, min_zoom, max_zoom) for zoom in range(min_zoom, max_zoom + 1): feedback.pushConsoleInfo('Generating tiles for zoom level: %s' % zoom) for i, metatile in enumerate(metatiles_by_zoom[zoom]): size = QSize(tile_width * metatile.rows(), tile_height * metatile.columns()) extent = QgsRectangle(*metatile.extent()) settings.setExtent(wgs_to_dest.transformBoundingBox(extent)) settings.setOutputSize(size) label_area = QgsRectangle(settings.extent()) lab_buffer = label_area.width() * (lab_buffer_px / size.width()) label_area.set( label_area.xMinimum() + lab_buffer, label_area.yMinimum() + lab_buffer, label_area.xMaximum() - lab_buffer, label_area.yMaximum() - lab_buffer ) settings.setLabelBoundaryGeometry(QgsGeometry.fromRect(label_area)) image = QImage(size, QImage.Format_ARGB32_Premultiplied) image.fill(Qt.transparent) dpm = settings.outputDpi() / 25.4 * 1000 image.setDotsPerMeterX(dpm) image.setDotsPerMeterY(dpm) painter = QPainter(image) job = QgsMapRendererCustomPainterJob(settings, painter) job.renderSynchronously() painter.end() # For analysing metatiles (labels, etc.) # metatile_dir = os.path.join(output_dir, str(zoom)) # os.makedirs(metatile_dir, exist_ok=True) # image.save(os.path.join(metatile_dir, 'metatile_%s.png' % i)) for r, c, tile in metatile.tiles: tile_img = image.copy(tile_width * r, tile_height * c, tile_width, tile_height) writer.writeTile(tile, tile_img) progress += 1 feedback.setProgress(100 * (progress / metatiles_count)) writer.close() results = {} if output_format == 'Directory': results['OUTPUT_DIRECTORY'] = output_dir else: # MBTiles results['OUTPUT_FILE'] = output_file return results
def writeLeaflet(cls, iface, feedback, folder, layer_list, visible, cluster, json, getFeatureInfo, params, popup): outputProjectFileName = folder QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) legends = {} mapUnitLayers = [] canvas = iface.mapCanvas() project = QgsProject.instance() mapSettings = canvas.mapSettings() title = project.title() pluginDir = os.path.dirname(os.path.realpath(__file__)) stamp = datetime.now().strftime("%Y_%m_%d-%H_%M_%S_%f") outputProjectFileName = os.path.join(outputProjectFileName, 'qgis2web_' + unicode(stamp)) outputIndex = os.path.join(outputProjectFileName, 'index.html') mapLibLocation = params["Data export"]["Mapping library location"] minify = params["Data export"]["Minify GeoJSON files"] precision = params["Data export"]["Precision"] debugLibs = params["Data export"]["Use debug libraries"] extent = params["Scale/Zoom"]["Extent"] minZoom = params["Scale/Zoom"]["Min zoom level"] maxZoom = params["Scale/Zoom"]["Max zoom level"] restrictToExtent = params["Scale/Zoom"]["Restrict to extent"] matchCRS = params["Appearance"]["Match project CRS"] addressSearch = params["Appearance"]["Add address search"] locate = params["Appearance"]["Geolocate user"] measure = params["Appearance"]["Measure tool"] highlight = params["Appearance"]["Highlight on hover"] layerSearch = params["Appearance"]["Layer search"] popupsOnHover = params["Appearance"]["Show popups on hover"] template = params["Appearance"]["Template"] widgetAccent = params["Appearance"]["Widget Icon"] widgetBackground = params["Appearance"]["Widget Background"] usedFields = [ALL_ATTRIBUTES] * len(popup) QgsApplication.initQgis() dataStore, cssStore = writeFoldersAndFiles( pluginDir, feedback, outputProjectFileName, cluster, measure, matchCRS, layerSearch, canvas, mapLibLocation, addressSearch, locate, debugLibs) writeCSS(cssStore, mapSettings.backgroundColor().name(), feedback, widgetAccent, widgetBackground) wfsLayers = "" labelCode = "" vtLabels = {} vtStyles = {} useMultiStyle = False useHeat = False useVT = False useShapes = False useOSMB = False useWMS = False useWMTS = False useRaster = False scaleDependentLayers = "" labelVisibility = "" new_src = "" jsons = "" crs = QgsCoordinateReferenceSystem.EpsgCrsId exp_crs = QgsCoordinateReferenceSystem(4326, crs) lyrCount = 0 for layer, jsonEncode, eachPopup, clst in zip(layer_list, json, popup, cluster): rawLayerName = layer.name() safeLayerName = safeName(rawLayerName) + "_" + unicode(lyrCount) vts = layer.customProperty("VectorTilesReader/vector_tile_url") if layer.providerType() != 'WFS' or jsonEncode is True: if layer.type() == QgsMapLayer.VectorLayer and vts is None: feedback.showFeedback('Exporting %s to JSON...' % layer.name()) exportVector(layer, safeLayerName, dataStore, restrictToExtent, iface, extent, precision, exp_crs, minify) jsons += jsonScript(safeLayerName) scaleDependentLabels = \ scaleDependentLabelScript(layer, safeLayerName) labelVisibility += scaleDependentLabels feedback.completeStep() elif layer.type() == QgsMapLayer.RasterLayer: if layer.dataProvider().name() != "wms": layersFolder = os.path.join(outputProjectFileName, "data") exportRaster(layer, lyrCount, layersFolder, feedback, iface, matchCRS) if layer.hasScaleBasedVisibility(): scaleDependentLayers += scaleDependentLayerScript( layer, safeLayerName, clst) lyrCount += 1 if scaleDependentLayers != "": scaleDependentLayers = scaleDependentScript(scaleDependentLayers) crsSrc = mapSettings.destinationCrs() crsAuthId = crsSrc.authid() crsProj4 = crsSrc.toProj4() middle = """ """ if highlight or popupsOnHover: selectionColor = mapSettings.selectionColor().name() middle += highlightScript(highlight, popupsOnHover, selectionColor) if extent == "Canvas extent": pt0 = canvas.extent() crsDest = QgsCoordinateReferenceSystem(4326) try: xform = QgsCoordinateTransform(crsSrc, crsDest, QgsProject.instance()) except: xform = QgsCoordinateTransform(crsSrc, crsDest) pt1 = xform.transformBoundingBox(pt0) bbox_canvas = [ pt1.yMinimum(), pt1.yMaximum(), pt1.xMinimum(), pt1.xMaximum() ] bounds = '[[' + unicode(pt1.yMinimum()) + ',' bounds += unicode(pt1.xMinimum()) + '],[' bounds += unicode(pt1.yMaximum()) + ',' bounds += unicode(pt1.xMaximum()) + ']]' if matchCRS and crsAuthId != 'EPSG:4326': middle += crsScript(crsAuthId, crsProj4) else: bounds = 0 if matchCRS and crsAuthId != 'EPSG:4326': middle += crsScript(crsAuthId, crsProj4) middle += mapScript(extent, matchCRS, crsAuthId, measure, maxZoom, minZoom, bounds, locate) middle += featureGroupsScript() extentCode = extentScript(extent, restrictToExtent) new_src += middle new_src += extentCode for count, layer in enumerate(layer_list): rawLayerName = layer.name() safeLayerName = safeName(rawLayerName) + "_" + unicode(count) if layer.type() == QgsMapLayer.VectorLayer: (new_src, legends, wfsLayers, labelCode, vtLabels, vtStyles, useMapUnits, useMultiStyle, useHeat, useVT, useShapes, useOSMB) = writeVectorLayer( layer, safeLayerName, usedFields[count], highlight, popupsOnHover, popup[count], outputProjectFileName, wfsLayers, cluster[count], visible[count], json[count], legends, new_src, canvas, count, restrictToExtent, extent, feedback, labelCode, vtLabels, vtStyles, useMultiStyle, useHeat, useVT, useShapes, useOSMB) if useMapUnits: mapUnitLayers.append(safeLayerName) elif layer.type() == QgsMapLayer.RasterLayer: if layer.dataProvider().name() == "wms": feedback.showFeedback('Writing %s as WMS layer...' % layer.name()) new_obj, useWMS, useWMTS = wmsScript( layer, safeLayerName, useWMS, useWMTS, getFeatureInfo[count]) feedback.completeStep() else: useRaster = True feedback.showFeedback('Writing %s as raster layer...' % layer.name()) new_obj = rasterScript(layer, safeLayerName) feedback.completeStep() if visible[count]: new_obj += """ map.addLayer(overlay_""" + safeLayerName + """);""" new_src += new_obj the_src = new_src new_src = jsons + """ <script>""" if len(mapUnitLayers) > 0: new_src += """ var m2px = 1; function newM2px() { var centerLatLng = map.getCenter(); var pointC = map.latLngToContainerPoint(centerLatLng); var pointX = [pointC.x + 100, pointC.y]; var latLngC = map.containerPointToLatLng(pointC); var latLngX = map.containerPointToLatLng(pointX); var distanceX = latLngC.distanceTo(latLngX)/100; reciprocal = 1 / distanceX; m2px = reciprocal; } function geoStyle(m) { return Math.ceil(m * m2px); }""" new_src += getVTStyles(vtStyles) new_src += getVTLabels(vtLabels) new_src += the_src + scaleDependentLayers if title != "": titleStart = unicode(titleSubScript(title)) new_src += unicode(titleStart) if addressSearch: address_text = addressSearchScript() new_src += address_text if (params["Appearance"]["Add layers list"] and params["Appearance"]["Add layers list"] != "" and params["Appearance"]["Add layers list"] != "None"): new_src += addLayersList( [], matchCRS, layer_list, cluster, legends, params["Appearance"]["Add layers list"] == "Expanded") if project.readBoolEntry("ScaleBar", "/Enabled", False)[0]: # placement = project.readNumEntry("ScaleBar", "/Placement", 0)[0] # placement = PLACEMENT[placement] # end = scaleBar(placement) end = scaleBar() else: end = '' layerType = "layer" try: if cluster[count]: layerType = "cluster" except: pass searchLayer = "%s_%s" % (layerType, params["Appearance"]["Search layer"]) labelList = [] for count, layer in enumerate(layer_list): vts = layer.customProperty("VectorTilesReader/vector_tile_url") safeLayerName = re.sub(r'[\W_]+', '', layer.name()) + "_" + unicode(count) if (layer.type() == QgsMapLayer.VectorLayer and vts is None): labelling = layer.labeling() if labelling is not None: palyr = labelling.settings() if palyr.fieldName and palyr.fieldName != "": labelList.append("layer_%s" % safeLayerName) labelsList = ",".join(labelList) end += endHTMLscript(wfsLayers, layerSearch, labelCode, labelVisibility, searchLayer, useHeat, useRaster, labelsList, mapUnitLayers) new_src += end try: writeHTMLstart(outputIndex, title, cluster, addressSearch, measure, matchCRS, layerSearch, canvas, mapLibLocation, locate, new_src, template, feedback, debugLibs, useMultiStyle, useHeat, useShapes, useOSMB, useWMS, useWMTS, useVT) except Exception as e: QgsMessageLog.logMessage(traceback.format_exc(), "qgis2web", level=QgsMessageLog.CRITICAL) QApplication.restoreOverrideCursor() finally: QApplication.restoreOverrideCursor() return outputIndex
def run(self): """Experimental impact function.""" self.validate() self.prepare() # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword("field") self.hazard_class_mapping = self.hazard.keyword("value_map") self.exposure_class_attribute = self.exposure.keyword("structure_class_field") # Prepare Hazard Layer hazard_provider = self.hazard.layer.dataProvider() # Check affected field exists in the hazard layer affected_field_index = hazard_provider.fieldNameIndex(self.hazard_class_attribute) if affected_field_index == -1: message = ( tr( 'Field "%s" is not present in the attribute table of the ' "hazard layer. Please change the Affected Field parameter in " "the IF Option." ) % self.hazard_class_attribute ) raise GetDataError(message) srs = self.exposure.layer.crs().toWkt() exposure_provider = self.exposure.layer.dataProvider() exposure_fields = exposure_provider.fields() # Check self.exposure_class_attribute exists in exposure layer building_type_field_index = exposure_provider.fieldNameIndex(self.exposure_class_attribute) if building_type_field_index == -1: message = ( tr( 'Field "%s" is not present in the attribute table of ' "the exposure layer. Please change the Building Type " "Field parameter in the IF Option." ) % self.exposure_class_attribute ) raise GetDataError(message) # If target_field does not exist, add it: if exposure_fields.indexFromName(self.target_field) == -1: exposure_provider.addAttributes([QgsField(self.target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex(self.target_field) exposure_fields = exposure_provider.fields() # Create layer to store the lines from E and extent building_layer = QgsVectorLayer("Polygon?crs=" + srs, "impact_buildings", "memory") building_provider = building_layer.dataProvider() # Set attributes building_provider.addAttributes(exposure_fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the requested extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:%i" % self._requested_extent_crs), self.hazard.layer.crs() ) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split building_layer by H and save as result: # 1) Filter from H inundated features # 2) Mark buildings as inundated (1) or not inundated (0) # make spatial index of affected polygons hazard_index = QgsSpatialIndex() hazard_geometries = {} # key = feature id, value = geometry has_hazard_objects = False for feature in self.hazard.layer.getFeatures(request): value = feature[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue hazard_index.insertFeature(feature) hazard_geometries[feature.id()] = QgsGeometry(feature.geometry()) has_hazard_objects = True if not has_hazard_objects: message = tr( "There are no objects in the hazard layer with %s " "value in %s. Please check your data or use another " "attribute." ) % (self.hazard_class_attribute, ", ".join(self.hazard_class_mapping[self.wet])) raise GetDataError(message) features = [] for feature in self.exposure.layer.getFeatures(request): building_geom = feature.geometry() affected = False # get tentative list of intersecting hazard features # only based on intersection of bounding boxes ids = hazard_index.intersects(building_geom.boundingBox()) for fid in ids: # run (slow) exact intersection test if hazard_geometries[fid].intersects(building_geom): affected = True break f = QgsFeature() f.setGeometry(building_geom) f.setAttributes(feature.attributes()) f[target_field_index] = 1 if affected else 0 features.append(f) # every once in a while commit the created features # to the output layer if len(features) == 1000: (_, __) = building_provider.addFeatures(features) features = [] (_, __) = building_provider.addFeatures(features) building_layer.updateExtents() # Generate simple impact report self.buildings = {} self.affected_buildings = OrderedDict([(tr("Flooded"), {})]) buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex(self.exposure_class_attribute) for building in buildings_data: record = building.attributes() building_type = record[building_type_field_index] if building_type in [None, "NULL", "null", "Null"]: building_type = "Unknown type" if building_type not in self.buildings: self.buildings[building_type] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][building_type] = OrderedDict([(tr("Buildings Affected"), 0)]) self.buildings[building_type] += 1 if record[target_field_index] == 1: self.affected_buildings[tr("Flooded")][building_type][tr("Buildings Affected")] += 1 # Lump small entries and 'unknown' into 'other' category self._consolidate_to_other() impact_summary = self.generate_html_report() # For printing map purpose map_title = tr("Buildings inundated") legend_title = tr("Structure inundated status") style_classes = [ dict(label=tr("Not Inundated"), value=0, colour="#1EFC7C", transparency=0, size=0.5), dict(label=tr("Inundated"), value=1, colour="#F31A1C", transparency=0, size=0.5), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type="categorizedSymbol") # Convert QgsVectorLayer to inasafe layer and return it. if building_layer.featureCount() < 1: raise ZeroImpactException(tr("No buildings were impacted by this flood.")) building_layer = Vector( data=building_layer, name=tr("Flooded buildings"), keywords={ "impact_summary": impact_summary, "map_title": map_title, "legend_title": legend_title, "target_field": self.target_field, "buildings_total": self.total_buildings, "buildings_affected": self.total_affected_buildings, }, style_info=style_info, ) self._impact = building_layer return building_layer
def print_map(self, mode="pdf"): """Open impact report dialog that used to tune report when print map button pressed.""" # Check if selected layer is valid impact_layer = self.iface.activeLayer() if impact_layer is None: # noinspection PyCallByClass,PyTypeChecker QtGui.QMessageBox.warning( self.parent, self.tr('InaSAFE'), self.tr('Please select a valid impact layer before ' 'trying to print.')) return # Open Impact Report Dialog print_dialog = ImpactReportDialog(self.iface) print_dialog.button_ok = QtGui.QPushButton(self.tr('OK')) print_dialog.buttonBox.addButton( print_dialog.button_ok, QtGui.QDialogButtonBox.ActionRole) print_dialog.button_ok.clicked.connect(print_dialog.accept) print_dialog.button_save_pdf.hide() print_dialog.button_open_composer.hide() if not print_dialog.exec_() == QtGui.QDialog.Accepted: self.show_dynamic_message( self, m.Message( m.Heading(self.tr('Map Creator'), **WARNING_STYLE), m.Text(self.tr('Report generation cancelled!')))) return # Get the extent of the map for report use_full_extent = print_dialog.analysis_extent_radio.isChecked() if use_full_extent: map_crs = self.iface.mapCanvas().mapRenderer().destinationCrs() layer_crs = self.iface.activeLayer().crs() layer_extent = self.iface.activeLayer().extent() if map_crs != layer_crs: # noinspection PyCallingNonCallable transform = QgsCoordinateTransform(layer_crs, map_crs) layer_extent = transform.transformBoundingBox(layer_extent) area_extent = layer_extent else: area_extent = self.iface.mapCanvas().extent() # Get selected template path to use if print_dialog.default_template_radio.isChecked(): template_path = print_dialog.template_combo.itemData( print_dialog.template_combo.currentIndex()) else: template_path = print_dialog.template_path.text() if not os.path.exists(template_path): # noinspection PyCallByClass,PyTypeChecker QtGui.QMessageBox.warning( self.parent, self.tr('InaSAFE'), self.tr('Please select a valid template before printing. ' 'The template you choose does not exist.')) return # Open in PDF or Open in Composer Flag (not used, the button is hidden # by this class. The wizard has two its own buttons for Open In PDF # and Open In Composer buttons. Use variable 'mode' from param instead) create_pdf_flag = print_dialog.create_pdf # Instantiate and prepare Report self.show_dynamic_message( self, m.Message( m.Heading(self.tr('Map Creator'), **PROGRESS_UPDATE_STYLE), m.Text(self.tr('Preparing map and report')))) impact_report = ImpactReport(self.iface, template_path, impact_layer) impact_report.extent = area_extent # Get other setting settings = QSettings() logo_path = settings.value( 'inasafe/organisation_logo_path', '', type=str) impact_report.organisation_logo = logo_path disclaimer_text = settings.value( 'inasafe/reportDisclaimer', '', type=str) impact_report.disclaimer = disclaimer_text north_arrow_path = settings.value( 'inasafe/north_arrow_path', '', type=str) impact_report.north_arrow = north_arrow_path template_warning_verbose = bool(settings.value( 'inasafe/template_warning_verbose', True, type=bool)) # Check if there's missing elements needed in the template component_ids = ['safe-logo', 'north-arrow', 'organisation-logo', 'impact-map', 'impact-legend'] impact_report.component_ids = component_ids if template_warning_verbose and \ len(impact_report.missing_elements) != 0: title = self.tr('Template is missing some elements') question = self.tr( 'The composer template you are printing to is missing ' 'these elements: %s. Do you still want to continue') % ( ', '.join(impact_report.missing_elements)) # noinspection PyCallByClass,PyTypeChecker answer = QtGui.QMessageBox.question( self.parent, title, question, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) if answer == QtGui.QMessageBox.No: return create_pdf_flag = bool(mode == 'pdf') self.show_busy() if create_pdf_flag: self.print_map_to_pdf(impact_report) else: self.open_map_in_composer(impact_report) self.hide_busy()
def processAlgorithm(self, parameters, context, feedback): expression = self.parameterAsString(parameters, self.EXPRESSION, context) layers = self.parameterAsLayerList(parameters, self.LAYERS, context) layersDict = {} if layers: layersDict = { os.path.basename(lyr.source().split(".")[0]): lyr for lyr in layers } crs = self.parameterAsCrs(parameters, self.CRS, context) if not layers and not crs.isValid(): raise QgsProcessingException( self.tr("No reference layer selected nor CRS provided")) if not crs.isValid() and layers: crs = list(layersDict.values())[0].crs() bbox = self.parameterAsExtent(parameters, self.EXTENT, context) if not layers and bbox.isNull(): raise QgsProcessingException( self.tr("No reference layer selected nor extent box provided")) if not bbox.isNull(): bboxCrs = self.parameterAsExtentCrs(parameters, self.EXTENT, context) if bboxCrs != crs: transform = QgsCoordinateTransform(bboxCrs, crs, context.project()) bbox = transform.transformBoundingBox(bbox) if bbox.isNull() and layers: bbox = QgsProcessingUtils.combineLayerExtents(layers, crs) cellsize = self.parameterAsDouble(parameters, self.CELLSIZE, context) if not layers and cellsize == 0: raise QgsProcessingException( self.tr( "No reference layer selected nor cellsize value provided")) def _cellsize(layer): ext = layer.extent() if layer.crs() != crs: transform = QgsCoordinateTransform(layer.crs(), crs, context.project()) ext = transform.transformBoundingBox(ext) return (ext.xMaximum() - ext.xMinimum()) / layer.width() if cellsize == 0: cellsize = min([_cellsize(lyr) for lyr in layersDict.values()]) for lyr in QgsProcessingUtils.compatibleRasterLayers( context.project()): name = lyr.name() if (name + "@") in expression: layersDict[name] = lyr entries = [] for name, lyr in layersDict.items(): for n in range(lyr.bandCount()): ref = '{:s}@{:d}'.format(name, n + 1) if ref in expression: entry = QgsRasterCalculatorEntry() entry.ref = ref entry.raster = lyr entry.bandNumber = n + 1 entries.append(entry) output = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) width = math.floor((bbox.xMaximum() - bbox.xMinimum()) / cellsize) height = math.floor((bbox.yMaximum() - bbox.yMinimum()) / cellsize) driverName = GdalUtils.getFormatShortNameFromFilename(output) calc = QgsRasterCalculator(expression, output, driverName, bbox, crs, width, height, entries) res = calc.processCalculation(feedback) if res == QgsRasterCalculator.ParserError: raise QgsProcessingException(self.tr("Error parsing formula")) return {self.OUTPUT: output}
def processAlgorithm(self, parameters, context, feedback): expression = self.parameterAsString(parameters, self.EXPRESSION, context) layers = self.parameterAsLayerList(parameters, self.LAYERS, context) layersDict = {} if layers: layersDict = {lyr.source(): lyr for lyr in layers} crs = self.parameterAsCrs(parameters, self.CRS, context) if crs is None or not crs.isValid(): if not layers: raise QgsProcessingException(self.tr("No reference layer selected nor CRS provided")) else: crs = list(layersDict.values())[0].crs() bbox = self.parameterAsExtent(parameters, self.EXTENT, context) if bbox.isNull() and not layers: raise QgsProcessingException(self.tr("No reference layer selected nor extent box provided")) if not bbox.isNull(): bboxCrs = self.parameterAsExtentCrs(parameters, self.EXTENT, context) if bboxCrs != crs: transform = QgsCoordinateTransform(bboxCrs, crs, context.project()) bbox = transform.transformBoundingBox(bbox) if bbox.isNull() and layers: bbox = QgsProcessingUtils.combineLayerExtents(layers, crs, context) cellsize = self.parameterAsDouble(parameters, self.CELLSIZE, context) if cellsize == 0 and not layers: raise QgsProcessingException(self.tr("No reference layer selected nor cellsize value provided")) def _cellsize(layer): ext = layer.extent() if layer.crs() != crs: transform = QgsCoordinateTransform(layer.crs(), crs, context.project()) ext = transform.transformBoundingBox(ext) return (ext.xMaximum() - ext.xMinimum()) / layer.width() if cellsize == 0: cellsize = min([_cellsize(lyr) for lyr in layersDict.values()]) # check for layers available in the model layersDictCopy = layersDict.copy() # need a shallow copy because next calls invalidate iterator for lyr in layersDictCopy.values(): expression = self.mappedNameToLayer(lyr, expression, layersDict, context) # check for layers available in the project for lyr in QgsProcessingUtils.compatibleRasterLayers(context.project()): expression = self.mappedNameToLayer(lyr, expression, layersDict, context) # create the list of layers to be passed as inputs to RasterCalculaltor # at this phase expression has been modified to match available layers # in the current scope entries = [] for name, lyr in layersDict.items(): for n in range(lyr.bandCount()): ref = '{:s}@{:d}'.format(name, n + 1) if ref in expression: entry = QgsRasterCalculatorEntry() entry.ref = ref entry.raster = lyr entry.bandNumber = n + 1 entries.append(entry) # Append any missing entry from the current project for entry in QgsRasterCalculatorEntry.rasterEntries(): if not [e for e in entries if e.ref == entry.ref]: entries.append(entry) output = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) width = round((bbox.xMaximum() - bbox.xMinimum()) / cellsize) height = round((bbox.yMaximum() - bbox.yMinimum()) / cellsize) driverName = GdalUtils.getFormatShortNameFromFilename(output) calc = QgsRasterCalculator(expression, output, driverName, bbox, crs, width, height, entries, context.transformContext()) res = calc.processCalculation(feedback) if res == QgsRasterCalculator.ParserError: raise QgsProcessingException(self.tr("Error parsing formula")) return {self.OUTPUT: output}
def processAlgorithm(self, parameters, context, model_feedback): """ Process algorithm. """ feedback = QgsProcessingMultiStepFeedback(13, model_feedback) results = {} outputs = {} project = QgsProject.instance() feedback.setCurrentStep(1) if feedback.isCanceled(): return {} # Points: # origin: user origin point in proj crs # wgs84_origin: origin point in wgs84 crs, used for choosing utm zone # utm_origin: origin point in utm crs # fire_origin: user fire point in proj crs # wgs84_fire_origin: fire point in wgs84 crs # utm_fire_origin: fire point in utm crs # CRSs: # project_crs: project crs # wgs84_crs: wgs84 crs # utm_crs: utm crs, calculated from wgs84_origin # dem_crs: dem crs, used for grid alignment # Extents: # extent: user terrain extent in any crs # mesh_extent: extent to utm crs, used for FDS MESH # as it is always contained in the terrain # dem_extent: mesh_extent to dem crs, used for grid # alignment with dem raster data # tex_extent: dem_extent to utm crs, used for texture, # that should be oriented as utm and perfectly # correspond to dem # Get some of the parameters chid = self.parameterAsString(parameters, "chid", context) project.writeEntry("qgis2fds", "chid", parameters["chid"]) path = self.parameterAsFile(parameters, "path", context) project.writeEntry("qgis2fds", "path", parameters["path"]) landuse_type = self.parameterAsEnum(parameters, "landuse_type", context) project.writeEntry("qgis2fds", "landuse_type", parameters["landuse_type"]) dem_sampling = self.parameterAsInt(parameters, "dem_sampling", context) project.writeEntry("qgis2fds", "dem_sampling", parameters["dem_sampling"]) extent = self.parameterAsExtent(parameters, "extent", context) # FIXME crs? project.writeEntry("qgis2fds", "extent", parameters["extent"]) # Get layers in their respective crs: dem_layer, landuse_layer, tex_layer dem_layer = self.parameterAsRasterLayer(parameters, "dem_layer", context) project.writeEntry("qgis2fds", "dem_layer", parameters["dem_layer"]) if not parameters["landuse_layer"]: # it is optional landuse_layer = None else: landuse_layer = self.parameterAsRasterLayer( parameters, "landuse_layer", context) project.writeEntry("qgis2fds", "landuse_layer", parameters["landuse_layer"]) if not parameters["tex_layer"]: # it is optional tex_layer = None else: tex_layer = self.parameterAsRasterLayer(parameters, "tex_layer", context) project.writeEntry("qgis2fds", "tex_layer", parameters["tex_layer"]) # Get tex_pixel_size tex_pixel_size = self.parameterAsDouble(parameters, "tex_pixel_size", context) project.writeEntryDouble("qgis2fds", "tex_pixel_size", parameters["tex_pixel_size"]) # Prepare CRSs and check their validity project_crs = QgsProject.instance().crs() project.writeEntry("qgis2fds", "project_crs", project_crs.description()) feedback.pushInfo(f"Project CRS: <{project_crs.description()}>") if not project_crs.isValid(): raise QgsProcessingException( "Project CRS is not usable, cannot proceed.") wgs84_crs = QgsCoordinateReferenceSystem("EPSG:4326") dem_crs = dem_layer.crs() feedback.pushInfo(f"DEM layer CRS: <{dem_crs.description()}>") if not dem_crs.isValid(): raise QgsProcessingException( "DEM layer CRS is not usable, cannot proceed.") if landuse_layer: landuse_crs = landuse_layer.crs() feedback.pushInfo( f"Landuse layer CRS: <{landuse_crs.description()}>") if not landuse_crs.isValid(): raise QgsProcessingException( "Landuse layer CRS is not usable, cannot proceed.") if tex_layer: tex_crs = tex_layer.crs() feedback.pushInfo(f"Texture layer CRS: <{tex_crs.description()}>") if not tex_crs.isValid(): raise QgsProcessingException( "Texture layer CRS is not usable, cannot proceed.") # Get extent in WGS84 CRS wgs84_extent = self.parameterAsExtent(parameters, "extent", context, crs=wgs84_crs) # Get origin in WGS84 CRS project_to_wgs84_tr = QgsCoordinateTransform(project_crs, wgs84_crs, QgsProject.instance()) if parameters["origin"] is not None: wgs84_origin = self.parameterAsPoint(parameters, "origin", context, crs=wgs84_crs) feedback.pushInfo("Using user origin.") project.writeEntry("qgis2fds", "origin", parameters["origin"]) else: # no origin wgs84_origin = wgs84_extent.center() feedback.pushInfo(f"Using terrain extent center as origin.") # Get fire origin in WGS84 CRS if parameters["fire_origin"] is not None: wgs84_fire_origin = self.parameterAsPoint(parameters, "fire_origin", context, crs=wgs84_crs) feedback.pushInfo("Using user fire origin.") else: wgs84_fire_origin = QgsPoint(wgs84_origin.x(), wgs84_origin.y()) feedback.pushInfo("Using origin as fire origin.") project.writeEntry("qgis2fds", "fire_origin", parameters["fire_origin"]) # Calc UTM CRS from wgs84_origin utm_epsg = utils.lonlat_to_epsg(lon=wgs84_origin.x(), lat=wgs84_origin.y()) utm_crs = QgsCoordinateReferenceSystem(utm_epsg) feedback.pushInfo(f"Using UTM CRS: <{utm_crs.description()}>") # Get origin in UTM CRS wgs84_to_utm_tr = QgsCoordinateTransform(wgs84_crs, utm_crs, QgsProject.instance()) utm_origin = QgsPoint(wgs84_origin.x(), wgs84_origin.y()) utm_origin.transform(wgs84_to_utm_tr) # Check for QGIS bug if utm_origin == wgs84_origin: raise QgsProcessingException( f"[QGIS bug] UTM Origin <{utm_origin}> and WGS84 Origin <{wgs84_origin}> are identical, cannot proceed." ) # Get fire origin in UTM CRS utm_fire_origin = QgsPoint(wgs84_fire_origin.x(), wgs84_fire_origin.y()) utm_fire_origin.transform(wgs84_to_utm_tr) # Get FDS MESH extent in UTM CRS, then obtain dem extent in DEM CRS mesh_extent = self.parameterAsExtent( parameters, "extent", context, crs=utm_crs, ) utm_to_dem_tr = QgsCoordinateTransform(utm_crs, dem_crs, QgsProject.instance()) dem_extent = utm_to_dem_tr.transformBoundingBox(mesh_extent) # Check DEM contains dem_extent if not dem_layer.extent().contains(dem_extent): feedback.reportError( "Terrain extent is larger than available DEM data.") feedback.setCurrentStep(2) if feedback.isCanceled(): return {} # QGIS geographic transformations # Creating sampling grid in DEM crs feedback.pushInfo("Creating sampling grid layer from DEM...") xspacing = dem_layer.rasterUnitsPerPixelX() yspacing = dem_layer.rasterUnitsPerPixelY() x0, y0, x1, y1 = ( # terrain extent in DEM CRS dem_extent.xMinimum(), dem_extent.yMinimum(), dem_extent.xMaximum(), dem_extent.yMaximum(), ) xd0, yd1 = ( # DEM extent in DEM CRS dem_layer.extent().xMinimum(), dem_layer.extent().yMaximum(), ) # align terrain extent to DEM grid (gridding starts from top left corner) x0 = xd0 + round((x0 - xd0) / xspacing) * xspacing + xspacing / 2.0 y1 = yd1 + round((y1 - yd1) / yspacing) * yspacing - yspacing / 2.0 dem_extent = QgsRectangle(x0, y0, x1, y1) # terrain extent in DEM CRS npoints = int( (x1 - x0) / xspacing * (y1 - y0) / yspacing / dem_sampling**2) if npoints < 9: raise QgsProcessingException( f"Too few sampling points, cannot proceed. (npoints: {npoints})" ) feedback.pushInfo( f"Sampling points: {npoints} (xspacing: {xspacing}, yspacing: {yspacing})" ) alg_params = { "CRS": dem_crs, "EXTENT": dem_extent, "HOVERLAY": 0, "HSPACING": xspacing * dem_sampling, # reduce sampling, if requested "TYPE": 0, # Points "VOVERLAY": 0, "VSPACING": yspacing * dem_sampling, # reduce sampling, if requested "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["CreateGrid"] = processing.run( "native:creategrid", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(3) if feedback.isCanceled(): return {} # Save texture dem_to_utm_tr = QgsCoordinateTransform(dem_crs, utm_crs, QgsProject.instance()) tex_extent = dem_to_utm_tr.transformBoundingBox(dem_extent) utils.write_image( feedback=feedback, tex_layer=tex_layer, tex_pixel_size=tex_pixel_size, # pixel size in meters destination_crs= utm_crs, # using UTM crs, texture aligned to axis in Smokeview destination_extent=tex_extent, filepath=f"{path}/{chid}_tex.png", imagetype="png", ) feedback.setCurrentStep(4) if feedback.isCanceled(): return {} # QGIS geographic transformations # Draping Z values to sampling grid in DEM crs feedback.pushInfo("Draping Z values from DEM...") alg_params = { "BAND": 1, "INPUT": outputs["CreateGrid"]["OUTPUT"], "NODATA": 0, "RASTER": dem_layer, "SCALE": 1, "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT, } outputs["DrapeSetZValueFromRaster"] = processing.run( "native:setzfromraster", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(5) if feedback.isCanceled(): return {} # QGIS geographic transformations # Reprojecting sampling grid to UTM CRS feedback.pushInfo("Reprojecting sampling grid layer to UTM CRS...") alg_params = { "INPUT": outputs["DrapeSetZValueFromRaster"]["OUTPUT"], "TARGET_CRS": utm_crs, "OUTPUT": landuse_layer and QgsProcessing.TEMPORARY_OUTPUT or parameters["sampling_layer"], } outputs["ReprojectLayer"] = processing.run( "native:reprojectlayer", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) feedback.setCurrentStep(6) if feedback.isCanceled(): return {} # QGIS geographic transformations # Sampling landuse layer with sampling grid in UTM CRS if landuse_layer: feedback.pushInfo("Sampling landuse...") alg_params = { "COLUMN_PREFIX": "landuse", "INPUT": outputs["ReprojectLayer"]["OUTPUT"], "RASTERCOPY": parameters["landuse_layer"], "OUTPUT": parameters["sampling_layer"], } outputs["sampling_layer"] = processing.run( "qgis:rastersampling", alg_params, context=context, feedback=feedback, is_child_algorithm=True, ) results["sampling_layer"] = outputs["sampling_layer"]["OUTPUT"] point_layer = context.getMapLayer(results["sampling_layer"]) else: feedback.pushInfo("No landuse layer provided, no sampling.") results["sampling_layer"] = outputs["ReprojectLayer"]["OUTPUT"] point_layer = context.getMapLayer(results["sampling_layer"]) # add fake landuse point_layer.dataProvider().addAttributes( (QgsField("landuse", QVariant.Int), )) point_layer.updateFields() feedback.setCurrentStep(7) if feedback.isCanceled(): return {} # Prepare geometry verts, faces, landuses = geometry.get_geometry( feedback=feedback, layer=point_layer, utm_origin=utm_origin, ) feedback.setCurrentStep(12) if feedback.isCanceled(): return {} # Write the FDS case file fds.write_case( feedback=feedback, dem_layer=dem_layer, landuse_layer=landuse_layer, path=path, chid=chid, wgs84_origin=wgs84_origin, utm_origin=utm_origin, wgs84_fire_origin=wgs84_fire_origin, utm_fire_origin=utm_fire_origin, utm_crs=utm_crs, verts=verts, faces=faces, landuses=landuses, landuse_type=landuse_type, mesh_extent=mesh_extent, ) return results