示例#1
0
    def btn_calculate(self):
        ######################################################################
        # Check that all needed output files are selected
        if not self.output_file_table.text():
            QtWidgets.QMessageBox.information(
                None, self.tr("Error"),
                self.tr("Choose an output file for the summary table."))
            return

        # Note that the super class has several tests in it - if they fail it
        # returns False, which would mean this function should stop execution
        # as well.
        ret = super(DlgCalculateTCSummaryTable, self).btn_calculate()
        if not ret:
            return

        ######################################################################
        # Check that all needed input layers are selected
        if len(self.combo_layer_f_loss.layer_list) == 0:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("You must add a forest loss layer to your map before you can use the carbon change summary tool."
                   ))
            return
        if len(self.combo_layer_tc.layer_list) == 0:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("You must add a total carbon layer to your map before you can use the carbon change summary tool."
                   ))
            return
            #######################################################################
            # Check that the layers cover the full extent needed
            if self.aoi.calc_frac_overlap(
                    QgsGeometry.fromRect(
                        self.combo_layer_f_loss.get_layer().extent())) < .99:
                QtWidgets.QMessageBox.critical(
                    None, self.tr("Error"),
                    self.
                    tr("Area of interest is not entirely within the forest loss layer."
                       ))
                return
            if self.aoi.calc_frac_overlap(
                    QgsGeometry.fromRect(
                        self.combo_layer_tc.get_layer().extent())) < .99:
                QtWidgets.QMessageBox.critical(
                    None, self.tr("Error"),
                    self.
                    tr("Area of interest is not entirely within the total carbon layer."
                       ))
                return

        #######################################################################
        # Check that all of the productivity layers have the same resolution
        # and CRS
        def res(layer):
            return (round(layer.rasterUnitsPerPixelX(),
                          10), round(layer.rasterUnitsPerPixelY(), 10))

        if res(self.combo_layer_f_loss.get_layer()) != res(
                self.combo_layer_tc.get_layer()):
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("Resolutions of forest loss and total carbon layers do not match."
                   ))
            return

        self.close()

        #######################################################################
        # Load all datasets to VRTs (to select only the needed bands)
        f_loss_vrt = self.combo_layer_f_loss.get_vrt()
        tc_vrt = self.combo_layer_tc.get_vrt()

        # Figure out start and end dates
        year_start = self.combo_layer_f_loss.get_band_info(
        )['metadata']['year_start']
        year_end = self.combo_layer_f_loss.get_band_info(
        )['metadata']['year_end']

        # Remember the first value is an indication of whether dataset is
        # wrapped across 180th meridian
        wkts = self.aoi.meridian_split('layer', 'wkt', warn=False)[1]
        bbs = self.aoi.get_aligned_output_bounds(f_loss_vrt)

        for n in range(len(wkts)):
            # Compute the pixel-aligned bounding box (slightly larger than
            # aoi). Use this instead of croptocutline in gdal.Warp in order to
            # keep the pixels aligned with the chosen productivity layer.

            # Combines SDG 15.3.1 input raster into a VRT and crop to the AOI
            indic_vrt = tempfile.NamedTemporaryFile(suffix='.vrt').name
            log(u'Saving indicator VRT to: {}'.format(indic_vrt))
            # The plus one is because band numbers start at 1, not zero
            gdal.BuildVRT(indic_vrt, [f_loss_vrt, tc_vrt],
                          outputBounds=bbs[n],
                          resolution='highest',
                          resampleAlg=gdal.GRA_NearestNeighbour,
                          separate=True)

            masked_vrt = tempfile.NamedTemporaryFile(suffix='.tif').name
            log(u'Saving forest loss/carbon clipped file to {}'.format(
                masked_vrt))
            geojson = json_geom_to_geojson(
                QgsGeometry.fromWkt(wkts[n]).asJson())
            clip_worker = StartWorker(
                ClipWorker,
                'masking layers (part {} of {})'.format(n + 1, len(wkts)),
                indic_vrt, masked_vrt, geojson, bbs[n])
            if not clip_worker.success:
                QtWidgets.QMessageBox.critical(
                    None, self.tr("Error"),
                    self.tr("Error masking carbon change input layers."))
                return

            ######################################################################
            #  Calculate carbon change table
            log('Calculating summary table...')
            tc_summary_worker = StartWorker(
                TCSummaryWorker,
                'calculating summary table (part {} of {})'.format(
                    n + 1, len(wkts)), masked_vrt, year_start, year_end)
            if not tc_summary_worker.success:
                QtWidgets.QMessageBox.critical(
                    None, self.tr("Error"),
                    self.tr("Error calculating carbon change summary table."))
                return
            else:
                if n == 0:
                    forest_change, \
                            carbon_change, \
                            area_missing, \
                            area_water, \
                            area_non_forest, \
                            area_site, \
                            initial_forest_area, \
                            initial_carbon_total = tc_summary_worker.get_return()
                else:
                    this_forest_change, \
                            this_carbon_change, \
                            this_area_missing, \
                            this_area_water, \
                            this_area_non_forest, \
                            this_area_site, \
                            this_initial_forest_area, \
                            this_initial_carbon_total = tc_summary_worker.get_return()
                    forest_change = forest_change + this_forest_change
                    carbon_change = carbon_change + this_carbon_change
                    area_missing = area_missing + this_area_missing
                    area_water = area_water + this_area_water
                    area_non_forest = area_non_forest + this_area_non_forest
                    area_site = area_site + this_area_site
                    initial_forest_area = initial_forest_area + this_initial_forest_area
                    initial_carbon_total = initial_carbon_total + this_initial_carbon_total

        log('area_missing: {}'.format(area_missing))
        log('area_water: {}'.format(area_water))
        log('area_non_forest: {}'.format(area_non_forest))
        log('area_site: {}'.format(area_site))
        log('initial_forest_area: {}'.format(initial_forest_area))
        log('initial_carbon_total: {}'.format(initial_carbon_total))
        log('forest loss: {}'.format(forest_change))
        log('carbon loss: {}'.format(carbon_change))

        make_summary_table(forest_change, carbon_change, area_missing,
                           area_water, area_non_forest, area_site,
                           initial_forest_area, initial_carbon_total,
                           year_start, year_end, self.output_file_table.text())
