def test_query_for_sentinel_scene_empty_query(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) result = gsqi.get_sentinel_urls([ "S2A_MSIL1C_20170208T092131_N0204_R093_T35TLF_NOPENOPENOPE", ], ["B04", "B08"]) pprint(result) self.assertTrue(not result)
def test_query_for_landsat_scene_empty_query(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) result = gsqi.get_landsat_urls([ "LE72_NOPENOPENOPE", ], ["B1", "MTL"]) pprint(result) self.assertTrue(not result)
def __init__(self, rdc): """ Setup the variables of this class Args: rdc (ResourceDataContainer): The data container that contains all required variables for processing """ EphemeralProcessingWithExport.__init__(self, rdc) self.query_interface = GoogleSatelliteBigQueryInterface(self.config) self.product_id = self.rdc.user_data self.sentinel2_band_file_list = {} self.gml_footprint = "" self.user_download_cache_path = os.path.join( self.config.DOWNLOAD_CACHE, self.user_id) self.raster_result_list = [ ] # The raster layer names which must be exported, stats computed # and preview image created self.module_results = [ ] # A list of r.univar output classes for each vegetation index self.response_model_class = SentinelNDVIResponseModel # The class that is used to create the response self.required_bands = [ "B08", "B04" ] # The Sentinel 2A bands that are required for NDVI processing self.query_result = None
def test_query_for_sentinel_scene_sinlge(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) # A very small scene result = gsqi.get_sentinel_urls([ "S2A_MSIL1C_20170212T104141_N0204_R008_T31TGJ_20170212T104138", ], ["B12", "B08"]) pprint(result) self.assertTrue(len(result) == 1)
def test_query_for_landsat_scene_one_missing(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) result = gsqi.get_landsat_urls([ "LC80440342016259LGN00", "LC80440342013106LGN01", "LC80440342013154LGN00_WRONG" ], ["B1", "MTL"]) pprint(result) self.assertTrue(len(result) == 2)
def test_query_for_sentinel_scene_two_missing(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) result = gsqi.get_sentinel_urls([ "S2A_MSIL1C_20170301T225331_N0204_R115_T56DNG_20170301T225347", "S2A_MSIL1C_20170221T093031_N0204_R136_T34UGV_20170221T093310", "S2A_MSIL1C_NOPE", "S2A_MSIL1C_POPE" ], ["B04", "B08"]) pprint(result) self.assertTrue(len(result) == 2)
def test_query_for_sentinel2_scene_time(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) start = "2017-01-01T10:05:00" end = "2017-01-01T11:00:00" result = gsqi.query_sentinel2_archive(start_time=start, end_time=end) pprint(result) self.assertEqual(len(result), 154)
def test_query_for_landsat_scene_time(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) start = "2017-01-01T10:45:00" end = "2017-01-01T10:55:00" result = gsqi.query_landsat_archive(start_time=start, end_time=end) pprint(result) self.assertTrue(len(result) >= 1)
def test_query_for_sentinel_scene_four(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) result = gsqi.get_sentinel_urls([ "S2A_MSIL1C_20170202T090201_N0204_R007_T36TVT_20170202T090155", "S2A_MSIL1C_20170221T093031_N0204_R136_T34UGV_20170221T093310", "S2A_MSIL1C_20170211T060951_N0204_R134_T44WPT_20170211T060950", "S2A_MSIL1C_20170218T143751_N0204_R096_T20PRT_20170218T143931" ], ["B04", "B08"]) pprint(result) self.assertTrue(len(result) == 4)
def test_query_for_sentinel2_scene_time_coords(self): gsqi = GoogleSatelliteBigQueryInterface(global_config) start = "2016-11-01T00:00:00" end = "2016-11-30T00:00:00" result = gsqi.query_sentinel2_archive(start_time=start, end_time=end, lon=-45.0, lat=-7.0) pprint(result) self.assertTrue(len(result) >= 1)
def _get(self, satellite): args = self.query_parser.parse_args() start_time = None end_time = None lon = None lat = None cloud_cover = None scene_id = None spacecraft_id = None if "start_time" in args: start_time = args["start_time"] if "end_time" in args: end_time = args["end_time"] if "lon" in args: lon = args["lon"] if "lat" in args: lat = args["lat"] if "cloud_cover" in args: cloud_cover = args["cloud_cover"] if "scene_id" in args: scene_id = args["scene_id"] if "spacecraft_id" in args: spacecraft_id = args["spacecraft_id"] try: iface = GoogleSatelliteBigQueryInterface(global_config) if satellite == "landsat": result = iface.query_landsat_archive( start_time=start_time, end_time=end_time, lon=lon, lat=lat, cloud_cover=cloud_cover, scene_id=scene_id, spacecraft_id=spacecraft_id) else: result = iface.query_sentinel2_archive(start_time=start_time, end_time=end_time, lon=lon, lat=lat, cloud_cover=cloud_cover, scene_id=scene_id) return make_response(jsonify(result), 200) except Exception as e: result = {"status": "error", "message": str(e)} return make_response(jsonify(result), 400)
def __init__(self, rdc): """Constructor Args: rdc (ResourceDataContainer): The data container that contains all required variables for processing """ PersistentProcessing.__init__(self, rdc) # This works only if the mapset snot already exists self.temp_mapset_name = self.mapset_name self.query_interface = GoogleSatelliteBigQueryInterface(self.config) self.product_ids = self.rdc.request_data["product_ids"] self.strds_ids = self.rdc.request_data["strds"] self.required_bands = self.rdc.request_data["bands"] self.user_download_cache_path = os.path.join( self.config.DOWNLOAD_CACHE, self.user_id) self.query_result = None
class LandsatTimeSeriesCreator(PersistentProcessing): """Create a space time raster dataset from all provided scene_ids for each Sentinel2A band in a new mapset. The Sentiel2A scenes are downloaded , imported and pre-processed before they are registered in the band specific space time datasets. """ def __init__(self, rdc): """Constructor Args: rdc (ResourceDataContainer): The data container that contains all required variables for processing """ PersistentProcessing.__init__(self, rdc) # This works only if the mapset snot already exists self.temp_mapset_name = self.mapset_name self.query_interface = GoogleSatelliteBigQueryInterface(self.config) self.scene_ids = self.rdc.request_data["scene_ids"] self.strds_basename = self.rdc.request_data["strds"] self.atcor_method = self.rdc.request_data["atcor_method"] self.user_download_cache_path = os.path.join( self.config.DOWNLOAD_CACHE, self.user_id) self.query_result = None self.required_bands = None self.sensor_id = None def _prepare_download(self): """Check the download cache if the file already exists, to avoid redundant downloads. The downloaded files will be stored in a temporary directory. After the download of all files completes, the downloaded files will be moved to the download cache. This avoids broken files in case a download was interrupted or stopped by termination. """ # Create the download cache directory if it does not exists if os.path.exists(self.config.DOWNLOAD_CACHE): pass else: os.mkdir(self.config.DOWNLOAD_CACHE) # Create the user specific download cache directory to put the downloaded files into it if os.path.exists(self.user_download_cache_path): pass else: os.mkdir(self.user_download_cache_path) # Switch into the tempfile directory os.chdir(self.temp_file_path) # We have to set the home directory to create the grass location os.putenv("HOME", "/tmp") sensor_ids = {} for scene_id in self.scene_ids: # Check if the the list of scenes contains scenes from different satellites self.sensor_id = extract_sensor_id_from_scene_id(scene_id=scene_id) sensor_ids[self.sensor_id] = self.sensor_id if len(sensor_ids) > 2: raise AsyncProcessError( "Different satellites are in the list of scenes to be imported. " "Only scenes from a single satellite an be imported at once." ) # All bands are imported, except the MTL file self.required_bands = SCENE_BANDS[self.sensor_id][0:-1] self._send_resource_update("Sending Google BigQuery request.") try: self.query_result = self.query_interface.get_landsat_urls( self.scene_ids, self.required_bands) except Exception as e: raise AsyncProcessError("Error in querying Landsat product <%s> " "in Google BigQuery Landsat database. " "Error: %s" % (self.scene_ids, str(e))) if not self.query_result: raise AsyncProcessError("Unable to find Landsat product <%s> " "in Google BigQuery Landsat database" % self.scene_ids) def _import_scenes(self): """Import all found Sentinel2 scenes with their bands and create the space time raster datasets to register the maps in. Raises: AsyncProcessError: In case something went wrong """ all_commands = [] counter = 0 for scene_id in self.query_result: process_lib = LandsatProcessing( config=self.config, scene_id=scene_id, temp_file_path=self.temp_file_path, download_cache=self.user_download_cache_path, send_resource_update=self._send_resource_update, message_logger=self.message_logger) download_commands, self.sentinel2_band_file_list = process_lib.get_download_process_list( ) # Download the Landsat scene if it is not in the download cache if download_commands: all_commands.extend(download_commands) # Import and atmospheric correction import_commands = process_lib.get_import_process_list() all_commands.extend(import_commands) atcor_method_commands = process_lib.get_i_landsat_toar_process_list( atcor_method=self.atcor_method) all_commands.extend(atcor_method_commands) # Run the commands self._update_num_of_steps(len(all_commands)) self._execute_process_list(process_list=all_commands) # IMPORTANT: # The registration must be performed in the temporary mapset with the same name as the target mapset, # since the temporal database will contain the mapset name. register_commands = {} result_dict = {} for i in range(len(self.required_bands)): band = self.required_bands[i] strds = self.strds_basename + "_%s" % band result_dict[band] = [] create_strds = { "module": "t.create", "inputs": { "output": strds, "title": "Landsat time series for band %s" % band, "description": "Landsat time series for band %s" % band, "temporaltype": "absolute", "type": "strds" } } register_commands[str(counter)] = create_strds counter += 1 # Create the input file map_list_file_name = os.path.join(self.user_download_cache_path, strds) map_list_file = open(map_list_file_name, "w") # Use only the product ids that were found in the big query for scene_id in self.query_result: # We need to create a time interval, otherwise the temporal algebra will not work :/ start_time = dtparser.parse( self.query_result[scene_id]["timestamp"].split(".")[0]) end_time = start_time + timedelta(seconds=1) index = SCENE_BANDS[self.sensor_id].index(band) raster_suffix = RASTER_SUFFIXES[self.sensor_id][index] map_name = "%s_%s%s" % (scene_id, self.atcor_method, raster_suffix) result_dict[band].append( [map_name, str(start_time), str(end_time)]) map_list_file.write("%s|%s|%s\n" % (map_name, str(start_time), str(end_time))) map_list_file.close() register_maps = { "module": "t.register", "inputs": { "input": strds, "file": map_list_file_name, "type": "raster" } } register_commands[str(counter)] = register_maps counter += 1 process_list = self._validate_process_chain( process_chain=register_commands, skip_permission_check=True) self._execute_process_list(process_list=process_list) self.module_results = result_dict def _execute(self): # Setup the user credentials and logger self._setup() # Check and lock the target and temp mapsets self._check_lock_target_mapset() if self.target_mapset_exists is True: raise AsyncProcessError( "Sentinel time series can only be create in a new mapset. " "Mapset <%s> already exists." % self.target_mapset_name) # Init GRASS environment and create the temporary mapset with the same name as the target mapset # This is required to register the raster maps in the temporary directory, but use them in # persistent directory # Create the temp database and link the # required mapsets into it self._create_temp_database(self.required_mapsets) # Initialize the GRASS environment and switch into PERMANENT # mapset, which is always linked self._create_grass_environment( grass_data_base=self.temp_grass_data_base, mapset_name="PERMANENT") # Create the temporary mapset and switch into it self._create_temporary_mapset(temp_mapset_name=self.target_mapset_name) # Setup the download cache and query the BigQuery database of google for scene_ids self._prepare_download() # Check if all product ids were found missing_scene_ids = [] for scene_id in self.scene_ids: if scene_id not in self.query_result: missing_scene_ids.append(scene_id) # Abort if a single scene is missing if len(missing_scene_ids) > 0: raise AsyncProcessError("Unable to find product ids <%s> in the " "Google BigQuery database" % str(missing_scene_ids)) self._import_scenes() # Copy local mapset to original location self._copy_merge_tmp_mapset_to_target_mapset()
class EphemeralSentinelProcessing(EphemeralProcessingWithExport): """ """ def __init__(self, rdc): """ Setup the variables of this class Args: rdc (ResourceDataContainer): The data container that contains all required variables for processing """ EphemeralProcessingWithExport.__init__(self, rdc) self.query_interface = GoogleSatelliteBigQueryInterface(self.config) self.product_id = self.rdc.user_data self.sentinel2_band_file_list = {} self.gml_footprint = "" self.user_download_cache_path = os.path.join( self.config.DOWNLOAD_CACHE, self.user_id) self.raster_result_list = [ ] # The raster layer names which must be exported, stats computed # and preview image created self.module_results = [ ] # A list of r.univar output classes for each vegetation index self.response_model_class = SentinelNDVIResponseModel # The class that is used to create the response self.required_bands = [ "B08", "B04" ] # The Sentinel 2A bands that are required for NDVI processing self.query_result = None def _prepare_sentinel2_download(self): """Check the download cache if the file already exists, to avoid redundant downloads. The downloaded files will be stored in a temporary directory. After the download of all files completes, the downloaded files will be moved to the download cache. This avoids broken files in case a download was interrupted or stopped by termination. """ # Create the download cache directory if it does not exists if os.path.exists(self.config.DOWNLOAD_CACHE): pass else: os.mkdir(self.config.DOWNLOAD_CACHE) # Create the user specific download cache directory to put the downloaded files into it if os.path.exists(self.user_download_cache_path): pass else: os.mkdir(self.user_download_cache_path) # Switch into the tempfile directory os.chdir(self.temp_file_path) # We have to set the home directory to create the grass location os.putenv("HOME", "/tmp") try: self.query_result = self.query_interface.get_sentinel_urls([ self.product_id, ], self.required_bands) except Exception as e: raise AsyncProcessError( "Error in querying Sentinel 2A product <%s> " "in Google BigQuery Sentinel 2A database. " "Error: %s" % (self.product_id, str(e))) if not self.query_result: raise AsyncProcessError("Unable to find Sentinel 2A product <%s> " "in Google BigQuery Sentinel 2A database" % self.product_id) def _create_temp_database(self, mapsets=[]): """Create a temporary gis database and location with a PERMANENT mapset for processing Raises: This function raises AsyncProcessError in case of an error. """ if not self.sentinel2_band_file_list: raise AsyncProcessError( "Unable to create a temporary GIS database, no data is available" ) try: geofile = self.sentinel2_band_file_list[self.required_bands[0]][0] self._send_resource_update(geofile) # We have to set the home directory to create the grass location os.putenv("HOME", "/tmp") # Switch into the GRASS temporary database directory os.chdir(self.temp_grass_data_base) executable_params = list() executable_params.append(self.config.GRASS_GIS_START_SCRIPT) executable_params.append("-e") executable_params.append("-c") executable_params.append(geofile) executable_params.append( os.path.join(self.temp_grass_data_base, self.location_name)) self.message_logger.info( "%s %s" % (self.config.GRASS_GIS_START_SCRIPT, executable_params)) self._update_num_of_steps(1) p = Process(exec_type="exec", executable="python2", executable_params=executable_params) # Create the GRASS location, this will create the location and mapset paths self._run_process(p) except Exception as e: raise AsyncProcessError( "Unable to create a temporary GIS database and location at <%s>" ", Exception: %s" % (os.path.join(self.temp_grass_data_base, self.location_name, "PERMANENT"), str(e))) def _run_r_univar_command(self, raster_name): """Compute the univariate statistics for a raster layer and put the result as dict in the module_result dict Args: raster_name: """ result_file = tempfile.mktemp(suffix=".univar", dir=self.temp_file_path) univar_command = dict() univar_command["1"] = { "module": "r.univar", "inputs": { "map": raster_name }, "outputs": { "output": { "name": result_file } }, "flags": "g" } process_list = self._validate_process_chain( process_chain=univar_command, skip_permission_check=True) self._execute_process_list(process_list=process_list) result_list = open(result_file, "r").readlines() results = {"name": raster_name} for line in result_list: if "=" in line: key, value = line.split("=") results[key] = float(value.strip()) self.module_results.append(UnivarResultModel(**results)) def _render_preview_image(self, raster_name): """Setup the render environment and create a g.region process chain entry to setup the extent from the options. Args: options: The parser options that contain n, s, e and w entries for region settings result_file: The resulting PNG file name Returns: A process chain entry of g.region """ result_file = tempfile.mktemp(suffix=".png", dir=self.temp_file_path) os.putenv("GRASS_RENDER_IMMEDIATE", "png") os.putenv("GRASS_RENDER_WIDTH", "1300") os.putenv("GRASS_RENDER_HEIGHT", "1000") os.putenv("GRASS_RENDER_TRANSPARENT", "TRUE") os.putenv("GRASS_RENDER_TRUECOLOR", "TRUE") os.putenv("GRASS_RENDER_FILE", result_file) os.putenv("GRASS_RENDER_FILE_READ", "TRUE") render_commands = {} render_commands["1"] = { "module": "d.rast", "inputs": { "map": raster_name } } # "flags":"n"} render_commands["2"] = { "module": "d.legend", "inputs": { "raster": raster_name, "at": "8,92,0,7" }, "flags": "n" } render_commands["3"] = { "module": "d.barscale", "inputs": { "style": "line", "at": "20,4" }, # "flags":"n" # No "n" flag in grass72 } # Run the selected modules process_list = self._validate_process_chain( process_chain=render_commands, skip_permission_check=True) self._execute_process_list(process_list) # Attach the png preview image to the resource URL list # Generate the resource URL's from the url base and the file name # Copy the png file to the resource directory # file_name = "%s_preview.png"%(raster_name) # resource_url = self.resource_url_base.replace("__None__", file_name) # self.storage_interface. # self.resource_url_list.append(resource_url) # export_path = os.path.join(self.resource_export_path, file_name) # shutil.move(result_file, export_path) # Store the temporary file in the resource storage # and receive the resource URL resource_url = self.storage_interface.store_resource(result_file) self.resource_url_list.append(resource_url) def _create_output_resources(self, raster_result_list): """Create the output resources from the raster layer that are the result of the processing The following resources will be computed - Univariate statistics as result dictionary for each raster layer - A PNG preview image for each raster layer - A gzipped GeoTiff file """ for raster_name in raster_result_list: self._run_r_univar_command(raster_name) # Render a preview image for this raster layer self._render_preview_image(raster_name) export_dict = { "name": raster_name, "export": { "format": "GTiff", "type": "raster" } } # Add the raster layer to the export list self.resource_export_list.append(export_dict) self._update_num_of_steps(len(raster_result_list)) # Export all resources and generate the finish response self._export_resources(use_raster_region=True) def _execute(self): """Overwrite this function in subclasses - Setup user credentials and working paths - Create the resource directory - Download and store the sentinel2 scene files - Initialize and create the temporal database and location - Analyse the process chains - Run the modules - Export the results - Cleanup """ # Setup the user credentials and logger self._setup() # Create and check the resource directory self.storage_interface.setup() # Setup the download cache self._prepare_sentinel2_download() process_lib = Sentinel2Processing( self.config, self.product_id, self.query_result, self.required_bands, self.temp_file_path, self.user_download_cache_path, self._send_resource_update, self.message_logger) download_commands, self.sentinel2_band_file_list = process_lib.get_sentinel2_download_process_list( ) # Download the sentinel scene if not in the download cache if download_commands: self._update_num_of_steps(len(download_commands)) self._execute_process_list(process_list=download_commands) # Setup GRASS self._create_temporary_grass_environment( source_mapset_name="PERMANENT") # Import and prepare the sentinel scenes import_commands = process_lib.get_sentinel2_import_process_list() self._update_num_of_steps(len(import_commands)) self._execute_process_list(process_list=import_commands) # Generate the ndvi command nir = self.sentinel2_band_file_list["B08"][1] red = self.sentinel2_band_file_list["B04"][1] ndvi_commands = process_lib.get_ndvi_r_mapcalc_process_list( red, nir, "ndvi") self._update_num_of_steps(len(ndvi_commands)) self._execute_process_list(process_list=ndvi_commands) self.raster_result_list.append("ndvi") # Create the output resources: stats, preview and geotiff self._create_output_resources(self.raster_result_list) def _final_cleanup(self): """Overwrite this function in subclasses to perform the final cleanup """ # Clean up and remove the temporary gisdbase self._cleanup() # Remove resource directories if "error" in self.run_state or "terminated" in self.run_state: self.storage_interface.remove_resources()