1515"""
1616
1717# Local imports
18+ import datetime
19+ import logging
20+ import typing
21+
22+ # # Utils
23+ from mapillary .utils .time import is_iso8601_datetime_format
1824
19- # # Exception Handling
25+ # # Models
26+ # # # Exception Handling
2027from mapillary .models .exceptions import InvalidFieldError , InvalidNumberOfArguments
28+ from mapillary .models .config import Config
29+ from mapillary .models .logger import Logger
30+
31+ logger : logging .Logger = Logger .setup_logger (name = "mapillary.config.api.entities" )
2132
22- # # Imports
23- import typing
2433
2534class Entities :
2635 """
@@ -90,7 +99,9 @@ def get_image(image_id: str, fields: list) -> str:
9099 return f"https://graph.mapillary.com/{ image_id } /?fields={ ',' .join (fields )} "
91100
92101 @staticmethod
93- def get_images (image_ids : typing .List [str ], fields : list ) -> str :
102+ def get_images (
103+ image_ids : typing .Union [typing .List [str ], typing .List [int ]], fields : list
104+ ) -> str :
94105 """
95106 Represents the metadata of the image on the Mapillary platform with
96107 the following properties.
@@ -101,7 +112,8 @@ def get_images(image_ids: typing.List[str], fields: list) -> str:
101112
102113 Parameters::
103114
104- A list of entity IDs separated by comma. The provided IDs must be in the same type (e.g. all image IDs, or all detection IDs)
115+ A list of entity IDs separated by comma. The provided IDs must be in the same type
116+ (e.g. all image IDs, or all detection IDs)
105117
106118 Fields::
107119
@@ -130,7 +142,7 @@ def get_images(image_ids: typing.List[str], fields: list) -> str:
130142 22. width - int, width of the original image uploaded
131143
132144 Raises::
133-
145+
134146 InvalidNumberOfArguments - if the number of ids passed is 0 or greater than 50
135147 """
136148
@@ -140,7 +152,11 @@ def get_images(image_ids: typing.List[str], fields: list) -> str:
140152 # in the future
141153
142154 if len (image_ids ) == 0 or len (image_ids ) > 50 :
143- raise InvalidNumberOfArguments (number_of_params_passed = len (image_ids ), actual_allowed_params = 50 , param = "image_ids" )
155+ raise InvalidNumberOfArguments (
156+ number_of_params_passed = len (image_ids ),
157+ actual_allowed_params = 50 ,
158+ param = "image_ids" ,
159+ )
144160
145161 fields = Entities .__field_validity (
146162 given_fields = fields ,
@@ -150,6 +166,193 @@ def get_images(image_ids: typing.List[str], fields: list) -> str:
150166
151167 return f"https://graph.mapillary.com/ids={ ',' .join (image_ids )} ?fields={ ',' .join (fields )} "
152168
169+ @staticmethod
170+ def search_for_images ( # noqa: C901, 'search_for_images is too complex'
171+ bbox : typing .List [float ],
172+ start_captured_at : typing .Optional [datetime .datetime ] = None ,
173+ end_captured_at : typing .Optional [datetime .datetime ] = None ,
174+ limit : typing .Optional [int ] = None ,
175+ organization_id : typing .Union [
176+ typing .Optional [int ], typing .Optional [str ]
177+ ] = None ,
178+ sequence_id : typing .Optional [typing .List [int ]] = None ,
179+ fields : typing .Optional [list ] = [],
180+ ) -> str :
181+ """
182+ Represents the metadata of the image on the Mapillary platform with
183+ the following properties.
184+
185+ Output Format::
186+
187+ >>> 'https://graph.mapillary.com/search?bbox=LONG1,LAT1,LONG2,LAT2' # endpoint
188+ >>> 'https://graph.mapillary.com/search?bbox=LONG1,LAT1,LONG2,LAT2&start_time='
189+ 'START_TIME' # endpoint
190+ >>> 'https://graph.mapillary.com/search?bbox=LONG1,LAT1,LONG2,LAT2&start_time='
191+ 'START_TIME&end_time=END_TIME' # endpoint
192+ >>> 'https://graph.mapillary.com/search?bbox=LONG1,LAT1,LONG2,LAT2&start_time='
193+ 'START_TIME&end_time=END_TIME&limit=LIMIT' # endpoint
194+ >>> 'https://graph.mapillary.com/search/images?bbox=LONG1,LAT1,LONG2,LAT2&start_time'
195+ '=START_TIME&end_time=END_TIME&limit=LIMIT&organization_id=ORGANIZATION_ID&'
196+ 'sequence_id=SEQUENCE_ID1' # endpoint
197+ >>> 'https://graph.mapillary.com/search/images?bbox=LONG1,LAT1,LONG2,LAT2&start_time='
198+ 'START_TIME&end_time=END_TIME&limit=LIMIT&organization_id=ORGANIZATION_ID&sequence_id'
199+ '=SEQUENCE_ID1,SEQUENCE_ID2,SEQUENCE_ID3' # endpoint
200+
201+ Usage::
202+
203+ >>> from mapillary.config.api.entities import Entities
204+ >>> bbox = [-180, -90, 180, 90]
205+ >>> start_captured_at = datetime.datetime(2020, 1, 1, 0, 0, 0)
206+ >>> end_captured_at = datetime.datetime(2022, 1, 1, 0, 0, 0)
207+ >>> organization_id = 123456789
208+ >>> sequence_ids = [123456789, 987654321]
209+ >>> Entities.search_for_images(bbox=bbox) # endpoint
210+ 'https://graph.mapillary.com/search?bbox=-180,-90,180,90' # endpoint
211+ >>> Entities.search_for_images(bbox=bbox, start_captured_at=start_captured_at)
212+ 'https://graph.mapillary.com/search?bbox=-180,-90,180,90&start_time=' # endpoint
213+ >>> Entities.search_for_images(bbox=bbox,
214+ ... start_captured_at=start_captured_at, end_captured_at=end_captured_at)
215+ 'https://graph.mapillary.com/search?bbox=-180,-90,180,90&start_time=&'
216+ 'end_time=' # endpoint
217+ >>> Entities.search_for_images(bbox=bbox,
218+ ... start_captured_at=start_captured_at, end_captured_at=end_captured_at,
219+ ... limit=100)
220+ 'https://graph.mapillary.com/search?bbox=-180,-90,180,90&start_time=&end_time=&limit'
221+ '=100' # endpoint
222+ >>> Entities.search_for_images(bbox=bbox,
223+ ... start_captured_at=start_captured_at, end_captured_at=end_captured_at,
224+ ... limit=100, organization_id=organization_id, sequence_id=sequence_ids)
225+ 'https://graph.mapillary.com/search/images?bbox=-180,-90,180,90&start_time=&end_time'
226+ '=&limit=100&organization_id=1234567890&sequence_id=1234567890' # endpoint
227+
228+ :param bbox: float,float,float,float: filter images in the bounding box. Specify in this
229+ order: left, bottom, right, top (or minLon, minLat, maxLon, maxLat).
230+ :type bbox: typing.Union[typing.List[float], typing.Tuple[float, float, float, float],
231+ list, tuple]
232+
233+ :param start_captured_at: filter images captured after. Specify in the ISO 8601 format.
234+ For example: "2022-08-16T16:42:46Z".
235+ :type start_time: typing.Union[typing.Optional[datetime.datetime], typing.Optional[str]]
236+ :default start_captured_at: None
237+
238+ :param end_captured_at: filter images captured before. Same format as
239+ "start_captured_at".
240+ :type end_time: typing.Union[typing.Optional[datetime.datetime], typing.Optional[str]]
241+ :default end_captured_at: None
242+
243+ :param limit: limit the number of images returned. Max and default is 2000. The 'default'
244+ here means the default value of `limit` assumed on the server's end if the limit param
245+ is not passed. In other words, if the `limit` parameter is set to `None`, the server will
246+ assume the `limit` parameter to be 2000, which is the same as setting the `limit`
247+ parameter to 2000 explicitly.
248+ :type limit: typing.Optional[int]
249+ :default limit: None
250+
251+ :param organization_id: filter images contributed to the specified organization Id.
252+ :type organization_id: typing.Optional[int]
253+ :default organization_id: None
254+
255+ :param sequence_id: filter images in the specified sequence Ids (separated by commas),
256+ For example, "[1234567890,1234567891,1234567892]".
257+ :type sequence_id: typing.Optional[typing.List[int], int]
258+ :default sequence_id: None
259+
260+ :param fields: filter the fields returned. For example, "['atomic_scale', 'altitude',
261+ 'camera_parameters']". For more information, see
262+ https://www.mapillary.com/developer/api-documentation/#image. To get list of all possible
263+ fields, please use Entities.get_image_fields()
264+ :type fields: typing.Optional[typing.List[str]]
265+ :default fields: []
266+
267+ :return: endpoint for searching an image
268+ :rtype: str
269+ """
270+
271+ fields = Entities .__field_validity (
272+ given_fields = fields ,
273+ actual_fields = Entities .get_image_fields (),
274+ endpoint = "https://graph.mapillary.com/images?bbox=,:parameters=,:fields=" ,
275+ )
276+
277+ parameter_string : str = ""
278+
279+ parameters = {
280+ "start_captured_at" : start_captured_at ,
281+ "end_captured_at" : end_captured_at ,
282+ "limit" : limit ,
283+ "organization_id" : organization_id ,
284+ "sequence_id" : sequence_id ,
285+ }
286+
287+ # For each item in the given parameters ...
288+ for key , value in parameters .items ():
289+
290+ # ... if it is not None ...
291+ if value is not None :
292+
293+ # ... if the key is about time ...
294+ if key in ["start_captured_at" , "end_captured_at" ]:
295+
296+ # ... if the datatype is a string ...
297+ if isinstance (value , str ):
298+
299+ # ... check if the string is a valid datetime in the ISO8601 format ...
300+ if not is_iso8601_datetime_format (value ) and Config .use_strict :
301+
302+ # ... if not, raise an error ...
303+ if Config .use_strict :
304+
305+ # Raising ValueError if strict mode is enabled
306+ raise ValueError (
307+ f"""{ key } must be in the ISO 8601 format. For example:
308+ '2022-08-16T16:42:46Z'."""
309+ )
310+
311+ else :
312+
313+ logger .warning (
314+ f"{ key } must be in the ISO 8601 format. For example:"
315+ f"'2022-08-16T16:42:46Z'. Disregarding { key } parameter."
316+ "Continuing without strict mode enabled."
317+ )
318+
319+ # ... if not, just move on - no assumptions on the `date string` ...
320+ continue
321+
322+ # ... if the value is a valid datetime object ...
323+ elif isinstance (value , datetime .datetime ):
324+
325+ # ... convert it to the ISO 8601 format required ...
326+ value = value .strftime ("%Y-%m-%dT%H:%M:%SZ" )
327+
328+ # ... if the key is limit ...
329+ if key == "limit" :
330+
331+ # Check if it is within limits
332+ if value > 2000 :
333+
334+ # Log warning if not, waring mode is enabled - logger object declared
335+ # globally is used
336+ logger .warning (f"{ key } is greater than 2000. Setting to 2000." )
337+
338+ # Set the value to 2000
339+ value = 2000
340+
341+ # ... if the key is sequence_id ...
342+ if key == "sequence_id" :
343+ # ... convert the list into string ...
344+ value = "," .join (map (str , value ))
345+
346+ # ... add it to the parameter string
347+ parameter_string += f"&{ key } ={ value } "
348+
349+ # Return the endpoint
350+ return (
351+ f"https://graph.mapillary.com/images?bbox={ ',' .join ([str (val ) for val in bbox ])} "
352+ f"{ parameter_string if parameter_string != '' else '' } "
353+ f"{ '&fields=' + ',' .join (fields ) if fields != [] else '' } "
354+ )
355+
153356 @staticmethod
154357 def get_image_fields () -> list :
155358 """
@@ -403,6 +606,9 @@ def __field_validity(
403606 :rtype: list
404607 """
405608
609+ if len (given_fields ) == 0 :
610+ return given_fields # empty list: []
611+
406612 # Converting the given_fields into lowercase
407613 given_fields = [field .lower () for field in given_fields ]
408614
0 commit comments