Московское метро меняется. Желающий представить себе схему, скажем, 1945 года без проблем соберёт данные из открытых источников; остаётся вопрос с представлением результата, — не на круговой диаграмме же его показывать. В статье я расскажу об основных шагах в создании proof-of-concept сервиса, позволяющего показать схему метро, например, на 1 мая 74 года (слева) или станции с глубиной заложения больше 30 метров (справа).



Формируем требования


  1. Показ станций и линий для выбранного диапазона дат
  2. Красота — «Студия Лебедева» разработала замечательную схему; её и возьмём за основу.
  3. Удобство использования.
    • Для ввода значений я решил использовать ползунки (range slider). С ними мы добьёмся интерактивности и изменений «на лету».
    • Я привык к функционалу картографических сервисов, позволяющих менять масштаб и двигать карту, — реализуем его; тем более что наша схема, по факту, и есть карта.
    • Добавим возможность поделиться ссылкой на выборку.


Предполагается, что читатель хотя бы поверхностно знаком с javascript, имеет представление о векторной графике и вообще молодец (заметит ошибку или неоптимальное решение и не поленится предложить лучший вариант комментариях).

Отображаем схему


Чтобы сделать схему интерактивной нам, для начала, нужна сама схема. Взглянем на то, что мы хотим получить и разберём картинку на простые элементы.

Станции


Самое главное на схеме, — станции, поэтому начнём с них. Всего 3 типа: конечные, пересадочные и обычные. 2 прямоугольника и круг. Что может быть проще? Кропотливо расставим их на полотне, а некоторые ещё и повернём.

1 совет
Сохраните схему как картинку, задайте невысокую прозрачность и расположите её под нашим svg-холстом, которому задайте такие же размеры. Это упростит расстановку станций.

Получим что-то вроде этого


Чтобы с комфортом двигаться дальше, я так же добавил в свойства станций их индекс (числовой) и линию, — чуть ниже вы поймёте, для чего это сделано.

2 совет
Если вы делаете нечто большее, нежели proof of concept, не пожалейте нескольких часов на написание редактора с возможностью перетаскивания станций и указания их свойств. Обслуживать вашу карту станет на порядок проще. Целесообразность прямо пропорциональна количеству станций и их свойств.

Линии


Что же такое линия? Это набор отрезков между станциями. У нас есть координаты, индексы и линии станций — а значит мы легко нарисуем отрезки. Пройдёмся по станциям и будем руководствоваться следующей логикой для каждой из них: если есть станция той же линии, но с индексом старше на единицу, то рисуем между ними сегмент.

Взгляните на схему и попробуйте ответить, какие 2 сегмента не будут нарисованы?

Ответ
1. отрезок на кольцевой, соединяющий «младшую» и «старшую» по индексу станции
2. отрезок между «Выставочной» и «Киевской»

Как мы увидим, начало и конец сегмента идут не совсем так, как нам надо. Добавим для каждого из 3 типов станций смещение, высчитанное опытным путём. Так же наши сегменты заданы прямыми, но сами линии много где причудливо изогнуты. Пожалуй, это самая кропотливая часть работы над проектом, для ряда сегментов добавим исключение в поведении. Благо, синтаксис несложный.

Вот наш промежуточный вариант


Переходы


Svg графика использует модель слоёв, а значит чтобы получить нужный результат, каждый переход сделаем с помощью двух кривых: цветная с толстой обводкой пониже и белая с тонкой повыше. Итогое расположение элементов следующее, от нижних к верхним: линии, отрезки переходов с цветной толстой обводкой, станции, отрезки переходов с тонкой белой обводкой, подложка названия станций, название станций.

Названия станций и подложка


Отобразим названия и выровняем текст с помощью свойства text-anchor. Способа осуществить перенос строки, кроме как созданием дополнительного элемента, к сожалению, нет.

Подложку текста сделаем обычным полупрозрачным прямоугольком. Размеры и координаты текста мы получим с помощью getBBox.

3 совет
Потратив некоторое время на поиски более изящного решения подложки текста, на stackoverflow вы найдёте заплюсованное предложение использовать фильтр. Я советую использовать это решение только в случае единичного отображения графики. Если предполагаются дальнейшие манипуляции с графикой, фильтр поведёт себя некорректно.

Перекрашиваем


Я выбрал цвета по умолчанию для станций как цвета их линий. Но что, если мы хотим перекрасить всё по произвольному градиенту? Это не так сложно как может показаться.

