Ejemplo n.º 1
0
    def get_sorted_feature_parameters(self, layer):
        """
        Generator to yield geometry and id of features sorted by feature ID. Careful: feat.id() is not necessarily
        permanent

        :param layer: source input layer.
        :type layer: QgsProcessingParameterFeatureSource
        """
        # First get coordinate transformer
        xformer = transform.transformToWGS(layer.sourceCrs())

        for feat in sorted(layer.getFeatures(), key=lambda f: f.id()):
            x_point = xformer.transform(feat.geometry().asPoint())

            yield (convert.build_coords([x_point.x(), x_point.y()]), feat)
Ejemplo n.º 2
0
    def _get_params_directions(line, profile, preference):
        """
        Build parameters for optimization endpoint

        :param line: individual polyline points
        :type line: list of QgsPointXY

        :param profile: transport profile to be used
        :type profile: str

        :param preference: routing preference, shortest/fastest
        :type preference: str

        :returns: parameters for optimization endpoint
        :rtype: dict
        """

        params = {
            'coordinates':
            convert.build_coords([[point.x(), point.y()] for point in line]),
            'profile':
            profile,
            'preference':
            preference,
            'geometry':
            'true',
            'format':
            'geojson',
            'geometry_format':
            'geojson',
            'instructions':
            'false',
            'elevation':
            True,
            'id':
            None
        }

        return params
Ejemplo n.º 3
0
def get_request_point_features(route_dict, row_by_row):
    """
    Processes input point features depending on the layer to layer relation in directions settings

    :param route_dict: all coordinates and ID field values of start and end point layers
    :type route_dict: dict

    :param row_by_row: Specifies whether row-by-row relation or all-by-all has been used.
    :type row_by_row: str

    :returns: tuple of coordinates and ID field value for each routing feature in route_dict
    :rtype: tuple
    """

    locations_list = list(
        product(route_dict['start']['geometries'],
                route_dict['end']['geometries']))
    values_list = list(
        product(route_dict['start']['values'], route_dict['end']['values']))

    # If row-by-row in two-layer mode, then only zip the locations
    if row_by_row == 'Row-by-Row':
        locations_list = list(
            zip(route_dict['start']['geometries'],
                route_dict['end']['geometries']))

        values_list = list(
            zip(route_dict['start']['values'], route_dict['end']['values']))

    for properties in zip(locations_list, values_list):
        # Skip if first and last location are the same
        if properties[0][0] == properties[0][-1]:
            continue

        coordinates = convert.build_coords(properties[0])
        values = properties[1]

        yield (coordinates, values)
Ejemplo n.º 4
0
    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}
Ejemplo n.º 5
0
    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}
Ejemplo n.º 6
0
    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