Перейти к содержимому

Веб-карта

В этой главе мы рассмотрим

  • понятие веб-карты
  • типы веб-карт
  • клиент-серверную архитектуру
  • HTML, CSS, JavaScript

В рамках практической части создадим карту мира на основе GeoJSON-файлов с использованием открытой библиотеки MapLibre GL JS. При желании посмотрите полный код и возможный результат.

Любую ли карту в Интернете можно назвать веб-картой?

Яндекс Карты — да!

Скан карты России, который я отправил по почте — пожалуй, нет.

И между ними ещё огромное множество различных вариантов карт в Интернете.

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

Веб-карта

Карта, предназначенная для использования в сети, называется веб-картой

Веб-карты можно разделить, во-первых, на интерактивные / неинтерактивные и, во-вторых, на статические / динамические. Интерактивность касается интерфейса и клиентской части, а деление на статические и динамические веб-карты связано с обработкой данных в серверной части.

статическиединамические
неинтерактивныекарты-картинки [↗]генераторы карт-картинок [↗]
интерактивные”простые” веб-картыкартографические приложения

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

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

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

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

Веб-ресурсы работают по клиент-серверной архитектуре. Рассмотрим пример работы веб-ресурса.

alt text

Пользователь вводит URL-адрес запрашиваемого веб-ресурса в адресную строку браузера и нажимает клавишу Enter (1). Браузер инициирует и выполняет HTTP-запрос к серверу на получение данных для создания веб-страницы (2). Сервер возвращает ответ (3). Из этого ответа браузер формирует веб-страницу, которую видит пользователь (4).

Короче

Браузер — это клиент. Сервер — это сервер. Клиент обращается к серверу с запросом, сервер возвращает клиенту ответ. Вот суть клиент-серверной архитектуры.

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

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

Запросы, выполняемые с веб-страницы

Запросы, выполняемые с веб-страницы

Веб-карты тоже строятся на основе клиент-серверной архитектуры. Рассмотрим интерактивную статическую карту (пока воображаемую).

Разметка, стили, логика (программный код) веб-карты, а также наборы пространственных данных веб-карты хранятся на сервере. Пользователь вводит адрес карты. Браузер запрашивает с сервера разметку веб-карты (HTML). Разметка приходит в браузер. Разметка содержит запросы к стилям (CSS) и логике веб-карты (JS). Логика веб-карты приходит в браузер и запрашивает наборы пространственных данных для создания веб-карты. Пространственные данные приходят [Взаимодействие с сервером закончено!] и кодом веб-карты превращаются в картографические слои.

В клиент-серверной архитектуре браузер это

Узнать ответ Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу

Мы рассмотрели интерактивную статическую карту, а сейчас мы её сделаем своими руками!

Выделим нашей первой веб-карте отдельную папку.

В качестве среды разработки можно использовать VS Code, а чтобы локально запустить сервер — расширение Live Server.

Создадим файл разметки index.html.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Население мира</title>
<!-- Запрашиваем стили 👇 -->
<link rel="stylesheet" href="style.css">
<!-- Запрашиваем библиотеку MapLibre 👇 -->
<script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet" />
</head>
<body>
<!-- Размечаем контейнер для карты 👇 -->
<div id="map"></div>
<!-- Запрашиваем логику карты 👇 -->
<script src="main.js"></script>
</body>
</html>

Библиотеку MapLibre мы запрашиваем из внешнего ресурса, а вот стили и логику карты нам нужно создать.

Создадим файл стилей style.css.

style.css
/* Объявляем, что контейнер карты должен занимать всю страницу */
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}

И файл с логикой карты main.js.

main.js
// Инициализируем карту
const map = new maplibregl.Map({
container: 'map',
style: "https://raw.githubusercontent.com/gtitov/basemaps/refs/heads/master/positron-nolabels.json",
center: [51, 0],
zoom: 4
});

Наименование файла index.html важно тем, что именно страница index.html загружается при обращении к корневому URL. Наименования файлов CSS и JavaScript особой роли не играют.

