Я некоторое время использовал Strava для своих случайных пробежек, и кое-что, что всегда привлекало мое внимание, это основная лента приложения, где вы можете найти красивую картинку или снимок пути, пройденного вами или вашими друзьями. Сначала я подумал, что приложение загружает набор Geojsons и делает рендеринг для каждого из сообщений во время пробежки, но после работы с мобильными картами в своем собственном проекте я понял, что рендеринг карты — непростая задача для любого устройства. Затем я заметил, что каждый пост содержит изображение маршрута на карте, и эта лента не слишком отличается от любого поста в социальных сетях, тем не менее, мне все еще было любопытно, как мы можем генерировать эти изображения.
Недавно у меня появилось немного свободного времени, и я начал экспериментировать с этим проектом, и сейчас я расскажу вам о своем пути.
Сначала о главном: как сгенерировать снимок карты?
через GIPHY
Моим первым шагом была попытка визуализировать карту и сделать ее снимок. Порывшись в Интернете пару минут, я нашел отличную статью «Создание художественных карт с помощью Python», в которой автор учит нас, как с помощью Python создать высококачественную карту любого города.
Следуя руководству и проведя несколько тестов, я заметил, что сценарию требуется слишком много времени между получением информации с Open Street Maps и ее обработкой. Принимая во внимание количество одновременных запросов, которые должна получать Strava, я сказал себе, что должна быть альтернатива, которая потребляет меньше ресурсов, и тогда я нашел API statice Images от Mapbox.
У MapBox есть набор API, которые продаются как программное обеспечение как услуга, но они позволяют вам генерировать API-ключ разработчика и даже создавать свои собственные стили карт. Static Images API позволяет создать полностью настраиваемую карту. Вы задаете такие параметры, как координаты, размеры, географические объекты (точки, линии, полигоны и т.д.) и другие. В ответ вы получаете изображение с картой, которую вы просили.
Сервис работает довольно гладко, и после работы с ним я заметил, что даже Strava использует его (вы можете увидеть отметку MapBox в сноске каждой карты), что определенно является сигналом того, что вы идете в правильном направлении.
import requests
API_URL = "https://api.mapbox.com/styles/v1/{0}/static/{1}/auto/{2}x{3}?access_token={4}"
...
request_url = API_URL.format(
style_id, # Map Style Ex. 'mapbox/darkv11'
polyline_hash, # Polyline is alghoritm for hashing list of coordinates.
width, height, # Dimenssions of the image
token # Access token provided by MapBox
)
response = requests.get(request_url)
if (response.status_code == 200):
open('map.png', 'wb').write(response.content)
else:
print(response.text)
...
Перейдем к серьезному: создание сервиса
через GIPHY
Как только я получил довольно базовый скрипт для генерации карт с помощью MapBox, мне нужно было начать думать о том, как будет работать наш сервис. Я знал, что должен получать набор координат и, возможно, пару параметров стиля, но в комнате оставались два слона: асинхронность и масштабируемость.
Поскольку для этого будет использоваться вызов внешнего сервиса, я не мог рассчитывать на синхронное выполнение каждого HTTP-вызова, который будет получать наш сервис, и горизонт становится все темнее, когда вы думаете о возможностях сбоя, учитывая количество запросов и общие проблемы с сетями. Ответ был очевиден: я должен получить запрос, немедленно закрыть его и затем начать обработку карты в новом потоке или фоновом процессе, всегда принимая во внимание, что мы должны быть масштабируемыми. Исходя из этого и стратегии «разделяй и властвуй», я пришел к выводу, что для моего решения потребуются следующие компоненты:
- Веб-сервер, который получает и проверяет запросы клиентов.
- Менеджер очереди, который составляет список подтвержденных запросов.
- Набор рабочих, которые считывают данные из менеджера очереди и выполняют обработку.
Для монтажа такой архитектуры я не мог представить себе более простого способа, чем использование подхода Docker Compose (или Docker + Kubernetes). В итоге у меня получилось дерево папок, как показано ниже.
├───service
│ ├───nginx
│ └───src
│ └───map_equests.py
│ └───main.py
│ └───Dokerfile
│ └───requirements.txt
├───worker
│ └───src
│ └───helpers
│ └───worker.py
│ └───Dokerfile
│ └───requirements.txt
│
├───docker-compose.yml
└───.env
Выводы
Это решение далеко от оптимального, и я уверен, что это не единственный ответ на эту проблему, но попытка выяснить, как ее решить в рамках инженерного упражнения revere, привела к довольно наглядному занятию, которое я определенно рекомендую любому разработчику.
Я опубликовал проект на своем аккаунте Github. Вы можете найти репозиторий здесь, чтобы погрузиться в код. Ниже я также приведу несколько ресурсов, которые могут помочь вам в случае, если вы работаете над чем-то подобным или просто ищете информацию об одной из этих технологий.
- Создание художественных карт с помощью Python
- Создание красивых карт с помощью Python
- API статических изображений MapBox
- Очередь из первых попавшихся
- Формат алгоритма кодированной полилинии