Как стать автором
Обновить

Интерактивная карта развития Московского метрополитена

Время на прочтение11 мин
Количество просмотров22K

15 мая 1935 была открыта первая очередь Московского метрополитена. С этого момента началась новая эра в истории города, в котором подземка, безусловно, играет важную роль.

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

Упрощенная схема читается быстрее. Но, в то же время, не дает представления о реальном расположении линий, скрывает детали. Длинные перегоны метро ничем не отличаются от коротких. Нет привязки к географии.

Давайте создадим интерактивную карту линий Московского метрополитена и посмотрим на его историю — как развивалась одна из самых больших сетей мира.

Для создания такой карты нам потребуются:

  • Механизм для отображения карты

  • Удобная модель хранения данных

  • Сами данные

Механизм

Для начала, определяемся с требования к механизму. Он должен уметь:

  • отображать базовый слой с «обычной» картой — дорогами, водоемами, улицами, зданиями и т.д.;

  • отображать поверх базового слоя круги и линии, которые будут изображать станции и линии метро соответственно;

  • обновлять отрисованные станции и линии метро в зависимости от выбранной даты;

  • переключать текущую дату в интервале от 15 мая 1935 года (запуск первой линии метро) до настоящего времени.

Первые три пункта из коробки есть в open source библиотеке Leaflet. Четвертый будем реализовывать самостоятельно.

Базовый слой карты

Пример 1: Создание базового слоя карты
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
</head>

<body>
<div id="map" style="width: 720px; height: 400px;"></div>
<script>
// Создаем карту	
const mymap = L.map('map').setView([55.754181, 37.622821], 12);

// Создаем tile layer и добавляем его на карту
const osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
			minZoom: 1,
			maxZoom: 18,
			attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(mymap);
</script>
</body>
</html>
Что такое 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 формате (без служебных данных):

<!-- Сокольники -->
<node id="1" lon="55.7888499" lat="37.6801924"/> 
<!-- Красносельская -->
<node id="2" lon="55.7798626" lat="37.6666841"/> 
<!-- Комсомольская -->
<node id="3" lon="55.7754719" lat="37.6566036"/> 
<!-- Красные Ворота -->
<node id="4" lon="55.7691137" lat="37.64887"/> 
<!-- Кировская -->
<node id="5" lon="55.7658915" lat="37.6388871"/> 
<!-- Дзержинская -->
<node id="6" lon="55.7598058" lat="37.6264977"/> 
<!-- Охотный Ряд -->
<node id="7" lon="55.7578487" lat="37.6163659"/> 
<!-- Библиотека имени Ленина -->
<node id="8" lon="55.7515247" lat="37.6102524"/> 
<!-- Дворец Советов -->
<node id="9" lon="55.7454796" lat="37.6036755"/> 
<!-- Парк культуры имени Горького -->
<node id="10" lon="55.7352856" lat="37.5940414"/> 
<!-- Смоленская -->
<node id="11" lon="55.7488633" lat="37.5826955"/> 
<!-- Арбатская -->
<node id="12" lon="55.7519189" lat="37.6007512"/> 
<!-- Улица Коминтерна -->
<node id="13" lon="55.7524242" lat="37.6086732"/>

Линия (Way)

Way — упорядоченная ломаная линия, состоящая из последовательно заданных точек (nodes).

Пример Way
<!-- Участок «Сокольники — Парк Культуры имени Горького» -->
<way id="1"> 
<nd rf="1"/>
<nd rf="2"/>
<nd rf="3"/>
<nd rf="4"/>
<nd rf="5"/>
<nd rf="6"/>
<nd rf="7"/>
<nd rf="8"/>
<nd rf="9"/>
<nd rf="10"/>
</way>
<!-- Участок «Охотный ряд — Смоленская» -->
<way id=”2”> 
<nd rf="7"/>
<nd rf="13"/>
<nd rf="12"/>
<nd rf="11"/>
</way>

Отношение (Relation)

Отношение (relation) — это группировка элементов (elements) по определенному признаку. В отношение могут входит точки (nodes), линии (ways), а также, другие отношения (relations).

Пример Relation

Например, объединение всех станций и участков метро могут объединяться под одним отношением (relation) — «Московский метрополитен».