Страница HTML является корневой. Ей необходимо дать информацию о том, какие внешние библиотеки и файлы будут использоваться. Например, style.css и main.js являются внешними файлами, а MapLibre является внешней библиотекой. Находящиеся на сервере файлы необходимо подключать по URL.

После этого запустим Live Server, перейдём по адресу локального сервера и увидим карту.

Live Server обычно запускается по адресу 127.0.0.1:5500. 127.0.0.1 или localhost — это внутренний адрес сервера на нашем компьютере. Он будет одним и тем же у всех компьютеров. И он недоступен для запросов снаружи. На одном веб-сервере может быть запущено несколько приложений. Для их разграничения используется порт, в нашем случае 5500.

Создадим подпапку data и загрузим в неё данные о странах, городах, реках и озёрах.

Получится такая структура. HTML отвечает за структуру веб-страницы, CSS за оформление веб-страницы, JavaScript за логику работы веб-страницы. GeoJSON-файлы хранят пространственные данные.

  • Директорияdata/ # данные
    • cities.geojson # города
    • countries.geojson # страны
    • lakes.geojson # озёра
    • rivers.geojson # реки
  • index.html # разметка
  • style.css # стили
  • main.js # логика

Все действия с картой выполняются после первичной загрузки исходной карты.

Добавление картографических слоёв включает два шага: добавление источника данных addSource и добавление слоя addLayer. На первом шаге указываем, откуда мы будем брать данные, а на втором — как их оформить. Из одного источника можно создать несколько слоёв.

main.js
// Инициализируем карту
// ... этим многоточием отмечен код, который мы уже написали
// в случае сомнений всегда можно свериться с полным кодом веб-карты
// ссылка на него есть в конце упражнения
map.on('load', () => {
// Выполняется после загрузки карты
// Добавление источника данных
map.addSource('countries', {
type: 'geojson',
data: './data/countries.geojson',
attribution: 'Natural Earth'
})
// Добавление слоя
map.addLayer({
id: 'countries-layer',
type: 'fill',
source: 'countries',
paint: {
'fill-color': 'lightgray',
}
})
})

Мы добавили полигональный слой (type: 'fill'). Аналогично добавляем слой линий и слой точек.

main.js
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.addSource('rivers', {
type: 'geojson',
data: './data/rivers.geojson'
})
map.addLayer({
id: 'rivers-layer',
type: 'line',
source: 'rivers',
paint: {
'line-color': '#00BFFF'
}
})
map.addSource('lakes', {
type: 'geojson',
data: './data/lakes.geojson'
})
map.addLayer({
id: 'lakes-layer',
type: 'fill',
source: 'lakes',
paint: {
'fill-color': 'lightblue',
'fill-outline-color': '#00BFFF'
}
})
map.addSource('cities', {
type: 'geojson',
data: './data/cities.geojson'
})
map.addLayer({
id: 'cities-layer',
type: 'circle',
source: 'cities',
paint: {
'circle-color': 'rgb(123, 12, 234)',
'circle-radius': 3
}
})
})

В MapLibre слои можно фильтровать и оформлять на основе атрибутов с помощью выражений.

Например, оставим только города с численностью населения больше 1 000 000.

main.js
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.addLayer({
id: 'cities-layer',
type: 'circle',
source: 'cities',
paint: {
'circle-color': 'rgb(123, 12, 234)',
'circle-radius': 3
},
filter: ['>', ['get', 'POP_MAX'], 1000000]
})
})

Изобразим красным (red) цветом страны, у которых атрибут MAPCOLOR7 равен 1, а остальные изобразим светло-серым (lightgray).

main.js
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.addLayer({
id: 'countries-layer',
type: 'fill',
source: 'countries',
paint: {
'fill-color': 'lightgray',
'fill-color': ['match', ['get', 'MAPCOLOR7'], 1, 'red', 'lightgray']
}
})
...
})

Созданная нами карта сразу даёт пользователю возможности перемещения, зума и даже наклона (попробуйте зажать правую кнопку мыши). Однако чтобы, например, выводить атрибутивные сведения о слое по клику, надо указать это в коде.

