11import re
22from typing import List
3- from app .auth import exchange_token
3+ from app .auth import exchange_token , get_current_user_claims
44from fastapi import Response
55from loguru import logger
66
3434@register_platform (ProcessTypeEnum .OGC_API_PROCESS )
3535class OGCAPIProcessPlatform (BaseProcessingPlatform ):
3636 input_type_map = {
37+ "date-time" : ParamTypeEnum .DATETIME ,
3738 "date-interval" : ParamTypeEnum .DATE_INTERVAL ,
3839 "bounding-box" : ParamTypeEnum .BOUNDING_BOX ,
3940 "boolean" : ParamTypeEnum .BOOLEAN ,
4041 "integer" : ParamTypeEnum .INTEGER ,
4142 "double" : ParamTypeEnum .DOUBLE ,
43+ "number" : ParamTypeEnum .DOUBLE ,
44+ "string" : ParamTypeEnum .STRING ,
4245 }
4346
4447 status_mapping = {
@@ -53,6 +56,12 @@ class OGCAPIProcessPlatform(BaseProcessingPlatform):
5356 r"(?P<namespace>.+)/processes/(?P<process_id>[^/]+)$"
5457 )
5558
59+ geojson_schema_references = {
60+ GEOJSON_FEATURECOLLECTION_SCHEMA ,
61+ "https://geojson.org/schema/FeatureCollection.json" ,
62+ "https://geojson.org/schema/Feature.json" ,
63+ }
64+
5665 """
5766 OGC API Process processing platform implementation.
5867 This class handles the execution of processing jobs on the OGC API Process platform.
@@ -64,6 +73,76 @@ def _split_job_id(self, job_id) -> tuple[str, ...]:
6473 return ("" , job_id )
6574 return tuple (parts )
6675
76+ def _get_type_from_schema (
77+ self , schema : dict | str | None , input_id : str = ""
78+ ) -> ParamTypeEnum :
79+ if isinstance (schema , str ):
80+ if schema in self .__class__ .geojson_schema_references :
81+ return ParamTypeEnum .POLYGON
82+ return self .__class__ .input_type_map .get (schema , ParamTypeEnum .STRING )
83+
84+ if not isinstance (schema , dict ):
85+ return ParamTypeEnum .STRING
86+
87+ schema_type = str (schema .get ("type" ))
88+ schema_format = schema .get ("format" )
89+ schema_subtype = schema .get ("subtype" )
90+
91+ if schema_type == "array" and schema_subtype == "date-interval" :
92+ return ParamTypeEnum .DATE_INTERVAL
93+ if schema_type == "array" and schema .get ("items" , {}).get ("type" ) == "string" :
94+ return ParamTypeEnum .ARRAY_STRING
95+ if schema_subtype == "geojson" :
96+ return ParamTypeEnum .POLYGON
97+ if schema_subtype == "bounding-box" :
98+ return ParamTypeEnum .BOUNDING_BOX
99+ if schema_format == "geojson" :
100+ return ParamTypeEnum .POLYGON
101+ if schema_format == "date-time" :
102+ return ParamTypeEnum .DATETIME
103+ if schema_type == "object" :
104+ required = schema .get ("required" ) or []
105+ if "type" in required and "coordinates" in required :
106+ type_properties = schema .get ("properties" , {}).get ("type" , {})
107+ type_instance = type_properties
108+ while "actual_instance" in type_instance :
109+ type_instance = type_instance ["actual_instance" ]
110+ if "Polygon" in type_instance .get ("enum" , []):
111+ return ParamTypeEnum .POLYGON
112+ if "Point" in type_instance .get ("enum" , []):
113+ return ParamTypeEnum .POINT
114+ elif "bbox" in required :
115+ return ParamTypeEnum .BOUNDING_BOX
116+
117+ if isinstance (schema .get ("$ref" ), str ):
118+ return self ._get_type_from_schema (schema .get ("$ref" ), input_id )
119+
120+ for variant_key in ("oneOf" , "anyOf" , "allOf" ):
121+ variants = schema .get (variant_key ) or []
122+ if not isinstance (variants , list ):
123+ continue
124+ for variant in variants :
125+ detected_type = self ._get_type_from_schema (variant , input_id )
126+ if detected_type != ParamTypeEnum .STRING :
127+ return detected_type
128+
129+ properties = schema .get ("properties" ) or {}
130+ if (
131+ schema .get ("title" ) == "GeoJSON"
132+ or "geometry" in properties
133+ or "features" in properties
134+ or input_id .lower () in {"aoi" , "geometry" , "geom" , "geojson" }
135+ ):
136+ return ParamTypeEnum .POLYGON
137+
138+ return self .__class__ .input_type_map .get (schema_type , ParamTypeEnum .STRING )
139+
140+ def _get_options_from_schema (self , schema : dict | str | None ) -> list :
141+ if not isinstance (schema , dict ):
142+ return []
143+ options = schema .get ("enum" )
144+ return options if isinstance (options , list ) else []
145+
67146 async def _create_api_client_instance (
68147 self ,
69148 endpoint : str ,
@@ -91,6 +170,8 @@ async def execute_job(
91170 ) -> str :
92171 logger .info (f"Executing OGC API job with title={ title } " )
93172
173+ parameters = await self ._transform_parameters (user_token , details , parameters )
174+
94175 # Exchanging token
95176 logger .debug ("Exchanging user token for OGC API Process execution..." )
96177 exchanged_token = await exchange_token (
@@ -112,7 +193,22 @@ async def execute_job(
112193 if exchanged_token :
113194 headers ["Authorization" ] = f"Bearer { exchanged_token } "
114195
115- data = {"inputs" : {key : value for key , value in parameters .items ()}}
196+ user_claims = get_current_user_claims (user_token )
197+ properties = {
198+ "title" : title ,
199+ "application" : details .application ,
200+ }
201+ if user_claims .get ("sub" ):
202+ properties ["user_id" ] = user_claims ["sub" ]
203+ if user_claims .get ("preferred_username" ):
204+ properties ["username" ] = user_claims ["preferred_username" ]
205+ if user_claims .get ("email" ):
206+ properties ["email" ] = user_claims ["email" ]
207+
208+ data = {
209+ "inputs" : parameters ,
210+ "properties" : properties ,
211+ }
116212
117213 content = api_client .execute_simple (
118214 process_id = details .application , execute = data , _headers = headers
@@ -125,6 +221,49 @@ async def execute_job(
125221 return f"{ details .namespace } :{ job_id } "
126222 return job_id
127223
224+ def _transform_bbox_parameter (self , param_name : str , value ) -> list [float ]:
225+ if isinstance (value , (list , tuple )) and len (value ) == 4 :
226+ return [float (coord ) for coord in value ]
227+
228+ if isinstance (value , dict ):
229+ if ["east" , "north" , "south" , "west" ] == sorted (value .keys ()):
230+ return [
231+ float (value ["west" ]),
232+ float (value ["south" ]),
233+ float (value ["east" ]),
234+ float (value ["north" ]),
235+ ]
236+
237+ raise ValueError (
238+ f"Unsupported bounding box value for parameter { param_name } : { value } "
239+ )
240+
241+ async def _transform_parameters (
242+ self , user_token : str , details : ServiceDetails , parameters : dict
243+ ) -> dict :
244+ service_params = await self .get_service_parameters (user_token , details )
245+
246+ modifiers = {
247+ ParamTypeEnum .BOUNDING_BOX : self ._transform_bbox_parameter ,
248+ }
249+
250+ transformed_parameters = parameters .copy ()
251+ for param in service_params :
252+ if param .name not in parameters :
253+ continue
254+
255+ modifier = modifiers .get (param .type )
256+
257+ if modifier :
258+ transformed_parameters [param .name ] = modifier (
259+ param .name , parameters [param .name ]
260+ )
261+
262+ logger .debug (
263+ f"Transformed parameters for OGC API Process: { transformed_parameters } "
264+ )
265+ return transformed_parameters
266+
128267 async def execute_synchronous_job (
129268 self ,
130269 user_token : str ,
@@ -303,53 +442,10 @@ async def get_service_parameters(
303442
304443 if process_description .inputs :
305444 for input_id , input_details in process_description .inputs .items ():
306- input_type = (
307- input_id ,
308- input_details .model_dump ()
309- .get ("var_schema" , {})
310- .get ("actual_instance" , {})
311- .get ("type" , "" ),
312- )
313- if isinstance (input_type , tuple ):
314- input_type_str = next (
315- (
316- t
317- for t in input_type
318- if t
319- in [
320- "date-interval" ,
321- "bounding-box" ,
322- "boolean" ,
323- "integer" ,
324- "double" ,
325- ]
326- ),
327- None ,
328- )
329- else :
330- input_type_str = None
331-
332- if input_type_str :
333- input_type_str = self .__class__ .input_type_map .get (input_type_str )
334-
335- if not input_type_str :
336- input_type_str = ParamTypeEnum .STRING
337- input_types = (
338- input_details .model_dump ()
339- .get ("var_schema" , {})
340- .get ("actual_instance" , {})
341- .get ("required" )
342- or []
343- )
344- if "bbox" in input_types :
345- input_type_str = ParamTypeEnum .BOUNDING_BOX
346-
347- input_options = (
445+ schema = (
348446 input_details .model_dump ()
349447 .get ("var_schema" , {})
350- .get ("actual_instance" , {})
351- .get ("enum" )
352- or []
448+ .get ("actual_instance" )
353449 )
354450 parameters .append (
355451 Parameter (
@@ -359,8 +455,8 @@ async def get_service_parameters(
359455 else f"Parameter: { input_id } " ,
360456 default = None ,
361457 optional = (input_details .min_occurs == 0 ),
362- type = input_type_str ,
363- options = input_options ,
458+ type = self . _get_type_from_schema ( schema , input_id ) ,
459+ options = self . _get_options_from_schema ( schema ) ,
364460 )
365461 )
366462
0 commit comments