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
|
|
|
|