示例#1
0
    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
示例#2
0
    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)
示例#3
0
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()