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}
예제 #3
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()

        # 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}
예제 #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)]

        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}
예제 #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..."))

        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}
예제 #6
0
    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}
예제 #7
0
    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'))
예제 #8
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)]

        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}
예제 #10
0
    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}
예제 #11
0
    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}
예제 #13
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
    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}
예제 #15
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()

        # 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}