|
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, check_getijdezone=0): |
|
159
|
|
|
""" |
|
160
|
|
|
:param geojson_input: geojson geometrie |
|
161
|
|
|
:param check_getijdezone: bool, check in tidal zone is necessary |
|
162
|
|
|
:return: True if the input geojson intersects with Flanders region, otherwise False |
|
163
|
|
|
""" |
|
164
|
|
|
in_flanders = False |
|
165
|
|
|
geojson_input_shape = geometry.shape(geojson_input) |
|
166
|
|
|
if geojson_input_shape.intersects(get_flanders_geometry()): |
|
167
|
|
|
in_flanders = True |
|
168
|
|
|
if not in_flanders and check_getijdezone: |
|
169
|
|
|
if geojson_input_shape.intersects(get_flanders_tidal_zone_geometry()): |
|
170
|
|
|
in_flanders = True |
|
171
|
|
|
return in_flanders |
|
172
|
|
|
|
|
173
|
|
|
|
|
174
|
|
|
def _get_geometry(file_path): |
|
175
|
|
|
with open(file_path) as geof: |
|
176
|
|
|
features = json.load(geof) |
|
177
|
|
|
output_geojson = features['features'][0]['geometry'] |
|
178
|
|
|
return geometry.shape(output_geojson) |
|
179
|
|
|
|
|
180
|
|
|
|
|
181
|
|
|
def get_flanders_geometry(): |
|
182
|
|
|
here = os.path.abspath(os.path.dirname(__file__)) |
|
183
|
|
|
return _get_geometry(here + '/fixtures/vlaams_gewest.geojson') |
|
184
|
|
|
|
|
185
|
|
|
|
|
186
|
|
|
def get_flanders_tidal_zone_geometry(): |
|
187
|
|
|
here = os.path.abspath(os.path.dirname(__file__)) |
|
188
|
|
|
return _get_geometry(here + '/fixtures/vlaams_gewest_getijdenzone.geojson') |
|
189
|
|
|
|
|
190
|
|
|
|
|
191
|
|
|
def provincie_niscode(gemeente_niscode): |
|
192
|
|
|
""" |
|
193
|
|
|
geef de niscode van de provincie op basis van de niscode van de gemeente |
|
194
|
|
|
|
|
195
|
|
|
:param gemeente_niscode: niscode van de gemeente |
|
196
|
|
|
:return: niscode van de provincie |
|
197
|
|
|
""" |
|
198
|
|
|
pcode = str(gemeente_niscode)[0] |
|
199
|
|
|
if pcode == '2': |
|
200
|
|
|
return int('20001') |
|
201
|
|
|
else: |
|
202
|
|
|
return int('{0}0000'.format(pcode)) |
|
203
|
|
|
|
|
204
|
|
|
|
|
205
|
|
|
def get_centroid_xy(value): |
|
206
|
|
|
""" |
|
207
|
|
|
Returns the centroid x,y of a geojson polygon. |
|
208
|
|
|
If the geojson is a multipolygon. It return the centroid of the biggest polygon |
|
209
|
|
|
:param value: geojson |
|
210
|
|
|
:return: x,y |
|
211
|
|
|
""" |
|
212
|
|
|
geometry_element = convert_geojson_to_geometry(value) |
|
213
|
|
|
if geometry_element.type == 'MultiPolygon': |
|
214
|
|
|
geometry_element_polygons = list(geometry_element.geoms) |
|
215
|
|
|
largest_polygon = max(geometry_element_polygons, key=attrgetter('area')) |
|
216
|
|
|
centroid = largest_polygon.centroid.wkt |
|
217
|
|
|
else: |
|
218
|
|
|
centroid = geometry_element.centroid.wkt |
|
219
|
|
|
return ','.join([x for x in re.findall(r'\d+.\d+', centroid)]) |
|
220
|
|
|
|
|
221
|
|
|
|
|
222
|
|
|
def add_crab_ids(crabpy_gateway, address): |
|
223
|
|
|
gemeente = address.get("gemeente") |
|
224
|
|
|
if gemeente: |
|
225
|
|
|
gewest_ids = [2, 1, 3] |
|
226
|
|
|
gemeente_val = None |
|
227
|
|
|
for gewest_id in gewest_ids: |
|
228
|
|
|
gemeenten = crabpy_gateway.list_gemeenten(gewest_id) |
|
229
|
|
|
gemeente_val = next((g for g in gemeenten if g.naam.lower() == gemeente.lower()), None) |
|
230
|
|
|
if gemeente_val: |
|
231
|
|
|
address['gemeente'] = "" + gemeente_val.naam |
|
232
|
|
|
address['gemeente_id'] = gemeente_val.id |
|
233
|
|
|
break |
|
234
|
|
|
straat_naam = address.get("straat") |
|
235
|
|
|
if gemeente_val and straat_naam: |
|
236
|
|
|
straat_val = next((s for s in gemeente_val.straten if s.label.lower() == straat_naam.lower()), None) |
|
237
|
|
|
if straat_val: |
|
238
|
|
|
address["straat"] = "" + straat_val.label |
|
239
|
|
|
address["straat_id"] = straat_val.id |
|
240
|
|
|
huisnummer_naam = address.get("huisnummer") |
|
241
|
|
|
if huisnummer_naam: |
|
242
|
|
|
huisnummer_val = next((n for n in straat_val.huisnummers |
|
243
|
|
|
if n.huisnummer.lower() == huisnummer_naam.lower()), None) |
|
244
|
|
|
if huisnummer_val: |
|
245
|
|
|
address["huisnummer"] = "" + huisnummer_val.huisnummer |
|
246
|
|
|
address["huisnummer_id"] = huisnummer_val.id |
|
247
|
|
|
return address |
|
248
|
|
|
|
|
249
|
|
|
|
|
250
|
|
|
def nearest_location(geojson_element, crabpy_gateway=None): |
|
251
|
|
|
""" |
|
252
|
|
|
Returns the nearest location. If a crabpy_gateway is given, the crab id's will be added to the address. |
|
253
|
|
|
Uses the agiv service https://loc.geopunt.be/geolocation/Location?xy=<x,y> |
|
254
|
|
|
where <x,y> are the coordinates of the centroid of the geometry (polygon). |
|
255
|
|
|
The agiv service returns crab, urbis en poi location types only in Flanders and Brussels. |
|
256
|
|
|
:param value: geojson |
|
257
|
|
|
:return: - crab address |
|
258
|
|
|
- None if the nearest address is nog crab type |
|
259
|
|
|
""" |
|
260
|
|
|
xy = get_centroid_xy(geojson_element) |
|
261
|
|
|
|
|
262
|
|
|
r = requests.get('https://loc.geopunt.be/geolocation/Location?xy={0}'.format(xy)) |
|
263
|
|
|
result = r.json() |
|
264
|
|
|
if 'LocationResult' not in result \ |
|
265
|
|
|
or len(result['LocationResult']) == 0 \ |
|
266
|
|
|
or 'crab' not in result['LocationResult'][0]['LocationType']: |
|
267
|
|
|
return None |
|
268
|
|
|
locres = result['LocationResult'][0]['FormattedAddress'] |
|
269
|
|
|
straat_huisnummer, postcode_gemeente = locres.rsplit(', ', 1) |
|
270
|
|
|
straat, huisnummer = straat_huisnummer.rsplit(' ', 1) |
|
271
|
|
|
postcode, gemeente = postcode_gemeente.split(' ', 1) |
|
272
|
|
|
gemeente = gemeente.replace(' ({})'.format(postcode), '') |
|
273
|
|
|
address = { |
|
274
|
|
|
'straat': straat, |
|
275
|
|
|
'huisnummer': huisnummer, |
|
276
|
|
|
'omschrijving_straat': straat + ', ' + huisnummer, |
|
277
|
|
|
'postcode': postcode, |
|
278
|
|
|
'gemeente': gemeente, |
|
279
|
|
|
'land': 'BE' |
|
280
|
|
|
} |
|
281
|
|
|
if crabpy_gateway: |
|
282
|
|
|
return add_crab_ids(crabpy_gateway, address) |
|
283
|
|
|
else: |
|
284
|
|
|
return address |
|
285
|
|
|
|
|
286
|
|
|
|
|
287
|
|
|
class AdminGrenzenClient: |
|
288
|
|
|
def __init__(self, base_url): |
|
289
|
|
|
self.base_url = base_url |
|
290
|
|
|
|
|
291
|
|
|
def get_admin_objecten(self, geojson_input, admin_grens_type, return_geometry=0): |
|
292
|
|
|
""" |
|
293
|
|
|
This function returns the administrative areas objects which intersects with the input geojson |
|
294
|
|
|
:param geojson_input: geojson |
|
295
|
|
|
:param admin_grens_type: type of returned administrative objects |
|
296
|
|
|
:param return_geometry: boolean indicating whether the geometry of the returned objects should also be included |
|
297
|
|
|
: |
|
298
|
|
|
:return: list of administrative areas objects |
|
299
|
|
|
""" |
|
300
|
|
|
if check_in_flanders(geojson_input): |
|
301
|
|
|
data = { |
|
302
|
|
|
'geef_geometrie': return_geometry, |
|
303
|
|
|
'type': admin_grens_type, |
|
304
|
|
|
'geometrie': geojson_input |
|
305
|
|
|
} |
|
306
|
|
|
res = requests.post( |
|
307
|
|
|
url=self.base_url, |
|
308
|
|
|
json=data, |
|
309
|
|
|
headers={ |
|
310
|
|
|
'Accept': 'application/json', |
|
311
|
|
|
'Content-Type': 'application/json' |
|
312
|
|
|
} |
|
313
|
|
|
) |
|
314
|
|
|
res.raise_for_status() |
|
315
|
|
|
return json.loads(res.text) |
|
316
|
|
|
else: |
|
317
|
|
|
return [] |
|
318
|
|
|
|
|
319
|
|
|
def get_gemeenten(self, geojson_input): |
|
320
|
|
|
""" |
|
321
|
|
|
This function returns the names of the municipalities which intersects with the input geojson |
|
322
|
|
|
:param geojson_input: geojson |
|
323
|
|
|
:return: list of municipalities |
|
324
|
|
|
""" |
|
325
|
|
|
results = self.get_admin_objecten(geojson_input, 'gemeente') |
|
326
|
|
|
return [gemeente['naam'] for gemeente in results] |
|
327
|
|
|
|
|
328
|
|
|
def get_provincies(self, geojson_input): |
|
329
|
|
|
""" |
|
330
|
|
|
This function returns the names of the provinces which intersects with the input geojson |
|
331
|
|
|
:param geojson_input: geojson |
|
332
|
|
|
:return: list of municipalities |
|
333
|
|
|
""" |
|
334
|
|
|
results = self.get_admin_objecten(geojson_input, 'provincie') |
|
335
|
|
|
return [provincie['naam'] for provincie in results] |
|
336
|
|
|
|
|
337
|
|
|
@staticmethod |
|
338
|
|
|
def _get_lagest_admin_obj(results, geojson_input): |
|
339
|
|
|
if len(results) == 0: |
|
340
|
|
|
return None |
|
341
|
|
|
elif len(results) == 1: |
|
342
|
|
|
return {'niscode': results[0]['id'], 'naam': results[0]['naam']} |
|
343
|
|
|
else: |
|
344
|
|
|
input_polygon = convert_geojson_to_geometry(geojson_input) |
|
345
|
|
|
for result in results: |
|
346
|
|
|
admin_geom = convert_geojson_to_geometry(result['geometrie']) |
|
347
|
|
|
result['intersection_area'] = admin_geom.intersection(input_polygon).area |
|
348
|
|
|
largest_m = max(results, key=lambda x: x['intersection_area']) |
|
349
|
|
|
return {'niscode': largest_m['id'], 'naam': largest_m['naam']} |
|
350
|
|
|
|
|
351
|
|
|
def get_gemeente(self, geojson_input): |
|
352
|
|
|
""" |
|
353
|
|
|
This function returns the name of the municipality which intersects with the input geojson. |
|
354
|
|
|
If more municipalities intersect. The municipality is returned with the largest intersection |
|
355
|
|
|
:param value: administratievegrenzen_url |
|
356
|
|
|
:param value: geojson |
|
357
|
|
|
:return: municipality |
|
358
|
|
|
""" |
|
359
|
|
|
results = self.get_admin_objecten(geojson_input, 'gemeente', 1) |
|
360
|
|
|
return self._get_lagest_admin_obj(results, geojson_input) |
|
361
|
|
|
|
|
362
|
|
|
def get_provincie(self, geojson_input): |
|
363
|
|
|
""" |
|
364
|
|
|
This function returns the name of the province which intersects with the input geojson. |
|
365
|
|
|
If more provinces intersect. The province is returned with the largest intersection. |
|
366
|
|
|
:param value: administratievegrenzen_url |
|
367
|
|
|
:param value: geojson |
|
368
|
|
|
:return: province |
|
369
|
|
|
""" |
|
370
|
|
|
results = self.get_admin_objecten(geojson_input, 'provincie', 1) |
|
371
|
|
|
return self._get_lagest_admin_obj(results, geojson_input) |
|
372
|
|
|
|
|
373
|
|
|
def check_erfgoedgemeenten(self, geojson_input, erfgoedgemeenten_list): |
|
374
|
|
|
""" |
|
375
|
|
|
:param geojson_input: geojson geometry |
|
376
|
|
|
:param erfgoedgemeenten_list: list of niscodes of municipalities |
|
377
|
|
|
:return: dict including key status and optional key message |
|
378
|
|
|
status "ok" when the geojson does not intersect with one of given municipalities |
|
379
|
|
|
status "warn" when the geojson intersects partially with one of given municipalities |
|
380
|
|
|
status "error" when the geojson is within one of given municipalities |
|
381
|
|
|
""" |
|
382
|
|
|
gemeenten = self.get_admin_objecten(geojson_input, 'gemeente') |
|
383
|
|
|
erfgoedgemeenten = [gemeente for gemeente in gemeenten if |
|
384
|
|
|
int(gemeente['id']) in json.loads(erfgoedgemeenten_list)] |
|
385
|
|
|
if len(erfgoedgemeenten) > 0: |
|
386
|
|
|
if len(gemeenten) == len(erfgoedgemeenten): |
|
387
|
|
|
return { |
|
388
|
|
|
"status": "error", |
|
389
|
|
|
"message": "Let op, de zone van deze melding is gelegen in een onroerenderfgoedgemeente en " |
|
390
|
|
|
"kan niet bewaard worden. Gelieve de melding in te dienen bij deze gemeente."} |
|
391
|
|
|
else: |
|
392
|
|
|
return { |
|
393
|
|
|
"status": "warn", |
|
394
|
|
|
"message": "Let op, deze melding ligt deels in een onroerenderfgoedgemeente . " |
|
395
|
|
|
"Gelieve de melding vooronderzoek eveneens in te dienen bij deze gemeente."} |
|
396
|
|
|
return {"status": "ok"} |
|
397
|
|
|
|