<!-- Московский метрополитен -->
<relation id="1">
  <member type="node" ref="1" role=""/>
  <member type="node" ref="2" role=""/>
  …
  <member type="node" ref="13" role=""/>
  <member type="way" ref="1 role=""/>
  <member type="way" ref="2" role=""/>
</relation>

Тег (Tag)

Также, у каждого из элементов (elements) могут быть заданы теги: пара "ключ=значение". Теги определяют свойства объекта, и используются для описания того, как объекты будут отображаться на карте.

Пример Tags

Например, станция метро может иметь такой набор тегов

 <!-- Сокольники -->
<node id="1" lon="55.7888499" lat="37.6801924">
	<tag k=”name” v=”Сокольники”/>
	<tag k=”type” v=”subway_station”/>
	<tag k=”color” v=”red”/>
</node>

Доработка модели данных 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 для переименованной станции метро
<relation id=”2”>
	<member type=”node” ref=”5”/>
	<tag k=”name” value=”Кировская”/>
	<tag k=”type” value=”subway_station”/>
	<tag k=”color” value=”red”/>
	<tag k=”from_date” value=”1935-05-15”/>
	<tag k=”to_date” value=”1990-11-05”/>
</relation>
<relation id=”3”>
	<member type=”node” ref=”5”/>
	<tag k=”name” value=”Чистые пруды”/>
	<tag k=”type” value=”subway_station”/>
	<tag k=”color” value=”red”/>
	<tag k=”from_date” value=”1990-11-05”/>
	<tag k=”to_date” value=”now”/>
</relation>

Аналогичная история с участками линий. Например, если с 1935 года участок путей принадлежал Сокольнической линии, а с 1938 года стал участком Арбатской линии, то нам потребуется три relation.

Пример relations для изменения линий
<relation id=”247”>
	<member type=”way” ref=”1”/>
	<member type=”way” ref=”102”/>
	<member type=”way” ref=”2”/>
	<member type=”way” ref=”3”/>
	<member type=”way” ref=”4”/>
	<tag k=”name” value=”Кировско-Фрунзенская линия”/>
	<tag k=”type” value=”subway_line”/>
	<tag k=”color” value=”red”/>
	<tag k=”from_date” value=”1937-03-20”/>
	<tag k=”to_date” value=”1938-03-13”/>
</relation>

<relation id=”248”>
	<member type=”way” ref=”1”/>
	<member type=”way” ref=”102”/>
	<tag k=”name” value=”Кировско-Фрунзенская линия”/>
	<tag k=”type” value=”subway_line”/>
	<tag k=”color” value=”red”/>
	<tag k=”from_date” value=”1938-03-13”/>
	<tag k=”to_date” value=”1957-05-01”/>
</relation>

<relation id=”249”>
	<member type=”way” ref=”3”/>
	<member type=”way” ref=”4”/>
	<member type=”way” ref=”5”/>
	<member type=”way” ref=”6”/>
	<tag k=”name” value=”Арбатско-покровская линия”/>
	<tag k=”type” value=”subway_line”/>
	<tag k=”color” value=”blue”/>
	<tag k=”from_date” value=”1938-03-13”/>
	<tag k=”to_date” value=”1944-01-18”/>
</relation>

Данные

Подготовка данных состояла из трех шагов.

Первым шагом была начальная загрузка данных из википедии со страницы список станций Московского метрополитена, которая содержит всю нужную информацию — список станций, их координаты, дату открытия. Все детали были загружены в электронные таблицы 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: '© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> | © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> | <strong><a href="https://www.mapbox.com/map-feedback/" target="_blank">Improve this map</a></strong>',
});

// Создаем и добавляем на карту контрол для переключениям между слоями
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

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

Поделитесь, пожалуйста, в комментариях если знаете:

  • С какой даты «Парк Культуры имени Горького» стал просто «Парком культуры»?

  • С какой даты «Измайловский парк культуры и отдыха имени Сталина» был переименован в «Измайловскую»?

  • С какой даты станция «Имени Кагановича» была переименована в «Охотный ряд»?

  • Детали о закрытии станции «Чистые пруды» во время Великой Отечественной войны

  • Детали о переименовании станции «Калининская» в станцию «Воздвиженка» (ныне «Александровский сад»)

Теги:
Хабы:
Всего голосов 63: ↑63 и ↓0+63
Комментарии35

Публикации

Истории

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань