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): # 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)] 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'] 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 # self.difference = self.parameterAsBool(parameters, self.IN_DIFFERENCE, context) source = self.parameterAsSource(parameters, self.IN_POINTS, context) # Make the actual requests requests = [] if source.wkbType() == 4: raise QgsProcessingException("TypeError: Multipoint Layers are not accepted. Please convert to single geometry layer.") # Get ID field properties 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] or None requests.append(deepcopy(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 feedback.isCanceled(): break # 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) continue feedback.setProgress(int(100.0 / source.featureCount() * num)) 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...")) 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() # 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)