Completed
Push — master ( eb068a...f252b6 )
by Bart
12s
created

process_location_elements()   B

Complexity

Conditions 4

Size

Total Lines 31

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 31
rs 8.5806
1
# -*- coding: utf8 -*-
2
from geoalchemy2 import WKTElement, shape
3
import json
4
import geojson
5
import re
6
from shapely import geometry
7
import requests
8
import os
9
from operator import attrgetter
10
11
from oe_geoutils.data.models import LocatieElement
12
13
14
def process_location_elements(crab_gateway, admin_grenzen_client, contour, municipalities_niscodes=None):
15
    """
16
17
    :param crab_gateway: gateway for accessing crab data
18
    :param admin_grenzen_client: client for accessing administrative borders
19
    :param contour: contour
20
    :param municipalities_niscodes: list of municipality niscodes not be considered
21
    :return: list of location elements
22
    """
23
    municipalities_niscodes = municipalities_niscodes if municipalities_niscodes else []
24
    admin_municipalities = admin_grenzen_client.get_admin_objecten(contour, 'gemeente')
25
    admin_municipalities_niscodes = set([int(admin_municipality['id']) for admin_municipality in admin_municipalities])
26
27
    location_elementens = []
28
    for municipality_niscode in admin_municipalities_niscodes.difference(municipalities_niscodes):
29
        crab_client = crab_gateway()
30
        crab_gem = crab_client.get_gemeente_by_niscode(municipality_niscode)
31
        gemeente_naam = crab_gem.naam
32
        gemeente_crab_id = crab_gem.id
33
        provincie_niscode = crab_gem.provincie.niscode
34
        provincie_naam = crab_gem.provincie.naam
35
        location_element = LocatieElement(
36
            type='https://id.erfgoed.net/vocab/ontology#LocatieElement',
37
            gemeente_niscode=municipality_niscode,
38
            gemeente_naam=gemeente_naam,
39
            gemeente_crab_id=gemeente_crab_id,
40
            provincie_niscode=provincie_niscode,
41
            provincie_naam=provincie_naam)
42
        location_elementens.append(location_element)
43
44
    return location_elementens
45
46
47
def convert_wktelement_to_geojson(wktelement):
48
    """
49
    This function converts a wktelement (from GeoAlchemy) to a geojson
50
    :param wktelement: WKTElement
51
    :return: geojson
52
    """
53
    if wktelement is None:
54
        return None
55
    try:
56
        geom = shape.to_shape(wktelement)
57
        srid = wktelement.srid
58
        json_element = json.dumps(geometry.mapping(geom))
59
        geojson_element = geojson.loads(json_element)
60
        geojson_element['crs'] = {
61
            "type": "name",
62
            "properties": {
63
                "name": "urn:ogc:def:crs:EPSG::{0}".format(srid)
64
            }
65
        }
66
67
        return geojson_element
68
69
    except (AssertionError, AttributeError):
70
        raise AssertionError("WKTelement is niet geldig: %s" % wktelement)
71
72
73
def convert_geojson_to_wktelement(value):
74
    """
75
    This function converts a geojson to a wktelement that can be used by GeoAlchemy.
76
    :param value: geojson
77
    :return: WKTElement
78
    """
79
    if value is None or not value:
80
        return None
81
    try:
82
        s = json.dumps(value)
83
        g1 = geojson.loads(s)
84
        g2 = geometry.shape(g1)
85
        srid = get_srid_from_geojson(g1)
86
87
        return WKTElement(data=g2.wkt, srid=srid)
88
89
    except ValueError:
90
        raise ValueError("GeoJson is niet geldig: %s" % value)
91
92
93
def convert_geojson_to_geometry(value):
94
    """
95
    This function converts a geojson to a Shapely.geometry.
96
    :param value: geojson
97
    :return: Shapely.geometry
98
    """
99
    geojson_element = geojson.loads(json.dumps(value))
100
    return geometry.shape(geojson_element)
101
102
103
def get_srid_from_geojson(value):
104
    """
105
    This function returns the srid of a geojson
106
    :param value: geojson
107
    :return: spatial reference identifier (srid)
108
    """
109
    if value is None or not value:
110
        return None
111
    try:
112
        if not value.get('crs'):
113
            return None
114
        srid = find_srid(value['crs']['properties']['name'])
115
        # todo make a better implementation for this?! Is there a fixed format in geojson to define CRS
116
117
        return srid
118
119
    except ValueError:  # pragma NO COVER
120
        # In deze situatie enkel key en attributeErrors mogelijk
121
        raise ValueError("Geen geldige Spatial Reference Identifier (SRID) gevonden" % value)
122
123
124
def find_srid(text):
125
    li = [int(x) for x in re.findall(r'\d+', text) if len(x) in [4, 5]]
126
    return max(li) if len(li) > 0 else None
127
128
129
def remove_dupl_values(list_coord):
130
    dupl_coord_indexes = [i for i in range(0, len(list_coord)) if i != 0 and
131
                          list_coord[i] == list_coord[i - 1]]
132
    [list_coord.pop(i) for i in sorted(dupl_coord_indexes, reverse=True)]
133
134
135
def remove_dupl_coords(coords, tree_types=(list, tuple)):
136
    """
137
    This function will remove duplicate consecutive coordinates
138
    :param coords: input coordinates
139
    """
140
    for o in coords:
141
        if isinstance(o[0][0], tree_types):
142
            remove_dupl_coords(o, tree_types)
143
        else:
144
            remove_dupl_values(o)
145
146
147
def check_within_flanders(geojson_input, tolerance=10):
148
    """
149
    :param geojson_input: geojson geometrie
150
    :param tolerance: buffer tolerance distance of Flanders
151
    :return: True if the input geojson is within the Flanders region, otherwise False
152
    """
153
    if geometry.shape(geojson_input).within(get_flanders_geometry().buffer(tolerance)):
154
        return True
155
    return False
156
157
158
def check_in_flanders(geojson_input):
159
    """
160
    :param geojson_input: geojson geometrie
161
    :return: True if the input geojson intersects with Flanders region, otherwise False
162
    """
163
    if geometry.shape(geojson_input).intersects(get_flanders_geometry()):
164
        return True
165
    return False
166
167
168
def get_flanders_geometry():
169
    here = os.path.abspath(os.path.dirname(__file__))
170
    with open(here + '/fixtures/vlaams_gewest.geojson') as geof:
171
        flanders_features = json.load(geof)
172
    flanders_geojson = flanders_features['features'][0]['geometry']
173
    return geometry.shape(flanders_geojson)
174
175
176
def provincie_niscode(gemeente_niscode):
177
    """
178
    geef de niscode van de provincie op basis van de niscode van de gemeente
179
180
    :param gemeente_niscode: niscode van de gemeente
181
    :return: niscode van de provincie
182
    """
183
    pcode = str(gemeente_niscode)[0]
184
    if pcode == '2':
185
        return int('20001')
186
    else:
187
        return int('{0}0000'.format(pcode))
188
189
190
def get_centroid_xy(value):
191
    """
192
    Returns the centroid x,y of a geojson polygon.
193
    If the geojson is a multipolygon. It return the centroid of the biggest polygon
194
    :param value: geojson
195
    :return: x,y
196
    """
197
    geometry_element = convert_geojson_to_geometry(value)
198
    if geometry_element.type == 'MultiPolygon':
199
        geometry_element_polygons = list(geometry_element.geoms)
200
        largest_polygon = max(geometry_element_polygons, key=attrgetter('area'))
201
        centroid = largest_polygon.centroid.wkt
202
    else:
203
        centroid = geometry_element.centroid.wkt
204
    return ','.join([x for x in re.findall(r'\d+.\d+', centroid)])
