Skip to content

Commit 4644f2f

Browse files
committed
Use custom markers on multi-stop maps
1 parent 6635901 commit 4644f2f

File tree

3 files changed

+50
-32
lines changed

3 files changed

+50
-32
lines changed

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ _La API puede devolver información de Paradas y listados en tiempo real de los
9595
- Add Near Stops endpoint
9696
- Add endpoint for static maps and StreetView acquisition
9797
- Add endpoints for static buses info
98-
- On multi-stop map generation, return markers showing stop IDs
9998
- Add integration tests
10099
- Add detailed install & configuration instructions
101100
- Improve endpoint and services/controllers in-code organization
@@ -107,7 +106,6 @@ _La API puede devolver información de Paradas y listados en tiempo real de los
107106
- _Añadir endpoint para consulta de paradas cercanas a ubicación_
108107
- _Añadir endpoint para obtención de mapas estáticos y StreetView_
109108
- _Añadir endpoints para consulta de información estática de buses_
110-
- _En generación de mapas de varias paradas, devolver marcadores mostrando IDs de parada_
111109
- _Añadir tests de integración_
112110
- _Añadir instrucciones detalladas de instalación y configuración_
113111
- _Mejorar organización en código de endpoints y servicios/controladores_

vigobusapi/app.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
# # Native # #
66
import io
7-
import json
87
from typing import Optional, Set
98

109
# # Installed # #
@@ -130,21 +129,11 @@ async def endpoint_get_stops_map(
130129
stops_ids: Set[int] = Query(None, alias="stop_id", min_items=1, max_items=35),
131130
map_params: MapQueryParams = Depends(),
132131
):
133-
"""Get a picture of a map with the locations of the given stops marked on it.
134-
The marks are labelled in the same order as the given stops ids.
135-
136-
A header "X-Stops-Tags" is returned, being a JSON associating the Stops IDs with the tag label on the map,
137-
with the format: {"<stop id>" : "<tag label>"}
138-
"""
132+
"""Get a picture of a map with the locations of the given stops marked on it."""
139133
stops = await get_stops(stops_ids)
140134

141-
stops_tags = list()
142-
stops_tags_relation = dict()
143-
for i, stop in enumerate(stops):
144-
tag_label = GoogleMapRequest.Tag.get_allowed_labels()[i]
145-
tag = GoogleMapRequest.Tag(label=tag_label, location_x=stop.lat, location_y=stop.lon)
146-
stops_tags.append(tag)
147-
stops_tags_relation[stop.stop_id] = tag_label
135+
# noinspection PyTypeChecker
136+
stops_tags = [GoogleMapRequest.Tag(label=stop.stop_id, location_x=stop.lat, location_y=stop.lon) for stop in stops]
148137

