В части N вам нужно было научиться рисовать карту маршрутов между остановками. Вы уже в части Е еще в коричневом поясе научились строить маршрут между остановками наиболее оптимально, затем вы в черном поясе только и делали, что рисовали карту, и вот наконец-то вы используете ваше умение рисовать карту, ваш класс MapRenderer для рисования карты конкретного произвольного, практически, маршрута. Мы начинаем разбор решения части N с того, что у нас меняется формат ответа на запрос route, ну и здесь в request.cpp понятно, что мы добавляем новый ключ в ответ, ключ-map и нам ничего не остается, кроме как добавить метод в транспорт каталог метод RenderRoute, метод для рендеринга маршрута и его здесь вызвать, здесь изменения минимальны. Дальше мы идем в класс транспорт-каталог и ожидаем там увидеть метод RenderRoute, метод рендеринга маршрута. Здесь вызывается приватный метод BuildRouteMap, в котором для построения карты маршрута используется класс MapRenderer, но теперь он существует не только в рамках конструктора транспорт-каталога, потому что, если вы помните, в предыдущих частях мы просто в конструкторе транспорт-каталога строили MapRenderer и использовали его, полученную карту сохраняли в поле транспорт-каталога и все, карта готова. Теперь же в класс транспорт-каталог с помощью метода RenderRoute могут приходить запросы на рендеринг разных маршрутов, поэтому класс MapRenderer нам пригодится, мы его сохраняем в поле транспорт-каталога и затем используем. Соответственно, мы эти поля можем увидеть в заголовочном файле танспорт-каталог.h. Мы здесь видим не только карту построенную, но и MapRenderer. ОК, посмотрим, давайте, на сам метод RenderRoute и увидим, что он принимает наряду с информацией о маршруте ровно в том формате, которму отвечал транспорт-роутер, он принимает саму целиковую карту всех маршрутов. Если вернуться к реализации BuildRouteMap, то здесь действительно передавалось приватное поле в метод RenderRoute. И если снова вернуться к сигнатуре метода RenderRoute, мы видим, что эта карта копируется, то есть, что мы делаем, мы строим целиковую карту всех автобусов, то, что и делали в предыдущих частях и эта карта нам понадобится, чтобы отвечать на запрос Map, но еще эта карта основа для карты любого маршрута, поэтому разумно эту уже построенную карту уметь копировать, чтобы затем добавлять к ней дополнительные объекты, вот этот полупрозрачный прямоугольник, линии про маршруты и так далее, и так далее. Нам нужно научиться копировать svg.document. И мы могли бы сказать: "А, что там, в любом плюсовом классе по умолчанию генерируется конструктор копирования". Но есть нюансы. Если мы посмотрим в svg.document, мы увидим, что объекты там хранятся в виде вектора Unique-пойнтеров. А Unique-пойнтер на то и Unique, что их нельзя копировать, потому что нельзя, чтобы несколько указателей владели одним объектом. Unique-пойнтер нельзя копировать, их может только перемещать, поэтому вектор Unique-пойнтер нельзя копировать и поэтому класс содержащий вектор Unique-пойнтеров не получает сгенерированного по умолчанию конструктора копирования. И здесь нам придется как-то порефакторить, без копипаста желательно, нашу иерархию svg-объектов. Соответственно, чтобы написать конструктор копирования, ну, вы видите, он тут уже написан, но без реализации, то есть интрига остается, чтобы скопировать вот такой вот вектор Unique-пойнтер, нужно уметь скопировать каждый конкретный объект. Но с точки зрения класса Document Unique-пойнтер на конкретный объект это просто указатель на базовый класс и мы не можем никак понять нормальными средствами, что это за тип там внутри лежит, только, если проверить тот это тип или не тот. Поэтому нужно использовать механизм виртуальных методов, вам уже известный, и логично добавить к объекту виртуальный метод "Скопируй меня", скопируй меня и дай мне вот, собственно, Unique-пойнтер на мою копию. Можно было бы в каждом объекте написать метод Copy или метод Clon, по разному называют, и там создавать просто Unique-пойнтер на копию. Но мы хотим избежать копипаста, поэтому мы используем ту же технологию, что мы делали с PathProps со свойствами всевозможных линий, которые мы выносили в отдельный класс, мы делаем класс CopyableObject, который является объектом с одной стороны, с другой стороны, он знает что за тип должен уметь копироваться, то есть он, опять же, шаблонизирован типом своего владельца и имеет метод Copy, который возвращает Unique-пойнтер на базовый класс Object. И здесь в этом методе Copy, если мы посмотрим на реализацию, мы кастуем указатель на текущий объект к дочернему классу Owner, то есть"*this" к ссылке на класс Owner, и вызываем MakeUnique от Owner-а передавая туда эту самую копию скастованный указатель "*this". В итоге метод Copy в зависимости от того, чем шаблонизирован наш тип CopyableObject возвращает, копирует нужный объект, возвращает Unique-пойнтер на копию и теперь, во-первых, нам нужно отнаследоваться во всех объектах от CopyableObject, вместо Object-а, у них будет доступен нужный Copy, он будет работать правильно, если мы здесь укажем правильный тип — для Circle это будет Circle, для Polyline-а — Polyline и так далее, ну так мы уже делали в PathProps, и теперь нужно написать конструктор копирования для Document. И оператор присваивания тоже может пригодиться. Вот, снова смотрим на Document, смотрим на реализацию svg:cpp, ищем здесь конструктор копирования Document и вот он. Мы резервируем нужное количество места под столько же объектов, сколько у нас было, итерируемся по всем объектам и для каждого вызываем метод Copy. Все, мы получили вектор скопированных Unique-пойнтеров. И у нас готов конструктор копирования для документа, аналогично можно легко реализовать оператор присваивания и у нас есть полноценный класс Document, его можно спокойно копировать. Напомню, что это было нужно для того, чтобы копировать целиковую карту автобусов и поверх нее рисовать карту маршрута. Возвращаясь к Renderer-у, наш Renderer хранился, раньше он существовал только в конструкторе, теперь же он начал храниться в поле транспорт-каталога, то есть теперь он должен жить дольше, чем конструктор. И такие преобразования, такие изменения времени жизни чреваты инвалидацией указателей и ссылок. Поэтому нужно внимательно посмотреть на поля MapRenderer-а, и раньше в MapRenderer-е, если вы помните, хранилась ссылка на словарь автобусов. Тот самый словарь автобусов, который в конструкторе транспорт-каталога строился. Вот этот самый BusesDict. Но он строился здесь и никуда не складывался. Более того, внутри него хранился указатель на структуру автобуса из исходного запроса. Если мы не будем менять код MapRenderer-а и оставим там ссылку на этот самый словарь, то этот словарь уничтожете после выхода из конструктора и исходная карта построится нормально, а карта маршрутов при построении будут пытаться получить доступ к уже уничтоженному словарю автобусов. Поэтому есть несколько вариантов, что с этим можно сделать. Есть долгий и хороший путь, путь такой а-ля Python. Вам нужно как-то легко решить проблемы с владением и временем жизни. Для этого вы можете все указатели, указатели на автобусы, например, и ссылки на этот словарь заменить на Share-пойнтеры и пусть, когда они нужны, тогда нужны, а когда уже будут не нужны, тогда они уничтожатся. Есть другой путь, чуть более простой и требующий меньшего рефакторинга, это в конструкторе MapRenderer-а, вот, давайте, на него посмотрим, просто получив ссылку на этот словарь, забрать его себе. Например, простым копированием. Давайте найдем конструктор MapRenderer-а, тут уже довольно много кода с предыдущих частей, вот у нас здесь рендеринг, рендеринг, рендеринг разных частей, туда мы еще посмотрим, а вот, собственно, конструктор MapRenderer-а, в котором мы теперь вызываем функцию CopyBusesDict. И функцией CopyBusesDict мы просто итерируемся по всем автобусам и копируем себе description::bus во внутренний словарь. Соответственно, надо везде порефакторить MapRenderer из срр, чтобы использовать вместо ссылки на словарь с указателями конкретное поле с самими автобусами. Такой, может быть, не самый оптимальный путь, но, тем не менее, с точки зрения человеческих ресурсов он довольно быстрый. И, если мы не упираемся в ограничения по времени, то, как вы помните, все у нас в порядке. Хорошо. Мы увидели, что нам приходится копировать словарь автобусов, что еще происходит в MapRenderer срр? Конечно, у нас здесь должен быть метод рендеринга маршрута, вот который принимает карту, копируя ее при принятии в параметры, принимает маршрут. Конечно, тут есть оперирование новым параметром render_settings_outer_margin, но это не очень интересно. Из более интересного, конечно, нужен новый словарь методов рендеринга, новый маппинг из названия слоя в метод рендеринга этого слоя для маршрута, и у нас появляются новые методы: RenderRoutBusLines, RenderRouteBusLables и так далее. Словарь выглядит точно так же, как он выглядел раньше, и мы это разбирали еще в части А и когда у нас появились слои. В самом методе здесь мы итерируемся по всем слоям и точно так же вызываем методы рендеринга конкретных слоев для маршрута и передаем туда не только svg документ, но и теперь информацию про маршрут. Давайте, посмотрим на методы рендеринга разных слоев для маршрута, например, RenderRouteStopLables, отрисовать названия остановок для конкретного маршрута. Здесь, конечно, код немножко другой, не такой, как метод рисования названия остановок для всей карты. Здесь мы итерируемся по частям маршрута и опять в соответствии с тем, что было написано в условии задачи, каким-то образом рисуем названия остановок. Но нам нужно избежать копирования кода между методом рисования всех названий остановок и методом рисования названия остановок для конкретного маршрута. Поэтому мы ожидаемо выделяем отдельный метод, приватный метод RenderStopLable для рисования названия одной остановки. Вы видите, каким этот метод получился. И этот метод используем и в RenderRouteStopLables и RenderStopLables. Тем самым код RenderStopLables значительно сократился. Итак, мы порефакторили немного MapRender из cpp вынеся общий код, общий код рендеринга всей карты и рендеринга маршрута. Здесь есть некоторые ToDo, давайте на него посмотрим из интереса. Вот, например, рендеринг всех кругов остановок, всех точек остановок для маршрута. Здесь повторяется один и тот же паттерн для разных методов, в которых мы рендерим части маршрута итерируясь по автобусам. И понятно, что здесь нам приходится проверять, правда ли этот item маршрута, этот кусок маршрута, это поездка на автобусе, если да, то мы вызываем метод Get, получаем BusName, получаем Stops, и в нескольких методах это используется. Почему я не стал это рефакторить? Потому что я жду С++20 и рейнджерс. Когда у меня будет рейнджерс, я напишу отдельный метод, который мне по набору item-ов маршрута отдает только item-ы на автобусы и этим самым я сокращу код. Пока это довольно небольшое копирование и с ним можно жить. И, наконец, еще один интересный момент, когда мы рендерим, например, все круги остановок, нам нужно итерироваться по всем установкам, по которым мы едем: от начальной остановки, от остановки, на которой мы сели в автобус, до остановки, где мы вышли из автобуса. И здесь недостаточно просто названия остановок, потому что, как вы помните, один автобус может проезжать через одну остановку несколько раз. И поэтому нам полезно в самом маршруте иметь не только названия остановок, но и их номера в самом маршруте. Вот здесь это start_stop.idx и finish_stop.idx. Чтобы мы их здесь получили, их нужно сложить в информацию о ребрах при создании транспорт-роутера. Опять же, наряду со span count мы эти индексы складываем и здесь мы имеем к ним доступ и спокойно используем. Итак, что мы изменили при написании кода части N? Мы немножко поменяли формат вывода, мы добавили метод рендеринга маршрутов в транспорт-каталоге, мы стали хранить MapRenderer в поле транспорт-каталога и нам понадобилось скопировать внутри MapRender-а словарь автобусов, чтобы ссылка не инвалидировалсь. Нам пришлось научиться копировать svg-документ, чтобы копировать целиковую карту автобусов и в самом MapRenderer нам пришлось вынести общий код рендеринга всей карты и рендеринга маршрутов. Вот и все решение части N.