Люблю жанр производственной драмы. Потому что, во-первых, это априори про преодоление и превозмоганиие, во-вторых, в конце почти всегда есть какая-то награда, и, в третьих, всегда что-то идёт не так и возникает проблема, которую нужно срочно решать.
Как я думал с Даварихом — арендовал пару серверов, написал приложеньку для управления подписками и всё, стриги бабло. Конечно же, никогда не бывает так красиво. В моём случае одной из проблем, которые предстояло решить, были веб-карты. Пост будет длинный, устраивайтесь поудобнее.
Карты — ключевая функциональность Давариха, потому что без них не получится осмысленно продемонстрировать перемещения пользователя по местности. Проблемы фактически не существовало, пока Даварих оставался исключительно селф-хостабл софтом. По-умолчанию я вписал в него использование бесплатных карт от OpenStreetMap, которые позволяют некоммерческий юзедж. Что такое для большой, пусть и некоммерческой, организации, 500-1000 пользователей, которые раз в неделю-месяц открывают Даварих и тянутся на сервера OSM за 200-1000 тайлами? Пшик. Поэтому они не парятся и позволяют встраивать свои карты на сайты и приложения, оставляя оговорку в Terms of usage, что будьте благоразумны пожалуйста.
Что там вообще по веб картам
Но давайте сделаем шаг назад. Или два, или три. Что такое вообще веб-карты? Грубо говоря, это несколько наборов данных и некоторый код, который склеивает эти наборы вместе и позволяет использовать их в привычной нам манере. Наборы во множественном числе, потому что их по меньшей мере два: визуальные данные, из которых рисуется карта, и, грубо говоря, текстовые данные, которые позволяют узнать, что конкретно находится в выбранном месте на карте. Аптека, например, и находится она по такому-то адресу, а ещё в этом здании есть отделение банка и детсад.
Ещё есть фронтенд-библиотека, подключаемая в приложение или на сайт, которая принимает на вход адрес, по которому расположены карты, а на выход, при правильном расположении звёзд и кода, выдаёт интерактивную (или нет, как наладишь) карту в приложение или, опять же, на сайт. Чисто снаружи, для меня, как для бэкендера, такие библиотеки не выглядят как что-то очень уж простое, но мне важно не так много: чтобы она позволяла скроллить и зумить карту, и чтобы средствами этой библиотеки на карте можно было рисовать точки, линии и всякие там панельки по моему вкусу. Leaflet идеально попадает в запрос за одним небольшим нюансом, но о нём поговорим попозже.
В Даварихе нас (пока) интересует один конкретный датасет: картинки, из которых рисуется карта. Обычно это квадратная или примерно квадратная картинка размером 256х256 пикселей, которая бесшовно прилегает к ещё нескольким таким картинкам, чтобы создать прямоугольный кусочек карты. Картинки называются тайлами (tile, плитка или черепичка) и лежат где-нибудь на сервере организации, которая предоставляет доступ к тайлам бесплатно или за деньги. Чаще всего за деньги, конечно. Потому что планета большая, а ещё потому что помимо перетаскивания карты на вашем ноуте или телефоне, вы ещё можете её позумить, что добавляет третье измерение, а значит на карте нужно показать больше деталей при приближении, а значит недостаточно хранить грубый рисунок карты на условном уровне зума 1, необходимо так же хранить более детальные тайлы карты для ровно той же локации на уровне зума 2, и так далее. Обычно карты имеют до 19 уровней зума, где 1 — это когда на экране видна вся планета, а 19 — это кусочек карты площадью примерно 35х50 метров. Оч близко. Не вдаваясь в калькуляции, скажу, что чтобы иметь детальную карту всей планеты, нужно иметь что-то вроде 69 миллиардов квадратных файлов размером 256х256 пикселей.
Вот поэтому никто толком карты бесплатно и не предоставляет. Это здоровенный набор данных, который весит сотни гигабайт, и эти данные состоят из мелких файликов, которые нужно отдавать по запросу ОЧЕНЬ БЫСССТРА, и эта сложность сразу отсекает кучу желающих быстренько захостить карты для своего сайтика. Потому что дорого и сложно. Из-за сложности и дороговизны все крупные картографические ребята делают это инхаус для экономии денег, а все мелкие ребята, которым нужны карты, либо платят крупным, либо настолько мелкие, что их юз-кейс попадает в бесплатные лимиты, предоставляемые крупными ребятами.
Как там с деньгами
Тарифные планы у картопровайдеров обычно базируются на том, сколько тайлов карты тебе нужно загрузить в месяц. По моим прикидкам, один средний пользователь Давариха использует едва ли больше тысячи тайлов в неделю (но выборка очень ограничена, конечно). Можно округлить до 5000 в месяц. Если быть оптимистом, то на старте у нас есть 100 пользователей в месяц в подписочной версии Давариха, которые потребляют по 5к тайлов, а значит это полмиллиона квадратиков в месяц. Для удобства подсчётов ещё раз округлим, до миллиона. Дальше нас ждёт неизбежный рост, скажем, до 1000 пользователей, это 5-10 миллионов тайлов в месяц.
На разбеге запроса в 1-10 миллионов тайлов в месяц мы получаем следующий прайс:
- Гугл: 400-3700 баксов
- Мапбокс: 400-3500 баксов
- Тандерфорест: 125-255 баксов
- Вай биляяять...
Какой-то адище для стартапчиков. На этом моменте мне стало так плохо, что я передумал искать дальше и решил, что Тандерфорест удовлетворит нашим нуждам и вообще не так уж и дорого — отдать 100-200 баксов, зато навырост. Ну то есть да, есть вариантики поселфхостить карты, нооо... Хостить ещё один подвижный элемент инфрастуктуры в одно лицо я готов настолько же, насколько готов заниматься немецкими налогами. Не готов. Проще уж деньги кинуть в монитор.
Ты айтишник или где
Технически подкованный читатель где-то на этом моменте может сказать: но погодите-ка, если карты — это просто статические изображения, и одни и те же изображения могут быть запрошены пользователем (или множеством пользователей) более одного раза, значит их можно закэшировать? И таким образом и ускорить отдачу карт, и сэкономить на лимитах провайдеров, отдавая клиенту закэшированное на нашей стороне и потребляя меньше, чем могли бы? Ты абсолютно прав, технически подкованный читатель, скажу я в ответ и укажу на Terms of Service всех этих перечисленных выше провайдеров карт. А термсы эти явно и прозрачно запрещают использование кэширующих прокси и других способов кэширования тайлов. Мы тут lawful good и делаем сириус бизнес (боже, как плохо эта фраза может состариться), так что играть будем по правилам. Однако, должен заметить, что прежде, чем раздуплить, что кэшировать-таки низя, я успел и потрогать MapProxy, и, когда он чего-то не завёлся, написать свой простейший кэширующий прокси. Последний потом пришлось переделать в некэширующий, но он всё равно был полезен в сценарии работы с Тандерфорест, потому что ключ апи, с которым надо обращаться за каждым тайликом карты, содержится прямо в урле. И если его не спрятать за прокси, какой-нибудь джиниус с навыками работы в dev tools браузера может выцепить, куда уходит запрос за картами и с каким апи ключом и высадить нам месячный лимит за полчаса чисто по приколу.
Написанный прокси я в итоге выкинул за ненадобностью, потому что в клубном селфхостерском чате мне подкинули ссылку на Protomaps. Я задвинул её в сохранёнки и подзабыл, но вся эта чехарда с картами никак не давала мне покоя, так что пришлось вспомнить, чего там было интересного в Протомапс и раскурить их как следует.
Дальше следует чистый восторг и радость, вам придётся потерпеть моё фангёрлство.
Короче, что меня зацепило в первую очередь, ещё в марте этого года, когда мне показали Протомапс, это то, что карты отдаются из одного большого файла. Это не ебучая прорва бесконечности микроплиточек, это натурально один здоровенный, на 120+ гигабайт, файл. И не надо там городить эластиксёрчей и налаживать джавы и докеры, засунь файл на S3-лайк бакет или просто в файловую систему куда-нибудб, натрави на него в JS-либу и будет тебе карта. Вот так просто. Всё.
Этого, конечно, недостаточно, поэтому щас будет деталей.
Бэк ин 2020, кажется, некто Брэндон Лиу решил, что хостить карты не должно быть настолько сложным и дорогим квестом, как я уже описал выше. А что если можно по-другому, подумал Брэндон и склеил нахрен все тайлы от OpenStreetMap в один файл. Так ведь удобнее! И приправил их несколькими дефолтными стилями карты, чтобы скучно не было.
Очень кстати пришлась фича протокола HTTP, которая называется HTTP Range Requests. Буду честен, я о ней узнал вот реально только разбираясь с Протомапс. Эта фича позволяет отправлять запрос, который просит вернуть в теле ответа лишь часть данных, а не всё, что может отдать сервер в ответ на такой запрос. Таким образом, если стадвадцатигигабайтный файл с картинками карты структурировать, держа в уме эту возможность, потенциально можно обращаться к этому файлу, запрашивая лишь несколько килобайт данных в чётко очерченных границах, и получать в ответ ровно те кусочки карты, которые были запрошены клиентом. Уф, я надеюсь, не слишком сложно закрутил. Но прикол в том, что всё, больше не нужно хранить миллиарды файлов, нужен один, правильно структурированный. А, и ещё нужна JS-библиотека, которая будет уметь всё делать именно так, как нужно. И плагины для существующих библиотек, чтобы они тоже умели запрашивать кусочки карты и рисовать их.
Наш герой сделал это всё. И файл карт, и JS-либу, и плагины, и ещё целый зоопарк инструментов, который позволяет использовать Protomaps в разных юзкейсах. И автоматизировал ежедневную сборку карты, чтобы поддерживать её в актуальном состоянии. А ещё он сделал возможность кэшировать эти карты, чтобы ускорить отдачу тайлов, файлы которых хранятся в хранилищах в большими пингами.
Долго ли, коротко ли, я скачал файл с картами, за ночь (привет non-existent немецкий канал на аплоад) закачал его на R2 бакет у Cloudflare, чуток повозился с воркерами всё того же Cloudflare, и теперь у нас есть рабочая карта, которая отдаётся в виде статичного файла, перед ней лежит воркер, который кэширует запросы и отдаёт карту БЫСССТРА, и стоит всё это добро типа 12 баксов за 10 миллионов тайлов в месяц, которые платятся за воркер и хранение карты в R2 бакете. Судя по калькулятору на сайте Протомапс, которому хочется верить, 10кк тайлов нам обошлись бы несколько дороже на AWS S3 (около сотки в месяц), Cloudflare здесь прям ну конфетка что за выбор.
Честно, я прям давно не испытывал восторга от изящных софтверных решений (да и испытывал ли вообще?..), а здесь — чистый восторг. Просто, дёшево, статично. 120 гигов — это вся планета Земля, если какой-нибудь проект нуждается в картах, скажем, города, это вообще будут чистые копейки, потому что файл будет занимать с десяток-сотню мегабайт, и его можно положить ну просто куда угодно. При желании, можно вообще не возиться с облаками, а развернуть всё на своей инфрастуктуре: Minio в качестве селфхостед s3-лайк бакета, и погнали. Или, как предлагает сам Брэндон, закидываешь файл на гитхаб пейджес, и всё. Он вечный. Ну, условно, конечно, но достаточно вечный для множества целей.
Уф, спасибо за внимание.
Я упоминал про нюанс с Leaflet, давайте немного вернёмся к нему. Protomaps отдаёт векторные тайлы, а Leaflet искаропки умеет работать только с растровыми. Я не очень готов рассказывать разницу между этими типами тайлов, здесь важно, что пришлось чуток покорячить код, чтобы Лифлет мог поглощать скормленные ему тайлы Протомапс, но это было совсем несложно, потому что Брэндон запилил плагин и для него. Чисто мой герой.
Блин, ну ладно, придётся чутка затронуть типы тайлов. Дело в том, что растровые тайлы — это статичные картинки, а векторные — суть набор данных о картах, и рисуются они в вебе чисто визуально куда опрятнее, особенно когда речь заходит о разных уровнях зума. С растровыми вполне возможна ситуация, когда ты позумил карту, а она как пила, вся в пикселях, пошакаленная, и нужно зумануть ещё разик, чтобы подтянулись тайлы с более высоким разрешением карты и всё снова стало гладеньким. Векторные тайлы скалируются нативно вне зависимости от зума и там всегда всё гладко, а значит юзер экспириенс от скролла и зума таких карт куда как опрятнее. И там ещё есть ряд плюшек, но в нашем случае они пока не слишком важны. В Даварихе я с самого начала взял растровый Leaflet и уже навайбкодил под него много функциональности, так что переход на векторный, скажем, MapLibre, будет не очень простым, но я всё равно, скорее всего, захочу сделать это в будущем. А пока — красивенькая карта на Leaflet.
Так, ну вроде вот и всё. Меня ужасно привлекает возможность захостить карты от Протомапс дома чисто для себя на случай апокалипсиса, и, думается, я напишу об этом человеческий гайдик, а пока вы (я на вас смотрю, селфхостеры) можете повозиться сами, это довольно несложно: https://docs.protomaps.com/guide/getting-started
Задавайте ваши ответы, а ещё лучше, вписывайтесь в вейтлист на запуск SaaS Давариха (зелёная кнопочка ёрли аксес на главной), мы уже совсем скоро запустимся и это будет так круто, что ну ваще.
Чирс!
Для меня это было как фильм, когда всё идёт по накатанной и каждый следующий поворот безнадёжно предсказуем :)
В области веб-картографии инженерам всегда кажется поначалу, что всё несложно, а потом всплывают монстры из глубин, и люди помаленьку узнают базовые вещи. Я недавно объявил о своей компании AvatudKaart именно затем, чтобы другие не выясняли всё по-новой каждый раз, а просто позвонили и спросили.
Для растровых тайлов до Protomaps использовали MBTiles: по сути, базу sqlite, к которой на сервере ставится небольшой бэк (например, Martin), и делаем вжжж. У Protomaps есть свои проблемы: например, что браузеры (Firefox точно) не кэшируют запросы Ranged HTTP, из-за чего повторные открытия карты заново качают тайлы, а не берут из кэша браузера. Это не починить, а обойти можно так же через Martin или иной сервер с поддержкой pmtiles, который можно направить на файлик в CDN.
Ну и из коммерческих тайлов ещё посоветую Stadia Maps: та же сотня баксов за твою расчётную нагрузку, но стили приятнее, чем у Thunderforest, и сотрудников два человека, а не один.
Спасибо что поделился своими исследованиями
А можешь пожалуйста объяснить почему один большой файл лучше чем несколько маленьких? На мой взгляд маленькие файлы всё равно имеют какую-то адресацию, условно по координатам, точно так же как и внутри большого файла. Только к маленьким ты можешь обращаться напрямую а к большому только через либу.
И если что-то случится с большим файлом то можешь потерять доступ ко всей информации а при маленьких только к сбойнувшим файлам.
Круто!
Понимаю и разделяю радость и от инженерного решения (без десятка кубернетесов) и от потенциальной селф-хостед возможности
У векторных карт ещё сильное преимущество когда нужно точки какие-то поверх рисовать (=10 фпс будет в лучшем случае при 1к+ точек), в 2017 помню оч долго возились на работе, чтобы с gmaps на mapbox переехать из-за этого. Удивлён что проблемы с ценами остались всё те же, решение с одним файлом оч крутое :)
А еще целое другое измерение если хочешь какой-нибудь свой стиль карты, топографический например а не дефолтное что-то. И при этом существуют всякие osmand и прочие приложения которые позволяют вполне рисовать карты с произвольной детализацией в оффлайне с смартфона не занимая при этом сотни гигабайт места
А какая стратегия обновления? Раз в неделю качать?
На всякий случай глянул - на миллион тайлов мы (HERE Technologies) чуть дороже Thunderforest (175 евро). На 10 миллионов гораздо дороже, но похоже почти просто пропорциональный ценник.
Очень приятно видеть такое применение векторных карт!
В своё время немного экспериментировал с визуализацией данных на картах, интересно будет посмотреть как Dawarich будет показывать аггрегированные данные по часто посещаемым местам, например :-)