149138
map_request = GoogleMapRequest(
150139
size_x=map_params.size_x,
@@ -157,8 +146,7 @@ async def endpoint_get_stops_map(
157146

158147
return StreamingResponse(
159148
content=io.BytesIO(map_data),
160-
media_type="image/png",
161-
headers={"X-Stops-Tags": json.dumps(stops_tags_relation)}
149+
media_type="image/png"
162150
)
163151

164152

vigobusapi/services/google_maps.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import *
99

1010
# # Installed # #
11+
import pydantic
1112
from pydantic import BaseModel
1213

1314
# # Project # #
@@ -24,6 +25,12 @@
2425
GOOGLE_STREETVIEW_STATIC_API_URL = "https://maps.googleapis.com/maps/api/streetview"
2526

2627

28+
def get_labelled_icon_url(label: str) -> str:
29+
"""Get an URL pointing to a picture of a map marker, with a custom label on top of it"""
30+
# TODO self-hosted generation, and/or cache of generated labels
31+
return f"https://cdn.mapmarker.io/api/v1/font-awesome/v5/pin?text={label}&size=40&background=D94B43&color=000000&hoffset=-1"
32+
33+
2734
class _GoogleMapsBaseRequest(BaseModel, ChecksumableClass):
2835
location_x: float
2936
location_y: float
@@ -60,13 +67,23 @@ class Tag(BaseModel, ChecksumableClass):
6067
__ALLOWED_LABELS = [*[str(i) for i in range(1, 10)], *[c for c in string.ascii_uppercase]]
6168

6269
label: Optional[str] = None # TODO constrain values accepted (avoid enum?)
70+
icon_url: Optional[str] = None
6371
location_x: float
6472
location_y: float
6573

6674
@classmethod
6775
def get_allowed_labels(cls):
6876
return cls.__ALLOWED_LABELS
6977

78+
@pydantic.root_validator(pre=True)
79+
def label_to_icon(cls, kwargs: dict):
80+
"""If label is not an "allowed label", generate an icon for it and set it as "icon_url"."""
81+
label = kwargs.get("label")
82+
if label not in cls.__ALLOWED_LABELS:
83+
kwargs["label"] = None
84+
kwargs["icon_url"] = get_labelled_icon_url(label)
85+
return kwargs
86+
7087
@property
7188
def location_str(self):
7289
return f"{self.location_x},{self.location_y}"
@@ -154,15 +171,23 @@ async def _request(url: str, params: Union[dict, ListOfTuples], expect_http_erro
154171
)
155172

156173

157-
async def get_map(request: GoogleMapRequest) -> bytes:
158-
"""Get a static Map picture from the Google Maps Static API. Return the acquired PNG picture as bytes.
174+
def _get_map_tags_params(request_tags: List[GoogleMapRequest.Tag]) -> ListOfTuples:
175+
params = list()
176+
for tag in request_tags:
177+
tag_param_values = [tag.location_str] # Location always at the end
159178

160-
References:
161-
https://developers.google.com/maps/documentation/maps-static/overview
162-
https://developers.google.com/maps/documentation/maps-static/start
163-
"""
164-
logger.bind(map_request=request.dict()).debug("Requesting Google Static Map picture...")
165-
# TODO cache loaded pictures
179+
if tag.label:
180+
tag_param_values.insert(0, "label:" + tag.label)
181+
if tag.icon_url:
182+
tag_param_values.insert(0, "icon:" + tag.icon_url)
183+
184+
tag_param = "|".join(tag_param_values)
185+
params.append(("markers", tag_param))
186+
187+
return params
188+
189+
190+
def _get_map_params(request: GoogleMapRequest) -> ListOfTuples:
166191
params = [
167192
("size", request.size_str),
168193
("maptype", request.map_type.value),
@@ -176,15 +201,22 @@ async def get_map(request: GoogleMapRequest) -> bytes:
176201
params.append(("zoom", str(request.zoom)))
177202

178203
if request.tags:
179-
for tag in request.tags:
180-
tag_param_values = [tag.location_str] # Location always at the end
204+
params.extend(_get_map_tags_params(request.tags))
205+
206+
return params
181207

182-
if tag.label:
183-
tag_param_values.insert(0, "label:" + tag.label)
184208

185-
tag_param = "|".join(tag_param_values)
186-
params.append(("markers", tag_param))
209+
async def get_map(request: GoogleMapRequest) -> bytes:
210+
"""Get a static Map picture from the Google Maps Static API. Return the acquired PNG picture as bytes.
211+
212+
References:
213+
https://developers.google.com/maps/documentation/maps-static/overview
214+
https://developers.google.com/maps/documentation/maps-static/start
215+
"""
216+
logger.bind(map_request=request.dict()).debug("Requesting Google Static Map picture...")
217+
# TODO cache loaded pictures
187218

219+
params = _get_map_params(request)
188220
return (await _request(url=GOOGLE_MAPS_STATIC_API_URL, params=params)).content
189221

190222

0 commit comments

Comments
 (0)