def processAlgorithm(self, parameters, context, feedback): crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context) extent = self.parameterAsExtent(parameters, self.EXTENT, context, crs) value = self.parameterAsDouble(parameters, self.NUMBER, context) pixelSize = self.parameterAsDouble(parameters, self.PIXEL_SIZE, context) outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) outputFormat = QgsRasterFileWriter.driverForExtension(os.path.splitext(outputFile)[1]) rows = max([math.ceil(extent.height() / pixelSize) + 1, 1.0]) cols = max([math.ceil(extent.width() / pixelSize) + 1, 1.0]) writer = QgsRasterFileWriter(outputFile) writer.setOutputProviderKey('gdal') writer.setOutputFormat(outputFormat) provider = writer.createOneBandRaster(Qgis.Float32, cols, rows, extent, crs) provider.setNoDataValue(1, -9999) data = [value] * cols block = QgsRasterBlock(Qgis.Float32, cols, 1) block.setData(struct.pack('{}f'.format(len(data)), *data)) total = 100.0 / rows if rows else 0 for i in range(rows): if feedback.isCanceled(): break provider.writeBlock(block, 1, 0, i) feedback.setProgress(int(i * rows)) provider.setEditable(False) return {self.OUTPUT: outputFile}
def change_cell_value(self, vals, x=None, y=None): """Save new bands values to data provider""" if not self.rdp.isEditable(): success = self.rdp.setEditable(True) if not success: self.uc.show_warn('QGIS can\'t modify this type of raster') return if not x: x = self.px y = self.py for nr in range(1, min(4, self.band_count + 1)): rblock = QgsRasterBlock(self.bands[nr]['qtype'], 1, 1) rblock.setValue(0, 0, vals[nr - 1]) success = self.rdp.writeBlock(rblock, nr, x, y) if not success: self.uc.show_warn('QGIS can\'t modify this type of raster') return self.rdp.setEditable(False) self.raster.triggerRepaint() # prepare raster for next actions self.prepare_raster(True) self.check_undo_redo_btns()
def edit_pixel(self, point, new_value=None, check_bounds=True, inside_geom=None): if check_bounds and not self.check_point_inside_layer(point): return if new_value is None: old_value, new_value = self.get_the_old_new_pixel_value(point) if new_value is None: return else: old_value = self.get_pixel_value_from_pnt(point) if inside_geom and not inside_geom.contains(QgsGeometry.fromPointXY(point)): return px = int((point.x() - self.bounds[0]) / self.qgs_layer.rasterUnitsPerPixelX()) # num column position in x py = int((self.bounds[3] - point.y()) / self.qgs_layer.rasterUnitsPerPixelY()) # num row position in y rblock = QgsRasterBlock(self.data_provider.dataType(self.band), 1, 1) rblock.setValue(0, 0, new_value) if self.data_provider.writeBlock(rblock, self.band, px, py): # write and check if writing status is ok return point, old_value
def processAlgorithm(self, parameters, context, feedback): inp_rast = self.parameterAsRasterLayer(parameters, self.INPUT, context) grib_filename = self.parameterAsString(parameters, self.OUTPUT, context) if not grib_filename: raise QgsProcessingException( self.tr('You need to specify output filename.')) idp = inp_rast.dataProvider() map_settings = iface.mapCanvas().mapSettings() crs = map_settings.destinationCrs() outputFormat = QgsRasterFileWriter.driverForExtension('.tif') height = inp_rast.height() width = inp_rast.width() inp_block = idp.block(1, idp.extent(), width, height) rfw = QgsRasterFileWriter(grib_filename + '.tif') rfw.setOutputProviderKey('gdal') rfw.setOutputFormat(outputFormat) rdp = rfw.createMultiBandRaster(Qgis.Float32, width, height, idp.extent(), crs, 2) rdp.setEditable(True) x_block = QgsRasterBlock(Qgis.Float32, width, height) y_block = QgsRasterBlock(Qgis.Float32, width, height) diag = 1. / sqrt(2) # resulting raster has no NODATA value set, which # is not treated correctly in MDAL 0.2.0. See # see https://github.com/lutraconsulting/MDAL/issues/104 # therefore set some small value to overcome the issue dir_map = { 0: (1e-7, 1), 1: (diag, diag), 2: (1, 1e-7), 3: (diag, -diag), 4: (1e-7, -1), 5: (-diag, -diag), 6: (-1, 1e-7), 7: (-diag, diag), 255: (0, 0) } for row in range(height): for col in range(width): dir_ind = inp_block.value(row, col) try: x_val, y_val = dir_map.get(dir_ind, 255) except TypeError: x_val, y_val = (0, 0) x_block.setValue(row, col, x_val) y_block.setValue(row, col, y_val) rdp.writeBlock(x_block, 1) rdp.writeBlock(y_block, 2) rdp.setNoDataValue(1, inp_block.noDataValue()) rdp.setNoDataValue(2, inp_block.noDataValue()) rdp.setEditable(False) # rewrite the resulting raster as GRIB using GDAL for setting metadata res_tif = gdal.Open(grib_filename + '.tif') grib_driver = gdal.GetDriverByName('GRIB') grib = grib_driver.CreateCopy(grib_filename, res_tif) band_names = ('x-flow', 'y-flow') for i in range(2): band_nr = i + 1 band_name = band_names[i] grib_band = grib.GetRasterBand(band_nr) grib_band.SetMetadataItem('grib_comment', band_name) grib_band.SetNoDataValue(255) grib_band.SetDescription(band_name) res_tif_band_array = res_tif.GetRasterBand(band_nr).ReadAsArray() grib_band.WriteArray(res_tif_band_array) feedback.setProgress(band_nr * 50) grib = None return {self.OUTPUT: grib_filename}
def write_block(self, const_values=None, low_pass_filter=False): """ Construct raster block for each band, apply the values and write to file. If const_values are given (a list of const values for each band) they are used for each selected cell. In other case the memory layer with values calculated for each cell selected will be used. Alternatively, selected cells values can be filtered using low-pass 3x3 filter. """ if self.logger: vals = f"const values ({const_values})" if const_values else "expression values." self.logger.debug(f"Writing blocks with {vals}") if not self.provider.isEditable(): res = self.provider.setEditable(True) if not res: if self.uc: self.uc.show_warn('QGIS can\'t modify this type of raster') return None if self.logger: self.logger.debug("Calculating block origin coordinates...") b_orig_x, b_orig_y = self.index_to_point(self.block_row_min, self.block_col_min) cols = self.block_col_max - self.block_col_min + 1 rows = self.block_row_max - self.block_row_min + 1 b_end_x = b_orig_x + cols * self.pixel_size_x b_end_y = b_orig_y - rows * self.pixel_size_y block_bbox = QgsRectangle(b_orig_x, b_end_y, b_end_x, b_orig_y) if self.logger: self.logger.debug(f"Block bbox: {block_bbox.toString()}") self.logger.debug( f"Nr of cells in the block: rows={rows}, cols={cols}") old_blocks = [] new_blocks = [] cell_values = dict() if const_values is None and not low_pass_filter: for feat in self.cell_pts_layer.getFeatures(): cell_values[feat.id()] = feat.attribute(self.exp_field_idx) for band_nr in self.active_bands: block = self.provider.block(band_nr, block_bbox, cols, rows) new_blocks.append(block) block_data = block.data().data() old_block = QgsRasterBlock(self.data_types[band_nr - 1], cols, rows) old_block.setData(block_data) for abs_row, abs_col in self.selected_cells: row = abs_row - self.block_row_min col = abs_col - self.block_col_min if const_values: idx = band_nr - 1 if len(self.active_bands) > 1 else 0 new_val = const_values[idx] elif low_pass_filter: # the filter is applied for cells inside the block only if block.height() < 3 or block.width() < 3: # the selected block is too small for filtering -> keep the old value new_val = None else: new_val = low_pass_filtered( old_block, row, col, self.nodata_values[band_nr - 1]) else: # set the expression value feat_id = self.selected_cells_feats[(abs_row, abs_col)] if cell_values[feat_id] is not None: new_val = None if math.isnan(cell_values[feat_id]) or \ cell_values[feat_id] is None else cell_values[feat_id] else: new_val = None new_val = old_block.value(row, col) if new_val is None else new_val set_res = block.setValue(row, col, new_val) if self.logger: self.logger.debug( f"Setting block value for band {band_nr}, row {row}, col: {col}: {set_res}" ) old_blocks.append(old_block) band_res = self.provider.writeBlock(block, band_nr, self.block_col_min, self.block_row_min) if self.logger: self.logger.debug( f"Writing block for band {band_nr}: {band_res}") self.provider.setEditable(False) change = RasterChange(self.active_bands, self.block_row_min, self.block_col_min, old_blocks, new_blocks) self.raster_changed.emit(change) return True
def processAlgorithm(self, parameters, context, feedback): inp_rast = self.parameterAsRasterLayer(parameters, self.INPUT, context) grib_filename = self.parameterAsString(parameters, self.OUTPUT, context) if not grib_filename: raise QgsProcessingException(self.tr('You need to specify output filename.')) idp = inp_rast.dataProvider() map_settings = iface.mapCanvas().mapSettings() crs = map_settings.destinationCrs() outputFormat = QgsRasterFileWriter.driverForExtension('.tif') height = inp_rast.height() width = inp_rast.width() inp_block = idp.block(1, idp.extent(), width, height) rfw = QgsRasterFileWriter(grib_filename + '.tif') rfw.setOutputProviderKey('gdal') rfw.setOutputFormat(outputFormat) rdp = rfw.createMultiBandRaster( Qgis.Float32, width, height, idp.extent(), crs, 2 ) rdp.setEditable(True) x_block = QgsRasterBlock(Qgis.Float32, width, height) y_block = QgsRasterBlock(Qgis.Float32, width, height) diag = 1. / sqrt(2) # resulting raster has no NODATA value set, which # is not treated correctly in MDAL 0.2.0. See # see https://github.com/lutraconsulting/MDAL/issues/104 # therefore set some small value to overcome the issue dir_map = { 0: (1e-7, 1), 1: (diag, diag), 2: (1, 1e-7), 3: (diag, -diag), 4: (1e-7, -1), 5: (-diag, -diag), 6: (-1, 1e-7), 7: (-diag, diag), 255: (0, 0) } for row in range(height): for col in range(width): dir_ind = inp_block.value(row, col) try: x_val, y_val = dir_map.get(dir_ind, 255) except TypeError: x_val, y_val = (0, 0) x_block.setValue(row, col, x_val) y_block.setValue(row, col, y_val) rdp.writeBlock(x_block, 1) rdp.writeBlock(y_block, 2) rdp.setNoDataValue(1, inp_block.noDataValue()) rdp.setNoDataValue(2, inp_block.noDataValue()) rdp.setEditable(False) # rewrite the resulting raster as GRIB using GDAL for setting metadata res_tif = gdal.Open(grib_filename + '.tif') grib_driver = gdal.GetDriverByName('GRIB') grib = grib_driver.CreateCopy(grib_filename, res_tif) band_names = ('x-flow', 'y-flow') for i in range(2): band_nr = i + 1 band_name = band_names[i] grib_band = grib.GetRasterBand(band_nr) grib_band.SetMetadataItem('grib_comment', band_name) grib_band.SetNoDataValue(255) grib_band.SetDescription(band_name) res_tif_band_array = res_tif.GetRasterBand(band_nr).ReadAsArray() grib_band.WriteArray(res_tif_band_array) feedback.setProgress(band_nr * 50) grib = None return {self.OUTPUT: grib_filename}
def processAlgorithm(self, parameters, context, feedback): """ Here is where the processing itself takes place. """ if self.parameterAsRasterLayer(parameters, self.INITIAL_ROAD_NETWORK, context): initial_road_network_raster = self.parameterAsRasterLayer( parameters, self.INITIAL_ROAD_NETWORK, context) else: initial_road_network_raster = None basic_distance_cost = self.parameterAsDouble(parameters, self.BASIC_DISTANCE_COST, context) if self.parameterAsRasterLayer(parameters, self.COARSE_ELEVATION_RASTER, context): coarse_elevation_raster = self.parameterAsRasterLayer( parameters, self.COARSE_ELEVATION_RASTER, context) else: coarse_elevation_raster = None if self.parameterAsMatrix(parameters, self.COARSE_ELEVATION_COSTS, context): coarse_elevation_costs = self.parameterAsMatrix( parameters, self.COARSE_ELEVATION_COSTS, context) coarse_elevation_costs = CostRasterCreatorHelper.CollapsedTableToMatrix( coarse_elevation_costs, 3) else: coarse_elevation_costs = None if self.parameterAsRasterLayer(parameters, self.FINE_ELEVATION_RASTER, context): fine_elevation_raster = self.parameterAsRasterLayer( parameters, self.FINE_ELEVATION_RASTER, context) else: fine_elevation_raster = None if self.parameterAsMatrix(parameters, self.FINE_ELEVATION_COSTS, context): fine_elevation_costs = self.parameterAsMatrix( parameters, self.FINE_ELEVATION_COSTS, context) fine_elevation_costs = CostRasterCreatorHelper.CollapsedTableToMatrix( fine_elevation_costs, 3) else: fine_elevation_costs = None if self.parameterAsRasterLayer(parameters, self.COARSE_WATER_RASTER, context): coarse_water_raster = self.parameterAsRasterLayer( parameters, self.COARSE_WATER_RASTER, context) else: coarse_water_raster = None if self.parameterAsDouble(parameters, self.COARSE_WATER_COST, context): coarse_water_cost = self.parameterAsDouble(parameters, self.COARSE_WATER_COST, context) else: coarse_water_cost = None if self.parameterAsRasterLayer(parameters, self.FINE_WATER_RASTER, context): fine_water_raster = self.parameterAsRasterLayer( parameters, self.FINE_WATER_RASTER, context) else: fine_water_raster = None if self.parameterAsDouble(parameters, self.FINE_WATER_COST, context): fine_water_cost = self.parameterAsDouble(parameters, self.FINE_WATER_COST, context) else: fine_water_cost = None if self.parameterAsRasterLayer(parameters, self.SOIL_RASTER, context): soil_raster = self.parameterAsRasterLayer(parameters, self.SOIL_RASTER, context) else: soil_raster = None if self.parameterAsRasterLayer(parameters, self.ADDITIONAL_COST_RASTER, context): special_raster = self.parameterAsRasterLayer( parameters, self.ADDITIONAL_COST_RASTER, context) else: special_raster = None feedback.pushInfo(self.tr("Checking inputs...")) # If source was not found, throw an exception to indicate that the algorithm # encountered a fatal error. The exception text can be any string, but in this # case we use the pre-built invalidSourceError method to return a standard # helper text for when a source cannot be evaluated if basic_distance_cost is None or basic_distance_cost == 0: raise QgsProcessingException( self.invalidSourceError(parameters, self.BASIC_DISTANCE_COST)) # Now, we check to see if the CRS, extent and resolution of every raster is equal, so that their align properly. listOfRastersToCheck = list() if initial_road_network_raster is not None: listOfRastersToCheck.append(initial_road_network_raster) if coarse_elevation_raster is not None: listOfRastersToCheck.append(coarse_elevation_raster) if fine_elevation_raster is not None: listOfRastersToCheck.append(fine_elevation_raster) if coarse_water_raster is not None: listOfRastersToCheck.append(coarse_water_raster) if fine_water_raster is not None: listOfRastersToCheck.append(fine_water_raster) if soil_raster is not None: listOfRastersToCheck.append(soil_raster) if special_raster is not None: listOfRastersToCheck.append(special_raster) if len(listOfRastersToCheck) == 0: raise QgsProcessingException( self. tr("At least one input raster is needed ! Please, input one raster." )) # We check that every raster has the same CRS, extent and resolution. CostRasterCreatorHelper.CheckRastersCompatibility(listOfRastersToCheck) # We also check that the matrix of parameters entered for the coarse elevation costs and fine elevation costs # are correct : meaning that there are no holes between the thresholds, and that every lower threshold is lower # than the upper threshold. if coarse_elevation_costs is not None: CostRasterCreatorHelper.CheckThresholds(coarse_elevation_costs, "Coarse elevation costs") if fine_elevation_costs is not None: CostRasterCreatorHelper.CheckThresholds(fine_elevation_costs, "Fine elevation costs") # Then, we create the raster blocks if initial_road_network_raster is not None: initial_road_network_raster_block = CostRasterCreatorHelper.get_all_block( initial_road_network_raster) else: initial_road_network_raster_block = None if coarse_elevation_raster is not None: coarse_elevation_raster_block = CostRasterCreatorHelper.get_all_block( coarse_elevation_raster) else: coarse_elevation_raster_block = None if fine_elevation_raster is not None: fine_elevation_raster_block = CostRasterCreatorHelper.get_all_block( fine_elevation_raster) else: fine_elevation_raster_block = None if coarse_water_raster is not None: coarse_water_raster_block = CostRasterCreatorHelper.get_all_block( coarse_water_raster) else: coarse_water_raster_block = None if fine_water_raster is not None: fine_water_raster_block = CostRasterCreatorHelper.get_all_block( fine_water_raster) else: fine_water_raster_block = None if soil_raster is not None: soil_raster_block = CostRasterCreatorHelper.get_all_block( soil_raster) else: soil_raster_block = None if special_raster is not None: special_raster_block = CostRasterCreatorHelper.get_all_block( special_raster) else: special_raster_block = None feedback.pushInfo(self.tr("Preparing output...")) # We set the output to be ready : It is a QgsDataProvider outputFile = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) outputFormat = QgsRasterFileWriter.driverForExtension( os.path.splitext(outputFile)[1]) crs = listOfRastersToCheck[0].crs() extent = listOfRastersToCheck[0].extent() rows = max([ math.ceil(extent.height() / listOfRastersToCheck[0].rasterUnitsPerPixelY()), 1.0 ]) cols = max([ math.ceil(extent.width() / listOfRastersToCheck[0].rasterUnitsPerPixelX()), 1.0 ]) # We will need this value for the fine water computation later pixelSide = (listOfRastersToCheck[0].rasterUnitsPerPixelY() + listOfRastersToCheck[0].rasterUnitsPerPixelX()) / 2 writer = QgsRasterFileWriter(outputFile) writer.setOutputProviderKey('gdal') writer.setOutputFormat(outputFormat) provider = writer.createOneBandRaster(Qgis.Float32, cols, rows, extent, crs) if provider is None: raise QgsProcessingException( self.tr("Could not create raster output: {}").format( outputFile)) if not provider.isValid(): raise QgsProcessingException( self.tr("Could not create raster output {}").format( outputFile)) provider.setNoDataValue(1, -9999) # We create the data block for the output raster, and we fill it with the values we need dataBlock = QgsRasterBlock(Qgis.Float32, cols, rows) # i = float(1.0) feedback.pushInfo(self.tr("Calculating cost raster...")) progress = 0 feedback.setProgress(0) errorMessages = list() for y in range(dataBlock.height()): for x in range(dataBlock.width()): if feedback.isCanceled(): raise QgsProcessingException( self.tr("ERROR: Operation was cancelled.")) # If there is a road already on this pixel, then the cost is 0. if initial_road_network_raster_block is not None and \ (initial_road_network_raster_block.value(y, x) != 0 and not initial_road_network_raster_block.isNoData( y, x)): finalValue = 0 # If there is a water body on this pixel, we stop everything : # The cost will be the construction of a bridge elif coarse_water_raster_block is not None and coarse_water_raster_block.value( y, x) != 0: if coarse_water_cost is not None: finalValue = coarse_water_cost else: raise QgsProcessingException( self. tr("A coarse water raster has been given, but no coarse " + "water cost. Please input a coarse water cost.") ) # feedback.pushInfo("Seems like there was a road on this pixel. Final value is " + str(finalValue)) # Else, if we are not on a road or on a body of water... else: # We start with the base cost of crossing the pixel finalValue = basic_distance_cost # feedback.pushInfo("No road on this pixel, we put basic distance cost. Final value is " + str(finalValue)) # Then, if we have it, we add the soil cost if soil_raster_block is not None: finalValue += soil_raster_block.value(y, x) # feedback.pushInfo("After soils, final value is " + str(finalValue)) if special_raster_block is not None: finalValue += special_raster_block.value(y, x) # Then the coarse elevation value if coarse_elevation_raster_block is not None: additionalValue, errorMessage = CostRasterCreatorHelper.CalculateCoarseElevationCost( y, x, coarse_elevation_raster_block, coarse_elevation_costs, pixelSide) finalValue += additionalValue if errorMessage is not None: errorMessages.append(errorMessage) # feedback.pushInfo("After coarse elevation, final value is " + str(finalValue)) # Then the fine water value if fine_water_raster_block is not None: finalValue += CostRasterCreatorHelper.CalculateFineWaterCost( y, x, fine_water_raster_block, fine_water_cost, pixelSide) # feedback.pushInfo("After fine water, final value is " + str(finalValue)) # Then, we multiply everything with the fine elevation cost. if fine_elevation_raster_block is not None: finalValue, errorMessage = CostRasterCreatorHelper.CalculateFineElevationCost( y, x, fine_elevation_raster_block, fine_elevation_costs, finalValue) if errorMessage is not None: errorMessages.append(errorMessage) # feedback.pushInfo("After fine elevation, final value is " + str(finalValue)) dataBlock.setValue(y, x, float(finalValue)) progress += 1 feedback.setProgress( 100 * (progress / (dataBlock.height() * dataBlock.width()))) # We write the values in the provider of our files provider.writeBlock(dataBlock, 1) # We stop the edition of the output raster provider.setEditable(False) # We display the warning messages about the thresholds if len(errorMessages) > 0: above = 0 below = 0 for errorMessage in errorMessages: if errorMessage == "Above": above += 1 elif errorMessage == "Below": below += 1 feedback.pushInfo( self. tr("WARNING : There were " + str(below) + " situations where the value of a pixel was under" " the lowest threshold given as a parameter; and " + str(above) + " when it was above the" " highest. In those cases, the lowest or highest value of the parameter range was used." " To avoid that, please make sure to use thresholds that cover all of the range of values in" " your rasters.")) # We make the output return {self.OUTPUT: outputFile}