Search
Write a publication
Pull to refresh

Интерактивная карта Республики Коми с отображением социально-экономических показателей

Level of difficultyEasy
Reading time9 min
Views1.2K

В рамках выпускной квалификационной работы мне предложили две интересные темы: интерактивную карту или тренажёр для SQL-запросов. Я хотел посложнее, чтоб получить побольше навыков и поднабраться опыта к окончанию бакалавриата, поэтому выбрал первое. Получилась небольшая ГИС, полностью написанная на JavaScript при помощи D3.js и Charts.js.

Сразу скажу, что сейчас появилась небольшая проблема с тем, что фреймворк работает через Cloudflare, который блокируется РКН.
Из-за чего js-код
не прогружается, потому что нет доступа к фреймворку. Что поможет в таких случаях? Внутреннее принятие неизбежного.

Сейчас все работает вроде хорошо, но если не работает, то читаем зачеркнутый текст.

Забыл представиться, меня зовут Артём, я закончил бакалавриат по направлению «Математика и компьютерные науки» в СГУ им. Питирима Сорокина.

Этот проект, как сказано выше - моя выпускная квалификационная работа, и по совместительству - первое серьёзное веб-приложение, в котором я в одиночку соединил открытые данные, картографию, JavaScript и страдания упорство.

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

Функционал

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

Интерактивная карта в действии

Ниже — как выглядит карта в светлой и темной теме. На скриншотах ниже отображены административные районы Республики Коми. Это основной уровень, с которого пользователь начинает взаимодействие.

Интерактивная карта в светлой теме
Интерактивная карта в светлой теме
Интерактивная карта в светлой теме ()
Интерактивная карта в темной теме

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

Интерактивная карта в светлой теме (уровень муниципальных поселений)
Интерактивная карта в светлой теме (уровень муниципальных поселений)
Интерактивная карта в темной теме (уровень муниципальных поселений)
Интерактивная карта в темной теме (уровень муниципальных поселений)

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

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

Интерактивная карта в темной теме (город Сыктывкар)
Интерактивная карта в темной теме (город Сыктывкар)
Интерактивная карта в темной теме (город Сыктывкар)
Интерактивная карта в темной теме (город Сыктывкар)

Реализация

Итак, для визуализации карты я выбрал D3.js.
Конечно, есть более привычные решения вроде Leaflet или Mapbox. Но они работают с тайлами, а мне хотелось чего-то "настоящего" - полного контроля над отрисовкой и максимального погружения.
Да и честно говоря, хотелось немного усложнить себе жизнь - всё-таки диплом.

Данные

Честно сказать, на момент написания диплома, JS я знал только на уровне одного пройденного курса на интернет-платформе, ведь в вузе мы его не касались, но мне помогли упорство и нейросети. Начал заниматься этим в феврале, чтоб точно успеть сделать к июню, и не зря так рано.

Первым делом я начал искать готовые .svg-карты, потому что вообще не понимал, с чего начинается работа с геоданными. Все, что я находил - платные карты, а из бесплатных за первый час нашёл только карту России с делением по регионам.
Позже наткнулся на ресурс Geofabrik, там уже я нашел .shp карты, был очень этому рад. Но за этой радостью стоял один нюанс - карта не республики, а Северо-Западного Федерального Округа.

Понял что придется избавляться от лишнего.

Хотя сперва подумал, что буду работать со всеми регионами и просто поменяю название темы, но рад что отказался от этой идеи, потому что времени бы не хватило.
Позже выяснилось, что с .shp форматом JS не любит работать. Тогда мне посоветовали скачать QGIS.

Вот так выглядел исходный набор данных в QGIS, перед тем как я вырезал всё лишнее:

Набор разбит на карты рек, местностей, болот и т.д.
Я понял, что нужно вырезать все лишнее и оставить только все, что касается республики Коми. Сначала я пытался вырезать всё вручную, но в процессе выделения карты самой республики понял, что это слишком долго.
Пришла идея оптимизировать: я не случайно выше оставил часть про бесплатную карту РФ, потому что при помощи поиска по атрибутам выделил оттуда РК. С её помощью я выделил контур Республики Коми через таблицу атрибутов. Затем в QGIS применил фильтр «Пересечение» к каждому из слоёв СЗФО — оставляя только те объекты, которые попадали внутрь границ Коми.
Так получилось автоматически отфильтровать и собрать отдельный набор геоданных только по нужному региону.

Получившаяся карта
Получившаяся карта

Разработка карты