Мы имеем произвольный градиент от одного цвета к другому, который для удобства берём в каналах, значение станции (например, глубину заложения 23 метра) и максимальное и минимальное возможые значения (0 для наземных станций и 80 для самой глубокой «Парк Победы»). Посчитаем процентное соотношение значения станции к разности крайних значений и полученное отношение применим к разности значений по кажому каналу. Вот он наш цвет.

Остальные элементы на странице покрасим градиентом, у нас есть координаты и цвета станций — а это всё что нужно.

Фильтруем


Здесь тоже нестрашно. Проверим, находится ли значение станции в выбранном диапозоне и спрячем станцию, если нет. Когда разберёмся со станциями, поймём, что делать с остальными элементами: если станция спрятана, ни название, ни переход к ней, ни отрезок ветки не должны быть отображены.

4 совет
Используйте свойство visibility, так как opacity хоть и спрячет элементы, но оставит их выделяемыми и кликабельными.

Так же мы видим, что некоторые станции имеют одно название. Соответственно, прятать название нужно только когда спрятана последняя из станций, связанных с этим названием.

Остаётся настроить понравившийся вам range slider. По определенным причинам я написал свой. Ленивым адекватным людям посоветую, например, этот.

Масштабируем и двигаем


Ответственность за это возьмёт на себя свойство viewBox. Ребята из «Микрософт» написали отличную статью с примерами. Для перехвата скрола я использовал jQuery Mousewheel. Признаться, не самая тривиальная задача, так как при изменении масштаба нужно учитывать сдвиг относительно изначального положения с соответсвующим зуму коэффициентом.

Почти всё


Я использовал модульную архитектуру (вышло где-то полтора десятка модулей), в помощь взял Snap.svg. Данные подгружались динамично, просчитывались и, так как часть вещей была сделана с помощью promises (я использовал jquery, так что их взял оттуда же), я даже смог добавить нехитрый прогресс бар пока всё загружается.

Радость длилась, пока я не решил зайти с телефона… На весьма неглупой lumia 1020 сервис грузился дольше, чем пол минуты. Я же абсолютно забыли о цене манипуляций с dom. У нас, на секунду, больше тысячи элементов! Да и манипуляции с viewBox на мобильном устройстве не работали нормально.

Исправляем недочёты


С клиента на сервер


Волевым решением переносим логику на сервер и кешируем выдачу, чтобы не считать каждый раз. Теперь можно отказаться от Snap.svg на клиенте!

А, нет, нельзя
Самописный генератор градиентов для svg весил всего ничего и был изящен, но иногда не срабатывал: к некоторым элементам не применялся ни один из градиентов, только цвет. В то же время с другими элементами градиенты работали абсолютно нормально. Устав от «чёрной магии» я вернул библиотеку в строй. Всё же я пишу доказательство работоспособности, а не «продукт».

Нужно закрыть вопрос с масштабированием и возможностью двигать на мобильных. Подумайте, как бы вы справились с этим.

Я поступил так
Начал с поиска библиотек (остановился на hammer.js) и даже ознакомится с документацией, но вспомнил про ТРИЗ. Генрих Альтшуллер определяет идеальный объект, как объект, которого нет, а его функция выполняется.

У нас на носимых устройствах уже есть замечательная поддержка масштабирования и сдвига. А значит… Да, я просто вырубил для телефонов и планшетов манипуляцию с viewBox-свойством.

Что принёс перенос в цифрах?

До


После


Выводы


Проблемы с «Mozilla Firefox»


В процессе работы выяснилось, что «Mozilla Firefox» переоптимизированна. В то время как другие ребята исправно отображают всю графику, «Mozilla» допускает пропуск рендера элементов, если элемент не виден пользователю — закрыт div'ом сверху или просто вне области видимости монитора. Как сознательный гражданин я добавил баг, который до сих пор неподтверждён, так как удовлетворить просьбу предоставить the simplest possible testcase у меня не получается, на малом количестве элементов всё работает нормально, а ссылки на проект, видимо, не достаточно.

Если это читает представитель «Mozilla»: Ребят, работает криво независимо от платформы (windows/mac) и версии (наблюдается и в старых версиях), чесслово.

В качестве заключения


Несмотря на то, что в html5 осталась некоторая сырость, пришло время его использовать. Не нужно нагружать процессоры пользователей, добавляя на каждый второй сайт «модные» эффекты, которые ещё вчера были в рассылке для frontend-разработчиков.