205
206
207
def add_crab_ids(crabpy_gateway, address):
208
    gemeente = address.get("gemeente")
209
    if gemeente:
210
        gewest_ids = [2, 1, 3]
211
        gemeente_val = None
212
        for gewest_id in gewest_ids:
213
            gemeenten = crabpy_gateway.list_gemeenten(gewest_id)
214
            gemeente_val = next((g for g in gemeenten if g.naam.lower() == gemeente.lower()), None)
215
            if gemeente_val:
216
                address['gemeente'] = "" + gemeente_val.naam
217
                address['gemeente_id'] = gemeente_val.id
218
                break
219
        straat_naam = address.get("straat")
220
        if gemeente_val and straat_naam:
221
            straat_val = next((s for s in gemeente_val.straten if s.label.lower() == straat_naam.lower()), None)
222
            if straat_val:
223
                address["straat"] = "" + straat_val.label
224
                address["straat_id"] = straat_val.id
225
                huisnummer_naam = address.get("huisnummer")
226
                if huisnummer_naam:
227
                    huisnummer_val = next((n for n in straat_val.huisnummers
228
                                           if n.huisnummer.lower() == huisnummer_naam.lower()), None)
229
                    if huisnummer_val:
230
                        address["huisnummer"] = "" + huisnummer_val.huisnummer
231
                        address["huisnummer_id"] = huisnummer_val.id
232
        return address
233
234
235
def nearest_location(geojson_element, crabpy_gateway=None):
236
    """
237
    Returns the nearest location. If a crabpy_gateway is given, the crab id's will be added to the address.
238
    Uses the agiv service https://loc.geopunt.be/geolocation/Location?xy=<x,y>
239
    where <x,y> are the coordinates of the centroid of the geometry (polygon).
240
    The agiv service returns crab, urbis en poi location types only in Flanders and Brussels.
241
    :param value: geojson
242
    :return: - crab address
243
             - None if the nearest address is nog crab type
244
    """
245
    xy = get_centroid_xy(geojson_element)
246
247
    r = requests.get('https://loc.geopunt.be/geolocation/Location?xy={0}'.format(xy))
248
    result = r.json()
249
    if 'LocationResult' not in result \
250
            or len(result['LocationResult']) == 0 \
251
            or 'crab' not in result['LocationResult'][0]['LocationType']:
252
        return None
253
    locres = result['LocationResult'][0]['FormattedAddress']
254
    straat_huisnummer, postcode_gemeente = locres.rsplit(', ', 1)
255
    straat, huisnummer = straat_huisnummer.rsplit(' ', 1)
256
    postcode, gemeente = postcode_gemeente.split(' ', 1)
257
    gemeente = gemeente.replace(' ({})'.format(postcode), '')
258
    address = {
259
        'straat': straat,
260
        'huisnummer': huisnummer,
261
        'omschrijving_straat': straat + ', ' + huisnummer,
262
        'postcode': postcode,
263
        'gemeente': gemeente,
264
        'land': 'BE'
265
    }
266
    if crabpy_gateway:
267
        return add_crab_ids(crabpy_gateway, address)
268
    else:
269
        return address
270
271
272
class AdminGrenzenClient:
273
    def __init__(self, base_url):
274
        self.base_url = base_url
275
276
    def get_admin_objecten(self, geojson_input, admin_grens_type, return_geometry=0):
277
        """
278
        This function returns the administrative areas objects which intersects with the input geojson
279
        :param geojson_input: geojson
280
        :param admin_grens_type: type of returned administrative objects
281
        :param return_geometry: boolean indicating whether the geometry of the returned objects should also be included
282
        :
283
        :return: list of administrative areas objects
284
        """
285
        if check_in_flanders(geojson_input):
286
            data = {
287
                'geef_geometrie': return_geometry,
288
                'type': admin_grens_type,
289
                'geometrie': geojson_input
290
            }