示例#2
0
    def calculate_locally(self, method, biomass_data):
        if not self.use_custom.isChecked():
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("Due to the options you have chosen, this calculation must occur offline. You MUST select a custom land cover dataset."
                   ))
            return

        year_baseline = self.lc_setup_tab.get_initial_year()
        year_target = self.lc_setup_tab.get_final_year()
        if int(year_baseline) >= int(year_target):
            QtWidgets.QMessageBox.information(
                None, self.tr("Warning"),
                self.
                tr('The baseline year ({}) is greater than or equal to the target year ({}) - this analysis might generate strange results.'
                   .format(year_baseline, year_target)))

        if self.aoi.calc_frac_overlap(
                QgsGeometry.fromRect(self.lc_setup_tab.use_custom_initial.
                                     get_layer().extent())) < .99:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("Area of interest is not entirely within the initial land cover layer."
                   ))
            return

        if self.aoi.calc_frac_overlap(
                QgsGeometry.fromRect(self.lc_setup_tab.use_custom_final.
                                     get_layer().extent())) < .99:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("Area of interest is not entirely within the final land cover layer."
                   ))
            return

        out_f = self.get_save_raster()
        if not out_f:
            return

        self.close()

        # Select the initial and final bands from initial and final datasets
        # (in case there is more than one lc band per dataset)
        lc_initial_vrt = self.lc_setup_tab.use_custom_initial.get_vrt()
        lc_final_vrt = self.lc_setup_tab.use_custom_final.get_vrt()
        lc_files = [lc_initial_vrt, lc_final_vrt]
        lc_years = [
            self.lc_setup_tab.get_initial_year(),
            self.lc_setup_tab.get_final_year()
        ]
        lc_vrts = []
        for i in range(len(lc_files)):
            f = tempfile.NamedTemporaryFile(suffix='.vrt').name
            # Add once since band numbers don't start at zero
            gdal.BuildVRT(
                f,
                lc_files[i],
                bandList=[i + 1],
                outputBounds=self.aoi.get_aligned_output_bounds_deprecated(
                    lc_initial_vrt),
                resolution='highest',
                resampleAlg=gdal.GRA_NearestNeighbour,
                separate=True)
            lc_vrts.append(f)

        climate_zones = os.path.join(os.path.dirname(__file__), 'data',
                                     'IPCC_Climate_Zones.tif')
        in_files = [climate_zones]
        in_files.extend(lc_vrts)
        in_vrt = tempfile.NamedTemporaryFile(suffix='.vrt').name
        log(u'Saving SOC input files to {}'.format(in_vrt))
        gdal.BuildVRT(
            in_vrt,
            in_files,
            resolution='highest',
            resampleAlg=gdal.GRA_NearestNeighbour,
            outputBounds=self.aoi.get_aligned_output_bounds_deprecated(
                lc_initial_vrt),
            separate=True)
        # Lc bands start on band 3 as band 1 is initial soc, and band 2 is
        # climate zones
        lc_band_nums = np.arange(len(lc_files)) + 3

        log(u'Saving total carbon to {}'.format(out_f))
        soc_worker = StartWorker(SOCWorker,
                                 'calculating change in total carbon', in_vrt,
                                 out_f, lc_band_nums, lc_years)

        if not soc_worker.success:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.tr("Error calculating change in toal carbon."))
            return

        band_infos = [
            BandInfo("Total carbon (change)",
                     add_to_map=True,
                     metadata={
                         'year_start': lc_years[0],
                         'year_end': lc_years[-1]
                     })
        ]
        for year in lc_years:
            if (year == lc_years[0]) or (year == lc_years[-1]):
                # Add first and last years to map
                add_to_map = True
            else:
                add_to_map = False
            band_infos.append(
                BandInfo("Total carbon",
                         add_to_map=add_to_map,
                         metadata={'year': year}))
        for year in lc_years:
            band_infos.append(
                BandInfo("Land cover (7 class)", metadata={'year': year}))

        out_json = os.path.splitext(out_f)[0] + '.json'
        create_local_json_metadata(out_json, out_f, band_infos)
        schema = BandInfoSchema()
        for band_number in range(len(band_infos)):
            b = schema.dump(band_infos[band_number])
            if b['add_to_map']:
                # The +1 is because band numbers start at 1, not zero
                add_layer(out_f, band_number + 1, b)
