def download_cloud_results(job, f, tr, add_to_map=True): results = job['results'] json_file = f + '.json' if len(results['urls']) > 1: # Save a VRT if there are multiple files for this download urls = results['urls'] tiles = [] for n in range(len(urls)): tiles.append(f + '_{}.tif'.format(n)) # If file already exists, check its hash and skip redownloading if # it matches if os.access(tiles[n], os.R_OK): if check_hash_against_etag( urls[n]['url'], tiles[n], binascii.hexlify(base64.b64decode( urls[n]['md5Hash'])).decode()): continue resp = download_result( urls[n]['url'], tiles[n], job, binascii.hexlify(base64.b64decode( urls[n]['md5Hash'])).decode()) if not resp: return # Make a VRT mosaicing the tiles so they can be treated as one file # during further processing out_file = f + '.vrt' gdal.BuildVRT(out_file, tiles) else: url = results['urls'][0] out_file = f + '.tif' resp = download_result( url['url'], out_file, job, binascii.hexlify(base64.b64decode(url['md5Hash'])).decode()) if not resp: return create_gee_json_metadata(json_file, job, out_file) if add_to_map: for band_number in range(1, len(results['bands']) + 1): # The minus 1 is because band numbers start at 1, not zero band_info = results['bands'][band_number - 1] if band_info['add_to_map']: add_layer(out_file, band_number, band_info) mb.pushMessage(tr("Downloaded"), tr(u"Downloaded results to {}".format(out_file)), level=0, duration=5)
def calculate_locally(self): if not self.cqi_setup_tab.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 precipitation or evapotranspiration layer dataset.")) return if len(self.cqi_setup_tab.use_custom_ppt.layer_list) == 0: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("You must add a yearly mean precipitation layer to your map before you can run the calculation.")) return if len(self.cqi_setup_tab.use_custom_pet.layer_list) == 0: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("You must add mean potential evapotranspiration 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) custom_pet_vrt = self.cqi_setup_tab.use_custom_pet.get_vrt() custom_ppt_vrt = self.cqi_setup_tab.use_custom_ppt.get_vrt() if self.aoi.calc_frac_overlap(QgsGeometry.fromRect(self.cqi_setup_tab.use_custom_ppt.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("Area of interest is not entirely within the precipitation layer.")) return if self.aoi.calc_frac_overlap(QgsGeometry.fromRect(self.cqi_setup_tab.use_custom_pet.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("Area of interest is not entirely within the potential evapotranspiration layer.")) return out_f = self.get_save_raster() if not out_f: return self.close() crosses_180th, geojsons = self.aoi.bounding_box_gee_geojson() val = [] n = 1 if self.area_tab.area_fromfile.isChecked(): for f in self.aoi.get_layer_wgs84().getFeatures(): # Get an OGR geometry from the QGIS geometry geom = f.geometry() val.append(geom) n += 1 # stringify json object val_string = '{}'.format(json.loads(val[0].asJson())) # create ogr geometry val_geom = ogr.CreateGeometryFromJson(val_string) # simplify polygon to tolerance of 0.003 val_geom_simplified = val_geom.Simplify(0.003) # fetch coordinates from json coords= json.loads(val_geom_simplified.ExportToJson())['coordinates'] geometries = { "coordinates":coords, "type":"Polygon" } elif self.area_tab.area_fromadmin.isChecked(): geometries ={ "coordinates":self.get_admin_poly_geojson()['geometry']['coordinates'][0], "type":"Polygon" } elif self.area_tab.area_frompoint.isChecked(): point = QgsPointXY(float(self.area_tab.area_frompoint_point_x.text()), float(self.area_tab.area_frompoint_point_y.text())) crs_src = QgsCoordinateReferenceSystem(self.area_tab.canvas.mapSettings().destinationCrs().authid()) point = QgsCoordinateTransform(crs_src, self.aoi.crs_dst, QgsProject.instance()).transform(point) geometries = json.loads(QgsGeometry.fromPointXY(point).asJson()) # write aoi geometry to file for masking output aoi_geom = { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": {}, "geometry": geometries } ] } aoi_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'aoi.geojson') with open(aoi_file, 'w') as filetowrite: filetowrite.write(json.dumps(aoi_geom)) # Add the custom 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, [custom_pet_vrt, custom_ppt_vrt], resolution='highest', resampleAlg=gdal.GRA_NearestNeighbour, outputBounds=self.aoi.get_aligned_output_bounds_deprecated(custom_ppt_vrt), separate=True) lc_change_worker = StartWorker(ClimateQualityWorker, 'calculating climate quality index', in_vrt, out_f) if not lc_change_worker.success: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("Error calculating climate quality index."), None) return band_info = [BandInfo("Climate Quality Index (year)", add_to_map=True)] 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 calculate_locally(self): if not self.groupBox_custom_SOC.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 soil organic carbon dataset." )) return if not self.lc_setup_tab.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 if len(self.comboBox_custom_soc.layer_list) == 0: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("You must add a soil organic carbon layer to your map before you can run the calculation." )) 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) soc_vrt = self.comboBox_custom_soc.get_vrt() climate_zones = os.path.join(os.path.dirname(__file__), 'data', 'IPCC_Climate_Zones.tif') in_files = [soc_vrt, 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 soil organic carbon to {}'.format(out_f)) soc_worker = StartWorker(SOCWorker, 'calculating change in soil organic carbon', in_vrt, out_f, lc_band_nums, lc_years, self.get_fl()) if not soc_worker.success: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self.tr("Error calculating change in soil organic carbon.")) return band_infos = [ BandInfo("Soil organic carbon (degradation)", 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("Soil organic 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 not self.lc_setup_tab.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 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."), None) 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): ###################################################################### # 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])) return True
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(DlgCalculateSDISummaryTableAdmin, self).btn_calculate() if not ret: return ###################################################################### # Check that all needed input layers are selected if len(self.combo_layer_sqi.layer_list) == 0: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("You must add a Soil Quality Indicator layer to your map before you can use the SDI calculation tool." )) return if len(self.combo_layer_vqi.layer_list) == 0: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("You must add a Vegatation Quality Indicator layer to your map before you can use the SDI calculation tool." )) return if len(self.combo_layer_cqi.layer_list) == 0: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("You must add a Climate Quality Indicator layer to your map before you can use the SDI calculation tool." )) return if len(self.combo_layer_mqi.layer_list) == 0: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("You must add a Management Quality Indicator layer to your map before you can use the SDI calculation tool." )) return ####################################################################### # Check that the layers cover the full extent needed if self.aoi.calc_frac_overlap( QgsGeometry.fromRect( self.combo_layer_sqi.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("Area of interest is not entirely within the Soil Quality Indicator layer." )) return if self.aoi.calc_frac_overlap( QgsGeometry.fromRect( self.combo_layer_vqi.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("Area of interest is not entirely within the Vegetation Quality Indicator layer." )) return if self.aoi.calc_frac_overlap( QgsGeometry.fromRect( self.combo_layer_cqi.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("Area of interest is not entirely within the Climate Quality Indicator layer." )) return if self.aoi.calc_frac_overlap( QgsGeometry.fromRect( self.combo_layer_mqi.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self. tr("Area of interest is not entirely within the Management Quality Indicator layer." )) return out_f = self.get_save_raster() if not out_f: return self.close() crosses_180th, geojsons = self.aoi.bounding_box_gee_geojson() val = [] n = 1 if self.area_tab.area_fromfile.isChecked(): for f in self.aoi.get_layer_wgs84().getFeatures(): # Get an OGR geometry from the QGIS geometry geom = f.geometry() val.append(geom) n += 1 # stringify json object val_string = '{}'.format(json.loads(val[0].asJson())) # create ogr geometry val_geom = ogr.CreateGeometryFromJson(val_string) # simplify polygon to tolerance of 0.003 val_geom_simplified = val_geom.Simplify(0.003) # fetch coordinates from json coords = json.loads( val_geom_simplified.ExportToJson())['coordinates'] geometries = {"coordinates": coords, "type": "Polygon"} elif self.area_tab.area_fromadmin.isChecked(): geometries = { "coordinates": self.get_admin_poly_geojson()['geometry']['coordinates'][0], "type": "Polygon" } elif self.area_tab.area_frompoint.isChecked(): point = QgsPointXY( float(self.area_tab.area_frompoint_point_x.text()), float(self.area_tab.area_frompoint_point_y.text())) crs_src = QgsCoordinateReferenceSystem( self.area_tab.canvas.mapSettings().destinationCrs().authid()) point = QgsCoordinateTransform( crs_src, self.aoi.crs_dst, QgsProject.instance()).transform(point) geometries = json.loads(QgsGeometry.fromPointXY(point).asJson()) # write aoi geometry to file for masking output aoi_geom = { "type": "FeatureCollection", "features": [{ "type": "Feature", "properties": {}, "geometry": geometries }] } aoi_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'aoi.geojson') with open(aoi_file, 'w') as filetowrite: filetowrite.write(json.dumps(aoi_geom)) ####################################################################### # Load all datasets to VRTs (to select only the needed bands) sqi_vrt = self.combo_layer_sqi.get_vrt() vqi_vrt = self.combo_layer_vqi.get_vrt() cqi_vrt = self.combo_layer_cqi.get_vrt() mqi_vrt = self.combo_layer_mqi.get_vrt() # Add the custom 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, [sqi_vrt, vqi_vrt, cqi_vrt, mqi_vrt], resolution='highest', resampleAlg=gdal.GRA_NearestNeighbour, outputBounds=self.aoi.get_aligned_output_bounds_deprecated( sqi_vrt), separate=True) lc_change_worker = StartWorker( SDIWorker, 'calculating Sensitivity Desertification index', in_vrt, out_f) if not lc_change_worker.success: QtWidgets.QMessageBox.critical( None, self.tr("Error"), self.tr( "Error calculating Sensitivity Desertification index."), None) return band_info = [ BandInfo("Sensitivity Desertification Index", add_to_map=True) ] 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 calculate_locally(self): if not self.sqi_setup_tab.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 if len(self.sqi_setup_tab.use_custom_pm.layer_list) == 0: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("You must add an parent material layer to your map before you can run the calculation.")) return if len(self.sqi_setup_tab.use_custom_rock_frag.layer_list) == 0: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("You must add a rock fragment layer to your map before you can run the calculation.")) return if len(self.sqi_setup_tab.use_custom_texture.layer_list) == 0: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("You must add a soil texture layer to your map before you can run the calculation.")) return if len(self.sqi_setup_tab.use_custom_drainage.layer_list) == 0: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("You must add a drainage layer to your map before you can run the calculation.")) return # select the parent material, slope, soil texture, drainage and rock fragment datasets pm_vrt = self.sqi_setup_tab.use_custom_pm.get_vrt() drainage_vrt = self.sqi_setup_tab.use_custom_drainage.get_vrt() texture_vrt = self.sqi_setup_tab.use_custom_texture.get_vrt() rock_frag_vrt = self.sqi_setup_tab.use_custom_rock_frag.get_vrt() soil_depth = self.sqi_setup_tab.use_depth.value() if self.aoi.calc_frac_overlap(QgsGeometry.fromRect(self.sqi_setup_tab.use_custom_pm.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("Area of interest is not entirely within the parent material layer.")) return if self.aoi.calc_frac_overlap(QgsGeometry.fromRect(self.sqi_setup_tab.use_custom_drainage.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("Area of interest is not entirely within the drainage layer.")) return if self.aoi.calc_frac_overlap(QgsGeometry.fromRect(self.sqi_setup_tab.use_custom_rock_frag.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("Area of interest is not entirely within the rock fragment layer.")) return if self.aoi.calc_frac_overlap(QgsGeometry.fromRect(self.sqi_setup_tab.use_custom_texture.get_layer().extent())) < .99: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("Area of interest is not entirely within the soil texture layer.")) return out_f = self.get_save_raster() if not out_f: return self.close() crosses_180th, geojsons = self.aoi.bounding_box_gee_geojson() val = [] n = 1 if self.area_tab.area_fromfile.isChecked(): for f in self.aoi.get_layer_wgs84().getFeatures(): # Get an OGR geometry from the QGIS geometry geom = f.geometry() val.append(geom) n += 1 # stringify json object val_string = '{}'.format(json.loads(val[0].asJson())) # create ogr geometry val_geom = ogr.CreateGeometryFromJson(val_string) # simplify polygon to tolerance of 0.003 val_geom_simplified = val_geom.Simplify(0.003) # fetch coordinates from json coords= json.loads(val_geom_simplified.ExportToJson())['coordinates'] geometries = { "coordinates":coords, "type":"Polygon" } elif self.area_tab.area_fromadmin.isChecked(): geometries ={ "coordinates":self.get_admin_poly_geojson()['geometry']['coordinates'][0], "type":"Polygon" } elif self.area_tab.area_frompoint.isChecked(): point = QgsPointXY(float(self.area_tab.area_frompoint_point_x.text()), float(self.area_tab.area_frompoint_point_y.text())) crs_src = QgsCoordinateReferenceSystem(self.area_tab.canvas.mapSettings().destinationCrs().authid()) point = QgsCoordinateTransform(crs_src, self.aoi.crs_dst, QgsProject.instance()).transform(point) geometries = json.loads(QgsGeometry.fromPointXY(point).asJson()) # write aoi geometry to file for masking output aoi_geom = { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": {}, "geometry": geometries } ] } aoi_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'aoi.geojson') with open(aoi_file, 'w') as filetowrite: filetowrite.write(json.dumps(aoi_geom)) # Add the sqi layers to a VRT in case they don't match in resolution, # and set proper output bounds sqi_files = [pm_vrt, texture_vrt, drainage_vrt, rock_frag_vrt] # in_vrt = tempfile.NamedTemporaryFile(suffix='.vrt').name sqi_vrts = [] for i in range(len(sqi_files)): f = tempfile.NamedTemporaryFile(suffix='.vrt').name gdal.BuildVRT(f, sqi_files[i], bandList=[i + 1], resolution='highest', resampleAlg=gdal.GRA_NearestNeighbour, outputBounds=self.aoi.get_aligned_output_bounds_deprecated(texture_vrt), separate=True) sqi_vrts.append(f) slope_v = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'slope.tif') if not os.path.exists(slope_v): slope_zip = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', 'slope.zip') slope_unzip = ZipFile(slope_zip) slope_unzip.extractall(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data')) slope_unzip.close() log("Extracting slope file...") else: log("Slope.tif already exists") in_files = [slope_v] in_files.extend(sqi_vrts) in_vrt = tempfile.NamedTemporaryFile(suffix='.vrt').name log(u'Saving SQI 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(texture_vrt), separate=True) # sqi_band_nums = np.arange(len(lc_files)) + 6 sqi_worker = StartWorker(SoilQualityWorker, 'calculating soil quality index', soil_depth, in_vrt, out_f ) if not sqi_worker.success: QtWidgets.QMessageBox.critical(None, self.tr("Error"), self.tr("Error calculating soil quality index."), None) return band_info = [BandInfo("Soil Quality Index (cm deep)", add_to_map=True, metadata={'depth': soil_depth})] 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): ###################################################################### # 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 indicator layer.")) 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(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_file_layer.text() 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_file_table.text()) # 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