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'] 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'] = ranges_proc interval_raw = self.parameterAsString(parameters, self.IN_INTERVAL, context) if interval_raw: params['interval'] = interval_raw smoothing_raw = self.parameterAsString(parameters, self.IN_SMOOTH, context) if smoothing_raw: params['smoothing'] = smoothing_raw point = self.parameterAsPoint(parameters, self.IN_POINT, context, self.crs_out) # Make the actual requests # If layer source is set requests = [] self.isochrones.set_parameters(profile, dimension, factor) params['locations'] = [[round(point.x(), 6), round(point.y(), 6)]] 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) # If feature causes error, report and continue with next try: # Populate features from response response = clnt.request('/v2/isochrones/' + profile, {}, post_json=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) return {self.OUT: self.dest_id}
def processAlgorithm(self, parameters, context, feedback): ors_client = self._get_ors_client_from_provider( parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] dimension = dict(enumerate(DIMENSIONS))[parameters[self.IN_METRIC]] factor = 60 if dimension == 'time' else 1 ranges_raw = parameters[self.IN_RANGES] ranges_proc = [x * factor for x in map(int, ranges_raw.split(','))] options = self.parseOptions(parameters, context) point = self.parameterAsPoint(parameters, self.IN_POINT, context, self.crs_out) # Make the actual requests # If layer source is set self.isochrones.set_parameters(profile, dimension, factor) params = { "locations": [[round(point.x(), 6), round(point.y(), 6)]], "range_type": dimension, "range": ranges_proc, "attributes": ['total_pop'], "id": None, "options": options } (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) try: response = ors_client.request('/v2/isochrones/' + profile, {}, post_json=params) # Populate features from response for isochrone in self.isochrones.get_features( response, params['id']): sink.addFeature(isochrone) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = f"Feature ID {params['id']} caused a {e.__class__.__name__}:\n{str(e)}" feedback.reportError(msg) logger.log(msg, 2) return {self.OUT: self.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() # Get profile value profile = PROFILES[self.parameterAsEnum( parameters, self.IN_PROFILE, context )] # Get parameter values source = self.parameterAsSource( parameters, self.IN_START, context ) source_field_name = self.parameterAsString( parameters, self.IN_START_FIELD, context ) destination = self.parameterAsSource( parameters, self.IN_END, context ) destination_field_name = self.parameterAsString( parameters, self.IN_END_FIELD, context ) # Get fields from field name source_field_id = source.fields().lookupField(source_field_name) source_field = source.fields().field(source_field_id) destination_field_id = destination.fields().lookupField(destination_field_name) destination_field = destination.fields().field(destination_field_id) # Abort when MultiPoint type if (source.wkbType() or destination.wkbType()) == 4: raise QgsProcessingException("TypeError: Multipoint Layers are not accepted. Please convert to single geometry layer.") # Get source and destination features sources_features = list(source.getFeatures()) destination_features = list(destination.getFeatures()) # Get feature amounts/counts sources_amount = source.featureCount() destinations_amount = destination.featureCount() # Allow for 50 features in source if source == destination source_equals_destination = parameters['INPUT_START_LAYER'] == parameters['INPUT_END_LAYER'] if source_equals_destination: features = sources_features xformer = transform.transformToWGS(source.sourceCrs()) features_points = [xformer.transform(feat.geometry().asPoint()) for feat in features] else: xformer = transform.transformToWGS(source.sourceCrs()) sources_features_xformed = [xformer.transform(feat.geometry().asPoint()) for feat in sources_features] xformer = transform.transformToWGS(destination.sourceCrs()) destination_features_xformed = [xformer.transform(feat.geometry().asPoint()) for feat in destination_features] features_points = sources_features_xformed + destination_features_xformed # Get IDs sources_ids = list(range(sources_amount)) if source_equals_destination else list(range(sources_amount)) destination_ids = list(range(sources_amount)) if source_equals_destination else list(range(sources_amount, sources_amount + destinations_amount)) # Populate parameters further params.update({ 'locations': [[point.x(), point.y()] for point in features_points], 'sources': sources_ids, 'destinations': destination_ids, 'metrics': ["duration", "distance"], 'id': 'Matrix' }) # Make request and catch ApiError try: response = clnt.request('/v2/matrix/' + profile, {}, post_json=params) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "{}: {}".format( e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg) (sink, dest_id) = self.parameterAsSink( parameters, self.OUT, context, self.get_fields( source_field.type(), destination_field.type() ), QgsWkbTypes.NoGeometry ) sources_attributes = [feat.attribute(source_field_name) for feat in sources_features] destinations_attributes = [feat.attribute(destination_field_name) for feat in destination_features] for s, source in enumerate(sources_attributes): for d, destination in enumerate(destinations_attributes): duration = response['durations'][s][d] distance = response['distances'][s][d] feat = QgsFeature() feat.setAttributes([ source, destination, duration / 3600 if duration is not None else None, distance / 1000 if distance is not None else None ]) sink.addFeature(feat) 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...")) profile = PROFILES[self.parameterAsEnum(parameters, self.IN_PROFILE, context)] preference = PREFERENCES[self.parameterAsEnum(parameters, self.IN_PREFERENCE, context)] optimize = self.parameterAsBool(parameters, self.IN_OPTIMIZE, 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) (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 try: if optimize: params = self._get_params_optimize(line, profile) response = clnt.request('/optimization', {}, post_json=params) sink.addFeature( directions_core.get_output_features_optimization( response, profile, from_value=field_value)) else: params = self._get_params_directions( line, profile, preference) response = clnt.request( '/v2/directions/' + profile + '/geojson', params) sink.addFeature( directions_core.get_output_feature_directions( response, profile, preference, from_value=field_value)) 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 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...")) 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): ors_client = self._get_ors_client_from_provider(parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] dimension = dict(enumerate(DIMENSIONS))[parameters[self.IN_METRIC]] factor = 60 if dimension == 'time' else 1 ranges_raw = parameters[self.IN_RANGES] ranges_proc = [x * factor for x in map(int, ranges_raw.split(','))] # self.difference = self.parameterAsBool(parameters, self.IN_DIFFERENCE, context) source = self.parameterAsSource(parameters, self.IN_POINTS, context) options = self.parseOptions(parameters, context) # Make the actual requests requests = [] if QgsWkbTypes.flatType(source.wkbType()) == QgsWkbTypes.MultiPoint: raise QgsProcessingException( "TypeError: Multipoint Layers are not accepted. Please convert to single geometry layer.") # Get ID field properties id_field_name = parameters[self.IN_FIELD] parameter_options = list() if id_field_name: id_field = source.fields().field(id_field_name) parameter_options = [id_field.type(), id_field_name] self.isochrones.set_parameters(profile, dimension, factor, *parameter_options) for locations, id_value in self.get_sorted_feature_parameters(source, id_field_name): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break requests.append({ "locations": locations, "range_type": dimension, "range": ranges_proc, "attributes": ['total_pop'], "id": id_value, "options": options }) (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 feedback.isCanceled(): break # If feature causes error, report and continue with next try: # Populate features from response response = ors_client.request('/v2/isochrones/' + profile, {}, post_json=params) for isochrone in self.isochrones.get_features(response, params['id']): sink.addFeature(isochrone) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = f"Feature ID {params['id']} caused a {e.__class__.__name__}:\n{str(e)}" feedback.reportError(msg) logger.log(msg, 2) continue feedback.setProgress(int(100.0 / source.featureCount() * num)) return {self.OUT: self.dest_id}
def request(self, url, params, first_request_time=None, retry_counter=0, post_json=None): """Performs HTTP GET/POST with credentials, returning the body as JSON. :param url: URL extension for request. Should begin with a slash. :type url: string :param params: HTTP GET parameters. :type params: dict or list of key/value tuples :param first_request_time: The time of the first request (None if no retries have occurred). :type first_request_time: datetime.datetime :param retry_counter: Amount of retries with increasing timeframe before raising a timeout exception :type retry_counter: int :param post_json: Parameters for POST endpoints :type post_json: dict :param retry_counter: Duration the requests will be retried for before raising a timeout exception. :type retry_counter: int :raises ORStools.utils.exceptions.ApiError: when the API returns an error. :returns: openrouteservice response body :rtype: dict """ if not first_request_time: first_request_time = datetime.now() elapsed = datetime.now() - first_request_time if elapsed > self.retry_timeout: raise exceptions.Timeout() if retry_counter > 0: # 0.5 * (1.5 ^ i) is an increased sleep time of 1.5x per iteration, # starting at 0.5s when retry_counter=1. The first retry will occur # at 1, so subtract that first. delay_seconds = 1.5**(retry_counter - 1) # Jitter this value by 50% and pause. time.sleep(delay_seconds * (random.random() + 0.5)) authed_url = self._generate_auth_url( url, params, ) self.url = self.base_url + authed_url # Default to the client-level self.requests_kwargs, with method-level # requests_kwargs arg overriding. # final_requests_kwargs = self.requests_kwargs # Determine GET/POST # requests_method = self.session.get requests_method = 'GET' body = None if post_json is not None: # requests_method = self.session.post # final_requests_kwargs["json"] = post_json body = post_json requests_method = 'POST' logger.log( f"url: {self.url}\nParameters: {json.dumps(body, indent=2)}", 0) try: response, content = self.nam.request(self.url, method=requests_method, body=body, headers=self.headers, blocking=True) except networkaccessmanager.RequestsExceptionTimeout: raise exceptions.Timeout except networkaccessmanager.RequestsException: try: self._check_status() except exceptions.OverQueryLimit as e: # Let the instances know something happened # noinspection PyUnresolvedReferences self.overQueryLimit.emit() logger.log(f"{e.__class__.__name__}: {str(e)}", 1) return self.request(url, params, first_request_time, retry_counter + 1, post_json) except exceptions.ApiError as e: logger.log( f"Feature ID {post_json['id']} caused a {e.__class__.__name__}: {str(e)}", 2) raise raise # Write env variables if successful if self.ENV_VARS: for env_var in self.ENV_VARS: configmanager.write_env_var( env_var, response.headers.get(self.ENV_VARS[env_var], 'None')) return json.loads(content.decode('utf-8'))
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)] mode = self.MODE_SELECTION[self.parameterAsEnum( parameters, self.IN_MODE, context)] # Get parameter values source = self.parameterAsSource(parameters, self.IN_START, context) source_field_name = self.parameterAsString(parameters, self.IN_START_FIELD, context) destination = self.parameterAsSource(parameters, self.IN_END, context) destination_field_name = self.parameterAsString( parameters, self.IN_END_FIELD, context) # Get fields from field name source_field_id = source.fields().lookupField(source_field_name) source_field = source.fields().field(source_field_id) destination_field_id = destination.fields().lookupField( destination_field_name) destination_field = destination.fields().field(destination_field_id) params = { 'preference': preference, 'geometry': 'true', 'instructions': 'false', 'elevation': True, 'id': None } route_dict = self._get_route_dict(source, source_field, destination, destination_field) if mode == 'Row-by-Row': route_count = min( [source.featureCount(), destination.featureCount()]) else: route_count = source.featureCount() * destination.featureCount() (sink, dest_id) = self.parameterAsSink( parameters, self.OUT, context, directions_core.get_fields(source_field.type(), destination_field.type()), QgsWkbTypes.LineString, QgsCoordinateReferenceSystem(4326)) counter = 0 for coordinates, values in directions_core.get_request_point_features( route_dict, mode): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break params['coordinates'] = coordinates try: response = clnt.request('/v2/directions/' + profile + '/geojson', {}, post_json=params) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "Route from {} to {} caused a {}:\n{}".format( values[0], values[1], e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg) continue sink.addFeature( directions_core.get_output_feature_directions( response, profile, preference, from_value=values[0], to_value=values[1])) counter += 1 feedback.setProgress(int(100.0 / route_count * counter)) 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 processAlgorithm(self, parameters, context, feedback): ors_client = self._get_ors_client_from_provider( parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] preference = dict( enumerate(PREFERENCES))[parameters[self.IN_PREFERENCE]] optimization_mode = parameters[self.IN_OPTIMIZE] options = self.parseOptions(parameters, context) # Get parameter values source = self.parameterAsSource(parameters, self.IN_LINES, context) # parameters[self.IN_FIELD] returns a PyQt5.QtCore.QVariant with "NULL" as content # in case of absence of self.IN_FIELD. # qgis overwrites this type's __bool__ in # https://github.com/qgis/QGIS/blob/master/python/PyQt/PyQt5/QtCore.py: # def __bool__(self): # return not self.isNull() # The check below works because of that. source_field_name = parameters[self.IN_FIELD] get_fields_options = dict() if source_field_name: get_fields_options.update( from_type=source.fields().field(source_field_name).type(), from_name=source_field_name) sink_fields = directions_core.get_fields(**get_fields_options, line=True) (sink, dest_id) = self.parameterAsSink( parameters, self.OUT, context, sink_fields, source.wkbType(), QgsCoordinateReferenceSystem.fromEpsgId(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 try: if optimization_mode is not None: params = get_params_optimize(line, profile, optimization_mode) response = ors_client.request('/optimization', {}, post_json=params) sink.addFeature( directions_core.get_output_features_optimization( response, profile, from_value=field_value)) else: params = directions_core.build_default_parameters( preference, point_list=line, options=options) response = ors_client.request('/v2/directions/' + profile + '/geojson', {}, post_json=params) sink.addFeature( directions_core.get_output_feature_directions( response, profile, preference, from_value=field_value)) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = f"Feature ID {num} caused a {e.__class__.__name__}:\n{str(e)}" feedback.reportError(msg) logger.log(msg) continue feedback.setProgress(int(100.0 / count * num)) return {self.OUT: dest_id}
def processAlgorithm(self, parameters, context, feedback): ors_client = self._get_ors_client_from_provider(parameters[self.IN_PROVIDER], feedback) # Get profile value profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] # TODO: enable once core matrix is available # options = self.parseOptions(parameters, context) # Get parameter values source = self.parameterAsSource( parameters, self.IN_START, context ) source_field_name = parameters[self.IN_START_FIELD] source_field = source.fields().field(source_field_name) if source_field_name else None destination = self.parameterAsSource( parameters, self.IN_END, context ) destination_field_name = parameters[self.IN_END_FIELD] destination_field = destination.fields().field(destination_field_name) if destination_field_name else None # Abort when MultiPoint type if (QgsWkbTypes.flatType(source.wkbType()) or QgsWkbTypes.flatType(destination.wkbType()))\ == QgsWkbTypes.MultiPoint: raise QgsProcessingException( "TypeError: Multipoint Layers are not accepted. Please convert to single geometry layer.") # Get source and destination features sources_features = list(source.getFeatures()) destination_features = list(destination.getFeatures()) # Get feature amounts/counts sources_amount = source.featureCount() destinations_amount = destination.featureCount() # Allow for 50 features in source if source == destination source_equals_destination = parameters['INPUT_START_LAYER'] == parameters['INPUT_END_LAYER'] if source_equals_destination: features = sources_features x_former = transform.transformToWGS(source.sourceCrs()) features_points = [x_former.transform(feat.geometry().asPoint()) for feat in features] else: x_former = transform.transformToWGS(source.sourceCrs()) sources_features_x_formed = [x_former.transform(feat.geometry().asPoint()) for feat in sources_features] x_former = transform.transformToWGS(destination.sourceCrs()) destination_features_x_formed = [x_former.transform(feat.geometry().asPoint()) for feat in destination_features] features_points = sources_features_x_formed + destination_features_x_formed # Get IDs sources_ids = list(range(sources_amount)) if source_equals_destination else list(range(sources_amount)) destination_ids = list(range(sources_amount)) if source_equals_destination else list( range(sources_amount, sources_amount + destinations_amount)) params = { 'locations': [[point.x(), point.y()] for point in features_points], 'sources': sources_ids, 'destinations': destination_ids, 'metrics': ["duration", "distance"], 'id': 'Matrix' # 'options': options } # get types of set ID fields field_types = dict() if source_field: field_types.update({"source_type": source_field.type()}) if destination_field: field_types.update({"destination_type": destination_field.type()}) sink_fields = self.get_fields(**field_types) # Make request and catch ApiError try: response = ors_client.request('/v2/matrix/' + profile, {}, post_json=params) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = f"{e.__class__.__name__}: {str(e)}" feedback.reportError(msg) logger.log(msg) (sink, dest_id) = self.parameterAsSink( parameters, self.OUT, context, sink_fields, QgsWkbTypes.NoGeometry ) sources_attributes = [feat.attribute(source_field_name) if source_field_name else feat.id() for feat in sources_features] destinations_attributes = [feat.attribute(destination_field_name) if destination_field_name else feat.id() for feat in destination_features] for s, source in enumerate(sources_attributes): for d, destination in enumerate(destinations_attributes): duration = response['durations'][s][d] distance = response['distances'][s][d] feat = QgsFeature() feat.setAttributes([ source, destination, duration / 3600 if duration is not None else None, distance / 1000 if distance is not None else None ]) sink.addFeature(feat) 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...")) profile = PROFILES[self.parameterAsEnum(parameters, self.IN_PROFILE, context)] preference = PREFERENCES[self.parameterAsEnum(parameters, self.IN_PREFERENCE, context)] optimize = self.parameterAsBool(parameters, self.IN_OPTIMIZE, context) # Get parameter values source = self.parameterAsSource(parameters, self.IN_POINTS, context) source_field_idx = self.parameterAsEnum(parameters, self.IN_FIELD, context) source_field_name = self.parameterAsString(parameters, self.IN_FIELD, context) (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), QgsWkbTypes.LineString, QgsCoordinateReferenceSystem(4326)) count = source.featureCount() input_points = list() from_values = list() xformer = transform.transformToWGS(source.sourceCrs()) if source.wkbType() == QgsWkbTypes.Point: points = list() for feat in sorted(source.getFeatures(), key=lambda f: f.id()): points.append( xformer.transform(QgsPointXY(feat.geometry().asPoint()))) input_points.append(points) from_values.append('') elif source.wkbType() == QgsWkbTypes.MultiPoint: # loop through multipoint features for feat in sorted(source.getFeatures(), key=lambda f: f.id()): points = list() for point in feat.geometry().asMultiPoint(): points.append(xformer.transform(QgsPointXY(point))) input_points.append(points) from_values.append(feat[source_field_name]) for num, (points, from_value) in enumerate(zip(input_points, from_values)): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break try: if optimize: params = self._get_params_optimize(points, profile) response = clnt.request('/optimization', {}, post_json=params) sink.addFeature( directions_core.get_output_features_optimization( response, profile, from_value=from_value)) else: params = self._get_params_directions( points, profile, preference) response = clnt.request('/v2/directions/' + profile + '/geojson', {}, post_json=params) sink.addFeature( directions_core.get_output_feature_directions( response, profile, preference, from_value=from_value)) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "Feature ID {} caused a {}:\n{}".format( from_value, e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg) continue feedback.setProgress(int(100.0 / count * num)) return {self.OUT: 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
def processAlgorithm(self, parameters, context, feedback): ors_client = self._get_ors_client_from_provider( parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] preference = dict( enumerate(PREFERENCES))[parameters[self.IN_PREFERENCE]] mode = dict(enumerate(self.MODE_SELECTION))[parameters[self.IN_MODE]] options = self.parseOptions(parameters, context) # Get parameter values source = self.parameterAsSource(parameters, self.IN_START, context) source_field_name = parameters[self.IN_START_FIELD] source_field = source.fields().field( source_field_name) if source_field_name else None sort_start_by = parameters[self.IN_SORT_START_BY] if sort_start_by: def sort_start(f): return f.attribute(sort_start_by) else: def sort_start(f): return f.id() destination = self.parameterAsSource(parameters, self.IN_END, context) destination_field_name = parameters[self.IN_END_FIELD] destination_field = destination.fields().field( destination_field_name) if destination_field_name else None sort_end_by = parameters[self.IN_SORT_END_BY] if sort_end_by: def sort_end(f): return f.attribute(sort_end_by) else: def sort_end(f): return f.id() route_dict = self._get_route_dict(source, source_field, sort_start, destination, destination_field, sort_end) if mode == 'Row-by-Row': route_count = min( [source.featureCount(), destination.featureCount()]) else: route_count = source.featureCount() * destination.featureCount() # get types of set ID fields field_types = dict() if source_field: field_types.update({"from_type": source_field.type()}) if destination_field: field_types.update({"to_type": destination_field.type()}) sink_fields = directions_core.get_fields(**field_types) (sink, dest_id) = self.parameterAsSink( parameters, self.OUT, context, sink_fields, QgsWkbTypes.LineString, QgsCoordinateReferenceSystem.fromEpsgId(4326)) counter = 0 for coordinates, values in directions_core.get_request_point_features( route_dict, mode): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break params = directions_core.build_default_parameters( preference, coordinates=coordinates, options=options) try: response = ors_client.request('/v2/directions/' + profile + '/geojson', {}, post_json=params) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = f"Route from {values[0]} to {values[1]} caused a {e.__class__.__name__}:\n{str(e)}" feedback.reportError(msg) logger.log(msg) continue sink.addFeature( directions_core.get_output_feature_directions( response, profile, preference, from_value=values[0], to_value=values[1])) counter += 1 feedback.setProgress(int(100.0 / route_count * counter)) return {self.OUT: 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() # Associate annotations with map layer, so they get deleted when layer is deleted for annotation in self.dlg.annotations: # Has the potential to be pretty cool: instead of deleting, associate with mapLayer, you can change order after optimization # Then in theory, when the layer is remove, the annotation is removed as well # Doesng't work though, the annotations are still there when project is re-opened # annotation.setMapLayer(layer_out) self.project.annotationManager().removeAnnotation(annotation) self.dlg.annotations = [] provider_id = self.dlg.provider_combo.currentIndex() provider = configmanager.read_config()['providers'][provider_id] # if there are no coordinates, throw an error message if not self.dlg.routing_fromline_list.count(): QMessageBox.critical( self.dlg, "Missing API key", """ Did you forget to set routing waypoints?<br><br> Use the 'Add Waypoint' button to add up to 50 waypoints. """) return # if no API key is present, when ORS is selected, throw an error message if not provider['key'] and provider['base_url'].startswith( 'https://api.openrouteservice.org'): 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) params = directions.get_parameters() try: if self.dlg.optimization_group.isChecked(): if len(params['jobs'] ) <= 1: # Start/end locations don't count as job QMessageBox.critical( self.dlg, "Wrong number of waypoints", """At least 3 or 4 waypoints are needed to perform routing optimization. Remember, the first and last location are not part of the optimization. """) return response = clnt.request('/optimization', {}, post_json=params) feat = directions_core.get_output_features_optimization( response, params['vehicles'][0]['profile']) else: params['coordinates'] = directions.get_request_line_feature() profile = self.dlg.routing_travel_combo.currentText() response = clnt.request('/v2/directions/' + profile + '/geojson', {}, post_json=params) feat = directions_core.get_output_feature_directions( response, profile, params['preference'], directions.options) layer_out.dataProvider().addFeature(feat) 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) raise 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>Parameters:<br>{1}'.format( clnt.url, json.dumps(params, indent=2)) self.dlg.debug_text.setHtml(clnt_msg)
def processAlgorithm(self, parameters, context, feedback): ors_client = self._get_ors_client_from_provider( parameters[self.IN_PROVIDER], feedback) profile = dict(enumerate(PROFILES))[parameters[self.IN_PROFILE]] preference = dict( enumerate(PREFERENCES))[parameters[self.IN_PREFERENCE]] optimization_mode = parameters[self.IN_OPTIMIZE] options = self.parseOptions(parameters, context) # Get parameter values source = self.parameterAsSource(parameters, self.IN_POINTS, context) source_field_name = parameters[self.IN_FIELD] get_fields_options = dict() if source_field_name: get_fields_options.update( from_type=source.fields().field(source_field_name).type(), from_name=source_field_name) sink_fields = directions_core.get_fields(**get_fields_options, line=True) (sink, dest_id) = self.parameterAsSink( parameters, self.OUT, context, sink_fields, QgsWkbTypes.LineString, QgsCoordinateReferenceSystem.fromEpsgId(4326)) sort_by = parameters[self.IN_SORTBY] if sort_by: def sort(f): return f.attribute(sort_by) else: def sort(f): return f.id() count = source.featureCount() input_points = list() from_values = list() x_former = transform.transformToWGS(source.sourceCrs()) if QgsWkbTypes.flatType(source.wkbType()) == QgsWkbTypes.Point: points = list() for feat in sorted(source.getFeatures(), key=sort): points.append( x_former.transform(QgsPointXY(feat.geometry().asPoint()))) input_points.append(points) from_values.append(None) elif QgsWkbTypes.flatType(source.wkbType()) == QgsWkbTypes.MultiPoint: # loop through multipoint features for feat in sorted(source.getFeatures(), key=sort): points = list() for point in feat.geometry().asMultiPoint(): points.append(x_former.transform(QgsPointXY(point))) input_points.append(points) from_values.append( feat[source_field_name] if source_field_name else None) for num, (points, from_value) in enumerate(zip(input_points, from_values)): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break try: if optimization_mode is not None: params = get_params_optimize(points, profile, optimization_mode) response = ors_client.request('/optimization', {}, post_json=params) sink.addFeature( directions_core.get_output_features_optimization( response, profile, from_value=from_value)) else: params = directions_core.build_default_parameters( preference, point_list=points, options=options) response = ors_client.request('/v2/directions/' + profile + '/geojson', {}, post_json=params) sink.addFeature( directions_core.get_output_feature_directions( response, profile, preference, from_value=from_value)) except (exceptions.ApiError, exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = f"Feature ID {from_value} caused a {e.__class__.__name__}:\n{str(e)}" feedback.reportError(msg) logger.log(msg) continue feedback.setProgress(int(100.0 / count * num)) return {self.OUT: dest_id}