示例#3
0
    def calculate_locally(self):
        trans_matrix = [[
            11, 12, 13, 14, 15, 16, 17, 21, 22, 23, 24, 25, 26, 27, 31, 32, 33,
            34, 35, 36, 37, 41, 42, 43, 44, 45, 46, 47, 51, 52, 53, 54, 55, 56,
            57, 61, 62, 63, 64, 65, 66, 67
        ],
                        self.lc_define_deg_tab.trans_matrix_get()]

        # Remap the persistence classes so they are sequential, making them
        # easier to assign a clear color ramp in QGIS
        persistence_remap = [[
            11, 12, 13, 14, 15, 16, 17, 21, 22, 23, 24, 25, 26, 27, 31, 32, 33,
            34, 35, 36, 37, 41, 42, 43, 44, 45, 46, 47, 51, 52, 53, 54, 55, 56,
            57, 61, 62, 63, 64, 65, 66, 67, 71, 72, 73, 74, 75, 76, 77
        ],
                             [
                                 1, 12, 13, 14, 15, 16, 17, 21, 2, 23, 24, 25,
                                 26, 27, 31, 32, 3, 34, 35, 36, 37, 41, 42, 43,
                                 4, 45, 46, 47, 51, 52, 53, 54, 5, 56, 57, 61,
                                 62, 63, 64, 65, 6, 67, 71, 72, 73, 74, 75, 76,
                                 7
                             ]]

        if len(self.lc_setup_tab.use_custom_initial.layer_list) == 0:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("You must add an initial land cover layer to your map before you can run the calculation."
                   ))
            return

        if len(self.lc_setup_tab.use_custom_final.layer_list) == 0:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("You must add a final land cover layer to your map before you can run the calculation."
                   ))
            return

        # Select the initial and final bands from initial and final datasets
        # (in case there is more than one lc band per dataset)
        lc_initial_vrt = self.lc_setup_tab.use_custom_initial.get_vrt()
        lc_final_vrt = self.lc_setup_tab.use_custom_final.get_vrt()

        year_baseline = self.lc_setup_tab.get_initial_year()
        year_target = self.lc_setup_tab.get_final_year()
        if int(year_baseline) >= int(year_target):
            QtWidgets.QMessageBox.information(
                None, self.tr("Warning"),
                self.
                tr('The initial year ({}) is greater than or equal to the target year ({}) - this analysis might generate strange results.'
                   .format(year_baseline, year_target)))

        if self.aoi.calc_frac_overlap(
                QgsGeometry.fromRect(self.lc_setup_tab.use_custom_initial.
                                     get_layer().extent())) < .99:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("Area of interest is not entirely within the initial land cover layer."
                   ))
            return

        if self.aoi.calc_frac_overlap(
                QgsGeometry.fromRect(self.lc_setup_tab.use_custom_initial.
                                     get_layer().extent())) < .99:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("Area of interest is not entirely within the final land cover layer."
                   ))
            return

        out_f = self.get_save_raster()
        if not out_f:
            return

        self.close()

        # Add the lc layers to a VRT in case they don't match in resolution,
        # and set proper output bounds
        in_vrt = tempfile.NamedTemporaryFile(suffix='.vrt').name
        gdal.BuildVRT(
            in_vrt, [lc_initial_vrt, lc_final_vrt],
            resolution='highest',
            resampleAlg=gdal.GRA_NearestNeighbour,
            outputBounds=self.aoi.get_aligned_output_bounds_deprecated(
                lc_initial_vrt),
            separate=True)

        lc_change_worker = StartWorker(LandCoverChangeWorker,
                                       'calculating land cover change', in_vrt,
                                       out_f, trans_matrix, persistence_remap)
        if not lc_change_worker.success:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.tr("Error calculating land cover change."))
            return

        band_info = [
            BandInfo("Land cover (degradation)",
                     add_to_map=True,
                     metadata={
                         'year_baseline': year_baseline,
                         'year_target': year_target
                     }),
            BandInfo("Land cover (7 class)", metadata={'year': year_baseline}),
            BandInfo("Land cover (7 class)", metadata={'year': year_target}),
            BandInfo("Land cover transitions",
                     add_to_map=True,
                     metadata={
                         'year_baseline': year_baseline,
                         'year_target': year_target
                     })
        ]
        out_json = os.path.splitext(out_f)[0] + '.json'
        create_local_json_metadata(out_json, out_f, band_info)
        schema = BandInfoSchema()
        for band_number in range(len(band_info)):
            b = schema.dump(band_info[band_number])
            if b['add_to_map']:
                # The +1 is because band numbers start at 1, not zero
                add_layer(out_f, band_number + 1, b)
