Обсудим решение части G. Чем оно отливается от решения части E? Что нам пришлось доработать в том коде, который мы вам показывали в решении части E? Во-первых, появляются настройки отрисовки карты. Ну, и наряду с настройками маршрутизации мы их тоже считываем и передаем в конструктор transport-каталога, это всё довольно логично. Ну, и больше функция main, по сути, не меняется. Ну да, у нас появляется новый тип запросов в базе — это карта, там нужно вывести карту. Ну, главное сделать метод в transport-каталоге, который будет возвращать этот самый SVG-документ. Самое интересное, конечно, в transport-каталоге. Давайте на него посмотрим. Вот тот самый конструктор, который принимает ещё и render_settings_json наряду с routing_settings_json, и он получил метод RenderMap, который возвращает строчку с картой. А как мы дальше, в какой момент мы будем рисовать карту? Предлагается непосредственно в конструкторе transport-каталога построить весь нужный SVG-документ, положить его в приватное поле — и в RenderMap просто этот SVG-документ рендерить. Вот так выглядит RenderMap. Создаём ostringstream — поток, вызываем метод render у SVG -документа и выводим строчку полученную — и всё. Значит, самое интересное — построить карту в конструкторе transport-каталога. Окей. Смотрим в конструктор TransportCatalog. Тут помимо того, что было раньше, добавляется строчка map_ = BuildMap. А BuildMap — это... Ну давайте я до него докручу для начала здесь... Это статический метод, приватный статический метод transport-каталога, который принимает всего лишь три параметра — это остановки в виде того самого словаря. Ну это словарь вида... Давайте я открою descriptions.h чтобы вас не путать. Вот здесь у меня есть Dict — это словарь из строки в указатель на остановку, ну и вот StopsDict — это словарь из строки в указатель на остановку, BusesDict — это словарь из строки в указатель на автобус. Ну, потому что ровно это мы строили вот здесь. Ну и, соответственно, BuildMap принимает ровно в таком виде остановки, автобусы, настройки отрисовки. Сам BuildMap при этом довольно скучный, как вы можете видеть. Он строит объект типа MapRender — ну отрисовщик, с теми же самыми аргументами — и вызывает, оттого что получился метод render. И всё, и он дальше всё наружу прорастает спокойно. Значит, самое интересное у нас в классе Map Renderer, который строится по остановкам, автобусам и настройкам отрисовки и умеет нарисовать карту. Хорошо. Значит, идём глубже в Map Renderer. Как он выглядит? Descriptions я закрою... Map Renderer — ну, здесь хранятся настройки отрисовки уже в нормальном формате. Здесь есть вот те самые ширина/высота карты, отступ от краёв, цветовая палитра, ширина линии и т.д., и т.д., и т.д. Вот он строится по тому самому, что мы видели — остановки, автобусы и json — с настройками отрисовки; ну и у него единственный публичный метод render, который возвращает SVG-документ. У него довольно интересные приватные поля и довольно ожидаемые приватные методы. Приватные поля — это те самые настройки отрисовки, ну вот та самая большая структура, это всё-таки словарь автобусов (ну он нам пригодится), это координаты остановок (ну видимо мы посчитали их как раз в конструкторе, это довольно удобно для композиции программы) и цвета автобусов (ну и здесь просто словари; для каждого названия автобуса — цвет этого автобуса ну в самой линии и названия автобусов на конечных). И для каждого названия остановки — просто координаты на карте для этой остановки Svg: Point. Ну и довольно понятные методы: нарисуй мне линии автобусов, нарисуй мне круги остановок, нарисуй мне названия остановок. Благодаря тому, что все нужные данные для отрисовки слоёв уже есть в полях Map Renderer, все эти методы имеют довольно простую и понятную сигнатуру: возвращают в void константные и просто принимают SVG-документ, в который нужно дописать ещё какие-то SVG-объекты. Ну и, соответственно, в конструкторе Map Renderer происходит действительное вычисление координат остановок и цветов автобусов. Это выглядит как? Это выглядит... (сейчас я найду конструктор... вот этот конструктор!) Ну, во-первых, мы вызываем ParseRendersSettings, и весь парсинг этого самого json действительно происходит в этом же файле. Помимо этого, мы просто перекладываем словарь с автобусами, а ещё мы вычисляем координаты остановок и вычисляем цвета автобусов. Вычислять цвета автобусов — довольно скучное занятие; вот этот код умещается в пол-экрана, потому что очень простой алгоритм распределения цветов в автобусах. А вычисление координат остановок — чуть более интересная история. Давайте я открою этот метод. На самом деле это просто реализация этого самого математического алгоритма, который был описан в условии. Я просто беру условно все точки, которые у меня есть, сохраняю ширину, высоту и отступ, ну и теперь, по сути, мне нужно написать следующий алгоритм. У меня есть набор точек, у меня есть тот диапазон, в который их нужно вместить — тот прямоугольник с отступами, и мне нужно для каждой точки научиться вычислять её новую координату. Я это просто решил выселить в отдельный класс — Sphere Projector, и вот в файле sphere_projection.h он и лежит. Это вот такой простой класс, который строится по набору точек (вот два итератора принимаются), по ширине, высоте и паддингу, и умеет с помощью оператора круглой скобки по произвольной точке вычислять её новые координаты. Ну и что-то вспомогательное внутри хранит. Ну и внутри, по сути, написана просто вот та самая формула линейного преобразования исходных географических координат к новым координатам на карте. В чём эта логика просто отсеивания в отдельный класс? Потому что так удобно. И дальше с помощью оператора круглой скобки для этого самого Projector вычисляются новые координаты всех остановок. Всё. Всё, что мы посчитали, мы сложили в поля — приватные поля Map Renderer, и теперь, когда нас просят построить карту (это ещё конструктор transport-каталога нас просит методом render) в методе render, я просто говорю: «Нарисуй мне линии автобусов, нарисуй мне круги остановок, нарисуй мне названия остановок». Просто по очереди вызвал три этих метода (это нам ещё, кстати, пригодится в части I). Ну и в самих этих методах мы просто так, как сказано, создаём все нужные SVG-объекты — тут вообще ничего интересного, просто цикл по автобусам. Например, вот RenderBusLines в RenderStopLabels — цикл по остановкам, ну и в RenderStopPoints — тоже просто цикл по остановкам; здесь уже ничего особенно интересного. Вот RenderBusLines, RenderStopPoints, RenderStopLabels — просто простейшие циклы и создание нужных SVG-объектов с нужными характеристиками. Вот и всё. Ну и на самом деле класс Map Renderer нужен в основном для того, чтобы вот эти render-методы были максимально простыми и использовали уже предпосчитанные поля этого самого класса: маппинг из остановок и их координат и маппинг из автобусов и их цвета. Это довольно удобно. Ну из того, что ещё здесь было интересного — это доработки в библиотеку SVG. Поскольку нам нужно было выводить полупрозрачные подложки под всеми надписями, то нам нужен был ещё один способ создания цвета и вывода цвета — это RGBA, ну то есть RGB с прозрачностью. Ну и как мы это сделали? Мы написали новый класс RGBA, не стали повторять все те поля red, green, blue, которые уже были в RGB, просто отнаследовались и добавили поле прозрачности, а color — это variant из monostate, из string, rgb и rgba. Ну и, в общем, на этом доработки в SVG закончились. Итого, благодаря исходно хорошей структуре решения функции main изменения минимальны. Ну, добавился новый тип ввода — это Render Settings — окей, мы его просчитали, прокинув transport catalog. Всё, в функции main небольшое изменение. В transport-каталоге, да, понадобилось подумать о том, что я карту сохраню в поле. Ну окей, я сохранил её в поле, я создал её в конструкторе — больше ничего в transport-каталоге не писал, по сути. Всё самое интересное вынес в отдельный новый класс, который занимается построением карты Ну и именно его мы будем дорабатывать в следующих частях.