Skip to content

feature_info

feature_info

GetFeatureInfoRunner

Bases: MapRunner

Source code in src/qgis_server_light/worker/runner/feature_info.py
19
20
21
22
23
24
25
26
27
28
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
class GetFeatureInfoRunner(MapRunner):
    job_info_class = QslJobInfoFeatureInfo

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

    def _clean_attribute(self, attribute, idx, layer):
        if attribute == NULL:
            return None
        setup = layer.editorWidgetSetup(idx)
        fieldFormatter = QgsApplication.fieldFormatterRegistry().fieldFormatter(
            setup.type()
        )
        return fieldFormatter.representValue(
            layer, idx, setup.config(), None, attribute
        )

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

    def run(self):
        for job_layer_definition in self.job_info.job.layers:
            self._provide_layer(job_layer_definition)
        map_settings = self._get_map_settings(self.map_layers)
        # Estimate queryable bbox (2mm)
        map_to_pixel = map_settings.mapToPixel()
        map_point = map_to_pixel.toMapCoordinates(
            self.job_info.job.x, self.job_info.job.y
        )
        # Create identifiable bbox in map coordinates, ±2mm
        tolerance = 0.002 * 39.37 * map_settings.outputDpi()
        tl = QgsPointXY(map_point.x() - tolerance, map_point.y() - tolerance)
        br = QgsPointXY(map_point.x() + tolerance, map_point.y() + tolerance)
        rect = QgsRectangle(tl, br)
        render_context = QgsRenderContext.fromMapSettings(map_settings)

        features = list()
        for layer in self.map_layers:
            renderer = layer.renderer().clone() if layer.renderer() else None
            if renderer:
                renderer.startRender(render_context, layer.fields())

            if layer.type() == QgsMapLayerType.VectorLayer:
                layer_rect = map_settings.mapToLayerCoordinates(layer, rect)
                request = (
                    QgsFeatureRequest()
                    .setFilterRect(layer_rect)
                    .setFlags(QgsFeatureRequest.ExactIntersect)
                )
                for feature in layer.getFeatures(request):
                    if renderer.willRenderFeature(feature, render_context):
                        properties = OrderedDict(
                            zip(
                                feature.fields().names(),
                                self._clean_attributes(feature.attributes(), layer),
                            )
                        )
                        features.append({"type": "Feature", "properties": properties})
            else:
                raise RuntimeError(
                    f"Layer type `{layer.type().name}` of layer `{layer.shortName()}` not supported by GetFeatureInfo"
                )
            if renderer:
                renderer.stopRender(render_context)

        featurecollection = {"features": features, "type": "FeatureCollection"}
        return JobResult(
            id=self.job_info.id,
            data=json.dumps(featurecollection).encode("utf-8"),
            content_type="application/json",
        )

job_info_class = QslJobInfoFeatureInfo class-attribute instance-attribute

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

Source code in src/qgis_server_light/worker/runner/feature_info.py
22
23
24
25
26
27
28
29
def __init__(
    self,
    qgis: QgsApplication,
    context: JobContext,
    job_info: QslJobInfoFeatureInfo,
    layer_cache: Optional[Dict] = None,
) -> None:
    super().__init__(qgis, context, job_info, layer_cache)

run()

Source code in src/qgis_server_light/worker/runner/feature_info.py
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
def run(self):
    for job_layer_definition in self.job_info.job.layers:
        self._provide_layer(job_layer_definition)
    map_settings = self._get_map_settings(self.map_layers)
    # Estimate queryable bbox (2mm)
    map_to_pixel = map_settings.mapToPixel()
    map_point = map_to_pixel.toMapCoordinates(
        self.job_info.job.x, self.job_info.job.y
    )
    # Create identifiable bbox in map coordinates, ±2mm
    tolerance = 0.002 * 39.37 * map_settings.outputDpi()
    tl = QgsPointXY(map_point.x() - tolerance, map_point.y() - tolerance)
    br = QgsPointXY(map_point.x() + tolerance, map_point.y() + tolerance)
    rect = QgsRectangle(tl, br)
    render_context = QgsRenderContext.fromMapSettings(map_settings)

    features = list()
    for layer in self.map_layers:
        renderer = layer.renderer().clone() if layer.renderer() else None
        if renderer:
            renderer.startRender(render_context, layer.fields())

        if layer.type() == QgsMapLayerType.VectorLayer:
            layer_rect = map_settings.mapToLayerCoordinates(layer, rect)
            request = (
                QgsFeatureRequest()
                .setFilterRect(layer_rect)
                .setFlags(QgsFeatureRequest.ExactIntersect)
            )
            for feature in layer.getFeatures(request):
                if renderer.willRenderFeature(feature, render_context):
                    properties = OrderedDict(
                        zip(
                            feature.fields().names(),
                            self._clean_attributes(feature.attributes(), layer),
                        )
                    )
                    features.append({"type": "Feature", "properties": properties})
        else:
            raise RuntimeError(
                f"Layer type `{layer.type().name}` of layer `{layer.shortName()}` not supported by GetFeatureInfo"
            )
        if renderer:
            renderer.stopRender(render_context)

    featurecollection = {"features": features, "type": "FeatureCollection"}
    return JobResult(
        id=self.job_info.id,
        data=json.dumps(featurecollection).encode("utf-8"),
        content_type="application/json",
    )