def handle_response(self, response, feat_id): """ Casts JSON response to dict :raises valhalla.utils.exceptions.OverQueryLimitError: when rate limit is exhausted, HTTP 429 :raises valhalla.utils.exceptions.ApiError: when the backend API throws an error, HTTP 400 :raises valhalla.utils.exceptions.InvalidKey: when API key is invalid (or quota is exceeded), HTTP 403 :raises valhalla.utils.exceptions.GenericServerError: all other HTTP errors :returns: response body :rtype: dict """ self.status_code = response.attribute( QNetworkRequest.HttpStatusCodeAttribute) if response.error(): # First try non-HTTP error codes error_code = response.error() error_msg = response.errorString() if error_code in (QNetworkReply.ConnectionRefusedError, QNetworkReply.HostNotFoundError): raise exceptions.GenericServerError( 1, f"Host {self.base_url} not valid.") elif error_code == QNetworkReply.TimeoutError: raise exceptions.Timeout("Request timed out.") if self.status_code == 401: raise exceptions.InvalidKey(str(self.status_code), error_msg) elif self.status_code == 429: logger.log("{}: {}".format(exceptions.OverQueryLimit.__name__, "Query limit exceeded")) raise exceptions.OverQueryLimit(str(429), error_msg) # Internal error message for Bad Request elif self.status_code and 400 <= self.status_code < 500: logger.log("Feature ID {} caused a {}: {}".format( feat_id, exceptions.ApiError.__name__, error_msg, 2)) raise exceptions.ApiError(str(self.status_code), error_msg) else: raise exceptions.GenericServerError(str(self.status_code), error_msg)
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")) mode = self.MODE_TYPES[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) avoid_layer = self.parameterAsSource(parameters, self.IN_AVOID, 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) (sink, dest_id) = self.parameterAsSink( parameters, self.OUT, context, matrix_core.get_fields(source_field.type(), destination_field.type()), QgsWkbTypes.NoGeometry) # 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 feature amounts/counts sources_amount = source.featureCount() destinations_amount = destination.featureCount() if (sources_amount or destinations_amount) > 10000: raise QgsProcessingException( "ProcessingError: Too large input, please decimate.") sources_features = list(source.getFeatures()) destinations_features = list(destination.getFeatures()) # Get source and destination features xformer_source = transform.transformToWGS(source.sourceCrs()) sources_points = [ xformer_source.transform(feat.geometry().asPoint()) for feat in sources_features ] xformer_destination = transform.transformToWGS(destination.sourceCrs()) destination_points = [ xformer_destination.transform(feat.geometry().asPoint()) for feat in destinations_features ] # Build params params = dict(costing=self.PROFILE) # Sets all advanced parameters as attributes of self.costing_options self.costing_options.set_costing_options(self, parameters, context) costing_params = get_costing_options(self.costing_options, self.PROFILE, mode) if costing_params: params['costing_options'] = costing_params if avoid_layer: params['avoid_locations'] = get_avoid_locations(avoid_layer) sources_attributes = [ feat.attribute(source_field_name) for feat in sources_features ] destinations_attributes = [ feat.attribute(destination_field_name) for feat in destinations_features ] source_attr_iter = self._chunks(sources_attributes, 50) for sources in self._chunks(sources_points, 50): params["sources"] = get_locations(sources) source_attributes = next(source_attr_iter) destination_attr_iter = self._chunks(destinations_attributes, 50) for destinations in self._chunks(destination_points, 50): params["targets"] = get_locations(destinations) params["id"] = "matrix" destination_attributes = next(destination_attr_iter) # Make request and catch ApiError try: response = clnt.request('/sources_to_targets', post_json=params) except (exceptions.ApiError) as e: msg = "{}: {}".format(e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg) continue except (exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "{}:\n{}".format(e.__class__.__name__, str(e)) logger.log(msg) raise feats = matrix_core.get_output_features_matrix( response, self.PROFILE, costing_params, source_attributes, destination_attributes) for feat in feats: 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...")) mode = self.MODE_TYPES[self.parameterAsEnum(parameters, self.IN_MODE, context)] # Get parameter values source = self.parameterAsSource( parameters, self.IN_POINT, context ) source_field_name = self.parameterAsString( parameters, self.IN_FIELD, context ) avoid_layer = self.parameterAsLayer( parameters, self.IN_AVOID, 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)) input_points = list() from_values = list() xformer_source = 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_source.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_source.transform(QgsPointXY(point))) input_points.append(points) from_values.append(feat[source_field_name]) count = source.featureCount() params = dict() if avoid_layer: params['avoid_locations'] = get_avoid_locations(avoid_layer) # Sets all advanced parameters as attributes of self.costing_options self.costing_options.set_costing_options(self, parameters, context) 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 params.update(get_directions_params(points, self.PROFILE, self.costing_options, mode)) params['id'] = from_value try: response = clnt.request('/route', post_json=params) except (exceptions.ApiError) as e: msg = "Feature ID {} caused a {}:\n{}".format( from_value, e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg) continue except (exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "{}:\n{}".format( e.__class__.__name__, str(e)) logger.log(msg) raise options = {} if params.get('costing_options'): options = params['costing_options'] sink.addFeature(directions_core.get_output_feature_directions( response, self.PROFILE, options.get(self.PROFILE), from_value=from_value )) 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...")) mode = self.MODE_TYPES[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 ) matrix_mode = self.MODE_SELECTION[self.parameterAsEnum( parameters, self.IN_MATRIX_MODE, context )] avoid_layer = self.parameterAsLayer( parameters, self.IN_AVOID, 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) route_dict = self._get_route_dict( source, source_field, destination, destination_field ) if matrix_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 params = dict() if avoid_layer: params['avoid_locations'] = get_avoid_locations(avoid_layer) # Sets all advanced parameters as attributes of self.costing_options self.costing_options.set_costing_options(self, parameters, context) for points, values in directions_core.get_request_point_features(route_dict, matrix_mode): # Stop the algorithm if cancel button has been clicked if feedback.isCanceled(): break params.update(get_directions_params(points, self.PROFILE, self.costing_options, mode)) params['id'] = f"{values[0]} & {values[1]}" try: response = clnt.request('/route', post_json=params) except (exceptions.ApiError) 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 except (exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "{}:\n{}".format( e.__class__.__name__, str(e)) logger.log(msg) raise options = {} if params.get('costing_options'): options = params['costing_options'] sink.addFeature(directions_core.get_output_feature_directions( response, self.PROFILE, options.get(self.PROFILE), 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() geometry_param = self.GEOMETRY_TYPES[self.parameterAsEnum( parameters, self.IN_GEOMETRY, context)] params[ self.IN_GEOMETRY] = True if geometry_param == 'Polygon' else False mode = self.MODE_TYPES[self.parameterAsEnum(parameters, self.IN_MODE, context)] source = self.parameterAsSource(parameters, self.IN_POINTS, context) 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(self.PROFILE, geometry_param, id_field.type(), id_field_name) layer_time = QgsVectorLayer(f'{geometry_param}?crs=EPSG:4326', f'Isochrones {self.PROFILE.capitalize()}', 'memory') self.isos_time_id = layer_time.id() layer_time_pr = layer_time.dataProvider() layer_time_pr.addAttributes(self.isochrones.get_fields()) layer_time.updateFields() layer_dist = QgsVectorLayer( f'{geometry_param}?crs=EPSG:4326', f'Isodistances {self.PROFILE.capitalize()}', 'memory') self.isos_dist_id = layer_dist.id() layer_dist_pr = layer_dist.dataProvider() layer_dist_pr.addAttributes(self.isochrones.get_fields()) layer_dist.updateFields() layer_snapped_points = QgsVectorLayer( f'MultiPoint?crs=EPSG:4326', f'Snapped Points {self.PROFILE.capitalize()}', 'memory') self.points_snapped_id = layer_snapped_points.id() layer_snapped_points_pr = layer_snapped_points.dataProvider() layer_snapped_points_pr.addAttributes( self.isochrones.get_point_fields()) layer_snapped_points.updateFields() layer_input_points = QgsVectorLayer( f'Point?crs=EPSG:4326', f'Input Points {self.PROFILE.capitalize()}', 'memory') self.points_input_id = layer_input_points.id() layer_input_points_pr = layer_input_points.dataProvider() layer_input_points_pr.addAttributes(self.isochrones.get_point_fields()) layer_input_points.updateFields() denoise = self.parameterAsDouble(parameters, self.IN_DENOISE, context) if denoise: params[self.IN_DENOISE] = denoise generalize = self.parameterAsDouble(parameters, self.IN_GENERALIZE, context) if generalize: params[self.IN_GENERALIZE] = generalize avoid_layer = self.parameterAsLayer(parameters, self.IN_AVOID, context) if avoid_layer: params['avoid_locations'] = get_avoid_locations(avoid_layer) show_locations = self.parameterAsBool(parameters, self.IN_SHOW_LOCATIONS, context) # Sets all advanced parameters as attributes of self.costing_options self.costing_options.set_costing_options(self, parameters, context) intervals_time = self.parameterAsString(parameters, self.IN_INTERVALS_TIME, context) intervals_distance = self.parameterAsString(parameters, self.IN_INTERVALS_DISTANCE, context) feat_count = source.featureCount( ) if not intervals_time else source.featureCount() * 2 self.intervals = { "time": [{ "time": int(x) } for x in intervals_time.split(',')] if intervals_time else [], "distance": [{ "distance": int(x) } for x in intervals_distance.split(',')] if intervals_distance else [] } counter = 0 for metric, interv in self.intervals.items(): if feedback.isCanceled(): break if not interv: continue # Make the actual requests requests = [] for properties in self.get_sorted_feature_parameters(source): if feedback.isCanceled(): break r_params = deepcopy(params) r_params['contours'] = interv # Get transformed coordinates and feature locations, feat = properties r_params.update( get_directions_params(locations, self.PROFILE, self.costing_options, mode)) r_params['id'] = feat[id_field_name] requests.append(r_params) for params in requests: counter += 1 if feedback.isCanceled(): break # If feature causes error, report and continue with next try: # Populate features from response response = clnt.request('/isochrone', post_json=params) except (exceptions.ApiError) as e: msg = "Feature ID {} caused a {}:\n{}".format( params['id'], e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg, 2) continue except (exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "{}:\n{}".format(e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg) raise options = {} if params.get('costing_options'): options = params['costing_options'] self.isochrones.set_response(response) for isochrone in self.isochrones.get_features( params['id'], options.get(self.PROFILE)): if metric == 'time': layer_time_pr.addFeature(isochrone) elif metric == 'distance': layer_dist_pr.addFeature(isochrone) if show_locations: for point_feat in self.isochrones.get_multipoint_features( params['id']): layer_snapped_points_pr.addFeature(point_feat) for point_feat in self.isochrones.get_point_features( params['id']): layer_input_points_pr.addFeature(point_feat) feedback.setProgress(int((counter / feat_count) * 100)) temp = [] if layer_time.hasFeatures(): layer_time.updateExtents() context.temporaryLayerStore().addMapLayer(layer_time) temp.append( ("Isochrones " + self.PROFILE.capitalize(), self.OUT_TIME, layer_time.id())) if layer_dist.hasFeatures(): layer_dist.updateExtents() context.temporaryLayerStore().addMapLayer(layer_dist) temp.append( ("Isochrones " + self.PROFILE.capitalize(), self.OUT_DISTANCE, layer_dist.id())) if show_locations: layer_snapped_points.updateExtents() context.temporaryLayerStore().addMapLayer(layer_snapped_points) temp.append(("Snapped Points " + self.PROFILE.capitalize(), self.POINTS_SNAPPED, layer_snapped_points.id())) layer_input_points.updateExtents() context.temporaryLayerStore().addMapLayer(layer_input_points) temp.append(("Input Points " + self.PROFILE.capitalize(), self.POINTS_INPUT, layer_input_points.id())) results = dict() for l_name, e_id, l_id in temp: results[e_id] = l_id context.addLayerToLoadOnCompletion( l_id, QgsProcessingContext.LayerDetails(l_name, context.project(), l_name)) return results
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...")) # Get parameter values source = self.parameterAsSource(parameters, self.IN_LINES, context) source_field_name = self.parameterAsString(parameters, self.IN_FIELD, context) avoid_layer = self.parameterAsLayer(parameters, self.IN_AVOID, context) mode = self.MODE_TYPES[self.parameterAsEnum(parameters, self.IN_MODE, context)] params = dict() # Sets all advanced parameters as attributes of self.costing_options self.costing_options.set_costing_options(self, parameters, context) if avoid_layer: params['avoid_locations'] = get_avoid_locations(avoid_layer) (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.update( get_directions_params(line, self.PROFILE, self.costing_options, mode)) params['id'] = field_value try: response = clnt.request('/route', post_json=params) except (exceptions.ApiError) as e: msg = "Feature ID {} caused a {}:\n{}".format( field_value, e.__class__.__name__, str(e)) feedback.reportError(msg) logger.log(msg) continue except (exceptions.InvalidKey, exceptions.GenericServerError) as e: msg = "{}:\n{}".format(e.__class__.__name__, str(e)) logger.log(msg) raise options = {} if params.get('costing_options'): options = params['costing_options'] sink.addFeature( directions_core.get_output_feature_directions( response, self.PROFILE, options.get(self.PROFILE), from_value=field_value)) feedback.setProgress(int(100.0 / count * num)) return {self.OUT: dest_id}
def request(self, url, 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 first_request_time: The time of the first request (None if no retries have occurred). :type first_request_time: datetime.datetime :param post_json: Parameters for POST endpoints :type post_json: dict :raises valhalla.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)) # Define the request params = {'access_token': self.key} authed_url = self._generate_auth_url( url, params, ) url_object = QUrl(self.base_url + authed_url) self.url = url_object.url() body = QJsonDocument.fromJson(json.dumps(post_json).encode()) request = QNetworkRequest(url_object) request.setHeader(QNetworkRequest.ContentTypeHeader, 'application/json') logger.log( "url: {}\nParameters: {}".format( self.url, # final_requests_kwargs json.dumps(post_json, indent=2)), 0) start = time.time() response: QgsNetworkReplyContent = self.nam.blockingPost( request, body.toJson()) self.response_time = time.time() - start try: self.handle_response(response, post_json['id']) except exceptions.OverQueryLimit: # Let the instances know smth happened self.overQueryLimit.emit() return self.request(url, first_request_time, retry_counter + 1, post_json) response_content = json.loads(bytes(response.content())) # Mapbox treats 400 errors with a 200 status code if 'error' in response_content: raise exceptions.ApiError(str(response_content['status_code']), response_content['error']) return response_content
def run_gui_control(self): """Slot function for OK button of main dialog.""" self.dlg: ValhallaDialog # 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, "No waypoints", """ Did you forget to set routing waypoints?<br><br> Use the 'Add Waypoint' button to add up to 20 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 Mapbox?<br><br> If you don't have an API key, please visit https://account.mapbox.com/auth/signup/?route-to="/" to get one. <br><br> Then enter the API key in Web ► Valhalla ► Provider Settings or the settings symbol in the main Valhalla GUI, next to the provider dropdown. """) return clnt = client.Client(provider) clnt_msg = '' method = self.dlg.routing_method.currentText() profile = self.dlg.routing_travel_combo.currentText() params = {} # Add extra params extra_params_text = self.dlg.dlg_params.extra_params_text.toPlainText() extra_params = {} if extra_params_text: extra_params = json.loads(extra_params_text) try: if method == 'route': layer_out = QgsVectorLayer("LineString?crs=EPSG:4326", f"Route {profile.capitalize()}", "memory") layer_out.dataProvider().addAttributes( directions_core.get_fields()) layer_out.updateFields() directions = directions_gui.Directions(self.dlg) params = directions.get_parameters() params.update(extra_params) response = clnt.request('/route', {}, post_json=params) feat = directions_core.get_output_feature_directions( response, profile, directions.costing_options, "{}, {}".format(params['locations'][0]['lon'], params['locations'][0]['lat']), "{}, {}".format(params['locations'][-1]['lon'], params['locations'][-1]['lat'])) layer_out.dataProvider().addFeature(feat) layer_out.updateExtents() self.project.addMapLayer(layer_out) elif method == 'isochrone': geometry_type = self.dlg.polygons.currentText() isochrones = isochrones_core.Isochrones() isochrones.set_parameters(profile, geometry_type) locations = get_locations(self.dlg.routing_fromline_list) aggregate = self.dlg.iso_aggregate.isChecked() locations = [locations] if aggregate else locations no_points = self.dlg.iso_no_points.isChecked() metrics = [] if self.dlg.contours_distance.text(): metrics.append('distance') if self.dlg.contours.text(): metrics.append('time') for metric in metrics: isochrones_ui = isochrones_gui.Isochrones(self.dlg) params = isochrones_ui.get_parameters( metric) # change once isodistances are there too params.update(extra_params) name = 'Isodistance' if metric == 'distance' else 'Isochrone' layer_out = QgsVectorLayer( f"{geometry_type}?crs=EPSG:4326", f"{name} {params['costing']}", "memory") layer_out.dataProvider().addAttributes( isochrones.get_fields()) layer_out.updateFields() for i, location in enumerate(locations): params['locations'] = location if aggregate else [ location ] isochrones.set_response( clnt.request('/isochrone', {}, post_json=params)) for feat in isochrones.get_features( str(i), isochrones_ui.costing_options): layer_out.dataProvider().addFeature(feat) layer_out.updateExtents() isochrones.stylePoly(layer_out, metric) self.project.addMapLayer(layer_out) if not no_points: multipoint_layer = QgsVectorLayer( "MultiPoint?crs=EPSG:4326", f"Snapped Points {params['costing']}", "memory") point_layer = QgsVectorLayer( "Point?crs=EPSG:4326", f"Input Points {params['costing']}", "memory") multipoint_layer.dataProvider().addAttributes( isochrones.get_point_fields()) multipoint_layer.updateFields() point_layer.dataProvider().addAttributes( isochrones.get_point_fields()) point_layer.updateFields() for feat in isochrones.get_multipoint_features('0'): multipoint_layer.dataProvider().addFeature(feat) for feat in isochrones.get_point_features('0'): point_layer.dataProvider().addFeature(feat) multipoint_layer.updateExtents() point_layer.updateExtents() self.project.addMapLayer(multipoint_layer) self.project.addMapLayer(point_layer) elif method == 'sources_to_targets': layer_out = QgsVectorLayer("None", 'Matrix_Valhalla', "memory") layer_out.dataProvider().addAttributes( matrix_core.get_fields()) layer_out.updateFields() matrix = matrix_gui.Matrix(self.dlg) params = matrix.get_parameters() params.update(extra_params) response = clnt.request('/sources_to_targets', post_json=params) feats = matrix_core.get_output_features_matrix( response, profile, matrix.costing_options) for feat in feats: layer_out.dataProvider().addFeature(feat) self.project.addMapLayer(layer_out) elif method == 'locate': locate_dlg = ValhallaDialogLocateMain() locate_dlg.setWindowTitle('Locate Response') locate = locate_gui.Locate(self.dlg) params = locate.get_parameters() params.update(extra_params) response = clnt.request('/locate', post_json=params) locate_dlg.responseArrived.emit(json.dumps(response, indent=4)) locate_dlg.exec_() elif method == 'extract-osm': if not which('osmium'): QMessageBox.critical( self.dlg, "ModuleNotFoundError", """<a href="https://osmcode.org/osmium-tool/">osmium</a> wasn\'t found in your PATH. <br/><br/>Please install before trying again.""" ) return if not self.dlg.pbf_file.filePath(): QMessageBox.critical( self.dlg, "FileNotFoundError", """Seems like you forgot to set a PBF file path in the configuration for the Identity tool.""" ) return identify = identify_gui.Identify(self.dlg) params = identify.get_locate_parameters() response = clnt.request('/locate', post_json=params) way_dict = identify.get_tags(response) for way_id in way_dict: way = way_dict[way_id] layer_out = QgsVectorLayer("LineString?crs=EPSG:4326", "Way " + str(way_id), "memory") layer_out.dataProvider().addAttributes( identify.get_fields(way["tags"])) layer_out.updateFields() feat = identify.get_output_feature(way) layer_out.dataProvider().addFeature(feat) layer_out.updateExtents() self.project.addMapLayer(layer_out) elif method == 'centroid [experimental]': layer_routes = QgsVectorLayer("LineString?crs=EPSG:4326", f"Centroid Routes {profile}", "memory") layer_gravity = QgsVectorLayer("Point?crs=EPSG:4326", f"Centroid Point {profile}", "memory") layer_routes.dataProvider().addAttributes( gravity_core.get_fields()) layer_gravity.dataProvider().addAttributes( gravity_core.get_fields()) layer_routes.updateFields() layer_gravity.updateFields() directions = directions_gui.Directions(self.dlg) params = directions.get_parameters() params.update(extra_params) response = clnt.request('/centroid', {}, post_json=params) line_feats, point_feat = gravity_core.get_output_feature_gravity( response, profile, directions.costing_options) layer_routes.dataProvider().addFeatures(line_feats) layer_gravity.dataProvider().addFeature(point_feat) layer_routes.updateExtents() layer_gravity.updateExtents() self.project.addMapLayer(layer_routes) self.project.addMapLayer(layer_gravity) except exceptions.Timeout as e: msg = "The connection has timed out!" logger.log(msg, 2) self.dlg.debug_text.setText(msg) self._display_error_popup(e) 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) self._display_error_popup(e) return except Exception as e: msg = [e.__class__.__name__, str(e)] logger.log("{}: {}".format(*msg), 2) clnt_msg += "<b>{}</b>: {}<br>".format(*msg) self._display_error_popup(e) raise finally: # Set URL in debug window clnt_msg += '<a href="{0}">{0}</a><br>Parameters:<br>{1}<br><b>timing</b>: {2:.3f} secs'.format( clnt.url, json.dumps(params, indent=2), clnt.response_time) self.dlg.debug_text.setHtml(clnt_msg)