def _get_sorted_lines(layer, field_name): """ Generator to yield geometry and ID value sorted by feature ID. Careful: feat.id() is not necessarily permanent :param layer: source input layer :type layer: QgsProcessingParameterFeatureSource :param field_name: name of ID field :type field_name: str """ # First get coordinate transformer xformer = transform.transformToWGS(layer.sourceCrs()) for feat in sorted(layer.getFeatures(), key=lambda f: f.id()): line = None field_value = feat[field_name] # for if layer.wkbType() == QgsWkbTypes.MultiLineString: # TODO: only takes the first polyline geometry from the multiline geometry currently # Loop over all polyline geometries line = [ xformer.transform(QgsPointXY(point)) for point in feat.geometry().asMultiPolyline()[0] ] elif layer.wkbType() == QgsWkbTypes.LineString: line = [ xformer.transform(QgsPointXY(point)) for point in feat.geometry().asPolyline() ] yield line, field_value
def get_avoid_locations(avoid_layer): """ Get the avoid locations parameter value. :param avoid_layer: The point layer to be avoided :type avoid_layer: QgsProcessingFeatureSource :returns: Valhalla formatted locations list :rtype: list of dict """ locations = [] xformer_avoid = transform.transformToWGS(avoid_layer.sourceCrs()) if avoid_layer.wkbType() != QgsWkbTypes.MultiPoint: points = [] for feat in avoid_layer.getFeatures(): points.append( xformer_avoid.transform(QgsPointXY(feat.geometry().asPoint()))) for point in points: locations.append({ "lon": round(point.x(), 6), "lat": round(point.y(), 6) }) return locations
def _on_linetool_map_click(self, point, idx): """Adds an item to QgsListWidget and annotates the point in the map canvas""" transformer = transform.transformToWGS( self._mapCanvas.mapSettings().destinationCrs()) point_wgs = transformer.transform(point) self.routing_fromline_list.addItem( "Point {0}: {1:.6f}, {2:.6f}".format(idx, point_wgs.x(), point_wgs.y())) annotation = self._linetool_annotate_point(point, idx) self.annotations.append(annotation) self.project.annotationManager().addAnnotation(annotation)
def _get_route_dict(self, source, source_field, destination, destination_field): """ Compute route_dict from input layer. :param source: Input from layer :type source: QgsProcessingParameterFeatureSource :param source_field: ID field from layer. :type source_field: QgsField :param destination: Input to layer. :type destination: QgsProcessingParameterFeatureSource :param destination_field: ID field to layer. :type destination_field: QgsField :returns: route_dict with coordinates and ID values :rtype: dict """ route_dict = dict() source_feats = list(source.getFeatures()) xformer_source = transform.transformToWGS(source.sourceCrs()) route_dict['start'] = dict( geometries=[xformer_source.transform(feat.geometry().asPoint()) for feat in source_feats], values= [feat.attribute(source_field.name()) for feat in source_feats], ) destination_feats = list(destination.getFeatures()) xformer_destination = transform.transformToWGS(destination.sourceCrs()) route_dict['end'] = dict( geometries=[xformer_destination.transform(feat.geometry().asPoint()) for feat in destination_feats], values= [feat.attribute(destination_field.name()) for feat in destination_feats], ) return route_dict
def get_sorted_feature_parameters(self, layer): """ Generator to yield geometry and id of features sorted by feature ID. Careful: feat.id() is not necessarily permanent :param layer: source input layer. :type layer: QgsProcessingParameterFeatureSource """ # First get coordinate transformer xformer = transform.transformToWGS(layer.sourceCrs()) for feat in sorted(layer.getFeatures(), key=lambda f: f.id()): x_point = xformer.transform(feat.geometry().asPoint()) yield ([x_point], feat)
def get_parameters(self): """ Builds parameters across directions functionalities. :returns: All parameter mappings. :rtype: dict """ # API parameters profile = self.dlg.routing_travel_combo.currentText() mode = self.dlg.routing_mode_combo.currentText() params = { 'costing': profile, 'narrative': False, 'id': 1, } params['locations'] = get_locations(self.dlg.routing_fromline_list) # Get Advanced parameters if self.dlg.routing_costing_options_group.isChecked( ) or mode == 'shortest': params['costing_options'] = dict() self.costing_options = params['costing_options'][ profile] = get_costing_options( self.dlg.routing_costing_options_group, profile) if mode == 'shortest': params['costing_options'][profile]['shortest'] = True if self.dlg.avoidlocation_group.isChecked(): point_layer = self.dlg.avoidlocation_dropdown.currentLayer() if point_layer: locations = list() transformer = transform.transformToWGS(point_layer.sourceCrs()) for feat in point_layer.getFeatures(): geom = feat.geometry() geom.transform(transformer) point = geom.asPoint() locations.append({ 'lon': round(point.x(), 6), 'lat': round(point.y(), 6) }) params['avoid_locations'] = locations return params
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}