1
1
import json
2
2
from io import BytesIO
3
3
from pathlib import Path
4
- from uuid import UUID , uuid4
4
+ from uuid import UUID
5
5
6
6
import geojson
7
7
from celery import chord , group
15
15
send_from_directory ,
16
16
url_for ,
17
17
)
18
+ from psycopg2 .errors import UndefinedTable
18
19
from werkzeug import Response
19
20
20
21
from sketch_map_tool import celery_app , config , definitions , tasks
28
29
UploadLimitsExceededError ,
29
30
UUIDNotFoundError ,
30
31
)
31
- from sketch_map_tool .helpers import extract_errors , merge , to_array , zip_
32
+ from sketch_map_tool .helpers import N_ , extract_errors , merge , to_array , zip_
32
33
from sketch_map_tool .models import Bbox , Layer , PaperFormat , Size
33
34
from sketch_map_tool .tasks import (
34
35
cleanup_blobs ,
35
- digitize_sketches ,
36
- georeference_sketch_map ,
36
+ upload_processing ,
37
37
)
38
38
from sketch_map_tool .validators import (
39
39
validate_type ,
@@ -114,9 +114,7 @@ def create_results_post(lang="en") -> Response:
114
114
"""Create the sketch map"""
115
115
# Request parameters
116
116
bbox_raw = json .loads (request .form ["bbox" ])
117
- bbox_wgs84_raw = json .loads (request .form ["bboxWGS84" ])
118
117
bbox = Bbox (* bbox_raw )
119
- bbox_wgs84 = Bbox (* bbox_wgs84_raw )
120
118
format_raw = request .form ["format" ]
121
119
format_ : PaperFormat = getattr (definitions , format_raw .upper ())
122
120
orientation = request .form ["orientation" ]
@@ -125,30 +123,33 @@ def create_results_post(lang="en") -> Response:
125
123
scale = float (request .form ["scale" ])
126
124
layer = Layer (request .form ["layer" ].replace (":" , "-" ).replace ("_" , "-" ).lower ())
127
125
128
- # feature flag for enabling aruco markers
126
+ # Feature flag for enabling aruco markers
129
127
if request .args .get ("aruco" ) is None :
130
128
aruco = False
131
129
else :
132
130
aruco = True
133
131
134
- # Unique id for current request
135
- uuid = str (uuid4 ())
136
-
137
132
# Tasks
138
133
task_sketch_map = tasks .generate_sketch_map .apply_async (
139
- args = (uuid , bbox , format_ , orientation , size , scale , layer , aruco )
140
- )
141
- task_quality_report = tasks .generate_quality_report .apply_async (
142
- args = tuple ([bbox_wgs84 ])
134
+ args = (bbox , format_ , orientation , size , scale , layer , aruco )
143
135
)
136
+ return redirect (url_for ("create_results_get" , lang = lang , uuid = task_sketch_map .id ))
144
137
145
- # Map of request type to multiple Async Result IDs
146
- map_ = {
147
- "sketch-map" : str (task_sketch_map .id ),
148
- "quality-report" : str (task_quality_report .id ),
149
- }
150
- db_client_flask .set_async_result_ids (uuid , map_ )
151
- return redirect (url_for ("create_results_get" , lang = lang , uuid = uuid ))
138
+
139
+ def get_async_result_id (uuid : str , type_ : REQUEST_TYPES ):
140
+ """Get Celery Async or Group Result UUID for given request UUID.
141
+
142
+ Try to get Celery UUID for given request from datastore.
143
+ If no Celery UUID has been found the request UUID is the same as the Celery UUID.
144
+
145
+ This function exists only for legacy support.
146
+ """
147
+ # TODO: Legacy support: Delete this function after PR 515 has been deployed
148
+ # for 1 day
149
+ try :
150
+ return db_client_flask .get_async_result_id (uuid , type_ )
151
+ except (UUIDNotFoundError , UndefinedTable ):
152
+ return uuid
152
153
153
154
154
155
@app .get ("/create/results" )
@@ -160,8 +161,8 @@ def create_results_get(lang="en", uuid: str | None = None) -> Response | str:
160
161
return redirect (url_for ("create" , lang = lang ))
161
162
validate_uuid (uuid )
162
163
# Check if celery tasks for UUID exists
163
- _ = db_client_flask . get_async_result_id (uuid , "sketch-map" )
164
- _ = db_client_flask . get_async_result_id ( uuid , "quality-report " )
164
+ id_ = get_async_result_id (uuid , "sketch-map" )
165
+ _ = get_async_result ( id_ , "sketch-map " )
165
166
return render_template ("create-results.html" , lang = lang )
166
167
167
168
@@ -205,11 +206,10 @@ def digitize_results_post(lang="en") -> Response:
205
206
bboxes_ [uuid ] = bbox
206
207
layers_ [uuid ] = layer
207
208
208
- tasks_vector = []
209
- tasks_raster = []
209
+ tasks = []
210
210
for file_id , file_name , uuid in zip (file_ids , file_names , uuids ):
211
- tasks_vector .append (
212
- digitize_sketches .signature (
211
+ tasks .append (
212
+ upload_processing .signature (
213
213
(
214
214
file_id ,
215
215
file_name ,
@@ -219,40 +219,24 @@ def digitize_results_post(lang="en") -> Response:
219
219
)
220
220
)
221
221
)
222
- tasks_raster .append (
223
- georeference_sketch_map .signature (
224
- (
225
- file_id ,
226
- file_name ,
227
- map_frames [uuid ],
228
- layers_ [uuid ],
229
- bboxes_ [uuid ],
230
- )
231
- )
232
- )
233
- async_result_raster = group (tasks_raster ).apply_async ()
234
- c = chord (
235
- group (tasks_vector ),
222
+ chord_ = chord (
223
+ group (tasks ),
236
224
cleanup_blobs .signature (
237
225
kwargs = {"file_ids" : list (set (file_ids ))},
238
226
immutable = True ,
239
227
),
240
228
).apply_async ()
241
- async_result_vector = c .parent
229
+ async_group_result = chord_ .parent
242
230
243
231
# group results have to be saved for them to be able to be restored later
244
- async_result_raster .save ()
245
- async_result_vector .save ()
246
-
247
- # Unique id for current request
248
- uuid = str (uuid4 ())
249
- # Mapping of request id to multiple tasks id's
250
- map_ = {
251
- "raster-results" : str (async_result_raster .id ),
252
- "vector-results" : str (async_result_vector .id ),
253
- }
254
- db_client_flask .set_async_result_ids (uuid , map_ )
255
- return redirect (url_for ("digitize_results_get" , lang = lang , uuid = uuid ))
232
+ async_group_result .save ()
233
+ return redirect (
234
+ url_for (
235
+ "digitize_results_get" ,
236
+ lang = lang ,
237
+ uuid = async_group_result .id ,
238
+ )
239
+ )
256
240
257
241
258
242
@app .get ("/digitize/results" )
@@ -266,19 +250,32 @@ def digitize_results_get(lang="en", uuid: str | None = None) -> Response | str:
266
250
return render_template ("digitize-results.html" , lang = lang )
267
251
268
252
253
+ def get_async_result (uuid : str , type_ : REQUEST_TYPES ) -> AsyncResult | GroupResult :
254
+ """Get Celery `AsyncResult` or restore `GroupResult` for given Celery UUID."""
255
+ if type_ in ("sketch-map" , "quality-report" ):
256
+ async_result = celery_app .AsyncResult (uuid )
257
+ elif type_ in ("vector-results" , "raster-results" ):
258
+ async_result = celery_app .GroupResult .restore (uuid )
259
+ else :
260
+ raise TypeError ()
261
+
262
+ if async_result is None :
263
+ raise UUIDNotFoundError (
264
+ N_ ("There are no tasks for UUID {UUID}" ),
265
+ {"UUID" : uuid },
266
+ )
267
+ else :
268
+ return async_result
269
+
270
+
269
271
@app .get ("/api/status/<uuid>/<type_>" )
270
272
@app .get ("/<lang>/api/status/<uuid>/<type_>" )
271
273
def status (uuid : str , type_ : REQUEST_TYPES , lang = "en" ) -> Response :
272
274
validate_uuid (uuid )
273
275
validate_type (type_ )
274
276
275
- id_ = db_client_flask .get_async_result_id (uuid , type_ )
276
-
277
- # due to legacy support it is not possible to check only `type_`
278
- # (in the past every Celery result was of type `AsyncResult`)
279
- async_result = celery_app .GroupResult .restore (id_ )
280
- if async_result is None :
281
- async_result = celery_app .AsyncResult (id_ )
277
+ id_ = get_async_result_id (uuid , type_ )
278
+ async_result = get_async_result (id_ , type_ )
282
279
283
280
href = ""
284
281
info = ""
@@ -336,18 +333,18 @@ def download(uuid: str, type_: REQUEST_TYPES, lang="en") -> Response:
336
333
validate_uuid (uuid )
337
334
validate_type (type_ )
338
335
339
- id_ = db_client_flask .get_async_result_id (uuid , type_ )
336
+ id_ = get_async_result_id (uuid , type_ )
337
+ async_result = get_async_result (id_ , type_ )
340
338
341
- # due to legacy support it is not possible to check only `type_`
342
- # (in the past every Celery result was of type `AsyncResult`)
343
- async_result = celery_app .GroupResult .restore (id_ )
344
- if async_result is None :
345
- async_result = celery_app .AsyncResult (id_ )
346
- if not async_result .ready () or async_result .failed ():
339
+ # Abort if result not ready or failed.
340
+ # No nice error message here because user should first check /api/status.
341
+ if isinstance (async_result , GroupResult ):
342
+ if not async_result .ready () or all ([r .failed () for r in async_result .results ]):
347
343
abort (500 )
348
344
else :
349
- if not async_result .ready () or all ([ r .failed () for r in async_result . results ] ):
345
+ if not async_result .ready () or async_result .failed ():
350
346
abort (500 )
347
+
351
348
match type_ :
352
349
case "quality-report" :
353
350
mimetype = "application/pdf"
@@ -361,16 +358,19 @@ def download(uuid: str, type_: REQUEST_TYPES, lang="en") -> Response:
361
358
mimetype = "application/zip"
362
359
download_name = type_ + ".zip"
363
360
if isinstance (async_result , GroupResult ):
364
- file : BytesIO = zip_ (async_result .get (propagate = False ))
361
+ results = async_result .get (propagate = False )
362
+ raster_results = [r [:- 1 ] for r in results ]
363
+ file : BytesIO = zip_ (raster_results )
365
364
else :
366
365
# support legacy results
367
366
file : BytesIO = async_result .get ()
368
367
case "vector-results" :
369
368
mimetype = "application/geo+json"
370
369
download_name = type_ + ".geojson"
371
370
if isinstance (async_result , GroupResult ):
372
- result : list = async_result .get (propagate = False )
373
- raw = geojson .dumps (merge (result ))
371
+ results = async_result .get (propagate = False )
372
+ vector_results = [r [- 1 ] for r in results ]
373
+ raw = geojson .dumps (merge (vector_results ))
374
374
file : BytesIO = BytesIO (raw .encode ("utf-8" ))
375
375
else :
376
376
# support legacy results
0 commit comments