Skip to content

Commit 9cc594c

Browse files
committed
Add endpoint & logic for getting stop photo (StreetView)
1 parent 61cb6b0 commit 9cc594c

File tree

4 files changed

+79
-20
lines changed

4 files changed

+79
-20
lines changed

sample.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,9 @@ google_maps_stop_map_default_size_y=720
4949
google_maps_stop_map_default_zoom=17
5050
google_maps_stop_map_default_type=roadmap
5151

52+
# Default values for GET Photo/Streetview
53+
stop_photo_default_size_x=2000
54+
stop_photo_default_size_y=2000
55+
5256
# Language in which print texts in static Maps pictures (2 characters country code)
5357
google_maps_language=es

vigobusapi/app.py

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from vigobusapi.request_handler import request_handler
1717
from vigobusapi.settings import settings, google_maps_settings
1818
from vigobusapi.vigobus_getters import get_stop, get_stops, get_buses, search_stops
19-
from vigobusapi.services.google_maps import GoogleMapRequest, get_map
19+
from vigobusapi.services.google_maps import GoogleMapRequest, GoogleStreetviewRequest, get_map, get_photo
2020
from vigobusapi.services import MongoDB
2121
from vigobusapi.logger import logger
2222

@@ -96,22 +96,41 @@ async def endpoint_get_stop_map(
9696
map_type: GoogleMapRequest.MapTypes = google_maps_settings.stop_map_default_type
9797
):
9898
"""Get a picture of a map with the stop location marked on it."""
99-
with logger.contextualize(**locals()):
100-
stop = await get_stop(stop_id)
101-
if (stop.lat, stop.lon) == (None, None):
102-
raise HTTPException(status_code=409, detail="The stop does not have information about the location")
103-
104-
map_request = GoogleMapRequest(
105-
location_x=stop.lat,
106-
location_y=stop.lon,
107-
size_x=size_x,
108-
size_y=size_y,
109-
zoom=zoom,
110-
map_type=map_type,
111-
tags=[GoogleMapRequest.Tag(location_x=stop.lat, location_y=stop.lon)]
112-
)
113-
map_data = await get_map(map_request)
114-
return StreamingResponse(io.BytesIO(map_data), media_type="image/png")
99+
stop = await get_stop(stop_id)
100+
if (stop.lat, stop.lon) == (None, None):
101+
raise HTTPException(status_code=409, detail="The stop does not have information about the location")
102+
103+
map_request = GoogleMapRequest(
104+
location_x=stop.lat,
105+
location_y=stop.lon,
106+
size_x=size_x,
107+
size_y=size_y,
108+
zoom=zoom,
109+
map_type=map_type,
110+
tags=[GoogleMapRequest.Tag(location_x=stop.lat, location_y=stop.lon)]
111+
)
112+
map_data = await get_map(map_request)
113+
return StreamingResponse(io.BytesIO(map_data), media_type="image/png")
114+
115+
116+
@app.get("/stop/{stop_id}/photo")
117+
async def endpoint_get_stop_photo(
118+
stop_id: int,
119+
size_x: int = google_maps_settings.stop_photo_default_size_x,
120+
size_y: int = google_maps_settings.stop_photo_default_size_y,
121+
):
122+
stop = await get_stop(stop_id)
123+
if (stop.lat, stop.lon) == (None, None):
124+
raise HTTPException(status_code=409, detail="The stop does not have information about the location")
125+
126+
photo_request = GoogleStreetviewRequest(
127+
location_x=stop.lat,
128+
location_y=stop.lon,
129+
size_x=size_x,
130+
size_y=size_y
131+
)
132+
photo_data = await get_photo(photo_request)
133+
return StreamingResponse(io.BytesIO(photo_data), media_type="image/png")
115134

116135

117136
def run():

vigobusapi/services/google_maps.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
from vigobusapi.logger import logger
1616
from .http_requester import http_request, ListOfTuples
1717

18-
__all__ = ("GoogleMapRequest", "get_map")
18+
__all__ = ("GoogleMapRequest", "GoogleStreetviewRequest", "get_map", "get_photo")
1919

2020
# TODO May refactor in package with different modules (at least split classes and logic)
2121

2222
GOOGLE_MAPS_STATIC_API_URL = "https://maps.googleapis.com/maps/api/staticmap"
23+
GOOGLE_STREETVIEW_STATIC_API_URL = "https://maps.googleapis.com/maps/api/streetview"
2324

2425

2526
class _GoogleMapsBaseRequest(BaseModel, ChecksumableClass):
@@ -112,12 +113,17 @@ def checksum_hash(self):
112113
map_type: MapTypes
113114

114115

115-
async def _request(url: str, params: Union[dict, ListOfTuples]):
116+
class GoogleStreetviewRequest(_GoogleMapsBaseRequest):
117+
pass
118+
119+
120+
async def _request(url: str, params: Union[dict, ListOfTuples], expect_http_error: bool = False):
116121
"""HTTP requester for Google Maps API calls, automatically including the configured API key.
117122
Raises exception if the API Key is not configured.
118123
119124
:param url: URL for the Google API, WITHOUT query parameters
120125
:param params: query parameters
126+
:param expect_http_error: if True, raise_for_status=False and not_retry_400_errors=True
121127
"""
122128
if not settings.enabled:
123129
raise Exception("Google Maps API Key not set in settings")
@@ -131,7 +137,9 @@ async def _request(url: str, params: Union[dict, ListOfTuples]):
131137
url=url,
132138
method="GET",
133139
params=params,
134-
retries=1
140+
retries=1,
141+
raise_for_status=not expect_http_error,
142+
not_retry_400_errors=expect_http_error
135143
)
136144

137145

@@ -164,3 +172,29 @@ async def get_map(request: GoogleMapRequest) -> bytes:
164172
params.append(("markers", tag_param))
165173

166174
return (await _request(url=GOOGLE_MAPS_STATIC_API_URL, params=params)).content
175+
176+
177+
async def get_photo(request: GoogleStreetviewRequest) -> Optional[bytes]:
178+
"""Get a static StreetView picture from the Google StreetView Static API. Return the acquired PNG picture as bytes.
179+
If the requested location does not have an available picture, returns None.
180+
181+
References:
182+
https://developers.google.com/maps/documentation/streetview/overview
183+
"""
184+
logger.bind(streetview_request=request.dict()).debug("Requesting Google Static StreetView picture...")
185+
# TODO cache loaded pictures
186+
# TODO Support specific parameters for tuning camera, if required
187+
params = [
188+
("location", request.location_str),
189+
("size", request.size_str),
190+
("return_error_code", "true"),
191+
("source", "outdoor")
192+
]
193+
194+
response = await _request(GOOGLE_STREETVIEW_STATIC_API_URL, params=params, expect_http_error=True)
195+
if response.status_code == 404:
196+
logger.debug("No StreetView picture available for the request")
197+
return None
198+
199+
response.raise_for_status()
200+
return response.content

vigobusapi/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class GoogleMapsSettings(_BaseSettings):
4747
stop_map_default_size_y: int = 720
4848
stop_map_default_zoom: int = 17
4949
stop_map_default_type: str = "roadmap" # TODO use enum (after refactoring to avoid circular dependency issue)
50+
stop_photo_default_size_x: int = 2000
51+
stop_photo_default_size_y: int = 2000
5052
language: str = "es"
5153

5254
@property

0 commit comments

Comments
 (0)