Дальше я уже начал работать с картой и JS. При помощи нейросеток понял как работать с d3, они помогли накидать макет, чтоб отображать саму карту, разделенную на районы.

Карта

Изначально я хотел, чтобы при большом масштабе отображались все подробные слои, а при отдалении — только карта Республики Коми. Казалось бы, логично. Но на практике такая карта требовала невероятное количество ресурсов: всё тормозило, даже при средней детализации.

Чтобы снизить нагрузку, я пошёл другим путём: стал подгружать только тот район, к которому приближается пользователь. Да, в ГИС-системах это делается автоматически — подгружается только то, что в пределах экрана (например можно было делать это через quadtree в d3.js), но на тот момент у меня не было понимания как это реализовать, поэтому я выбрал этот вариант.

Теперь я вырезал карты для каждого района снова, используя тот же метод, при помощи таблицы атрибутов выделяя районы и нужные мне для них карты. Я оставил следующие карты : "Границы", "Населенные пункты", "Терминалы", "Болота", "Инфраструктура", "Пляжи", "Парковки", "Церкви", "Здания", "Железные дороги", "Дороги", "Реки".

Получилось всего 240 карт (по 12 карт для 20 районов). Занимаемое место на диске - 1.12гб. Ближе к защите я уменьшил объем данных примерно на 10%, сократив геоданные до 5 знаков после запятой. А после удалил нулевые атрибуты. Итоговый набор карт занимает 359 мб. Я добавил функцию подсветки регионов, а дальше пошла оптимизация отображения и работа над дизайном.

Дизайн

После реализации отображения я сразу приступил к подбору цветов объектов. Я старался не ориентироваться на другие решения а собрать свою личную цветовую гамму. И в этот же момент подумал «как круто будет сделать две темы». Взял за основные цвета зеленый и бежевый для светлой темы, а для темной синий и оранжевый.

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

Иконки интерфейса
Иконки интерфейса

Итого: у меня есть кнопка смены темы,
кнопка вк/вык_лючающая все карты,
кнопки переключающие отображение кнопок (в правой части экрана) и иконок (в верхней части экрана), которыми можно вк/вык_лючать определенный тип карт.

