Completed
Push — master ( 0fd553...80744a )
by
unknown
01:16 queued 32s
created

_validate_flanders_specifications()   A

Complexity

Conditions 4

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
c 1
b 0
f 0
dl 0
loc 13
rs 9.2
1
# -*- coding: utf-8 -*-
2
'''
3
This module validates a polygon in Flanders.
4
'''
5
6
import colander
7
import json
8
from shapely.geometry import shape, box, mapping
9
from oe_geoutils.utils import get_srid_from_geojson, remove_dupl_coords
10
import logging
11
12
log = logging.getLogger(__name__)
13
14
15
class GeometryValidator:
16
17
    def __init__(self, node, gj):
18
        """
19
        :param node: A colander SchemaNode.
20
        :param gj: geojson to be validated
21
        """
22
        self.node = node
23
        self.gj = gj
24
        if self.gj is None or not self.gj:
25
            raise colander.Invalid(self.node, "geometrie is verplicht en mag niet leeg zijn")
26
        try:
27
            self.geom = shape(self.gj)
28
        except ValueError:
29
            raise colander.Invalid(self.node, 'geometrie is geen geldig GeoJson')
30
        self.srid = get_srid_from_geojson(self.gj)
31
32
    def _validate_geometry_type(self, geomtype):
33
        """
34
        :param geomtype: geojson geometrie type: "MultiPolygon", "Point", ...
35
        :return: 
36
        """
37
        if self.geom.type != geomtype:
38
            raise colander.Invalid(self.node, 'geometrie is niet van het type {}'.format(geomtype))
39
40
    def _validate_flanders_specifications(self):
41
        """
42
        *coordinate system of Lambert72 is required
43
        *geometry must be inside bounding box of Flanders
44
        """
45
        if self.srid is None or self.srid != 31370:
46
            raise colander.Invalid(self.node,
47
                                   'Geen geldige srid gevonden. '
48
                                   'Enkel geometrien met srid 31370 (Lambert 72) worden ondersteund')
49
        b = box(19680.692555495727, 146642.51885241456, 274994.53266103653,
50
                245606.4642544454)  # bounding box vlaanderen
51
        if not b.contains(self.geom):
52
            raise colander.Invalid(self.node, 'Geometrie ligt niet binnen de bounding box van Vlaanderen')
53
54
    def validate_multipolygon(self, max_area):
55
        """
56
        Validate a geojson if it is a valid geometry.
57
        :param max_area: the maximum area of the geojson.
58
        Current checks are:
59
        *geojson geometrie type: "MultiPolygon"
60
        *coordinate system of Lambert72 is required
61
        *geometry must be inside bounding box of Flanders
62
        *point must be valid according to OGC-specifications        
63
        *geometry must be smaller than the given max_area
64
65
        :raise colander.Invalid: when geojson does not satisfy the upper conditions
66
        """
67
        self._validate_geometry_type("MultiPolygon")
68
        self._validate_flanders_specifications()
69
        if self.geom.is_empty or not self.geom.is_valid or not self.geom.is_simple:
70
            self.geom = self.geom.buffer(0)
71
            if self.geom.is_empty or not self.geom.is_valid or not self.geom.is_simple:
72
                raise colander.Invalid(self.node,
73
                                       'Geometrie is niet geldig '
74
                                       '(bv: leeg, voldoet niet aan OGC, zelf-intersecterend,...)')
75
            self.gj.update(json.loads(json.dumps(mapping(self.geom))))
76
            if self.gj['type'] == 'Polygon':
77
                self.gj.update({'type': 'MultiPolygon', 'coordinates': [self.gj['coordinates']]})
78
        if self.geom.area > max_area:
79
            raise colander.Invalid(self.node, 'Geometrie is {} ha, groter dan {} ha, '
80
                                              'gelieve de polygoon te verkleinen.'.format(self.geom.area / 10000,
81
                                                                                          max_area / 10000))
82
        # remove duplicate consecutive coordinates (OGC SFA en ISO 19107:2003 standard)
83
        remove_dupl_coords(self.gj["coordinates"])
84
        return self.gj
85
86
    def validate_point(self):
87
        """
88
        Validate a geojson if it is a valid point.
89
        Current checks are:
90
        *Type of geometry must be "Point"
91
        *coordinate system of Lambert72 is required
92
        *geometry must be inside bounding box of Flanders
93
        :param node: A colander SchemaNode.
94
        :param gj: geojson to be validated
95
        :raise colander.Invalid: when geojson does not satisfy the upper conditions
96
        """
97
        self._validate_geometry_type("Point")
98
        self._validate_flanders_specifications()
99
        return self.gj
100
101
102
class GeoJson(colander.SchemaType):
103
    """ GeoJson object Type """
104
105
    def serialize(self, node, appstruct):
106
        raise NotImplementedError()
107
108
    def deserialize(self, node, cstruct):
109
        if cstruct != 0 and not cstruct:
110
            return colander.null
111
        try:
112
            return self.build_shape(cstruct)
113
        except Exception:
114
            raise colander.Invalid(node, "Geen geldige GeoJson: %s" % cstruct)
115
116
    @staticmethod
117
    def build_shape(cstruct):
118
        """
119
        converts a value into GeoJson (if valid)
120
        raises a colander.Invalid if value cannot be transformed into GeoJson
121
        :param cstruct: The structure to be validated
122
        :return: geojson
123
        :raise colander.Invalid: when the value is no valid geojson
124
        """
125
        if cstruct is None or not cstruct:
126
            return None
127
        s = json.dumps(cstruct)
128
        g1 = json.loads(s)
129
        shape(g1)
130
        return g1
131
132
133
class GeometrieSchemaNode(colander.SchemaNode):
134
    title = 'geometrie'
135
    schema_type = GeoJson
136
    max_area = 8000
137
138
    def __init__(self, *args, **kwargs):
139
        if len(args) > 0 and isinstance(args[0], int):
140
            self.max_area = args[0]
141
            args = args[1:]
142
        super(GeometrieSchemaNode, self).__init__(*args, **kwargs)
143
        try:
144
            self.max_area = float(self.max_area) * 10000
145
        except ValueError:
146
            log.error("oe_geoutils: invalid configuration for max area geometry: {}".format(self.max_area))
147
            self.max_area = 80000000
148
149
    def validator(self, node, cstruct):
150
        return GeometryValidator(node, cstruct).validate_multipolygon(self.max_area)
151
152
153
class PointGeometrieSchemaNode(colander.SchemaNode):
154
    title = 'point geometrie'
155
    schema_type = GeoJson
156
157
    def validator(self, node, cstruct):
158
        return GeometryValidator(node, cstruct).validate_point()
159