Skip to content

Commit 3757ddb

Browse files
committed
👽 feat: added API endpoint for image_search
Signed-off-by: saif <[email protected]>
1 parent a6d3ff1 commit 3757ddb

File tree

1 file changed

+213
-7
lines changed

1 file changed

+213
-7
lines changed

src/mapillary/config/api/entities.py

Lines changed: 213 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,21 @@
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
2027
from 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

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

Comments
 (0)