Отследим событие клика по слою cities-layer. Назовём событие клика переменной e. Посмотрим в консоли браузера, что собой представляет это событие. Если мы отслеживаем событие клика по конкретному слою, а не по всей карте, то мы можем обратиться к набору объектов, по которым был выполнен клик e.features.

main.js
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.on('click', ['cities-layer'], (e) => {
console.log(e)
console.log(e.features)
})
})

Закомментируем вывод в консоль и выведем по клику на слой попап.

main.js
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.on('click', ['cities-layer'], (e) => {
// console.log(e)
// console.log(e.features)
new maplibregl.Popup() // создадим попап
.setLngLat(e.features[0].geometry.coordinates) // установим на координатах объекта
.setHTML(e.features[0].properties.NAME) // заполним текстом из атрибута с именем объекта
.addTo(map); // добавим на карту
})
})

Попап отображается, но надо показать пользователю, что на объект можно кликать. При попадании мыши на слой cities-layer поменяем курсор на pointer, а при покидании слоя cities-layer вернём значение по умолчанию.

main.js
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.on('mouseenter', 'cities-layer', () => {
map.getCanvas().style.cursor = 'pointer'
})
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = ''
})
})

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

main.js
// Инициализируем карту
const map = new maplibregl.Map({
container: 'map',
style: "https://raw.githubusercontent.com/gtitov/basemaps/refs/heads/master/positron-nolabels.json",
style: {
"version": 8,
"sources": {},
"layers": []
},
center: [51, 0],
zoom: 4
});
map.on('load', () => {
// Выполняется после загрузки карты
map.addLayer({
id: 'background',
type: 'background',
paint: {
'background-color': 'lightblue'
}
})
...
})

У нас получилась отличная карта!

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

Откроем вкладку Сеть в инструментах разработчика и ещё разок проследим поток данных

geojson-network-tab

  1. Пользователь вводит адрес карты в браузере (в клиенте)
  2. Клиент выполняет запрос к серверу по введённому адресу
  3. Сервер обрабатывает запрос и возвращает разметку (HTML) (1)
  4. В разметке содержатся запросы к оформлению (CSS), картографической библиотеке (MapLibre) и программной логике работы (JavaScript) веб-страницы (2)
  5. Клиент (браузер), получив все необходимые сведения, отображает веб-страницу
  6. Программная логика работы полученной веб-страницы выполняется и в соответствии с кодом инициирует запросы к данным (GeoJSON) для составления карты (3)
  7. Полученные данные оформляются на веб-карте в рамках логики, описанной разработчиком на языке JavaScript, с использованием функций библиотеки MapLibre
  8. Пользователь получает веб-карту
  9. Веб-карта обогащается дополнительной интерактивностью в рамках описанной разработчиком логики

Такая карта удобна, когда мы показываем небольшое количество объектов. Мы отправляем пользователю данные как они есть. Это требует минимальных серверных мощностей. Подобные веб-ресурсы называются статическими. Их легко разместить в Интернете, есть варианты бесплатной публикации.

Прежде чем браться за следующие упражнения, полезно немного погрузиться в теорию веб-картографии

  1. Присвойте Москве красный цвет
  2. Выведите в попап один из атрибутов стран
  3. Добавьте слой с границами озёр, установите им толщину в 2 пикселя
  4. Замените курсор на перекрестие (crosshair) при расположении поверх стран
  5. ⁎ Внешний вид карты оставляет желать лучшего, оформите карту в соответствии с собственными представлениями о прекрасном

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

  1. В каком файле описывается разметка веб-страницы?
  2. Какой тег содержит ссылку на стили веб-страницы?
  3. Что означает container: 'map' в коде new maplibregl.Map({ container: 'map', ... })?
  4. Какой метод библиотеки MapLibre позволяет добавить картографический слой на карту?
  5. Какой тип слоя используется для отображения полигонов озёр?
  6. Какой текстовый формат пространственных данных может использоваться в качестве источника пространственных данных для библиотеки MapLibre?