Но пришло время делать удобные транспортные схемы и интерактивные продукты, доступные с любого устройства.

Я не понимаю, почему тот же «Яндекс» не переведёт Я.Метро на новую официальную схему, которая будет работать везде; пользователям мобильных устройств ребята предпочитают показывать ссылки на приложения. Магазины (приложений) полны клиентов для соц.сетей и крупных сайтов и, на мой взгляд, есть в этом что-то неправильное.

UPD


Работа над каждым проектом сопряжена с несколько идеализированными ожиданиями, действительность же отрезвляет.
Я посчитал нужным вынести сюда несколько моментов:
1. Спасибо за обратную связь и первое на моей практике пожертвование. Для авторов (меня в частности) это действитель важно.
2. В существующем независимом виде проект обновляться не будет.
Я допустил много ошибок и согласен с рядом улучшений, которые можно сделать. Их реализация заставила бы отказаться от того, над чем я работаю сейчас, а непрекращающееся развитие метро сделало бы процесс нескончаемым.
3. Я постараюсь ответить на вопросы о реализации или предоставить существующую информацию, если таковые нужны. Спрашивайте в комментариях.

Комментарии (31)


  1. tehnolog
    06.11.2015 13:09
    +2

    Шикарная задумка! Прямо как назад в будущее. Новые технологии для просмотра старых схем. С удовольствием поигрался с временной шкалой.


  1. k12th
    06.11.2015 13:20
    +1

    Я джва года думал о создании интерактивной анимированной карты московского метро, которая бы показавала, как оно росло. Спасибо, что исполнили мою мечту:)


  1. gubber
    06.11.2015 13:37
    +1

    Есть маленький баг. На хроме.
    Если взять верхний ползунок опустить его вниз. ТО он окажется под ползунком, отвечающим за дату старта и его двигать нельзя.


    1. philosophocat
      06.11.2015 13:52

      Дело не в хроме, но оснований спорить с вами у меня нет, как, к сожалению, и решения.
      Тешит, что создатели jquery слайдера и популярного noUiSlider выхода из ситуации тоже не нашли.
      Как в модели слоёв определить, какой ползунок вы ходите потянуть, если тянуть можно оба?


      1. k12th
        06.11.2015 13:54

        Не давать опускать верхний ползунок ниже нижнего.


        1. philosophocat
          06.11.2015 14:01

          Формально и так не даём; но думаю, вы имели в виду небольшое смещение каждого ползунка.
          Так, пожалуй, и правда можно. Но нужно будет добавить каждому ещё один элемент.


          1. k12th
            06.11.2015 14:37
            +1

            Я бы изобразил ползунки по другому вообще, чтобы избежать этого. Вот как-то так: docs.google.com/drawings/d/1Wp8z9Ao8MXxd4-tIErp6d-PXztKrdefpRwuIkfSNpzo/edit?usp=sharing (UX-дизайнер из меня аховый, но идея такова). Тогда они никогда не сольются, хотя интервал между ними может быть нулевой, если юзеру это надо. Идея @Aingix тоже недурна, можно совместить.


            1. philosophocat
              06.11.2015 14:51
              +1

              Мне нравится мысль. Оформить посимпатичнее и на гитхаб :)


              1. k12th
                06.11.2015 15:19

                Из меня верстальщик аховый и времени не густо, но я постараюсь на выходных, если раньше никто не сделает.


            1. marapper
              06.11.2015 15:19

              лучше тогда как на напольных советских весах, треугольниками, и не пеересекутся, и точное значение указывают:


              1. k12th
                06.11.2015 15:20

                Как вариант, да. Главное, что не пересекаются.


      1. Aingis
        06.11.2015 13:57
        +1

        Или нижний опускать вместе с верхним.


      1. marapper
        06.11.2015 15:16

        Сделать выносные элементы у контролов слайдера — у верхнего «гребешок» вверх, у нижнего — вниз. С одной стороны, это уменьшит точность слайдера — но и так огромные контролы делают точность маленькой. Можно пойти дальше и превратить их из квадратов, где точное значение где-то посередине — в «клюв» (КПДВ), где только кончик указывает на точное значение. Таким образом, они не всегда будут перекрывать друг друга на 100%.
        Альтернативно — можно выставлять z-index больший у того контрола, что перемещался последним.

        Перетаскивание перестает работать, если случайно зажал кнопку мыши, когда курсор был на тексте — от этого легко избавиться. Контрол зума расположен не в самом логичном месте. Если вынести его слева от карты, переместить шаринг вниз к информации, а переключатель типов данных сделать переключателем (для этого достаточно добавить стрелочку, которая покажет, что там выпдайка, и показывать две разные иконки в случае «времени постройки» и «глубины заложения»), то этого будет достаточно для того, чтобы появилась внутрення логика.

        Ну и сразу, как улучшить слайдер дальше:
        1) Отметить на нем год,
        2) Дать возможность уменьшать-увеличивать дату не неточным дрег-н-дропом мыши, а стрелочками «след.дата» — «пред.дата». Как они должны выглядеть, надо, окнечно, подумать,
        3) Цветным маркером отметить даты ввода в строй линий этого цвета
        4) Похожим маркером отметить дату «соединения» линии для тех случаев, когда она строилась «раздельно»
        5) Маркерами меньшего приоритета можно отметить даты ввода в строй и количество станций, при этом делать их светлее (неактивнее), если они выпадают за пределы текущего значения,
        6) Не хватает проектных и строящихся линий и станций — в любом случае, при первой загрузке лучше выставить верхнюю дату не 01.01.2016, а текущую.
        7) Не учтены переименования станций, что тоже весьма интересная информация, которую хорошо бы как-то визуализировать,
        8) Названия веток и их изменения?
        9) Возможно, стоит выделять добавленное на последнем перемещении слайдера,
        10) Еще, возможно, интереснее, чем вообще убирать линии и станции с экрана, было бы их «засветлять».
        11) Можно увеличить количество информации по каждой станции :)
        12) На слайдере глубины заложения (странно, что искусственные объекты заложены, а грунтовые воды залегают :) тоже не хватает ориентиров — промежуточных рисок, отметок станций, которые могут сформировать мини-карту распределения глубин.
        13) Ну и для тех казуалов, кто воспринимает информацию в «футбольных стадионах», можно около слайдера схематично набросать что-то высотой в 80 метров (или, лучше, что умещается в выбранный диапазон — считать в «попугаях» :)
        14) Для карты залегания дополнительная инфа — наличие рек, как минимум (вроде на лебедевской карте должна быть).

        Ну т.е. слайдер двигать-то интересно, но даже в интерактивной графике всегда хочется, чтобы несколько слоев информации всегда было видно на экране.

        Из этого вытекает необходимость в непрерывном, а не дискретном слайдере — точнее говоря, он должен быть непрерывным в плане того, что расстояния между датами должны быть пропорциональны количествам дней между ними, а вот «переключать» контрол можно только между датами изменений в метро (т.е. дискретно).

        Все равно, хорошая работа, сразу приходят в голову идеи посмотреть на фоне роста метро, как росла Москва за это время и популяция в некоторых районах, как появлялись культурные и т.п. объекты, обуславливающие необходимость новых станций метро, возможно, сравнить с изменением сети наземного общественного транспорта.


        1. philosophocat
          06.11.2015 15:34

          Ого! Масштабный комментарий, спасибо за него :)
          Думаю, большую часть пунктов я сниму, напомнив что это просто доказательство работоспособности :)
          Для меня и этот результат оказался непростым — ведь столько ещё можно доделать и поправить, а «заказчика» и дедлайна нет.

          Прокомментирую пару пунктов:
          2. Проект подразумевает возможность загружать больше выборок. Например, помимо глубины заложения, можно добавить статистику инцидентов, чтобы посмотреть наиболее проблемные станции и участки. То есть над каждым конкретным случаем нужно будет думать о «шаге» для стрелки
          3, 4, 5, 8, 9, 10, 13, 14. Всё сводится к дизайну. Я бы, например, был рад реализации в привязке к наземной карте (парки, достопримечательности и т.д.), — но ведь это уже просто огромный объём работ.
          6, 7. Дата выставляется из соображений экстремумных значений. Я поленился добавлять дизайн и функционал для строящихся и ремонтируемых станций. Тот же «Технопарк» у меня предварительно открыт :)


          1. marapper
            06.11.2015 15:38

            На гитхаб планируете? :)


            1. philosophocat
              06.11.2015 15:49

              Не планировал. Из-за жесткой привязки данных не представляю, как его кто-то сможет использовать :(
              Думаю, такие вещи проще писать самому, чем разбираться в чудом «полёте мысли» и модифицировать под свои нужды.
              P.S. Возможно, окажется полезным «вектор» — координаты и кривые. Хотя хема в pdf и в свободном доступе, разобрать её на составляющие не получится.


              1. marapper
                06.11.2015 16:00

                Жаль. Я думаю, сообщество бы придумало, как модифицировать :)


              1. k12th
                06.11.2015 16:01
                +1

                Использовать может нет, а помогать развивать — вполне да.


          1. Andriyan
            06.11.2015 19:40

            Жаль, что доделывать не планируете — очень уж наглядно получилось.
            На всякий случай напишу одно замечание по схеме — вдруг исправите.
            С момента ввода станции Курская-радиальная и до ввода станций Арбатская, Смоленская и Киевская глубокого залегания поезда ходили от Киевской-мелкой до Курской и дальше, то есть использовался (существующий до сих пор) перегон между Александровским садом и Площадью Революции.


            1. philosophocat
              06.11.2015 19:58

              Спасибо, интересный факт.
              Кто знает, может пройдёт немного времени и действительно приведу код в порядок и выложу на гитхаб, чтобы коллективно поддерживать. В конце концов, я и логику на сервер переводить не хотел, а всё же нашёл время.
              Сейчас останавливает то же отсутствие времени, а так же гарантий, что присоединятся энтузиасты и перевод не будет впустую. Второй волны интереса добиться будет в разы труднее.


  1. HunterXXI
    06.11.2015 14:05

    Очень хорошая работа!


  1. zagayevskiy
    06.11.2015 14:30

    пользователям мобильных устройств ребята предпочитают показывать ссылки на приложения.
    Приложение может работать в оффлайне.


    1. philosophocat
      06.11.2015 14:55

      Конечно, может.
      Я не предлагаю объявлять байкот магазинам приложений, просто прошу дать мне возможность воспользоваться сервисом без установки приложения.


  1. khim
    06.11.2015 15:39

    Задумка неплохая, но это действительно proof-of-concept. Схема метро менялась довольно сильно со временем. Не просто линии достраивались, но происходили разные другие изменения. Например в 43м году схема показывает отдельно живущую станцию «Автозавдская» что вряд ли соотвествует действительности: вряд ли её открыли как «действующий музей»…


    1. philosophocat
      06.11.2015 16:22

      Ваша правда, так и есть :)
      Помимо этого перегона, я пропустил ещё один между «Маяковской» и «Театральной».


  1. niko1aev
    06.11.2015 15:48
    +1

    И еще со станцией Тверская какое-то безобразие.
    Открыта 20 июля 1979 года на действующем перегоне «Маяковская» — «Театральная».

    Это один из самых старых перегонов Московского метро, открыт 11 сентября 1938 года! А на схеме появляется только в 1979 году, на 40 лет позже! Нужно сделать так же как со станцией Спартак. А так все очень круто. Если все эти исторические недочеты убрать (мне кажется их там намного больше чем 2) — то будет шикарнейшая схема) Спасибо


    1. philosophocat
      06.11.2015 15:57
      +1

      Спасибо за внимательность :)
      Обращал внимание на «короткие» случаи вроде Технопарка, Спартака и Дубровки и пропустил самый большой.


  1. berezuev
    06.11.2015 15:58

    Отличный проект.
    Некоторые пожелания (помимо тех, что уже упомянуты в комментариях):
    1) Добавить изменения названий станций.
    2) Приблизить внешний вид к схеме, которую брали как пример
    3) Взять генплан строительства метро до 2020 года (гуглится на раз-два), и добавить его в шкалу
    4) Ползунок логичнее сделать с отметками только на реальных изменениях (сейчас там просто календарь)
    5) Потом уже можно и другие города добавить)


    1. philosophocat
      06.11.2015 16:10

      Спасибо.
      Не хочу вас расстраивать, но шансов, что буду реализовывать пожелания из комментариев, очень мало.
      В свободное время уже работаю над своим «долгостроем», если доделаю — будет на порядок больше пользы.

      Плюс, по моему мнению, такими вещами должен всё же департамент транспорта заниматься или московский метрополитен. Поддерживать в актуальном состоянии схему «извне» очень сложно.
      Ребята планируют очередное повышение цен и раздают бесплатные газеты, в то время как сайтом пользоваться откровенно сложно, а существующая схема на флеше. И ладно бы умела что-то, но нет, просто ссылки :)


      1. berezuev
        06.11.2015 16:12

        github? :)


        1. philosophocat
          06.11.2015 16:14

          Ответил выше :)