Skip to content

feature

feature

GetFeatureRunner

Bases: MapRunner

Source code in src/qgis_server_light/worker/runner/feature.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
class GetFeatureRunner(MapRunner):
    job_info_class = QslJobInfoFeature

    def __init__(
        self,
        qgis: QgsApplication,
        context: JobContext,
        job_info: QslJobInfoFeature,
        layer_cache: Optional[Dict] = None,
        layer_style_cache: Optional[set] = None,
    ) -> None:
        super().__init__(qgis, context, job_info, layer_cache)

    def _clean_attribute(self, attribute_value: Any, idx: int, layer: QgsVectorLayer):
        if attribute_value == NULL:
            return None
        return attribute_value

    def _clean_attributes(self, attributes, layer):
        return [
            self._clean_attribute(attr, idx, layer)
            for idx, attr in enumerate(attributes)
        ]

    def _load_style(self, qgs_layer: QgsMapLayer, job_layer_definition: QslJobLayer):
        logging.info(" ✓ Omit style loading on WFS layer operation.")

    def run(self):
        query_collection = QueryCollection()
        numbers_matched = 0
        for query in self.job_info.job.queries:
            # we need to reset this because we want always only the layers related to the current query
            self.map_layers = []
            wfs_filter = query.filter
            for job_layer_definition in query.layers:
                self._provide_layer(job_layer_definition)

            for layer in self.map_layers:
                feature_collection = FeatureCollection(layer.name())
                query_collection.feature_collections.append(feature_collection)
                if isinstance(layer, QgsVectorLayer):
                    if wfs_filter is not None and wfs_filter.definition is not None:
                        # TODO: This is potentially bad: We always get all features from datasource. However, QGIS
                        #   does not seem to support sliding window feature filter out of the box...
                        logging.info(" QslJobLayer is filtered by:")
                        logging.info(f" {wfs_filter.definition}")
                        filter_doc = QDomDocument()
                        filter_doc.setContent(wfs_filter.definition)
                        # This is not correct in the WFS 2.0 way. We apply a filter to a job_layer_definition. But WFS 2.0
                        # allows filters on multiple layers.
                        expression = QgsOgcUtils.expressionFromOgcFilter(
                            filter_doc.documentElement(),
                            QgsOgcUtils.FilterVersion.FILTER_FES_2_0,
                        )
                        logging.info(
                            f" This was transformed to the QGIS expression (valid: {expression.isValid()})"
                        )
                        logging.info(f" '{expression.dump()}'")
                        feature_request = QgsFeatureRequest(expression)
                    else:
                        feature_request = QgsFeatureRequest()
                    layer_features = list(layer.getFeatures(feature_request))
                    numbers_matched += len(layer_features)
                    logging.info(f" Found {len(layer_features)} features")
                    if self.job_info.job.count:
                        layer_features = layer_features[
                            self.job_info.job.start_index : self.job_info.job.start_index
                            + self.job_info.job.count
                        ]
                    for layer_feature in layer_features:
                        property_list = zip(
                            layer_feature.fields().names(),
                            self._clean_attributes(layer_feature.attributes(), layer),
                        )
                        feature = Feature(
                            geometry=Geometry(
                                value=bytes(layer_feature.geometry().asWkb()),
                            )
                        )
                        feature_collection.features.append(feature)
                        for name, value in property_list:
                            feature.attributes.append(Attribute(name=name, value=value))
                else:
                    raise RuntimeError(
                        f"QslJobLayer type `{layer.type().name}` of layer `{layer.shortName()}` not supported by GetFeatureInfo"
                    )
        if numbers_matched > 0:
            query_collection.numbers_matched = numbers_matched
        with register_converters_at_runtime():
            data = JsonSerializer().render(query_collection).encode()
            return JobResult(
                id=self.job_info.id,
                data=data,
                content_type="application/qgis-server-light.interface.qgis.QueryCollection",
            )

job_info_class = QslJobInfoFeature class-attribute instance-attribute

__init__(qgis: QgsApplication, context: JobContext, job_info: QslJobInfoFeature, layer_cache: Optional[Dict] = None, layer_style_cache: Optional[set] = None) -> None

Source code in src/qgis_server_light/worker/runner/feature.py
32
33
34
35
36
37
38
39
40
def __init__(
    self,
    qgis: QgsApplication,
    context: JobContext,
    job_info: QslJobInfoFeature,
    layer_cache: Optional[Dict] = None,
    layer_style_cache: Optional[set] = None,
) -> None:
    super().__init__(qgis, context, job_info, layer_cache)

run()

Source code in src/qgis_server_light/worker/runner/feature.py
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def run(self):
    query_collection = QueryCollection()
    numbers_matched = 0
    for query in self.job_info.job.queries:
        # we need to reset this because we want always only the layers related to the current query
        self.map_layers = []
        wfs_filter = query.filter
        for job_layer_definition in query.layers:
            self._provide_layer(job_layer_definition)

        for layer in self.map_layers:
            feature_collection = FeatureCollection(layer.name())
            query_collection.feature_collections.append(feature_collection)
            if isinstance(layer, QgsVectorLayer):
                if wfs_filter is not None and wfs_filter.definition is not None:
                    # TODO: This is potentially bad: We always get all features from datasource. However, QGIS
                    #   does not seem to support sliding window feature filter out of the box...
                    logging.info(" QslJobLayer is filtered by:")
                    logging.info(f" {wfs_filter.definition}")
                    filter_doc = QDomDocument()
                    filter_doc.setContent(wfs_filter.definition)
                    # This is not correct in the WFS 2.0 way. We apply a filter to a job_layer_definition. But WFS 2.0
                    # allows filters on multiple layers.
                    expression = QgsOgcUtils.expressionFromOgcFilter(
                        filter_doc.documentElement(),
                        QgsOgcUtils.FilterVersion.FILTER_FES_2_0,
                    )
                    logging.info(
                        f" This was transformed to the QGIS expression (valid: {expression.isValid()})"
                    )
                    logging.info(f" '{expression.dump()}'")
                    feature_request = QgsFeatureRequest(expression)
                else:
                    feature_request = QgsFeatureRequest()
                layer_features = list(layer.getFeatures(feature_request))
                numbers_matched += len(layer_features)
                logging.info(f" Found {len(layer_features)} features")
                if self.job_info.job.count:
                    layer_features = layer_features[
                        self.job_info.job.start_index : self.job_info.job.start_index
                        + self.job_info.job.count
                    ]
                for layer_feature in layer_features:
                    property_list = zip(
                        layer_feature.fields().names(),
                        self._clean_attributes(layer_feature.attributes(), layer),
                    )
                    feature = Feature(
                        geometry=Geometry(
                            value=bytes(layer_feature.geometry().asWkb()),
                        )
                    )
                    feature_collection.features.append(feature)
                    for name, value in property_list:
                        feature.attributes.append(Attribute(name=name, value=value))
            else:
                raise RuntimeError(
                    f"QslJobLayer type `{layer.type().name}` of layer `{layer.shortName()}` not supported by GetFeatureInfo"
                )
    if numbers_matched > 0:
        query_collection.numbers_matched = numbers_matched
    with register_converters_at_runtime():
        data = JsonSerializer().render(query_collection).encode()
        return JobResult(
            id=self.job_info.id,
            data=data,
            content_type="application/qgis-server-light.interface.qgis.QueryCollection",
        )