def _execute(self, location_name, mapset_name, strds_name, timestamp): """Prepare and enqueue the raster area statistics Raises: InvalidUsage: In case the timestamp is wrong or the XML content is missing """ # Check the time stamp try: datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S") except ValueError as e: msg = "Wrong timestamp format. Required format is: " \ "YYYY-MM-DDTHH:MM:SS for example 2001-03-16T12:30:15" self.create_error_response(message=msg) return False rdc = self.preprocess(has_json=True, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=strds_name) if rdc: rdc.set_user_data(timestamp) enqueue_job(self.job_timeout, start_job, rdc) return True return False
def _delete(self, location_name, mapset_name): """Remove a list of layers identified by a pattern The g.remove "pattern" parameters must be provided:: http://<url>?pattern="*" Args: location_name (str): The name of the location mapset_name (str): The name of the mapset Return: flask.Response: HTTP 200 in case of success, HTTP 400 otherwise """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name) if rdc: args = glist_parser.parse_args() rdc.set_user_data((args, self.layer_type)) enqueue_job(self.job_timeout, remove_raster_layers, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def post(self, location_name): """Create a new location based on EPSG code in the user database. """ # Create only new locations if they did not exist in the global database location = os_path_normpath([self.grass_data_base, location_name]) # Check the location path if os.path.isdir(location): return self.get_error_response( message="Unable to create location. " "Location <%s> exists in global database." % location_name) # Check also for the user database location = os_path_normpath( [self.grass_user_data_base, self.user_group, location_name]) # Check the location path if os.path.isdir(location): return self.get_error_response( message="Unable to create location. " "Location <%s> exists in user database." % location_name) rdc = self.preprocess(has_json=True, has_xml=False, location_name=location_name, mapset_name="PERMANENT") if rdc: enqueue_job(self.job_timeout, create_location, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def post(self, executable): """Run a custom process The process is identified by executable Args: executable (str): Name of the executable Process arguments must be provided as JSON document in the POST request:: ["arg_1", "arg_2", "arg_3", "arg_4", ...] Returns: flask.Response: The HTTP status and a JSON document that includes the status URL of the export task that must be polled for updates. """ # Preprocess the post call rdc = self.preprocess(has_json=True) if rdc: rdc.set_user_data(executable) enqueue_job(self.job_timeout, start_job, rdc) html_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), html_code)
def get(self, location_name, mapset_name, vector_name): """Render a single vector map layer """ parser = self.create_parser() args = parser.parse_args() options = self.create_parser_options(args) if isinstance(options, dict) is False: return options rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=vector_name) rdc.set_user_data(options) enqueue_job(self.job_timeout, start_job, rdc) http_code, response_model = self.wait_until_finish(0.05) if http_code == 200: result_file = response_model["process_results"] # Open the image file, read it and then delete it if result_file: if os.path.isfile(result_file): image = open(result_file, "rb").read() os.remove(result_file) return Response(image, mimetype='image/png') return make_response(jsonify(response_model), http_code)
def get(self, location_name, mapset_name): """Get the location/mapset lock status. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name) enqueue_job(self.job_timeout, get_mapset_lock, rdc) http_code, response_model = self.wait_until_finish() return make_response(jsonify(response_model), http_code)
def delete(self, location_name, mapset_name): """Delete a location/mapset lock. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name) enqueue_job(self.job_timeout, unlock_mapset, rdc) http_code, response_model = self.wait_until_finish() return make_response(jsonify(response_model), http_code)
def post(self, location_name, mapset_name): """Create a new mapset in an existing location. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name) enqueue_job(self.job_timeout, create_mapset, rdc) http_code, response_model = self.wait_until_finish() return make_response(jsonify(response_model), http_code)
def post(self, location_name): """Execute a user defined process chain in an ephemeral location/mapset and store the processing result in an Google cloud storage bucket """ rdc = self.preprocess(has_json=True, location_name=location_name) rdc.set_storage_model_to_gcs() enqueue_job(self.job_timeout, start_job, rdc) html_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), html_code)
def delete(self): """Clean the download cache and remove all cached data""" rdc = self.preprocess(has_json=False, has_xml=False) if rdc: enqueue_job(self.job_timeout, start_download_cache_remove, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def _execute(self, location_name, mapset_name, strds_name): rdc = self.preprocess(has_json=True, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=strds_name) if rdc: enqueue_job(self.job_timeout, start_job, rdc) return rdc
def get(self, location_name, mapset_name): """Get the current computational region of the mapset and the projection of the location as WKT string. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name) enqueue_job(self.job_timeout, read_current_region, rdc) http_code, response_model = self.wait_until_finish() return make_response(jsonify(response_model), http_code)
def get(self): """Get the current size of the download cache""" rdc = self.preprocess(has_json=False, has_xml=False) if rdc: enqueue_job(self.job_timeout, start_download_cache_size, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def post(self, location_name, mapset_name, raster_name): """Create a new raster layer by uploading a GeoTIFF """ allowed_extensions = ['tif', 'tiff'] # TODO check if another content type can be used content_type = request.content_type.split(';')[0] if content_type != "multipart/form-data": return make_response(jsonify(SimpleResponseModel( status="error", message="Content type is not 'multipart/form-data'")), 400) if 'file' not in request.files: return make_response(jsonify(SimpleResponseModel( status="error", message="No file part indicated in postbody.")), 400) # create download cache path if does not exists if os.path.exists(self.download_cache): pass else: os.mkdir(self.download_cache) # save file from request id = str(uuid4()) file = request.files['file'] if file.filename == '': return make_response(jsonify(SimpleResponseModel( status="error", message="No selected file")), 400) if allowed_file(file.filename, allowed_extensions): name, extension = secure_filename(file.filename).rsplit('.', 1) filename = f"{name}_{id}.{extension}" file_path = os.path.join(self.download_cache, filename) file.save(file_path) else: os.remove(file_path) return make_response(jsonify(SimpleResponseModel( status="error", message="File has a not allowed extension. " f"Please use {','.join(allowed_extensions)}.")), 400) rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=raster_name) if rdc: rdc.set_request_data(file_path) enqueue_job(self.job_timeout, start_create_job, rdc) http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def get(self, location_name): """Get a list of all mapsets that are located in a specific location. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name="PERMANENT") if rdc: enqueue_job(self.job_timeout, list_raster_mapsets, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def _execute(self, location_name, mapset_name, raster_name, use_raster_region): rdc = self.preprocess(has_json=False, location_name=location_name, mapset_name=mapset_name, map_name=raster_name) if rdc: rdc.set_user_data(use_raster_region) enqueue_job(self.job_timeout, start_job, rdc) html_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), html_code)
def delete(self, location_name, mapset_name, raster_name): """Delete an existing raster map layer. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=raster_name) if rdc: enqueue_job(self.job_timeout, start_delete_job, rdc) http_code, response_model = self.wait_until_finish(0.1) else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def get(self, location_name): """Get the location projection and current computational region of the PERMANENT mapset """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name="PERMANENT") if rdc: enqueue_job(self.job_timeout, read_current_region, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def post(self, location_name): """Validate a process chain asynchronously, check the provided sources and the mapsets.""" rdc = self.preprocess(has_json=True, has_xml=True, location_name=location_name) if rdc: rdc.set_storage_model_to_file() enqueue_job(self.job_timeout, start_job, rdc) html_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), html_code)
def get(self, location_name, mapset_name, strds_name): """Get information about a STRDS that is located in a specific location/mapset. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=strds_name) if rdc: enqueue_job(self.job_timeout, strds_info, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def _put(self, location_name, mapset_name): """Rename a list of layers The old names and new names must be provided as a list of tuples: [(a, a_new),(b, b_new),(c, c_new), ...] Args: location_name (str): The name of the location mapset_name (str): The name of the mapset Return: flask.Response: HTTP 200 in case of success, HTTP 400 otherwise """ rdc = self.preprocess(has_json=True, has_xml=False, location_name=location_name, mapset_name=mapset_name) # Analyse the name list if isinstance(self.request_data, list) is False: return self.get_error_response( message="Wrong format for layer list") if len(self.request_data) == 0: return self.get_error_response(message="Empty layer list") for name_tuple in self.request_data: if (isinstance(name_tuple, tuple) is False and isinstance(name_tuple, list) is False): return self.get_error_response( message="List entry is not a tuple or list") if len(name_tuple) != 2: return self.get_error_response( message="A tuple of layer names must have 2 entries") if rdc: args = glist_parser.parse_args() rdc.set_user_data((args, self.layer_type)) enqueue_job(self.job_timeout, rename_raster_layers, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def post(self, location_name, mapset_name, strds_name): """Create a new STRDS in a specific location/mapset. """ rdc = self.preprocess(has_json=True, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=strds_name) if rdc: enqueue_job(self.job_timeout, strds_create, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def get(self, location_name, mapset_name, vector_name): """Get information about an existing vector map layer. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=vector_name) if rdc: enqueue_job(self.job_timeout, start_info_job, rdc) http_code, response_model = self.wait_until_finish(0.02) else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def get(self, location_name, mapset_name): """Render three raster map layer as composed RGB PNG image. """ parser = self.create_parser() parser.add_argument( 'red', required=True, type=str, help='The name of the raster layer associated with the color red') parser.add_argument( 'green', required=True, type=str, help='The name of the raster layer associated with the color green' ) parser.add_argument( 'blue', required=True, type=str, help='The name of the raster layer associated with the color blue') args = parser.parse_args() rgb_options = self.extract_rgb_maps(args, mapset_name) if isinstance(rgb_options, dict) is False: return rgb_options rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name) rdc.set_user_data(rgb_options) enqueue_job(self.job_timeout, start_rgb_job, rdc) http_code, response_model = self.wait_until_finish(0.05) if http_code == 200: result_file = response_model["process_results"] # Open the image file, read it and then delete it if result_file: if os.path.isfile(result_file): image = open(result_file, "rb").read() os.remove(result_file) return Response(image, mimetype='image/png') return make_response(jsonify(response_model), http_code)
def _get(self, location_name, mapset_name): """Return a collection of all available layers in the provided mapset. Optionally can g.list parameters be provided:: http://<url>?pattern="*" Args: location_name (str): The name of the location mapset_name (str): The name of the mapset Return: flask.Response: HTTP 200 and a list of layers as JSON document in case of success, HTTP 400 otherwise Example:: {"Process result": [ "lsat7_2002_10", "lsat7_2002_20", "lsat7_2002_30", "lsat7_2002_40", "lsat7_2002_50", "lsat7_2002_61", "lsat7_2002_62", "lsat7_2002_70", "lsat7_2002_80" ], "Status": "success" } """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name) if rdc: args = glist_parser.parse_args() rdc.set_user_data((args, self.layer_type)) enqueue_job(self.job_timeout, list_raster_layers, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def post(self, location_name, mapset_name): """Merge several existing mapsets into a single one. All mapsets that should be merged and the target mapset will be locked for the processing. Args: location_name (str): The name of the location target_mapset_name (str): The name of the target mapset, into other mapsets should be merged Process arguments must be provided as JSON document in the POST request:: ["mapset_A", "mapset_B", "mapset_C", "mapset_D", ...] Returns: flask.Response: The HTTP status and a JSON document that includes the status URL of the task that must be polled for updates. Example:: { "HTTP code": 200, "Messages": "Resource accepted", "Resource id": "resource_id-985164c9-1db9-49cf-b2c4-3e8e48500e31", "Status": "accepted", "URLs": { "Resources": [], "Status": "http://104.155.60.87/resources/soeren/" "resource_id-985164c9-1db9-49cf-b2c4-3e8e48500e31" }, "User id": "soeren" } """ # Preprocess the post call rdc = self.preprocess( has_json=True, location_name=location_name, mapset_name=mapset_name) if rdc: enqueue_job(self.job_timeout, start_job, rdc) html_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), html_code)
def get(self, location_name, mapset_name): """Get a list of all STRDS that are located in a specific location/mapset. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name) if rdc: args = where_parser.parse_args() rdc.set_user_data(args) enqueue_job(self.job_timeout, list_raster_mapsets, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def delete(self, location_name, mapset_name, strds_name): """Delete a STRDS that is located in a specific location/mapset. """ rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=strds_name) if rdc: args = recursive_parser.parse_args() rdc.set_user_data(args) enqueue_job(self.job_timeout, strds_delete, rdc) http_code, response_model = self.wait_until_finish() else: http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)
def post(self, location_name, mapset_name): """Execute a user defined process chain that creates a new mapset or runs in an existing one. The process chain that describes the GRASS modules that should be executed must be provided as JSON payload of the POST request. Args: location_name (str): The name of the location mapset_name (str): The name of the mapset Returns: flask.Response: The HTTP status and a JSON document that includes the status URL of the task that must be polled for updates. Process chain input format: { # A process chain is a dict with entries for each module that should be run Id:{ # Id must be numerical and indicates the process order "module": <module_name>, # Name of the module to run "stdin": <Id::stdout | Id::stderr> # Use the output of a specific module as input for this module # Id:stdout, Id:stderr are available "inputs ":{ # Definition of all input parameters as key:value pairs <parameter name>:<value>, # e.g.: value == "raster_name@mapset_name" or "degree" or 0.0 <parameter name>:<value> # e.g.: value == $file::slope_output_file to specify an output file # that name will be automatically generated by the API. }, "outputs": { # Definition of all outputs using key:value pairs <parameter name>: { "name":<value>, # Output name e.g. "my_aspect_map" or a temporary file id # definition: $file::id eg: $file::aspect_output_file # This file can be used in other module as input "export": { # Export options, if present this map will be exported "format": <value> # Set the export format raster=GeoTiff (default), vector = shape (default) "type": <output type>, # Output type e.g.: raster, vector, file, stds } }, <parameter name>: { "name": <value>, # Output name e.g. "my_slope_map" "export": { # Export options, if present this map will be exported "format": <value> # Set the export format raster=GeoTiff (default), vector = shape (default) "type": <output type>, # Output type e.g.: raster, vector, file, stds } } }, "flags": <flags>, # All flags in a string e.g.: "ge" "overwrite": <True|False>, # Set True to overwrite existing data "verbose": <True|False> # Verbosity of the module }, Id: { # The id of an executable command, that is not a grass module "executable": <path>, # The name and path of the executable e.g. /bin/cp "stdin": <Id::stdout | Id::stderr> # Use the output of a specific module as input for this module # Id::stdout, Id::stderr are available "parameters": [<parameter>,] # A list of strings that represent the parameters that may contain # temporary file definitions: $file::id eg: $file::aspect_output_file }, ... } """ # Preprocess the post call rdc = self.preprocess( has_json=True, location_name=location_name, mapset_name=mapset_name) if rdc: enqueue_job(self.job_timeout, start_job, rdc) html_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), html_code)
def post(self, location_name, mapset_name, vector_name): """Create a new vector layer by uploading a GPKG, zipped Shapefile, or GeoJSON. """ allowed_extensions = ['gpkg', 'zip', 'json', 'geojson'] # TODO check if another content type can be used content_type = request.content_type.split(';')[0] if content_type != "multipart/form-data": return make_response( jsonify( SimpleResponseModel( status="error", message="Content type is not 'multipart/form-data'")), 400) if 'file' not in request.files: return make_response( jsonify( SimpleResponseModel( status="error", message="No file part indicated in postbody.")), 400) # create download cache path if it does not exist if os.path.exists(self.download_cache): pass else: os.mkdir(self.download_cache) # save file from request id = str(uuid4()) file = request.files['file'] if file.filename == '': return make_response( jsonify( SimpleResponseModel(status="error", message="No selected file")), 400) if allowed_file(file.filename, allowed_extensions): name, extension = secure_filename(file.filename).rsplit('.', 1) filename = f"{name}_{id}.{extension}" file_path = os.path.join(self.download_cache, filename) file.save(file_path) else: os.remove(file_path) return make_response( jsonify( SimpleResponseModel( status="error", message="File has a not allowed extension. " f"Please use {','.join(allowed_extensions)}.")), 400) # Shapefile upload as zip if extension == 'zip': unzip_folder = os.path.join(self.download_cache, f'unzip_{id}') with ZipFile(file_path, "r") as zip_ref: zip_ref.extractall(unzip_folder) shp_files = [ entry for entry in os.listdir(unzip_folder) if entry.endswith('.shp') ] if len(shp_files) == 0: return make_response( jsonify( SimpleResponseModel( status="error", message="No .shp file found in zip file.")), 400) elif len(shp_files) > 1: return make_response( jsonify( SimpleResponseModel( status="error", message= f"{len(shp_files)} .shp files found in zip file." "Please put only one in the zip file.")), 400) else: os.remove(file_path) file_path = os.path.join(unzip_folder, shp_files[0]) rdc = self.preprocess(has_json=False, has_xml=False, location_name=location_name, mapset_name=mapset_name, map_name=vector_name) if rdc: rdc.set_request_data(file_path) enqueue_job(self.job_timeout, start_create_job, rdc) http_code, response_model = pickle.loads(self.response_data) return make_response(jsonify(response_model), http_code)