def processAlgorithm(self, parameters, context, feedback): # get input variables raster = self.parameterAsFile(parameters, self.INPUT, context) color_ramp = self.parameterAsEnum(parameters, self.COLORRAMP, context) colors = self.color_ramps[self.colors_list[color_ramp]] min = self.parameterAsInt(parameters, self.MIN, context) max = self.parameterAsInt(parameters, self.MAX, context) z_pos_down = self.parameterAsBoolean(parameters, self.Z_POS_DOWN, context) # set new default values in config feedback.pushConsoleInfo( self.tr(f'Storing new default settings in config...')) self.config.set(self.module, 'min', min) self.config.set(self.module, 'max', max) self.config.set(self.module, 'color_ramp', color_ramp) # get file info base_path, base_name, ext = utils.get_info_from_path(raster) # BATHY: # load grid feedback.pushConsoleInfo( self.tr(f'Creating new raster layer [ {base_name} ]...')) dem_layer = QgsRasterLayer(raster, base_name) # test if the files loads properly if not dem_layer.isValid(): raise QgsProcessingException( self.invalidSourceError(parameters, self.INPUT)) # create color scale values feedback.pushConsoleInfo(self.tr(f'Creating color ramp...')) n_values = len(colors) width = max - min step = width / (n_values - 1) values = [] value = min for i in range(n_values): values.append(value) value = value + step # create color_ramp ramp = [] for i, item in enumerate(colors): ramp.append( QgsColorRampShader.ColorRampItem(values[i], QColor(str(item)), str(values[i]))) color_ramp = QgsColorRampShader() color_ramp.setColorRampItemList(ramp) color_ramp.setColorRampType(QgsColorRampShader.Interpolated) # create shader and set color_ramp feedback.pushConsoleInfo(self.tr(f'Creating raster shader...')) shader = QgsRasterShader() shader.setRasterShaderFunction(color_ramp) # create renderer feedback.pushConsoleInfo(self.tr(f'Creating raster renderer...')) renderer = QgsSingleBandPseudoColorRenderer(dem_layer.dataProvider(), dem_layer.type(), shader) # set min max values renderer.setClassificationMin(min) renderer.setClassificationMax(max) # apply renderer to layer dem_layer.setRenderer(renderer) # apply brightness & contrast of layer feedback.pushConsoleInfo(self.tr(f'Adjusting display filters...')) brightness_filter = QgsBrightnessContrastFilter() brightness_filter.setBrightness(-20) brightness_filter.setContrast(10) dem_layer.pipe().set(brightness_filter) # apply resample filter (Bilinear) feedback.pushConsoleInfo(self.tr(f'Setting up resampling...')) resample_filter = dem_layer.resampleFilter() resample_filter.setZoomedInResampler(QgsBilinearRasterResampler()) resample_filter.setZoomedOutResampler(QgsBilinearRasterResampler()) # create group with layer base_name feedback.pushConsoleInfo(self.tr(f'Creating layer group...')) root = context.project().layerTreeRoot() bathy_group = root.addGroup(base_name) # add bathy layer to group bathy_group.insertChildNode(1, QgsLayerTreeLayer(dem_layer)) # add bathy layer to project dem_layer.triggerRepaint() context.project().addMapLayer(dem_layer, False) # 50% done feedback.setProgress(50) # HILLSHADE: # load grid again with layer style file style_hillshade.qml feedback.pushConsoleInfo( self.tr( f'Creating new hillshade layer [ {base_name}_hillshade ]...')) hillshade_layer = QgsRasterLayer(raster, base_name + '_hillshade') # if raster is geographic, load hillshade_geo style (different exaggeration) # if raster is Z positive down, load *_pos_down_* style feedback.pushConsoleInfo(self.tr(f'Setting hillshade style...\n')) if dem_layer.crs().isGeographic() and not z_pos_down: hillshade_layer.loadNamedStyle(self.style_hillshade_geo) elif dem_layer.crs().isGeographic() and z_pos_down: hillshade_layer.loadNamedStyle(self.style_hillshade_pos_down_geo) # else load hillste_prj style elif z_pos_down: hillshade_layer.loadNamedStyle(self.style_hillshade_pos_down_prj) else: hillshade_layer.loadNamedStyle(self.style_hillshade_prj) # add hillshade layer to group bathy_group.insertChildNode(0, QgsLayerTreeLayer(hillshade_layer)) # add hillshade layer to project hillshade_layer.triggerRepaint() context.project().addMapLayer(hillshade_layer, False) # 100% done feedback.setProgress(100) feedback.pushInfo( self.tr(f'{utils.return_success()}! Grid loaded successfully!\n')) result = { self.GROUP: bathy_group, self.DEM_LAYER: dem_layer, self.HILLSHADE_LAYER: hillshade_layer } return result
def process(self, data): # data = {upd/npd: {dating = [calendar years BP, ...], uncert = [calendar years, ...], coords = [[x, y], ...], accur = [accuracy, ...]}} UPD_t_ds = np.round(data["upd"]["dating"]).astype( int ) # mean datings of archaeological components (calendar years BP) UPD_uncert_ds = np.round(data["upd"]["uncert"]).astype( int) # uncertainties of the datings (calendar years) UPD_As = np.round(data["upd"]["coords"]).astype( int) # spatial coordinates of archaeological components (metres) UPD_accurs = np.round(data["upd"]["accur"]).astype( int ) # accuracies of spatial coordinates of archaeological components (+-metres) NPD_t_ds = np.round(data["npd"]["dating"]).astype( int ) # measured radiocarbon ages of archaeological components (radiocarbon years) NPD_uncert_ds = np.round(data["npd"]["uncert"]).astype( int ) # 1-sigma uncertainties of the measured radiocarbon ages (radiocarbon years) NPD_As = np.round(data["npd"]["coords"]).astype( int) # spatial coordinates of archaeological components (metres) NPD_accurs = np.round(data["npd"]["accur"]).astype( int ) # accuracies of spatial coordinates of archaeological components (+-metres) if (not UPD_t_ds.size) and (not NPD_t_ds.size): return s_halflife = self.s_duration / 2 # expected half-life of a settlement in years s_radius = self.s_diameter / 2 # expected radius of a settlement in metres # temporal extent t_min = np.inf t_max = -np.inf if UPD_t_ds.size: t_min = min(t_min, (UPD_t_ds - UPD_uncert_ds).min() - self.s_duration) t_max = max(t_max, (UPD_t_ds + UPD_uncert_ds).max() + self.s_duration) if NPD_t_ds.size: t_min = min(t_min, (NPD_t_ds - 2 * NPD_uncert_ds).min() - self.s_duration) t_max = max(t_max, (NPD_t_ds + 2 * NPD_uncert_ds).max() + self.s_duration) t_min, t_max = [ int(round(value / 10) * 10) for value in [t_min, t_max] ] if self.time_from is not None: t_max = min(t_max, self.time_from) if self.time_to is not None: t_min = max(t_min, self.time_to) ts_slices = np.arange(t_max, t_min - 2 * self.time_step, -self.time_step).tolist() # times of time slices # prepare lookup for probability distributions of 14C datings self.setLabelText("Calibrating radiocarbon dates") cal_curve = load_curve( os.path.join(os.path.dirname(__file__), "intcal13.14c") ) # [[CalBP, ConvBP, CalSigma], ...], sorted by CalBP # filter calibration curve to include only time-step dates cal_curve = cal_curve[(cal_curve[:, 0] >= t_min) & (cal_curve[:, 0] <= t_max)][::-1] ts = cal_curve[:, 0] curve_conv_age = cal_curve[:, 1] curve_uncert = cal_curve[:, 2] if ts[-1] < ts_slices[-1]: ts_slices.append(ts[-1]) # calculate probability distributions for all combinations of 14c age and uncertainty unique_dates = set() # ((age, uncert), ...) for idx in range(NPD_t_ds.shape[0]): unique_dates.add((NPD_t_ds[idx], NPD_uncert_ds[idx])) lookup_14c = defaultdict( dict ) # {age: {uncert: D, ...}, ...}; D[ti] = p; where ti = index in ts, p = probability cmax = len(unique_dates) cnt = 0 for age, uncert in unique_dates: QtWidgets.QApplication.processEvents() if not self.running: return self.setValue((cnt / cmax) * 100) cnt += 1 lookup_14c[age][uncert] = calibrate(age, uncert, curve_conv_age, curve_uncert) # prepare lookup of spatial probability distribution around evidence points self.setLabelText("Calculating spatial probability distribution") self.setValue(0) accurs = set() accurs.update(UPD_accurs.tolist()) accurs.update(NPD_accurs.tolist()) lookup_f_s = { } # {accur: M, ...}; M[n, n] = f_s(d, accur, s_radius); where center is point A and n is 2 * [maximum distance from A in raster units] + 1; where f_s > 0 cnt = 0 cmax = len(accurs) for accur in accurs: QtWidgets.QApplication.processEvents() if not self.running: return self.setValue((cnt / cmax) * 100) cnt += 1 r = int(round((accur + 2 * s_radius) / self.cell_size)) n = 2 * r + 1 lookup_f_s[accur] = np.zeros((n, n), dtype=float) rcs = np.argwhere(np.ones((n, n), dtype=bool)) mask = (rcs > r).all(axis=1) for row, col in rcs[mask]: d = (((row - r)**2 + (col - r)**2)**0.5) * self.cell_size if self.approximate: p = f_s_approx(d, accur, s_radius) else: p = f_S_lens(d, accur, s_radius) / f_S(accur, s_radius) if (p == np.inf) or np.isnan(p): p = 0 lookup_f_s[accur][row, col] = p lookup_f_s[accur][n - row, col] = p lookup_f_s[accur][row, n - col] = p lookup_f_s[accur][n - row, n - col] = p lookup_f_s[accur][0, 0] = lookup_f_s[accur][0, 1] # spatial extent row_min, col_min = np.inf, np.inf row_max, col_max = -np.inf, -np.inf for As, accurs in [[UPD_As, UPD_accurs], [NPD_As, NPD_accurs]]: for idx in range(As.shape[0]): A = As[idx] accur = accurs[idx] r = int(lookup_f_s[accur].shape[0] / 2) col, row = np.round(A / self.cell_size).astype(int) row_min = min(row_min, row - r - 1) col_min = min(col_min, col - r - 1) row_max = max(row_max, row + r) col_max = max(col_max, col + r) width, height = (col_max - col_min), (row_max - row_min) x0, y0 = col_min * self.cell_size, row_min * self.cell_size # calculate time-slices self.setLabelText("Generating time-slices") paths = [] summed = [] val_max = -np.inf grid_summed = np.zeros((height, width), dtype=float) t_slice_prev = ts_slices.pop(0) t_slice = ts_slices.pop(0) n_slice = 1 for ti in range(ts.shape[0]): QtWidgets.QApplication.processEvents() if not self.running: return self.setValue((ti / ts.shape[0]) * 100) grid = np.ones((height, width), dtype=float) for idx in range(UPD_t_ds.shape[0]): t_d = UPD_t_ds[idx] uncert_d = UPD_uncert_ds[idx] A = UPD_As[idx] accur = UPD_accurs[idx] M = 1 - lookup_f_s[accur] * f_t_UPD(ts[ti], t_d, uncert_d, s_halflife) r = int((M.shape[0] - 1) / 2) col0, row0 = np.round((A - [x0, y0]) / self.cell_size - r - 1).astype(int) grid[row0:row0 + M.shape[0], col0:col0 + M.shape[0]] *= M for idx in range(NPD_t_ds.shape[0]): t_d = NPD_t_ds[idx] uncert_d = NPD_uncert_ds[idx] A = NPD_As[idx] accur = NPD_accurs[idx] M = 1 - lookup_f_s[accur] * f_t_NPD( ts[ti], s_halflife, lookup_14c[t_d][uncert_d], ts) r = int((M.shape[0] - 1) / 2) col0, row0 = np.round((A - [x0, y0]) / self.cell_size - r - 1).astype(int) grid[row0:row0 + M.shape[0], col0:col0 + M.shape[0]] *= M grid = 1 - grid grid[np.isnan(grid)] = 0 grid[grid == np.inf] = 0 summed.append(grid.sum()) if ts[ti] <= t_slice: val_max = max(val_max, grid_summed.max()) t_ce, cebce = bp_to_ce(t_slice_prev) t_ce2, cebce2 = bp_to_ce(t_slice) datestr = "%03d_%d_%s_-_%d_%s" % (n_slice, t_ce, cebce, t_ce2, cebce2) paths.append([ datestr, os.path.join(self.path_layers, "ede_%s.tif" % (datestr)) ]) self.save_raster(grid_summed, x0, y0, paths[-1][1]) t_slice_prev = t_slice t_slice = ts_slices.pop(0) n_slice += 1 grid_summed[:] = grid else: grid_summed += grid if self.path_summed: self.save_summed(ts, summed) project = QgsProject.instance() val_max = val_max * 0.9 self.setLabelText("Rendering time-slices") cnt = 0 cmax = len(paths) for datestr, path in paths: QtWidgets.QApplication.processEvents() if not self.running: return self.setValue((cnt / cmax) * 100) cnt += 1 layer = QgsRasterLayer(path, "EDE_%s" % (datestr)) layer.setCrs(self.crs) s = QgsRasterShader() c = QgsColorRampShader() c.setColorRampType(QgsColorRampShader.Interpolated) i = [] i.append(QgsColorRampShader.ColorRampItem(0, self.colors[0])) i.append( QgsColorRampShader.ColorRampItem(val_max / 2, self.colors[1])) i.append(QgsColorRampShader.ColorRampItem(val_max, self.colors[2])) c.setColorRampItemList(i) s.setRasterShaderFunction(c) ps = QgsSingleBandPseudoColorRenderer(layer.dataProvider(), 1, s) ps.setClassificationMin(0) ps.setClassificationMax(val_max) layer.setRenderer(ps) self.save_rendered( layer, os.path.join(self.path_rendered, "%s.tif" % (datestr))) project.addMapLayer(layer)
def raster_apply_classified_renderer(raster_layer, rend_type, num_classes, color_ramp, invert=False, band_num=1, n_decimals=1): """ Applies quantile or equal intervals render to a raster layer. It also allows for the rounding of the values and legend labels. Args: raster_layer (QgsRasterLayer): The rasterlayer to apply classes to rend_type (str): The type of renderer to apply ('quantile' or 'equal interval') num_classes (int): The number of classes to create color_ramp (str): The colour ramp used to display the data band_num(int): The band number to use in the renderer invert (bool): invert the colour ramp n_decimals (int): the number of decimal places to round the values and labels Returns: """ # use an existing color ramp qgsStyles = QgsStyle().defaultStyle() # check to see if the colour ramp is installed if color_ramp != '' and color_ramp not in qgsStyles.colorRampNames(): raise ValueError( 'PAT symbology does not exist. See user manual for install instructions' ) ramp = qgsStyles.colorRamp(color_ramp) # get band statistics cbStats = raster_layer.dataProvider().bandStatistics( band_num, QgsRasterBandStats.All, raster_layer.extent(), 0) # create the renderer renderer = QgsSingleBandPseudoColorRenderer(raster_layer.dataProvider(), band_num) # set the max and min heights we found earlier renderer.setClassificationMin(cbStats.minimumValue) renderer.setClassificationMax(cbStats.maximumValue) if rend_type.lower() == 'quantile': renderer.createShader(ramp, QgsColorRampShader.Discrete, QgsColorRampShader.Quantile, num_classes) elif rend_type.lower() == 'equal interval': renderer.createShader(ramp, QgsColorRampShader.Discrete, QgsColorRampShader.EqualInterval, num_classes) # Round values off to the nearest decimal place and construct the label # get the newly created values and classes color_shader = renderer.shader().rasterShaderFunction() # iterate the values rounding and creating a range label. new_lst = [] for i, (value, color) in enumerate(color_shader.legendSymbologyItems(), start=1): value = float('{:.3g}'.format(float(value))) if i == 1: label = "<= {}".format(value) elif i == len(color_shader.legendSymbologyItems()): label = "> {}".format(last) else: label = "{} - {}".format(last, value) last = value new_lst.append(QgsColorRampShader.ColorRampItem(value, color, label)) # apply back to the shader then the layer color_shader.setColorRampItemList(new_lst) raster_layer.setRenderer(renderer) raster_layer.triggerRepaint()