291
            res = requests.post(
292
                url=self.base_url,
293
                json=data,
294
                headers={
295
                    'Accept': 'application/json',
296
                    'Content-Type': 'application/json'
297
                }
298
            )
299
            res.raise_for_status()
300
            return json.loads(res.text)
301
        else:
302
            return []
303
304
    def get_gemeenten(self, geojson_input):
305
        """
306
        This function returns the names of the municipalities which intersects with the input geojson
307
        :param geojson_input: geojson
308
        :return: list of municipalities
309
        """
310
        results = self.get_admin_objecten(geojson_input, 'gemeente')
311
        return [gemeente['naam'] for gemeente in results]
312
313
    def get_provincies(self, geojson_input):
314
        """
315
        This function returns the names of the provinces which intersects with the input geojson
316
        :param geojson_input: geojson
317
        :return: list of municipalities
318
        """
319
        results = self.get_admin_objecten(geojson_input, 'provincie')
320
        return [provincie['naam'] for provincie in results]
321
322
    @staticmethod
323
    def _get_lagest_admin_obj(results, geojson_input):
324
        if len(results) == 0:
325
            return None
326
        elif len(results) == 1:
327
            return {'niscode': results[0]['id'], 'naam': results[0]['naam']}
328
        else:
329
            input_polygon = convert_geojson_to_geometry(geojson_input)
330
            for result in results:
331
                admin_geom = convert_geojson_to_geometry(result['geometrie'])
332
                result['intersection_area'] = admin_geom.intersection(input_polygon).area
333
            largest_m = max(results, key=lambda x: x['intersection_area'])
334
            return {'niscode': largest_m['id'], 'naam': largest_m['naam']}
335
336
    def get_gemeente(self, geojson_input):
337
        """
338
        This function returns the name of the municipality which intersects with the input geojson.
339
        If more municipalities intersect. The municipality is returned with the largest intersection
340
        :param value: administratievegrenzen_url
341
        :param value: geojson
342
        :return: municipality
343
        """
344
        results = self.get_admin_objecten(geojson_input, 'gemeente', 1)
345
        return self._get_lagest_admin_obj(results, geojson_input)
346
347
    def get_provincie(self, geojson_input):
348
        """
349
        This function returns the name of the province which intersects with the input geojson.
350
        If more provinces intersect. The province is returned with the largest intersection.
351
        :param value: administratievegrenzen_url
352
        :param value: geojson
353
        :return: province
354
        """
355
        results = self.get_admin_objecten(geojson_input, 'provincie', 1)
356
        return self._get_lagest_admin_obj(results, geojson_input)
357
358
    def check_erfgoedgemeenten(self, geojson_input, erfgoedgemeenten_list):
359
        """
360
        :param geojson_input: geojson geometry
361
        :param erfgoedgemeenten_list: list of niscodes of municipalities
362
        :return: dict including key status and optional key message
363
                 status "ok" when the geojson does not intersect with one of given municipalities
364
                 status "warn" when the geojson intersects partially with one of given municipalities
365
                 status "error" when the geojson is within one of given municipalities
366
        """
367
        gemeenten = self.get_admin_objecten(geojson_input, 'gemeente')
368
        erfgoedgemeenten = [gemeente for gemeente in gemeenten if
369
                            int(gemeente['id']) in json.loads(erfgoedgemeenten_list)]
370
        if len(erfgoedgemeenten) > 0:
371
            if len(gemeenten) == len(erfgoedgemeenten):
372
                return {
373
                    "status": "error",
374
                    "message": "Let op, de zone van deze melding is gelegen in een onroerenderfgoedgemeente en "
375
                               "kan niet bewaard worden. Gelieve de melding in te dienen bij deze gemeente."}
376
            else:
377
                return {
378
                    "status": "warn",
379
                    "message": "Let op, deze melding ligt deels in een onroerenderfgoedgemeente . "
380
                               "Gelieve de melding vooronderzoek eveneens in te dienen bij deze gemeente."}
381
        return {"status": "ok"}
382