示例#4
0
    def btn_calculate(self):
        # Note that the super class has several tests in it - if they fail it
        # returns False, which would mean this function should stop execution
        # as well.
        ret = super(DlgCalculateUrbanSummaryTable, self).btn_calculate()
        if not ret:
            return

        ######################################################################
        # Check that all needed input layers are selected
        if len(self.combo_layer_urban_series.layer_list) == 0:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("You must add an urban series layer to your map before you can use the urban change summary tool."
                   ))
            return

        #######################################################################
        # Check that the layers cover the full extent needed
        if self.aoi.calc_frac_overlap(
                QgsGeometry.fromRect(
                    self.combo_layer_urban_series.get_layer().extent())) < .99:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("Area of interest is not entirely within the urban series layer."
                   ))
            return

        self.close()

        #######################################################################
        # Load all datasets to VRTs (to select only the needed bands)
        band_infos = get_band_infos(
            self.combo_layer_urban_series.get_data_file())

        urban_annual_band_indices = [
            i for i, bi in enumerate(band_infos) if bi['name'] == 'Urban'
        ]
        urban_annual_band_indices.sort(
            key=lambda i: band_infos[i]['metadata']['year'])
        urban_years = [
            bi['metadata']['year'] for bi in band_infos
            if bi['name'] == 'Urban'
        ]
        urban_files = []
        for i in urban_annual_band_indices:
            f = tempfile.NamedTemporaryFile(suffix='.vrt').name
            # Add once since band numbers don't start at zero
            gdal.BuildVRT(f,
                          self.combo_layer_urban_series.get_data_file(),
                          bandList=[i + 1])
            urban_files.append(f)

        pop_annual_band_indices = [
            i for i, bi in enumerate(band_infos) if bi['name'] == 'Population'
        ]
        pop_annual_band_indices.sort(
            key=lambda i: band_infos[i]['metadata']['year'])
        pop_years = [
            bi['metadata']['year'] for bi in band_infos
            if bi['name'] == 'Population'
        ]
        pop_files = []
        for i in pop_annual_band_indices:
            f = tempfile.NamedTemporaryFile(suffix='.vrt').name
            # Add once since band numbers don't start at zero
            gdal.BuildVRT(f,
                          self.combo_layer_urban_series.get_data_file(),
                          bandList=[i + 1])
            pop_files.append(f)

        assert (len(pop_files) == len(urban_files))
        assert (urban_years == pop_years)

        in_files = list(urban_files)
        in_files.extend(pop_files)
        urban_band_nums = np.arange(len(urban_files)) + 1
        pop_band_nums = np.arange(len(pop_files)) + 1 + urban_band_nums.max()

        # Remember the first value is an indication of whether dataset is
        # wrapped across 180th meridian
        wkts = self.aoi.meridian_split('layer', 'wkt', warn=False)[1]
        bbs = self.aoi.get_aligned_output_bounds(urban_files[1])

        ######################################################################
        # Process the wkts using a summary worker
        output_indicator_tifs = []
        output_indicator_json = self.output_tab.output_basename.text(
        ) + '.json'
        for n in range(len(wkts)):
            # Compute the pixel-aligned bounding box (slightly larger than
            # aoi). Use this instead of croptocutline in gdal.Warp in order to
            # keep the pixels aligned with the chosen productivity layer.

            # Combines SDG 15.3.1 input raster into a VRT and crop to the AOI
            indic_vrt = tempfile.NamedTemporaryFile(suffix='.vrt').name
            log(u'Saving indicator VRT to: {}'.format(indic_vrt))
            # The plus one is because band numbers start at 1, not zero
            gdal.BuildVRT(indic_vrt,
                          in_files,
                          outputBounds=bbs[n],
                          resolution='highest',
                          resampleAlg=gdal.GRA_NearestNeighbour,
                          separate=True)

            if len(wkts) > 1:
                output_indicator_tif = os.path.splitext(
                    output_indicator_json)[0] + '_{}.tif'.format(n)
            else:
                output_indicator_tif = os.path.splitext(
                    output_indicator_json)[0] + '.tif'
            output_indicator_tifs.append(output_indicator_tif)

            log(u'Saving urban clipped files to {}'.format(
                output_indicator_tif))
            geojson = json_geom_to_geojson(
                QgsGeometry.fromWkt(wkts[n]).asJson())
            clip_worker = StartWorker(
                ClipWorker,
                'masking layers (part {} of {})'.format(n + 1, len(wkts)),
                indic_vrt, output_indicator_tif, geojson, bbs[n])
            if not clip_worker.success:
                QtWidgets.QMessageBox.critical(
                    None, self.tr("Error"),
                    self.tr("Error masking urban change input layers."))
                return

            ######################################################################
            #  Calculate urban change table
            log('Calculating summary table...')
            urban_summary_worker = StartWorker(
                UrbanSummaryWorker,
                'calculating summary table (part {} of {})'.format(
                    n + 1, len(wkts)), output_indicator_tif, urban_band_nums,
                pop_band_nums, 9)
            if not urban_summary_worker.success:
                QtWidgets.QMessageBox.critical(
                    None, self.tr("Error"),
                    self.tr("Error calculating urban change summary table."))
                return
            else:
                if n == 0:
                    areas, \
                            populations = urban_summary_worker.get_return()
                else:
                    these_areas, \
                            these_populations = urban_summary_worker.get_return()
                    areas = areas + these_areas
                    populations = populations + these_populations

        make_summary_table(areas, populations,
                           self.output_tab.output_basename.text() + '.xlsx')

        # Add the indicator layers to the map
        output_indicator_bandinfos = [
            BandInfo("Urban", add_to_map=True, metadata={'year': 2000}),
            BandInfo("Urban", add_to_map=True, metadata={'year': 2005}),
            BandInfo("Urban", add_to_map=True, metadata={'year': 2010}),
            BandInfo("Urban", add_to_map=True, metadata={'year': 2015}),
            BandInfo("Population", metadata={'year': 2000}),
            BandInfo("Population", metadata={'year': 2005}),
            BandInfo("Population", metadata={'year': 2010}),
            BandInfo("Population", metadata={'year': 2015})
        ]
        if len(output_indicator_tifs) == 1:
            output_file = output_indicator_tifs[0]
        else:
            output_file = os.path.splitext(output_indicator_json)[0] + '.vrt'
            gdal.BuildVRT(output_file, output_indicator_tifs)
        create_local_json_metadata(
            output_indicator_json,
            output_file,
            output_indicator_bandinfos,
            metadata={
                'task_name': self.options_tab.task_name.text(),
                'task_notes': self.options_tab.task_notes.toPlainText()
            })
        schema = BandInfoSchema()
        add_layer(output_file, 1, schema.dump(output_indicator_bandinfos[0]))
        add_layer(output_file, 2, schema.dump(output_indicator_bandinfos[1]))
        add_layer(output_file, 3, schema.dump(output_indicator_bandinfos[2]))
        add_layer(output_file, 4, schema.dump(output_indicator_bandinfos[3]))

        return True
