def run_command(self): # Get the command parameter values. pv_GeoLayerIDs = self.get_parameter_value("GeoLayerIDs") pv_OutputGeoLayerID = self.get_parameter_value("OutputGeoLayerID") pv_AttributeMap = self.get_parameter_value("AttributeMap", default_value="") # Expand for ${Property} syntax. pv_GeoLayerIDs = self.command_processor.expand_parameter_value( pv_GeoLayerIDs, self) # Convert the AttributeMap parameter from string to a list of mapping entries. attribute_map_entry_list = string_util.delimited_string_to_list( pv_AttributeMap, delimiter=',') # The attribute map dictionary contains the attributes of the output merged GeoLayer and the corresponding # attributes of the input GeoLayers. # key (str): an attribute of the output, merged GeoLayer # value (list): a list of attributes from the input GeoLayers that should be mapped to the output attribute attribute_map_dic = {} # Iterate over each attribute mapping entry. for attribute_map_entry in attribute_map_entry_list: # Get a list of the keys (merged attributes) currently in the attribute map dictionary. curr_merged_attributes = list(attribute_map_dic.keys()) # If the attribute map entry has the correct format, continue. if ':' in attribute_map_entry: # Get the output merged attribute name from the entry. merged_attr = attribute_map_entry.split(':')[1].strip() # Get the input attribute name from the entry. input_attr = attribute_map_entry.split(':')[0].strip() # If the attribute map entry does has the correct format, the merged_attr and the input_attr variables # are set to empty strings. else: merged_attr = '' input_attr = '' # If the merged attribute name is already registered in the attribute mapping dictionary. if merged_attr in curr_merged_attributes: # Add the input attribute to the list of input attributes within the dictionary (associated with # the corresponding merged_attribute). curr_input_attrs = attribute_map_dic[merged_attr] curr_input_attrs.append(input_attr) attribute_map_dic[merged_attr] = curr_input_attrs # If the merged attribute is not already registered in the attribute mapping dictionary, add the input # attribute (as a one-item list) to the dictionary (associated with the corresponding merged_attribute). else: attribute_map_dic[merged_attr] = [input_attr] # Convert the GeoLayerIDs parameter from string to list format. # If configured, list all of the registered GeoLayer IDs. if pv_GeoLayerIDs == "*": list_of_geolayer_ids = [] # Iterate over each GeoLayer registered within the GeoProcessor. Add each GeoLayer's ID to the list. for geolayer_obj in self.command_processor.geolayers: list_of_geolayer_ids.append(geolayer_obj.id) # If specific GeoLayer IDs are listed, convert the string into list format. else: list_of_geolayer_ids = string_util.delimited_string_to_list( pv_GeoLayerIDs) # Run the checks on the parameter values. Only continue if the checks passed. if self.__should_merge(list_of_geolayer_ids, pv_OutputGeoLayerID): try: # A list to hold the GeoLayer IDs of the copied GeoLayers. Copied GeoLayers are only required for this # command. They will be removed from the GeoProcessor (to save processing space and speed) after the # processing has been completed. This list will be used to remove the copied GeoLayers. copied_geolayer_ids = [] # A list to hold the full pathname of the copied GeoLayers (written to disk). The # qgis:mergevectorlayers requires that the QGSVectorLayer objects are not in memory. This list will be # used as an input to the qgis:mergevectorlayers algorithm. copied_geolayer_sourcepath = [] first_geolayer = self.command_processor.get_geolayer( list_of_geolayer_ids[0]) first_crs = first_geolayer.get_crs() # Iterate over the GeoLayers to be merged. for geolayer_id in list_of_geolayer_ids: # Get the appropriate GeoLayer based on the GeoLayer ID. geolayer = self.command_processor.get_geolayer(geolayer_id) # Get an attribute dictionary mapping the GeoLayer attributes that are to be renamed. # Key: Existing attribute name. Value: New attribute name. attribute_dictionary = self.__create_attribute_dictionary( geolayer, attribute_map_dic) # Make a copy of the GeoLayer and add it to the GeoProcessor. Renaming of attributes will occur on # a copy of the GeoLayer so that the original GeoLayer's attribute values are not affected. copied_geolayer = geolayer.deepcopy( "{}_copyForMerge".format(geolayer_id)) self.command_processor.add_geolayer(copied_geolayer) # Add the copied GeoLayer ID to the master list. copied_geolayer_ids.append(copied_geolayer.id) # Iterate over the GeoLayer's attribute dictionary. for existing_attr_name, new_attr_name in attribute_dictionary.items( ): # If the attribute should be renamed, then rename the attribute in the copied GeoLayer. if not (existing_attr_name == new_attr_name): copied_geolayer.rename_attribute( existing_attr_name, new_attr_name) # Write copied GeoLayer (memory) to the temporary directory (written to disk). output_file_absolute = os.path.join( self.command_processor.get_property('TempDir'), copied_geolayer.id) on_disk_geolayer = copied_geolayer.write_to_disk( output_file_absolute) # Overwrite the copied (memory) GeoLayer in the geoprocessor with the on-disk GeoLayer. self.command_processor.add_geolayer(on_disk_geolayer) # Add the source path of the copied on-disk GeoLayer to the master list. copied_geolayer_sourcepath.append( on_disk_geolayer.source_path) # Merge all of the copied GeoLayers (the GeoLayers with the new attribute names). # Using QGIS algorithm but can also use saga:mergelayers algorithm. # saga:mergelayers documentation at http://www.saga-gis.org/saga_tool_doc/2.3.0/shapes_tools_2.html alg_parameters = { "LAYERS": copied_geolayer_sourcepath, "CRS": first_crs, "OUTPUT": "memory:" } merged_output = self.command_processor.qgis_processor.runAlgorithm( "qgis:mergevectorlayers", alg_parameters) # Create a new GeoLayer and add it to the GeoProcessor's geolayers list. # in QGIS3, merged_output["OUTPUT"] returns the returns the QGS vector layer object # see ClipGeoLayer.py for information about value in QGIS2 environment self.command_processor.add_geolayer( GeoLayer(pv_OutputGeoLayerID, merged_output["OUTPUT"], "MEMORY")) # Release the copied GeoLayers from the GeoProcessor. for copied_geolayer_id in copied_geolayer_ids: # Get the copied GeoLayer based on the GeoLayer ID. copied_geolayer = self.command_processor.get_geolayer( copied_geolayer_id) # Remove the copied GeoLayer from the GeoProcessor self.command_processor.free_geolayer(copied_geolayer) # Raise an exception if an unexpected error occurs during the process except Exception as e: self.warning_count += 1 message = "Unexpected error merging the following GeoLayers {}.".format( pv_GeoLayerIDs) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log( CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format( self.warning_count) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity( CommandPhaseType.RUN, CommandStatusType.SUCCESS)
def run_command(self): """ Run the command. Read the feature classes within a file geodatabase. For each desired feature class (can be specified by the Subset_Pattern parameter), create a GeoLayer object, and add to the GeoProcessor's geolayer list. Returns: None. Raises: RuntimeError if any warnings occurred during run_command method. """ # Obtain the required and optional parameter values pv_SpatialDataFolder = self.get_parameter_value("SpatialDataFolder") pv_ReadOnlyOneFeatureClass = self.get_parameter_value( "ReadOnlyOneFeatureClass") pv_Subset_Pattern = self.get_parameter_value("Subset_Pattern") pv_GeoLayerID_prefix = self.get_parameter_value("GeoLayerID_prefix") pv_GeoLayerID = self.get_parameter_value("GeoLayerID") pv_FeatureClass = self.get_parameter_value("FeatureClass") # Convert the ReadOnlyOneFeatureClass from a string value to a Boolean value. pv_ReadOnlyOneFeatureClass = string_util.str_to_bool( pv_ReadOnlyOneFeatureClass) # Convert the SpatialDataFolder parameter value relative path to an absolute path sd_folder_abs = io_util.verify_path_for_os( io_util.to_absolute_path( self.command_processor.get_property('WorkingDir'), self.command_processor.expand_parameter_value( pv_SpatialDataFolder, self))) # Run the initial checks on the parameter values. Only continue if the checks passed. if self.__should_read_gdb(sd_folder_abs): # If configured to only read one Feature Class into one GeoLayer. if pv_ReadOnlyOneFeatureClass: # Run the check to see if the GeoLayer should be read. if self.__should_read_geolayer(pv_GeoLayerID, True, pv_FeatureClass, sd_folder_abs): try: # Get the full pathname to the feature class # TODO egiles 2018-01-04 Need to research how to properly document feature class source path spatial_data_file_absolute = os.path.join( sd_folder_abs, str(pv_FeatureClass)) # Create a QgsVectorLayer object from the feature class. qgs_vector_layer = qgis_util.read_qgsvectorlayer_from_feature_class( sd_folder_abs, pv_FeatureClass) # Create a GeoLayer and add it to the geoprocessor's GeoLayers list geolayer_obj = GeoLayer(pv_GeoLayerID, qgs_vector_layer, spatial_data_file_absolute) self.command_processor.add_geolayer(geolayer_obj) # Raise an exception if an unexpected error occurs during the process except Exception as e: self.warning_count += 1 message = "Unexpected error reading feature class ({}) from file geodatabase ({}).".format( pv_FeatureClass, sd_folder_abs) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log( CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # If configured to read multiple Feature Classes into multiple GeoLayers. else: # Get a list of all of the feature classes in the file geodatabase. fc_list = self.__return_a_list_of_fc(sd_folder_abs) # Filter the fc_list to only include feature classes that meet the Subset Pattern configuration. If # the Subset Pattern configuration is None, all feature classes will remain in the fc_list. fc_list = string_util.filter_list_of_strings( fc_list, [pv_Subset_Pattern]) # Iterate over the feature classes in the fc_list. for feature_class in fc_list: # Determine the GeoLayerID. if pv_GeoLayerID_prefix: geolayer_id = "{}_{}".format(pv_GeoLayerID_prefix, feature_class) else: geolayer_id = feature_class # Run the secondary checks on the parameter values. Only continue if the checks passed. if self.__should_read_geolayer(geolayer_id, False): try: # Get the full pathname to the feature class # TODO egiles 2018-01-04 Need to research how to properly document feature class source path spatial_data_file_absolute = os.path.join( sd_folder_abs, str(feature_class)) # Create a QgsVectorLayer object from the feature class. qgs_vector_layer = qgis_util.read_qgsvectorlayer_from_feature_class( sd_folder_abs, feature_class) # Create a GeoLayer and add it to the geoprocessor's GeoLayers list geolayer_obj = GeoLayer( geolayer_id, qgs_vector_layer, spatial_data_file_absolute) self.command_processor.add_geolayer(geolayer_obj) # Raise an exception if an unexpected error occurs during the process except Exception as e: self.warning_count += 1 message = "Unexpected error reading feature class ({}) from file geodatabase ({}).".format( feature_class, sd_folder_abs) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log( CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format( self.warning_count) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity( CommandPhaseType.RUN, CommandStatusType.SUCCESS)
def run_command(self): """ Run the command. Create the GeoLayer with the input geometries. Add GeoLayer to the GeoProcessor's geolayers list. Returns: None. Raises: RuntimeError if any warnings occurred during run_command method. """ # Obtain the parameter values. pv_NewGeoLayerID = self.get_parameter_value("NewGeoLayerID") pv_GeometryFormat = self.get_parameter_value("GeometryFormat").upper() pv_GeometryData = self.get_parameter_value("GeometryData") pv_CRS = self.get_parameter_value("CRS") if self.__should_geolayer_be_created(pv_NewGeoLayerID, pv_CRS, pv_GeometryFormat, pv_GeometryData): try: # If the geometry format is bounding box, continue. if pv_GeometryFormat == "BOUNDINGBOX": # Convert the geometry input from a string to a list of strings. # Items are in the following order: # 1. Left (West) bound coordinate # 2. Bottom (South) bound coordinate # 3. Right (East) bound coordinate # 4. Top (North) bound coordinate NSWE_extents = string_util.delimited_string_to_list( pv_GeometryData) NW = "{} {}".format(NSWE_extents[0], NSWE_extents[3]) NE = "{} {}".format(NSWE_extents[2], NSWE_extents[3]) SE = "{} {}".format(NSWE_extents[2], NSWE_extents[1]) SW = "{} {}".format(NSWE_extents[0], NSWE_extents[1]) wkt_conversion = "POLYGON(({}, {}, {}, {}))".format( NW, NE, SE, SW) # Create the QgsVectorLayer. BoundingBox will always create a POLYGON layer. layer = qgis_util.create_qgsvectorlayer( "Polygon", pv_CRS, "layer") # Create the QgsGeometry object for the bounding box geometry. qgs_geometry = qgis_util.create_qgsgeometry( "WKT", wkt_conversion) # If the geometry format is Well-Known Text, continue. elif pv_GeometryFormat == "WKT": # Get the equivalent QGS geometry type to the input WKT geometry. # Ex: MultiLineString is converted to LineString. qgsvectorlayer_geom_type = qgis_util.get_geometrytype_qgis_from_wkt( pv_GeometryData) # Create the QgsVectorLayer. The geometry type will be determined from the WKT specifications. layer = qgis_util.create_qgsvectorlayer( qgsvectorlayer_geom_type, pv_CRS, "layer") # Create the QgsGeometry object for the Well-Known Text geometry. qgs_geometry = qgis_util.create_qgsgeometry( "WKT", pv_GeometryData) # If the geometry format is Well-Known Binary, continue. elif pv_GeometryFormat == "WKB": # Create the QgsGeometry object for the Well-Known Binary geometry. qgs_geometry = qgis_util.create_qgsgeometry( "WKB", pv_GeometryData) # Get the equivalent Well-Known Text for the geometry. qgs_geometry_as_wkt = qgs_geometry.exportToWkt() # Get the equivalent QGS geometry type to the input WKT geometry. # Ex: MultiLineString is converted to LineString. qgsvectorlayer_geom_type = qgis_util.get_geometrytype_qgis_from_wkt( qgs_geometry_as_wkt) # Create the QgsVectorLayer. The geometry type will be determined from the WKB specifications. layer = qgis_util.create_qgsvectorlayer( qgsvectorlayer_geom_type, pv_CRS, "layer") # Add the feature (with the appropriate geometry) to the Qgs Vector Layer. qgis_util.add_feature_to_qgsvectorlayer(layer, qgs_geometry) # Create a new GeoLayer with the QgsVectorLayer and add it to the GeoProcesor's geolayers list. new_geolayer = GeoLayer(pv_NewGeoLayerID, layer, "MEMORY") self.command_processor.add_geolayer(new_geolayer) # Raise an exception if an unexpected error occurs during the process. except Exception as e: self.warning_count += 1 message = "Unexpected error creating GeoLayer ({}).".format( pv_NewGeoLayerID) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log( CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format( self.warning_count) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity( CommandPhaseType.RUN, CommandStatusType.SUCCESS)
def run_command(self): """ Run the command. Read the layer file from a GeoJSON file, create a GeoLayer object, and add to the GeoProcessor's geolayer list. Returns: None. Raises: RuntimeError if any warnings occurred during run_command method. """ logger = logging.getLogger(__name__) # Obtain the parameter values. pv_SpatialDataFile = self.get_parameter_value("SpatialDataFile") pv_GeoLayerID = self.get_parameter_value("GeoLayerID", default_value='%f') # Expand for ${Property} syntax. pv_GeoLayerID = self.command_processor.expand_parameter_value(pv_GeoLayerID, self) # Convert the SpatialDataFile parameter value relative path to an absolute path and expand for ${Property} # syntax spatial_data_file_absolute = io_util.verify_path_for_os( io_util.to_absolute_path(self.command_processor.get_property('WorkingDir'), self.command_processor.expand_parameter_value(pv_SpatialDataFile, self))) # If the pv_GeoLayerID is a valid %-formatter, assign the pv_GeoLayerID the corresponding value. if pv_GeoLayerID in ['%f', '%F', '%E', '%P', '%p']: pv_GeoLayerID = io_util.expand_formatter(spatial_data_file_absolute, pv_GeoLayerID) # Run the checks on the parameter values. Only continue if the checks passed. if self.__should_read_geolayer(spatial_data_file_absolute, pv_GeoLayerID): try: # Create a QGSVectorLayer object with the GeoJSON SpatialDataFile. qgs_vector_layer = qgis_util.read_qgsvectorlayer_from_file(spatial_data_file_absolute) # Create a GeoLayer and add it to the geoprocessor's GeoLayers list. geolayer_obj = GeoLayer(geolayer_id=pv_GeoLayerID, geolayer_qgs_vector_layer=qgs_vector_layer, geolayer_source_path=spatial_data_file_absolute) self.command_processor.add_geolayer(geolayer_obj) # Raise an exception if an unexpected error occurs during the process. except Exception as e: self.warning_count += 1 message = "Unexpected error reading GeoLayer {} from GeoJSON file {}.".format(pv_GeoLayerID, pv_SpatialDataFile) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log(CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format(self.warning_count) logger.error(message) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity(CommandPhaseType.RUN, CommandStatusType.SUCCESS)
def run_command(self): """ Run the command. Read the layer file from a delimited file, create a GeoLayer object, and add to the GeoProcessor's geolayer list. Returns: None. Raises: RuntimeError if any warnings occurred during run_command method. """ # Obtain the parameter values. pv_DelimitedFile = self.get_parameter_value("DelimitedFile") pv_Delimiter = self.get_parameter_value("Delimiter", default_value=',') pv_GeometryFormat = self.get_parameter_value("GeometryFormat") pv_XColumn = self.get_parameter_value("XColumn", default_value=None) pv_YColumn = self.get_parameter_value("YColumn", default_value=None) pv_WKTColumn = self.get_parameter_value("WKTColumn", default_value=None) pv_CRS = self.get_parameter_value("CRS") pv_GeoLayerID = self.get_parameter_value("GeoLayerID", default_value='%f') # Convert the DelimitedFile parameter value relative path to an absolute path and expand for ${Property} # syntax delimited_file_abs = io_util.verify_path_for_os( io_util.to_absolute_path( self.command_processor.get_property('WorkingDir'), self.command_processor.expand_parameter_value( pv_DelimitedFile, self))) # If the pv_GeoLayerID is a valid %-formatter, assign the pv_GeoLayerID the corresponding value. if pv_GeoLayerID in ['%f', '%F', '%E', '%P', '%p']: pv_GeoLayerID = io_util.expand_formatter(delimited_file_abs, pv_GeoLayerID) # Run the checks on the parameter values. Only continue if the checks passed. if self.__should_read_geolayer(delimited_file_abs, pv_Delimiter, pv_GeometryFormat, pv_XColumn, pv_YColumn, pv_WKTColumn, pv_CRS, pv_GeoLayerID): try: if pv_GeometryFormat.upper() == "XY": # Create a QGSVectorLayer object with the delimited file. qgs_vector_layer = qgis_util.read_qgsvectorlayer_from_delimited_file_xy( delimited_file_abs, pv_Delimiter, pv_CRS, pv_XColumn, pv_YColumn) else: # Create a QGSVectorLayer object with the delimited file. qgs_vector_layer = qgis_util.read_qgsvectorlayer_from_delimited_file_wkt( delimited_file_abs, pv_Delimiter, pv_CRS, pv_WKTColumn) # Create a GeoLayer and add it to the geoprocessor's GeoLayers list. geolayer_obj = GeoLayer(pv_GeoLayerID, qgs_vector_layer, delimited_file_abs) self.command_processor.add_geolayer(geolayer_obj) # Raise an exception if an unexpected error occurs during the process. except Exception as e: self.warning_count += 1 message = "Unexpected error reading GeoLayer {} from delimited file {}.".format( pv_GeoLayerID, pv_DelimitedFile) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log( CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format( self.warning_count) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity( CommandPhaseType.RUN, CommandStatusType.SUCCESS)
def run_command(self): """ Run the command. Split the input GeoLayer by the selected Attribute. Create new GeoLayers based on unique attribute values. Returns: None. Raises: RuntimeError if any warnings occurred during run_command method. """ # Obtain the parameter values. # @jurentie # 1. Parameter values are passed into the SplitGeoLayerByAttribute command editor -> GenericCommandEditor # 2. The SplitGEoLayerByAttribute command is updated when user changes input to parameter # and parsed by AbstractCommand into parameter values and saved as command_parameters. # 3. Obtain parameter value by calling parent function get_parameter_value from AbstractCommand # Get the 'Input GeoLayerID' parameter pv_InputGeoLayerID = self.get_parameter_value("InputGeoLayerID") pv_AttributeName = self.get_parameter_value("AttributeName") # TODO jurentie 01/26/2019 Need to figure out how default should work in this case # @jurentie # I've commented out the below, this is specific to ClipGeoLayer, creating a default # value that makes sense for 'Value_splityBy_value' but we are looking for Output GeoLayerID's for this # specific command. Default might just need to be the name of the file's automatically output by # runAlgorithm("qgis:splitvectorlayer", alg_parameters) which can be handled down below... # pv_OutputGeoLayerIDs = self.get_parameter_value("OutputGeoLayerIDs", default_value="{}_splitBy_{}".format( # pv_InputGeoLayerID, pv_AttributeName)) # Get OutputGeoLayerID's and split on ',' to create an array of Output GeoLayerId's # ex OutputGeoLayerIDs = 'ouput1, output1, output3' # pv_OutputGeoLayerIDs = ['output1', 'output2', 'output3'] try: pv_OutputGeoLayerIDs = self.get_parameter_value( "OutputGeoLayerIDs").split(',') except: # Get the list of features from the GeoLayer. This returns all attributes for each feature listed. pv_OutputGeoLayerIDs = None # Create logger logger = logging.getLogger(__name__) # Run the checks on the parameter values. Only continue if the checks passed. # @jurentie # The following line is currently commented out but needs to be added back in once # __should_split_geolayer is functioning properly... if self.check_command_input(pv_InputGeoLayerID, pv_AttributeName, pv_OutputGeoLayerIDs): try: # Get the Input GeoLayer. # Get the GeoLayer which will be QgsVectorLayer # https://qgis.org/api/classQgsVectorLayer.html # Passes a GeoLayerID to GeoProcessor to return the GeoLayer that matches the ID input_geolayer = self.command_processor.get_geolayer( pv_InputGeoLayerID) logger.info('Input GeoLayer [GeoLayerID: ' + pv_InputGeoLayerID + '] has been read in successfully') # TODO jurentie 01/26/2019 still need to figure out what the code below is for... # If the input GeoLayer is an in-memory GeoLayer, make it an on-disk GeoLayer. if input_geolayer.source_path is None or input_geolayer.source_path.upper( ) in ["", "MEMORY"]: # Get the absolute path of the GeoLayer to write to disk. geolayer_disk_abs_path = os.path.join( self.command_processor.get_property('TempDir'), input_geolayer.id) logger.info('GeoLayer path ' + geolayer_disk_abs_path) # Write the GeoLayer to disk. Overwrite the (memory) GeoLayer in the geoprocessor with the # on-disk GeoLayer. input_geolayer = input_geolayer.write_to_disk( geolayer_disk_abs_path) self.command_processor.add_geolayer(input_geolayer) # Select the Attribute # NEED TO CREATE A 'input_geolayer.select_attribute' FUNCTION IN GEOLAYER.PY?? # SOMETHING LIKE: attribute_name = input_geolayer.select_attribute(pv_AttributeName) # OR SHOULD THE FOLLOWING JUST WORK? attribute_name = pv_AttributeName working_dir = self.command_processor.properties['WorkingDir'] # @jurentie # TODO @jurentie 01/31/2019 How to handle absolute path # Assuming relative path for the moment.... # Perform the QGIS split vector layer function. Refer to the reference below for parameter descriptions. # REF: https://docs.qgis.org/2.8/en/docs/user_manual/processing_algs/qgis/vector_general_tools.html#split-vector-layer # @jurentie # Check to see if parameter temporary folder has been specified, otherwise use the # default environment temp folder directory. #boolean to see if working with custom temp folder temp_directory_custom = False try: # Append specified temporary folder to working directory to create temp files in current # command file location. temp_directory = working_dir + "/" + self.get_parameter_value( "TemporaryFolder") temp_directory_custom = True except: # If using the default temp directory from environment variables create a temp folder to # easily remove all files temp_directory = tempfile.gettempdir() temp_directory += "/qgissplitvectorlayer-outputfiles" # @jurentie # Assign parameter to pass into runAlgorithm for splitting the GeoLayer # Input = input GeoLayer (QgsVectorLayer) # Field = Attribute name to split by # Output = path to write output GeoLayers to this creates a list of files following the naming # convention attributeName_attribute.extension # ex: GNIS_ID_00030007.shp # file types generated = .dbf, .prj, .qpj, .shp, .shx alg_parameters = { "INPUT": input_geolayer.qgs_vector_layer, "FIELD": attribute_name, "OUTPUT": temp_directory } # @jurentie # call runAlgorithm with the parameter "qgis:splitvectorlayer" (a list of possible parameters # that can be passed here can be found here # https://gist.github.com/jurentie/7b6c53d5a592991b6bb2491fcc5f01eb) # pass in the parameters defined above # This should result in separate GeoLayer shapefiles being written to the OUTPUT directory split_output = self.command_processor.qgis_processor.runAlgorithm( "qgis:splitvectorlayer", alg_parameters) # Create new GeoLayers and add them to the GeoProcessor's geolayers list. # @jurentie # TODO jurentie 01/26/2019 There probably needs to be some error handling happening below # Get the list of features from the GeoLayer. This returns all attributes for each feature listed. features = input_geolayer.qgs_vector_layer.getFeatures() # Set the extension for the filename's to get the geolayer from filename_extension = ".shp" # Parse through the list of features and also enumerate to get the index which # is used for accessing which OutputGeoLayerIDs to name each GeoLayer. # TODO jurentie 01/26/2019 need to decide what to do with a default OutputGeoLayerIDs # 1. Get the attribute of interest from each feature # TODO jurentie 01/26/2019 need to handle parsing out unique attributes only... # 2. Create the path name using the output folder specified in alg_parameters and passed in to # split_output, and the naming convention defaults for qgis:splitvectorlayer # 3. Construct a QgsVectorLayer() # Parameters: # path: The path or url of the parameter. Typically this encodes parameters used by the data # provider as url query items. # baseName: The name used to represent the layer in the legend # providerLib: The name of the data provider, e.g., "memory", "postgres" # options: layer load options # For more info see: # https://qgis.org/api/classQgsVectorLayer.html#a1e7827a9d7bd33549babdc3bd7a279fd # 4. Construct a new GeoLayer from the QgsVectorLayer() # Parameters: # geolayer_id (str): String that is the GeoLayer's reference ID. This ID is used to access the # GeoLayer from the GeoProcessor for manipulation. # geolayer_qgs_vector_layer (QGSVectorLayer): Object created by the QGIS processor. # All GeoLayer spatial manipulations are performed on the GeoLayer's qgs_vector_layer. # geolayer_source_path (str): The full pathname to the original spatial data file on the # user's local computer. If the geolayer was made in memory from the GeoProcessor, this value # is set to `MEMORY`. # 5. Add the new GeoLayer to the GeoProcessor for i, feature in enumerate(features): attribute = feature[attribute_name] path = temp_directory + "/" + attribute_name + "_" + str( attribute) + filename_extension layer = QgsVectorLayer(path, "layer" + str(attribute), "ogr") try: new_geolayer = GeoLayer(pv_OutputGeoLayerIDs[i], layer, path) except: # Default Output GeoLayerID's will be default title of output files from .runAlgorithm above new_geolayer = GeoLayer( attribute_name + "_" + str(attribute), layer, path) self.command_processor.add_geolayer(new_geolayer) # @jurentie # remove files if specified in parameters # TODO @jurentie figure out how to delete files after using them... # remove_files = self.get_parameter_value("RemoveTemporaryFiles") # files = glob.glob(temp_directory + "/*") # print(files) # if remove_files == None: # # Remove all files from directory # for f in files: # os.remove(f) # os.rmdir(temp_directory) # In QGIS 2 the clipped_output["OUTPUT"] returned the full file pathname of the memory output layer # (saved in a QGIS temporary folder) # qgs_vector_layer = qgis_util.read_qgsvectorlayer_from_file(clipped_output["OUTPUT"]) # new_geolayer = GeoLayer(pv_OutputGeoLayerID, qgs_vector_layer, "MEMORY") # Get this list of ID's, name can be changed later to make more sense # in a dynamic fashion # In QGIS 3 the clipped_output["OUTPUT"] returns the QGS vector layer object # new_geolayer = GeoLayer(pv_OutputGeoLayerIDs, split_output["OUTPUT"], "MEMORY") # self.command_processor.add_geolayer(new_geolayer) # Raise an exception if an unexpected error occurs during the process except Exception as e: self.warning_count += 1 message = "Unexpected error splitting GeoLayer {}.".format( pv_InputGeoLayerID) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log( CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format( self.warning_count) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity( CommandPhaseType.RUN, CommandStatusType.SUCCESS)
def run_command(self): """ Run the command. Simplify the GeoLayer by the tolerance. Returns: None. Raises: RuntimeError if any warnings occurred during run_command method. """ # Obtain the parameter values. pv_GeoLayerID = self.get_parameter_value("GeoLayerID") pv_Tolerance = self.get_parameter_value("Tolerance") tolerance_float = float(pv_Tolerance) pv_SimplifyMethod = self.get_parameter_value( "SimplifyMethod", default_value="DouglasPeucker").upper() default_simple_id = "{}_simple_{}".format(pv_GeoLayerID, pv_Tolerance) pv_SimplifiedGeoLayerID = self.get_parameter_value( "SimplifiedGeoLayerID", default_value=default_simple_id) # Run the checks on the parameter values. Only continue if the checks passed. if self.__should_simplify_geolayer(pv_GeoLayerID, pv_SimplifiedGeoLayerID): try: # Get the GeoLayer. geolayer = self.command_processor.get_geolayer(pv_GeoLayerID) # If the GeoLayer is an in-memory GeoLayer, make it an on-disk GeoLayer. if geolayer.source_path is None or geolayer.source_path.upper( ) in ["", "MEMORY"]: # Get the absolute path of the GeoLayer to write to disk. geolayer_disk_abs_path = os.path.join( self.command_processor.get_property('TempDir'), geolayer.id) # Write the GeoLayer to disk. Overwrite the (memory) GeoLayer in the geoprocessor with the # on-disk GeoLayer. geolayer = geolayer.write_to_disk(geolayer_disk_abs_path) self.command_processor.add_geolayer(geolayer) # If the SimplifyMethod is DouglasPeucker, continue. if pv_SimplifyMethod == "DOUGLASPEUCKER": # Perform the QGIS simplify geometries function. Refer to the REF below for parameter descriptions. # REF: https://docs.qgis.org/2.8/en/docs/user_manual/processing_algs/qgis/ # vector_geometry_tools/simplifygeometries.html alg_parameters = { "INPUT": geolayer.qgs_vector_layer, "METHOD": 0, "TOLERANCE": tolerance_float, "OUTPUT": "memory:" } simple_output = self.command_processor.qgis_processor.runAlgorithm( "native:simplifygeometries", alg_parameters) # Create a new GeoLayer and add it to the GeoProcessor's geolayers list. # in QGIS3, simple_output["OUTPUT"] returns the returns the QGS vector layer object # see ClipGeoLayer.py for information about value in QGIS2 environment new_geolayer = GeoLayer(pv_SimplifiedGeoLayerID, simple_output["OUTPUT"], "MEMORY") self.command_processor.add_geolayer(new_geolayer) # Raise an exception if an unexpected error occurs during the process. except Exception as e: self.warning_count += 1 message = "Unexpected error simplifying GeoLayer {}.".format( pv_GeoLayerID) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log( CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format( self.warning_count) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity( CommandPhaseType.RUN, CommandStatusType.SUCCESS)
def run_command(self): """ Run the command. Read all spatial data files within the folder. For each desired spatial data file (can be specified by the Subset_Pattern parameter), create a GeoLayer object, and add to the GeoProcessor's geolayer list. Returns: None. Raises: RuntimeError if any warnings occurred during run_command method. """ # Obtain the parameter values. pv_SpatialDataFolder = self.get_parameter_value("SpatialDataFolder") pv_Subset_Pattern = self.get_parameter_value("Subset_Pattern") pv_GeoLayerID_prefix = self.get_parameter_value("GeoLayerID_prefix") # Convert the SpatialDataFolder parameter value relative path to an absolute path sd_folder_abs = io_util.verify_path_for_os( io_util.to_absolute_path(self.command_processor.get_property('WorkingDir'), self.command_processor.expand_parameter_value(pv_SpatialDataFolder, self))) # Run the initial checks on the parameter values. Only continue if the checks passed. if self.__should_read_folder(sd_folder_abs): # Determine which files within the folder should be processed. All files will be processed if # pv_Subset_Pattern is set to None. Otherwise only files that match the given pattern will be processed. # Check that each file in the folder is: # 1. a file # 2. a spatial data file (ends in .shp or .geojson) # 3. follows the given pattern (if Subset_Pattern parameter value does not equal None) if pv_Subset_Pattern: spatial_data_files_abs = [os.path.join(sd_folder_abs, source_file) for source_file in glob.glob(os.path.join(sd_folder_abs, pv_Subset_Pattern)) if os.path.isfile(os.path.join(sd_folder_abs, source_file)) and (source_file.endswith(".shp") or source_file.endswith(".geojson"))] else: spatial_data_files_abs = [os.path.join(sd_folder_abs, source_file) for source_file in os.listdir(sd_folder_abs) if os.path.isfile(os.path.join(sd_folder_abs, source_file)) and (source_file.endswith(".shp") or source_file.endswith(".geojson"))] # Iterate through the desired spatial data files for spatial_data_file_absolute in spatial_data_files_abs: # Determine the GeoLayerID. if pv_GeoLayerID_prefix: geolayer_id = "{}_{}".format(pv_GeoLayerID_prefix, io_util.expand_formatter(spatial_data_file_absolute, '%f')) else: geolayer_id = io_util.expand_formatter(spatial_data_file_absolute, '%f') # Run the secondary checks on the parameter values. Only continue if the checks passed. if self.__should_read_geolayer(geolayer_id): try: # Create a QGSVectorLayer object with the GeoJSON SpatialDataFile qgs_vector_layer = qgis_util.read_qgsvectorlayer_from_file(spatial_data_file_absolute) # Create a GeoLayer and add it to the geoprocessor's GeoLayers list geolayer_obj = GeoLayer(geolayer_id, qgs_vector_layer, spatial_data_file_absolute) self.command_processor.add_geolayer(geolayer_obj) # Raise an exception if an unexpected error occurs during the process except Exception as e: self.warning_count += 1 message = "Unexpected error reading GeoLayer {} from" \ " file {}.".format(geolayer_id, spatial_data_file_absolute) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log(CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format(self.warning_count) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity(CommandPhaseType.RUN, CommandStatusType.SUCCESS)
def run_command(self): """ Run the command. Set the GeoLayer coordinate reference system. Returns: None. Raises: RuntimeError if any warnings occurred during run_command method. """ # Obtain the parameter values. pv_GeoLayerID = self.get_parameter_value("GeoLayerID") pv_CRS = self.get_parameter_value("CRS") # Convert the pv_GeoLayerID parameter to expand for ${Property} syntax. pv_GeoLayerID = self.command_processor.expand_parameter_value( pv_GeoLayerID, self) # Run the checks on the parameter values. Only continue if the checks passed. if self.__should_set_crs(pv_GeoLayerID, pv_CRS): # Run the process. try: # Get the input GeoLayer. input_geolayer = self.command_processor.get_geolayer( pv_GeoLayerID) # Check if the input GeoLayer already has an assigned CRS. if input_geolayer.get_crs(): # Reproject the GeoLayer. alg_parameters = { "INPUT": input_geolayer.qgs_vector_layer, "TARGET_CRS": pv_CRS, "OUTPUT": "memory:" } reprojected = self.command_processor.qgis_processor.runAlgorithm( "native:reprojectlayer", alg_parameters) # Create a new GeoLayer and add it to the GeoProcessor's geolayers list. # In QGIS 2 the reprojected["OUTPUT"] returned the full file pathname of the memory output layer # (saved in a QGIS temporary folder) # qgs_vector_layer = qgis_util.read_qgsvectorlayer_from_file(reprojected["OUTPUT"]) # new_geolayer = GeoLayer(input_geolayer.id, qgs_vector_layer, "MEMORY") # In QGIS 3 the reprojected["OUTPUT"] returns the QGS vector layer object new_geolayer = GeoLayer(input_geolayer.id, reprojected["OUTPUT"], "MEMORY") self.command_processor.add_geolayer(new_geolayer) else: alg_parameters = { "INPUT": input_geolayer.qgs_vector_layer, "CRS": pv_CRS } self.command_processor.qgis_processor.runAlgorithm( "qgis:definecurrentprojection", alg_parameters) # Raise an exception if an unexpected error occurs during the process except Exception as e: self.warning_count += 1 message = "Unexpected error setting CRS ({}) of GeoLayer ({})".format( pv_CRS, pv_GeoLayerID) recommendation = "Check the log file for details." self.logger.error(message, exc_info=True) self.command_status.add_to_log( CommandPhaseType.RUN, CommandLogRecord(CommandStatusType.FAILURE, message, recommendation)) # Determine success of command processing. Raise Runtime Error if any errors occurred if self.warning_count > 0: message = "There were {} warnings proceeding this command.".format( self.warning_count) raise RuntimeError(message) # Set command status type as SUCCESS if there are no errors. else: self.command_status.refresh_phase_severity( CommandPhaseType.RUN, CommandStatusType.SUCCESS)