def get_sorted_feature_parameters(self, layer): """ Generator to yield geometry and id of features sorted by feature ID. Careful: feat.id() is not necessarily permanent :param layer: source input layer. :type layer: QgsProcessingParameterFeatureSource """ # First get coordinate transformer xformer = transform.transformToWGS(layer.sourceCrs()) for feat in sorted(layer.getFeatures(), key=lambda f: f.id()): x_point = xformer.transform(feat.geometry().asPoint()) yield (convert.build_coords([x_point.x(), x_point.y()]), feat)
def _get_params_directions(line, profile, preference): """ Build parameters for optimization endpoint :param line: individual polyline points :type line: list of QgsPointXY :param profile: transport profile to be used :type profile: str :param preference: routing preference, shortest/fastest :type preference: str :returns: parameters for optimization endpoint :rtype: dict """ params = { 'coordinates': convert.build_coords([[point.x(), point.y()] for point in line]), 'profile': profile, 'preference': preference, 'geometry': 'true', 'format': 'geojson', 'geometry_format': 'geojson', 'instructions': 'false', 'elevation': True, 'id': None } return params
def get_request_point_features(route_dict, row_by_row): """ Processes input point features depending on the layer to layer relation in directions settings :param route_dict: all coordinates and ID field values of start and end point layers :type route_dict: dict :param row_by_row: Specifies whether row-by-row relation or all-by-all has been used. :type row_by_row: str :returns: tuple of coordinates and ID field value for each routing feature in route_dict :rtype: tuple """ locations_list = list( product(route_dict['start']['geometries'], route_dict['end']['geometries'])) values_list = list( product(route_dict['start']['values'], route_dict['end']['values'])) # If row-by-row in two-layer mode, then only zip the locations if row_by_row == 'Row-by-Row': locations_list = list( zip(route_dict['start']['geometries'], route_dict['end']['geometries'])) values_list = list( zip(route_dict['start']['values'], route_dict['end']['values'])) for properties in zip(locations_list, values_list): # Skip if first and last location are the same if properties[0][0] == properties[0][-1]: continue coordinates = convert.build_coords(properties[0]) values = properties[1] yield (coordinates, values)
def processAlgorithm(self, parameters, context, feedback): # Init ORS client providers = configmanager.read_config()['providers'] provider = providers[self.parameterAsEnum(parameters, self.IN_PROVIDER, context)] clnt = client.Client(provider) clnt.overQueryLimit.connect( lambda: feedback.reportError("OverQueryLimit: Retrying...")) profile = PROFILES[self.parameterAsEnum(parameters, self.IN_PROFILE, context)] preference = PREFERENCES[self.parameterAsEnum(parameters, self.IN_PREFERENCE, context)] # Get parameter values source = self.parameterAsSource(parameters, self.IN_LINES, context) source_field_idx = self.parameterAsEnum(parameters, self.IN_FIELD, context) source_field_name = self.parameterAsString(parameters, self.IN_FIELD, context) params = { 'profile': profile, 'preference': preference, 'geometry': 'true', 'format': 'geojson', 'geometry_format': 'geojson', 'instructions': 'false', 'id': None } (sink, dest_id) = self.parameterAsSink( parameters, self.OUT, context, directions_core.get_fields( from_type=source.fields().field(source_field_name).type(), from_name=source_field_name, line=True), source.wkbType(), QgsCoordinateReferenceSystem(4326)) count = source.featureCount() for num, (line, field_value) in enumerate( self.get_sorted_lines(source, source_field_name)): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break params['coordinates'] = convert.build_coords( [[point.x(), point.y()] for point in line]) try: response = clnt.request( provider['endpoints'][self.ALGO_NAME_LIST[0]], params) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "Feature ID {} caused a {}:\n{}".format( line[source_field_name], e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg) continue sink.addFeature( directions_core.get_output_feature(response, profile, preference, from_value=field_value, line=True)) feedback.setProgress(int(100.0 / count * num)) return {self.OUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): # Init ORS client providers = configmanager.read_config()['providers'] provider = providers[self.parameterAsEnum(parameters, self.IN_PROVIDER, context)] clnt = client.Client(provider) clnt.overQueryLimit.connect( lambda: feedback.reportError("OverQueryLimit: Retrying...")) params = dict() params['attributes'] = 'total_pop' params['profile'] = profile = PROFILES[self.parameterAsEnum( parameters, self.IN_PROFILE, context)] params['range_type'] = dimension = DIMENSIONS[self.parameterAsEnum( parameters, self.IN_METRIC, context)] factor = 60 if params['range_type'] == 'time' else 1 ranges_raw = self.parameterAsString(parameters, self.IN_RANGES, context) ranges_proc = [x * factor for x in map(int, ranges_raw.split(','))] params['range'] = convert.comma_list(ranges_proc) # self.difference = self.parameterAsBool(parameters, self.IN_DIFFERENCE, context) point = self.parameterAsPoint(parameters, self.IN_POINT, context, self.crs_out) source = self.parameterAsSource(parameters, self.IN_POINTS, context) # Make the actual requests # If layer source is set requests = [] if source: if source.wkbType() == 4: raise QgsProcessingException( "TypeError: Multipoint Layers are not accepted. Please convert to single geometry layer." ) # Get ID field properties # TODO: id_field should have a default (#90) id_field_name = self.parameterAsString(parameters, self.IN_FIELD, context) id_field_id = source.fields().lookupField(id_field_name) if id_field_name == '': id_field_id = 0 id_field_name = source.fields().field(id_field_id).name() id_field = source.fields().field(id_field_id) # Populate iso_layer instance with parameters self.isochrones.set_parameters(profile, dimension, factor, id_field.type(), id_field_name) for properties in self.get_sorted_feature_parameters(source): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break # Get transformed coordinates and feature params['locations'], feat = properties params['id'] = feat[id_field_name] requests.append(deepcopy(params)) # elif point source is set else: self.isochrones.set_parameters(profile, dimension, factor) params['locations'] = convert.build_coords([point.x(), point.y()]) params['id'] = None requests.append(params) (sink, self.dest_id) = self.parameterAsSink( parameters, self.OUT, context, self.isochrones.get_fields(), QgsWkbTypes. Polygon, # Needs Multipolygon if difference parameter will ever be reactivated self.crs_out) for num, params in enumerate(requests): # If feature causes error, report and continue with next try: # Populate features from response response = clnt.request(provider['endpoints'][self.ALGO_NAME], params) for isochrone in self.isochrones.get_features( response, params['id']): sink.addFeature(isochrone) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "Feature ID {} caused a {}:\n{}".format( params['id'], e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg, 2) continue if source: feedback.setProgress(int(100.0 / source.featureCount() * num)) return {self.OUT: self.dest_id}
def run_gui_control(self): """Slot function for OK button of main dialog.""" layer_out = QgsVectorLayer("LineString?crs=EPSG:4326", "Route_ORS", "memory") layer_out.dataProvider().addAttributes(directions_core.get_fields()) layer_out.updateFields() provider_id = self.dlg.provider_combo.currentIndex() provider = configmanager.read_config()['providers'][provider_id] # Check if API key was set when using ORS if provider['key'] is None: QMessageBox.critical( self.dlg, "Missing API key", """ Did you forget to set an <b>API key</b> for openrouteservice?<br><br> If you don't have an API key, please visit https://openrouteservice.org/sign-up to get one. <br><br> Then enter the API key for openrouteservice provider in Web ► ORS Tools ► Provider Settings or the settings symbol in the main ORS Tools GUI, next to the provider dropdown. """) return clnt = client.Client(provider) clnt_msg = '' directions = directions_gui.Directions(self.dlg, self.advanced) params = directions.get_basic_paramters() from_id = None to_id = None try: if self.dlg.routing_tab.currentIndex() == 0: x_start = self.dlg.routing_frompoint_start_x.value() y_start = self.dlg.routing_frompoint_start_y.value() x_end = self.dlg.routing_frompoint_end_x.value() y_end = self.dlg.routing_frompoint_end_y.value() params['coordinates'] = convert.build_coords( [[x_start, y_start], [x_end, y_end]]) from_id = convert.comma_list([x_start, y_start]) to_id = convert.comma_list([x_end, y_end]) elif self.dlg.routing_tab.currentIndex() == 1: params['coordinates'] = convert.build_coords( directions.get_request_line_feature()) response = clnt.request(provider['endpoints']['directions'], params) layer_out.dataProvider().addFeature( directions_core.get_output_feature(response, params['profile'], params['preference'], directions.avoid, from_id, to_id)) layer_out.updateExtents() self.project.addMapLayer(layer_out) # Update quota; handled in client module after successful request if provider.get('ENV_VARS'): self.dlg.quota_text.setText( self.get_quota(provider) + ' calls') except exceptions.Timeout: msg = "The connection has timed out!" logger.log(msg, 2) self.dlg.debug_text.setText(msg) return except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = (e.__class__.__name__, str(e)) logger.log("{}: {}".format(*msg), 2) clnt_msg += "<b>{}</b>: ({})<br>".format(*msg) return except Exception as e: msg = [e.__class__.__name__, str(e)] logger.log("{}: {}".format(*msg), 2) clnt_msg += "<b>{}</b>: {}<br>".format(*msg) raise finally: # Set URL in debug window clnt_msg += '<a href="{0}">{0}</a><br>'.format(clnt.url) self.dlg.debug_text.setHtml(clnt_msg) return