Все элементы интерфейса я рисовал вручную в Inkscape, в формате .svg. Хотелось сделать все иконки в минималистичном стиле, но фантазия закончилась после основных. Я же все-таки по образованию математик :(

Переключатели-кнопки (справа) реализованы в виде обычных HTML-кнопок, в них нет чего-то интересного. Нажимаешь - меняют цвет и текст, вк/вык_лючают карты.
А есть переключатели-иконки (сверху), вот они красивые (ну, как минимум старательные), и функциональные: переключают режимы отображения карты, а при нажатии меняют цвет или подсветку, в зависимости от текущей темы. Я пытался сделать их интуитивно понятными для пользователя и некоторые получили интересные детали.

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

Вот они — мои 48 шедевров SVG-графики:

Для того, чтоб нарисовать эти иконки мне понадобилось чуть больше 24 часов рабочего времени. А теперь можете их брать и вы, хотя я даже не знаю где они могут пригодиться.

А знаете на что у меня ушло столько же времени? А возможно даже больше.

Работа с данными

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

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

После этой фразы я приступаю к поиску данных. Я не понимал откуда их брать, подсказать не могли, но потом приглянулся Росстат.
Сразу же - первая подножка: когда я решил загрузить где-то третий по счёту показатель, сайт начал возвращать 404. Я предположил, что либо запросов слишком много, либо таблицы слишком большие. Поэтому начал брать только те таблицы, которые нравились мне самому, а с ними проблем уже не было. Я скачивал .csv-файлы и надеялся, что дальше будет легко. Но иногда всплывало что-то вроде:

«Необходимо, чтобы в заголовке, в шапке и в боковике таблицы был выбран не менее одного признака».

Хотя показатели как-то же должны автоматически там распределять.
В общем к программистам Росстата у меня большой вопрос.

А уж работа с этими .csv-файлами оттуда — просто мука. Мне нужен был унифицированный вид, но его не было. Я писал python‑скрипты, чтоб перевести данные для каждой из таблиц, потому что вид у них был не поддающийся логике. Где‑то значение в той же строке, где‑то в следующей, где‑то через одну и прочие проблемы. Спустя где‑то 25 часов я перевел 17 таблиц в JSON формат в единый вид и приступил к оформлению поля информационных показателей. Конец страданиям. На защите меня спросили: «а почему не использовали парсинг данных с Росстата с какой‑нибудь периодичностью, данные же могут обновляться" - идея конечно крутая, но мне кажется это адом.

Для визуализации самих показателей я подключил Charts.js. Потому что быстро, красиво, удобно, и хотелось, чтоб d3 был для логики карты, а charts для графиков.

Техническая часть

Немного про код: просто скажу что делают функции. Продублирую, что можно (и нужно) посмотреть полный проект на гитхабе.

Основные функции отрисовки и логики карты

  • render() – отвечает за полную перерисовку карты (отрисовывает фон, районные слои, слои объектов и подсвеченные элементы в зависимости от масштаба и темы).

  • drawMap(geojsonFile, fillColor, strokeColor, opacity) – загружает главный GeoJSON и запускает render().

  • updateProjection() – подгоняет проекцию D3 под размер канваса, чтобы карта занимала весь экран.

  • clearMap() – очищает текущие данные районов.

Загрузка и кеширование геоданных

  • loadDistrictMaps(district, colorScheme) – загружает слои объектов для выбранного района (здания, дороги, леса и т.д.), используя кэш и фильтрацию по зуму.

  • updateDistrictCenters() – загружает геометрию границ районов, чтобы потом определять ближайший к центру экрана район.

  • getClosestDistrict() – определяет, какой район сейчас ближе всего к центру карты.

Наведение и взаимодействие

  • isPointInPolygon(point, polygon) – проверяет, лежит ли точка внутри полигона (используется в getClosestDistrict()).

  • addRiverLabel(feature) – отрисовывает название реки и дороги по направлению потока.

  • capitalizeFirstLetter(str) – делает первую букву строки заглавной (используется для выделенного объекта).

Управление слоями карты и интерфейсом

  • createMapControls(mapType) – создаёт кнопки управления слоями (иконки и текстовые кнопки).

  • setButtonState(type, index, state) – переключает видимость слоя и обновляет текст кнопки.

  • updateMapMode() – проверяет, активен ли какой-либо слой и загружает соответствующие слои района.

  • updateIconButtonStyles() – обновляет иконки кнопок в зависимости от темы и состояния.

  • updateButtonStyles() – меняет стили текстовых кнопок в зависимости от темы и активности.

  • applyBoxShadows() – обновляет цвета подсказок в зависимости от темы.

  • changeIconColor() – меняет иконки главных кнопок управления (меню, темы и пр.) в зависимости от темы и состояния.

Переключение темы

  • themeButton.onclick – переключает тёмную/светлую тему, перерисовывает карту, включает нужную цветовую схему.

Интеграция с Wikidata и OSM

  • fetchWikiData(wikidataID) – загружает данные объекта по Wikidata ID и отображает их в infoBox.

  • fetchWikiIDfromOSM(osmID) – по OSM ID делает запрос к Overpass API, чтобы найти связанный Wikidata ID и вызвать fetchWikiData().

  • fetchEntityLabel(entityID) – получает название сущности Wikidata по ID (например, тип объекта, столицу и пр.).

  • updateInfoBoxWiki(entity, wikidataID) – собирает и отображает подробную информацию об объекте в infoBox, включая изображения, флаг, герб и кнопку социальных показателей.

Социальные показатели (данные)

  • getAvailableFiles() – возвращает список доступных JSON-файлов с социальными показателями.

  • findFilesContainingRegionName(regionName) – ищет файлы, в которых есть данные по данному региону.

  • showFileListInSocialTab() – отображает список файлов с показателями для выбранного региона.

  • loadRegionsData() – загружает JSON-файл с показателями и извлекает все доступные метрики.

  • findRegionData(regionName) – находит объект данных региона по его названию, учитывая исключения.

  • showSocialIndicators(regionName) – отображает таблицу и графики с показателями по региону (использует Chart.js).

  • getChartColors() – возвращает палитру цветов для графиков в зависимости от текущей темы.

Прочее

  • toggleSocialButton() – показывает/скрывает социальную вкладку с данными.

  • closeInfoBox() – скрывает окно информации.

Заключение

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

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

Я не стал подробно расписывать реализацию — скорее дал "удочку" в виде репозитория на GitHub, а дальше, думаю, вы разберётесь.

Тем, кто включилу себя внутреннее принятие неизбежного : Вот сылочка на саму карту: карта.
На телефон я физически не успел адаптировать, а сейчас уже не вижу смысла в этом, но можно включить «версию для пк», там будет уже поприятнее.

Буду рад обратной связи в комментариях.

Tags:
Hubs:
+6
Comments8

Articles