Интерактивная карта развития Московского метрополитена
15 мая 1935 была открыта первая очередь Московского метрополитена. С этого момента началась новая эра в истории города, в котором подземка, безусловно, играет важную роль.
Любопытная деталь, что в течение следующих десятилетий схема метро изображалась с привязкой к реальному расположению на карте города. И только в семидесятых годах прошлого века, когда общее количество станций приблизилось к сотне, начали использовать схематичное изображение линий метро, которое оказалось удобнее и проще для восприятия пассажирами.
Упрощенная схема читается быстрее. Но, в то же время, не дает представления о реальном расположении линий, скрывает детали. Длинные перегоны метро ничем не отличаются от коротких. Нет привязки к географии.
Давайте создадим интерактивную карту линий Московского метрополитена и посмотрим на его историю — как развивалась одна из самых больших сетей мира.
Для создания такой карты нам потребуются:
Механизм
Для начала, определяемся с требования к механизму. Он должен уметь:
отображать базовый слой с «обычной» картой — дорогами, водоемами, улицами, зданиями и т.д.;
отображать поверх базового слоя круги и линии, которые будут изображать станции и линии метро соответственно;
обновлять отрисованные станции и линии метро в зависимости от выбранной даты;
переключать текущую дату в интервале от 15 мая 1935 года (запуск первой линии метро) до настоящего времени.
Первые три пункта из коробки есть в open source библиотеке Leaflet. Четвертый будем реализовывать самостоятельно.
Базовый слой карты
Пример 1: Создание базового слоя карты
Что такое tile server?
Небольшое пояснение для тех, кто никогда не сталкивался с понятиями тайлов (tiles) и тайлового сервера (tile server).
Онлайн карты, к которым привыкло большинство пользователей, — OpenStreetMap, Яндекс.Карты, 2ГИС и т.п. — состоят из тайлов (tile): квадратов с длиной стороны в 256 пикселей.
Каждый тайл имеет координаты x, y и z:
z — масштаб, при котором он отображается (чем больше значение z, тем больше увеличение);
x — порядковый номер тайла по горизонтали при выбранном масштабе z;
y — порядковый номер тайла по вертикали при выбранном масштабе z.
Например, тайл с координатами x=2476, y=1280 и z=12 выглядит вот так:
Эти «квадраты» хранятся на тайловом сервере (tile server) в определенной структуре. Например, тайл выше доступен по адресу https://tile.openstreetmap.org/12/2476/1280.png
Полное изображение, которое мы, в конечном счете, видим, склеивается из множества тайлов в единую картину:
Когда мы работаем с картой в браузере — изменяем масштаб или прокручиваем в стороны — нужные тайлы, по мере необходимости, загружаются с сервера.
В нашем случае в качестве тайлового сервера используется сервер tile.openstreetmap.org.
Станции и линии метро
Для отображения станций метро используем circleMarker: круг с заданными в пикселях радиусом, цветом обводки и заливки, прозрачностью и прочими свойствами.
Пример 2: Добавляем станцию метро на карту
Создаем красную окружность с координатами 55.7578487° северной широты и 37.6163659° восточной долготы (станция «Охотный ряд»), с белой заливкой, радиусом 6 пикселей и добавляем его на карту:
L.circleMarker([55.7578487, 37.6163659], {
radius: 6,
width: 2.5,
color: "red",
fillColor: "white",
fillOpacity: 1
}).bindPopup("Охотный Ряд")
.addTo(mymap);
Для отображения линий метро будем использовать Polyline: ломаную с заданной шириной, цветом, прозрачностью и другими параметрами.
Пример: Добавляем линию метро на карту
Добавим первую очередь московского метро, открытую 15 мая 1935 года. Это будет ломаная красного цвета, шириной 4, проходящая через соответствующие станции метро.
const plln = [
[
[55.7888499, 37.6801924], // Сокольники
[55.7798626, 37.6666841], // Красносельская
[55.7754719, 37.6566036], // Комсомольская
[55.7691137, 37.64887], // Красные Ворота
[55.7658915, 37.6388871], // Кировская
[55.7598058, 37.6264977], // Дзержинская
[55.7578487, 37.6163659], // Охотный Ряд
[55.7515247, 37.6102524], // Библиотека имени Ленина
[55.7454796, 37.6036755], // Дворец Советов
[55.7352856, 37.5940414] // Парк культуры имени Горького
],
[
[55.7578487, 37.6163659], // Охотный Ряд
[55.7524242, 37.6086732], // Улица Коминтерна
[55.7519189, 37.6007512], // Арбатская
[55.7488633, 37.5826955] // Смоленская
]
];
L.polyline(plln, {
color: "red",
weight: 6,
}).bindPopup("Кировско-Фрунзенская линия")
.addTo(mymap);
Обратим внимание, что при движении от Сокольников, после Охотного ряда, была развилка. Поезда следовали в двух направлениях: в сторону Смоленской и в сторону Парка культуры имени Горького.
Удаление и добавление новых кругов (станций) и ломаных (линий метро) также является встроенными функциями Leaflet — это потребуется для перерисовки схемы метро в зависимости от выбранной даты.
Переключение текущей даты
Следующим шагом нужно будет прикрутить слайдер с диапазоном от »15 мая 1935» и до сего дня. С помощью него мы будем задавать «текущую» дату для отображения схемы метро, какой она была в этот день.
Алгоритм прост: при изменении значения слайдера выполняем по порядку:
очищаем текущую схему,
проходим по всем объектам (станциям и линиям) в «базе»,
выбираем те объекты, которые существовали в новую выбранную дату,
отображаем их на карте.
Чтобы определить, какие объекты существовали в указанный день, необходимы правильным образом подготовленные данные. Например, нужно сохранить в «базе» детали о том, что с момента открытия в 1935 году и до 1990 года станция метро «Чистые пруды» называлась «Кировской». Или, например, что первая версия станции «Первомайская» полностью закрылась в 1961 году.
Чем проще и удобнее будет модель хранения данных, тем удобнее будет работа с самими данными.
Модель
За основу возьмем готовое решение, прошедшим проверку временем — модель данных OpenStreetMap. И доработаем ее до наших целей там, где её будет не хватать.
В OSM все данные состоят из трех видов элементов (elements): точки (node), линии (way) и отношения (relation).
Точка (Node)
Node — простейший элемент, который хранит в себе координаты широты и долготы.
Пример Node
Nodes, с координатами станций первой линии метро на момент её открытия 15 мая 1935 года в нотации OSM XML формате (без служебных данных):
Линия (Way)
Way — упорядоченная ломаная линия, состоящая из последовательно заданных точек (nodes).
Пример Way
Отношение (Relation)
Отношение (relation) — это группировка элементов (elements) по определенному признаку. В отношение могут входит точки (nodes), линии (ways), а также, другие отношения (relations).
Пример Relation
Например, объединение всех станций и участков метро могут объединяться под одним отношением (relation) — «Московский метрополитен».
…
Тег (Tag)
Также, у каждого из элементов (elements) могут быть заданы теги: пара «ключ=значение». Теги определяют свойства объекта, и используются для описания того, как объекты будут отображаться на карте.
Пример Tags
Например, станция метро может иметь такой набор тегов
Доработка модели данных OSM
OpenStreetMap создана и развивается для отображения актуальных деталей — того, что существует в данный момент. Для некоторых типов объектов есть возможность указать год их возникновения (например, год постройки здания). Но про объекты, которых уже не существует, или которые изменялись в прошлом, данных нет.
Чтобы это исправить, к каждому объекту, который может отображаться на карте, добавляем две характеристики:
from_date — дата, с которой объект появляется на карте
to_date — дата, с которой объект более не должен отображаться на карте (например, временная станция Калужская: from_date = 1964–04–15, to_date = 1974–08–12).
С помощью этих характеристик можно будет управлять отображаемыми на карте данными, в зависимости от выбранной даты.
Собираем всё вместе
Точки (nodes) и линии (ways) будут использоваться для определения геометрии — точек и ломаных линий, из которых состоят все объекты. У них есть только уникальные идентификаторы (id) и их координаты: широта и долгота.
Отношения (relations) будут использоваться для определения самих объектов (станция или линия) и их характеристик. Relations состоят из комбинации nodes или ways, и дополняются необходимыми тегами. Теги from_date и to_date являются обязательными.
Такой подход позволяет использовать одни и те же nodes и ways повторно: вся информация об объекте хранится на уровне relation.
Например, чтобы отобразить, что станция метро Кировская была переименована в Чистые пруды, будет использовано два relation. Один: с момента открытия до момента переименования. Второй: с момента переименования и до настоящего времени.
Пример relations для переименованной станции метро
Аналогичная история с участками линий. Например, если с 1935 года участок путей принадлежал Сокольнической линии, а с 1938 года стал участком Арбатской линии, то нам потребуется три relation.
Пример relations для изменения линий
Данные
Подготовка данных состояла из трех шагов.
Первым шагом была начальная загрузка данных из википедии со страницы список станций Московского метрополитена, которая содержит всю нужную информацию — список станций, их координаты, дату открытия. Все детали были загружены в электронные таблицы Google документов для дальнейшей обработки и доработки.
Второй шаг — уточнение и добавление деталей по тем станциям и линиям, «свойства» которых изменялись.
Лирическое отступление
Переименование станций метро — история известная.
Переход участка линии из-под одной линии к другой — тоже не что-то из ряда вон выходящее (например, Каховская линия (ныне закрытая) до 20 ноября 1995 была частью Замоскворецкой; участок от Кунцевской до Крылатского принадлежал Филевской линии, а с 7 января 2008 стал частью Арбатско-Покровской линии).
Но некоторые изменения оказались довольно неожиданными. Например, что в Москве было две станции метро, которые изначально создавались как временные: Калужская и Первомайская.
Впрочем, мой победитель: переименование в 1963 году станций «Измайловская» и «Измайловский парк» в… «Измайловский парк» и «Измайловскую». Две подряд идущие станции «поменяли местами». Остается только догадываться, как должно было рвать шаблоны у москвичей и гостей столицы, которых это изменение непосредственно коснулось.
И, заключительный третий шаг: выгрузка таблиц в CSV и конвертация её в json структуру данных для использования через JavaScript.
Немного косметики
Карта-подложка, которую предоставляет OSM, — достаточно яркая и шумная. На таком фоне наша схема метро теряется. Чтобы сделать её более наглядной, добавим второй базовый слой, с менее выраженным акцентом. Здесь очень хорошо подойдет светлый слой от Mapbox — Mapbox Light.
Стоит заметить, что в отличии от тайлов OSM, бесплатное использование тайлов Mapbox ограничено, и при превышении определенного количества в месяц тайлы перестанут подгружаться.
Пример: Добавляем альтернативный базовый слой — Mapbox Light
// Создаем новый слой
const grayscale = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoidGltZWxhcHNlbWFwIiwiYSI6ImNrcWZuemE3dzBxbjIyb3Njazk4ODd4M3oifQ._f0k3m2zgmxrdleisUaymQ', {
minZoom: 1,
maxZoom: 18,
id: 'mapbox/light-v10',
tileSize: 512,
zoomOffset: -1,
attribution: '© Mapbox | © OpenStreetMap | Improve this map',
});
// Создаем и добавляем на карту контрол для переключениям между слоями
const mapLayers = {
"OpenStreetMap": osm,
"Grayscale": grayscale,
};
L.control.layers(mapLayers).addTo(mymap);
Ещё одно визуальное улучшение, которое напрашивается сразу, — изменение размеров станций и линий метро в зависимости от масштаба.
Для каждого масштаба (zoom) определяем стили окружностей и линий — толщину, прозрачность, радиус, толщину обводки. При изменении масштаба (zoom) вызываем принудительную перерисовку всех объектов с использованием соответствующих стилей.
Вот теперь — красота:
Итоги
Получилась интерактивная карта московского метро, на которой можно проследить, как развивалась сеть. Например, в каких направлениях она росла, с какой скоростью шел запуск новых станций, да и просто понастольгировать.
Итоговый результат можно посмотреть здесь: https://mm.timelapsemap.com.
Карта по ссылке выше будет развиваться дальше. Для истории здесь https://timelapsemap.com/h/bc4fc2 лежит сохраненная копия, соответствующая этой статье.
При подготовке использованы материалы OpenStreetMap и Wikipedia. КДПВ — с сайта http://n-metro.ru.
PS
Несмотря на большое количество данных в Википедии, не про все изменения удалось найти точную информации.
Поделитесь, пожалуйста, в комментариях если знаете:
С какой даты «Парк Культуры имени Горького» стал просто «Парком культуры»?
С какой даты «Измайловский парк культуры и отдыха имени Сталина» был переименован в «Партизанскую»?
С какой даты станция «Имени Кагановича» была переименована в «Охотный ряд»?
Детали о закрытии станции «Чистые пруды» во время Великой Отечественной войны
Детали о переименовании станции «Калининская» в станцию «Воздвиженка» (ныне «Александровский сад»)