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())
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)
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)
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
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]))