示例#5
0
    def btn_calculate(self):
        ######################################################################
        # Check that all needed output files are selected
        if not self.output_file_layer.text():
            QtWidgets.QMessageBox.information(
                None, self.tr("Error"),
                self.
                tr("Choose an output file for the biomass difference layers."))
            return

        if not self.output_file_table.text():
            QtWidgets.QMessageBox.information(
                None, self.tr("Error"),
                self.tr("Choose an output file for the summary table."))
            return

        # Note that the super class has several tests in it - if they fail it
        # returns False, which would mean this function should stop execution
        # as well.
        ret = super(DlgCalculateRestBiomassSummaryTable, self).btn_calculate()
        if not ret:
            return

        ######################################################################
        # Check that all needed input layers are selected
        if len(self.combo_layer_biomass_diff.layer_list) == 0:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("You must add a biomass layer to your map before you can use the summary tool."
                   ))
            return
        #######################################################################
        # Check that the layers cover the full extent needed
        if self.aoi.calc_frac_overlap(
                QgsGeometry.fromRect(
                    self.combo_layer_biomass_diff.get_layer().extent())) < .99:
            QtWidgets.QMessageBox.critical(
                None, self.tr("Error"),
                self.
                tr("Area of interest is not entirely within the biomass layer."
                   ))
            return

        self.close()

        #######################################################################
        # Prep files
        in_file = self.combo_layer_biomass_diff.get_data_file()

        # Remember the first value is an indication of whether dataset is
        # wrapped across 180th meridian
        wkts = self.aoi.meridian_split('layer', 'wkt', warn=False)[1]
        bbs = self.aoi.get_aligned_output_bounds(in_file)

        output_biomass_diff_tifs = []
        output_biomass_diff_json = self.output_file_layer.text()
        for n in range(len(wkts)):
            if len(wkts) > 1:
                output_biomass_diff_tif = os.path.splitext(
                    output_biomass_diff_json)[0] + '_{}.tif'.format(n)
            else:
                output_biomass_diff_tif = os.path.splitext(
                    output_biomass_diff_json)[0] + '.tif'
            output_biomass_diff_tifs.append(output_biomass_diff_tif)

            log(u'Saving clipped biomass file to {}'.format(
                output_biomass_diff_tif))
            geojson = json_geom_to_geojson(
                QgsGeometry.fromWkt(wkts[n]).asJson())
            clip_worker = StartWorker(
                ClipWorker,
                'masking layers (part {} of {})'.format(n + 1, len(wkts)),
                in_file, output_biomass_diff_tif, geojson, bbs[n])
            if not clip_worker.success:
                QtWidgets.QMessageBox.critical(
                    None, self.tr("Error"),
                    self.tr("Error masking input layers."))
                return

            ######################################################################
            #  Calculate biomass change summary table
            log('Calculating summary table...')
            rest_summary_worker = StartWorker(
                RestBiomassSummaryWorker,
                'calculating summary table (part {} of {})'.format(
                    n + 1, len(wkts)), output_biomass_diff_tif)
            if not rest_summary_worker.success:
                QtWidgets.QMessageBox.critical(
                    None, self.tr("Error"),
                    self.tr("Error calculating biomass change summary table."))
                return
            else:
                if n == 0:
                    biomass_initial, \
                        biomass_change, \
                        area_site = rest_summary_worker.get_return()
                else:
                    this_biomass_initial, \
                        this_biomass_change, \
                        this_area_site = rest_summary_worker.get_return()
                    biomass_initial = biomass_initial + this_biomass_initial
                    biomass_change = biomass_change + this_biomass_change
                    area_site = area_site + this_area_site

        log('area_site: {}'.format(area_site))
        log('biomass_initial: {}'.format(biomass_initial))
        log('biomass_change: {}'.format(biomass_change))

        # Figure out how many years of restoration this data is for, take this
        # from the second band in the in file
        band_infos = get_band_infos(in_file)
        length_yr = band_infos[1]['metadata']['years']
        # And make a list of the restoration types
        rest_types = [
            band_info['metadata']['type']
            for band_info in band_infos[1:len(band_infos)]
        ]

        make_summary_table(self.output_file_table.text(), biomass_initial,
                           biomass_change, area_site, length_yr, rest_types)

        # Add the biomass_dif layers to the map
        if len(output_biomass_diff_tifs) == 1:
            output_file = output_biomass_diff_tifs[0]
        else:
            output_file = os.path.splitext(
                output_biomass_diff_json)[0] + '.vrt'
            gdal.BuildVRT(output_file, output_biomass_diff_tifs)
        # Update the band infos to use the masking value (-32767) as the file
        # no data value, so that stretches are more likely to compute correctly
        for item in band_infos:
            item['no_data_value'] = -32767
        create_local_json_metadata(
            output_biomass_diff_json,
            output_file,
            band_infos,
            metadata={
                'task_name': self.options_tab.task_name.text(),
                'task_notes': self.options_tab.task_notes.toPlainText()
            })
        schema = BandInfoSchema()
        for n in range(1, len(band_infos)):
            add_layer(output_file, n + 1, schema.dump(band_infos[n]))