master
gman 6 months ago
parent 53c1d0e0e1
commit 15621cd4e8

@ -1,42 +1,33 @@
// @ts-check
import starlight from '@astrojs/starlight';
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
// https://astro.build/config
export default defineConfig({
redirects: {
'/': '/chapters',
},
integrations: [
starlight({
title: 'Веб-картография',
description: 'Веб-картография и веб-картографирование: практическое пособие',
head: [
{
tag: 'script',
attrs: {
src: "//gc.zgo.at/count.js",
'data-goatcounter': "https://webcartography.goatcounter.com/count",
async: true
}
}
],
locales: {
root: {
label: 'Русский',
lang: 'ru',
}
},
sidebar: [
{
label: 'Введение в веб-картографию',
autogenerate: { directory: 'chapters' },
}
],
components: {}
}),
react()
],
// @ts-check
import starlight from '@astrojs/starlight';
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
// https://astro.build/config
export default defineConfig({
integrations: [
starlight({
title: 'Практическое введение в веб-картографию',
description: 'Веб-картография и веб-картографирование: практическое пособие',
head: [
{
tag: 'script',
attrs: {
src: "//gc.zgo.at/count.js",
'data-goatcounter': "https://webcartography.goatcounter.com/count",
async: true
}
}
],
locales: {
root: {
label: 'Русский',
lang: 'ru',
}
},
components: {}
}),
react()
],
});

@ -0,0 +1,88 @@
---
title: Веб-картографирование
---
import { Card, LinkCard } from '@astrojs/starlight/components';
import Question from '../../components/Question.astro';
import MultipleChoice from '../../components/MultipleChoice.astro';
import Option from '../../components/Option.astro';
В этой главе мы совершим краткий теоретический обзор сферы веб-картографии
<LinkCard title='Руки чешутся' href='/2-webmap#создание-первой-веб-карты' description='Сразу перейти к практическому упражнению'/>
## Определение
Под веб-картографированием понимается создание карт для их распространения и использования во Всемирной сети. Веб-картографирование является одним из направлений геоинформационного картографирования. Суть веб-картографирования заключается в интеграции геоинформационных подходов к картографированию и сетевых технологий.
> Можно встретить и такие варианты употребления как «Интернет-картографирование», «WWW-картографирование», «телекоммуникационное картографирование», «web-картографирование». Однако в словаре их бы одарили меткой *устар.*
Появление большого числа картографических веб-ресурсов свидетельствует, что сетевые технологии являются одним из ключевых драйверов эволюции геоинформационной картографии.
## Особенности
Характерными чертами веб-картографирования являются:
1. Сетевая среда распространения
2. Интерактивность
3. Мультимасштабность
*Сетевая среда распространения* меняет подход к публикации картографических произведений. Карта генерируется для пользователя в момент запроса, поэтому обновление картографического содержания можно выполнять непрерывно без дополнительных действий пользователя. Например, изменение границ или переименование улиц не требует приобретения пользователем нового издания картографического произведения: если данные в картографическом произведении обновились, пользователь получит их при новом запросе.
Лучшие практики веб-разработки стимулируют активное использование *интерактивности* в картографическом произведении. Хотя возможности введения диалогового взаимодействия с картой появились на этапе компьютеризации картографии, по-настоящему доступной для широкого круга пользователей интерактивность карт стала именно с развитием Интернет-технологий. Пользователь по умолчанию ожидает возможности перемещения по карте, изменения масштаба, определения координат точки и получения дополнительной информации об объекте по клику. Часто веб-карта предоставляет и более широкие возможности, например, интерактивное изменение содержания карты, оформления слоёв, проекции, компоновки.
Одним из ключевых элементов интерактивности является функция изменения масштаба картографического изображения. Это требует от автора карты формирования *мультимасштабного* содержания или явного обозначения масштабов, которые соответствуют содержанию карты. Работа с мультимасштабным содержанием веб-карт должна начинаться на этапе проектирования.
Легко заметить соподчинённость выделенных особенностей. Веб-технологии позволяют вводить интерактивные элементы в содержание карты. Одним из ключевых интерактивных элементов веб-карт является возможность изменения масштаба, что обуславливает мультимасштабность наполнения карты.
Кроме того веб-картографирование характеризуют
4. Общедоступность
5. Сходство с программным обеспечением
6. Расширение выразительности
Веб-ресурсы во Всемирной сети обычно являются *общедоступными*: обратиться к ним может любой пользователь, имеющий выход в Интернет. Возможно ограничение доступа к веб-ресурсу, например с помощью системы авторизации, однако сохраняется принципиальная доступность при наличии соответствующих прав. Картографические произведения в Интернете не исключение. Коммерциализация веб-карт базируется не на платном доступе к содержанию, а на широком доступе аудитории к содержанию карты, которое можно монетизировать, например, с помощью рекламы.
На этапе проектирования картографического произведения проявляются черты, характерные для разработки *программного обеспечения* и информационных систем; составление карт сводится к написанию программного кода, который генерирует экземпляр карты для каждого пользователя; изданием картографического произведения становится его публикация на веб-сайте.
Веб-технологии расширяют круг *выразительных средств*: появляется возможность использовать мультимедийные материалы (фото, аудио, видео), давать ссылки на другие веб-страницы, создавать интерактивные картографические анимации. Технически это реализуемо и на электронных картах, локально размещаемых на компьютере пользователя, однако активное использование новых выразительных средств характерно именно для веб-карт. Карта в веб-среде становится динамичной интерактивной моделью.
<Card title='Главная особенность веб-картографрования это'>
<MultipleChoice>
<Option>
интерактивность
</Option>
<Option>
использование стандартов
</Option>
<Option isCorrect>
cетевая среда распространения
</Option>
</MultipleChoice>
<details>
<summary>Узнать ответ</summary>
Особенности веб-картографирования проистекают из сетевой среды распространения картографических материалов. Другими словами, сетевая среда распространения является ключевой особенностью веб-картографирования, а остальные производными от неё.
</details>
</Card>
## Веб-картография
Наличие характерных особенностей позволяет говорить о веб-картографировании как о специфической деятельности, требующей рассмотрения в рамках отдельного направления — веб-картографии.
Реальная практика показывает, что сфера интересов веб-картографии не ограничивается изучением процесса веб-картографирования. Это направление на пересечении картографии, геоинформатики и сетевых технологий.
![alt text](../../assets/image-19.png)
Веб-картография изучает особенности оборота пространственных данных в сетевой среде. Так как эти особенности проявляются на всех этапах жизненного цикла веб-карты, то и в область интересов веб-картографии входит вся система создания-использования карт от сбора данных до чтения веб-карты пользователем. Внимание уделяется хранению, кодированию, передаче, обработке пространственных данных в сетевой среде, каталогизации и организации поиска пространственных данных, методологии разработки картографических веб-приложений, методам визуализации и интерактивного взаимодействия.
<Card title="Короче">
Веб-картографирование — это создание карт для распространения и использования в Интернете. Оно характеризуется сетевой средой распространения и производными от этого особенностями, в частности интерактивностью и мультимасштабностью.
Веб-картография формируется на пересечении сетевых технологий, картографии и геоинформатики изучает особенности оборота пространственных данных в сетевой среде, включая хранение, кодирование, передачу, обработку данных, разработку веб-приложений и другие аспекты.
</Card>
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.

@ -1,445 +1,454 @@
---
title: Веб-карта
---
import { Card, FileTree, LinkCard } from '@astrojs/starlight/components';
import Question from '../../../components/Question.astro';
import MultipleChoice from '../../../components/MultipleChoice.astro';
import Option from '../../../components/Option.astro';
В этой главе мы рассмотрим
- понятие веб-карты
- типы веб-карт
- клиент-серверную архитектуру
- HTML, CSS, JavaScript
В рамках практической части создадим карту мира на основе статических GeoJSON-файлов с использованием открытой библиотеки MapLibre.
## Карты в Интернете
Любую ли карту в Интернете можно назвать веб-картой?
> Яндекс.Карты — конечно, да!
>
> Скан карты России, который я отправил по почте — пожалуй, нет.
>
> И между ними ещё огромное множество различных вариантов карт в Интернете.
### Определение веб-карты
Цель размещения карты в сети определяет веб-карту.
Файлам карт, передаваемым по сети, или размещаемым на сайтах, изображениям карт для печати, загрузки, иллюстрации, украшения будет отказано в праве называться веб-картой. А вот если карту разместили в Интернете для того, чтобы пользователи могли работать с ней по сети, то это веб-карта.
<Card title="Веб-карта —">это карта, предназначенная для использования в сети</Card>
### Типы веб-карт
Веб-карты можно разделить на интерактивные / неинтерактивные и статические / динамически
| | статические | динамические |
| --------------- | ------------------- | --------------------------- |
| неинтерактивные | карты-картинки | генераторы карт-картинок |
| интерактивные | "простые" веб-карты | картографические приложения |
Инерактивность подразумевает возможность пользователя перемещаться по карте, менять масштаб, получать подробную информацию по клику, скрывать слои, менять цвета и так далее. Неинтерактивные карты лишены этих возможностей. Но неинтерактивная карта будет веб-картой, если предназначена для использования в Интернете.
Статические веб-карты получают данные на клиент точно в том виде, в котором они хранятся на сервере, без какой-либо обработки. Из программного обеспечения требуется только веб-сервер, который будет принимать запросы от клиентов и возвращать в ответ файлы данных с сервера клиенту.
Динамические веб-карты подразумевают серверную обработку данных и позволяют запрашивать данные более гибко. Из программного обеспечения обычно используются база данных, программа для обработки данных и веб-сервер. В общем случае веб-сервер примет запрос и обратится к программе для обработки данных, она извлечёт данные из базы, обработает их и передаст веб-серверу, который отправит подготовленные данные клиенту.
Интерактивность касается интерфейса и клиентской части, а деление на статические и динамические веб-карты связано с обработкой данных в серверной части.
<LinkCard title='Другие продукты веб-картографии' href='/chapters/7-extra#кроме-веб-карт' description='Геопорталы, картографические веб-сервисы, веб-атласы, веб-ГИС'/>
## Клиент-серверная архитектура
Рассмотрим пример работы простого веб-ресурса.
![alt text](../../../assets/image-15.png)
Пользователь вводит адрес запрашиваемого веб-ресурса в адресную строку браузера и нажимает клавишу Enter (1). Браузер инициирует и выполняет запрос к серверу на получение данных для создания веб-страницы (2). Сервер возвращает ответ (3). Из этого ответа браузер формирует веб-страницу, которую видит пользователь (4).
<Card title="Короче">
Браузер -- это клиент. Сервер -- это сервер. Клиент обращается к серверу с запросом, сервер возвращает клиенту ответ. Вот суть клиент-серверной архитектуры.
</Card>
В реальности клиент может выполнять множество запросов к серверу, чтобы показать пользователю веб-страницу.
:::tip
Откройте инструменты разработчика, нажав на клавишу F12 или сочетание клавиш Ctrl+Shift+i, перейдите во вкладку Сеть (Network) и обновите страницу, чтобы увидеть, сколько запросов выполняет браузер, чтобы показать вашу любимую веб-страницу.
:::
Первый запрос инициирует пользователь выполняет путём ввода адреса. Дальнейшие действия пользователя на веб-странице могут инициировать последующие запросы. Запрашиваться могут как веб-страницы целиком, так и отдельные данные для обновления содержания текущей веб-страницы. Если мы обновляем только часть содержания веб-страницы данными с сервера, то речь идёт об асинхронном запросе.
![*Запросы, выполняемые с веб-страницы*](../../../assets/network-tab.png)
*Запросы, выполняемые с веб-страницы*
Веб-карты тоже строятся на основе клиент-серверной архитектуры. Рассмотрим интерактивную статическую карту.
Разметка, стили, логика (программный код) веб-карты, а также наборы пространственных данных веб-карты хранятся на сервере. Пользователь вводит адрес карты. Браузер запрашивает с сервера разметку веб-карты (html). Разметка приходит в браузер. Разметра содержит запросы к стилям и логике веб-карты. Логика веб-карты приходит в браузер и запрашивает наборы пространственных данных для создания веб-карты. Пространственные данные приходят [Взаимодействие с сервером закончено!] и кодом веб-карты превращаются в картографические слои.
{/* <Card title='В клиент-серверной архитектуре браузер это'>
<Question answer="клиент" ballast={['сервер', 'пользователь']} explanation="Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу"/>
</Card> */}
<Card title='В клиент-серверной архитектуре браузер это'>
<MultipleChoice>
<Option>
сервер
</Option>
<Option isCorrect>
клиент
</Option>
<Option >
пользователь
</Option>
</MultipleChoice>
<details>
<summary>Развёрнутый ответ</summary>
Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу
</details>
</Card>
## Создание первой веб-карты
Мы рассмотрели интерактивную статическую карту, а сейчас мы её сделаем своими руками!
Выделим нашей первой веб-карте отдельную папку.
> В качестве среды разработки можно использовать [VS Code](https://code.visualstudio.com/), а чтобы запустить сервер -- расширение [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer).
### Инициализация карты
Создадим файл разметки `index.html`.
```html title="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`.
```css title="style.css"
/* Объявляем, что контейнер карты должен занимать всю страницу */
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
```
И файл с логикой карты `main.js`.
```js title="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` и загрузим в неё данные о [странах](https://raw.githubusercontent.com/gtitov/geojson-maplibre-map/refs/heads/master/data/countries.geojson), [городах](https://raw.githubusercontent.com/gtitov/geojson-maplibre-map/refs/heads/master/data/cities.geojson), [реках](https://raw.githubusercontent.com/gtitov/geojson-maplibre-map/refs/heads/master/data/rivers.geojson) и [озёрах](https://raw.githubusercontent.com/gtitov/geojson-maplibre-map/refs/heads/master/data/lakes.geojson).
Должна получится такая структура. HTML отвечает за структуру веб-страницы, CSS за оформление веб-страницы, JavaScript за логику работы веб-страницы. GeoJSON файлы хранят пространственные данные.
<FileTree>
- data/ # данные
- cities.geojson # города
- countries.geojson # страны
- lakes.geojson # озёра
- rivers.geojson # реки
- index.html # разметка
- style.css # стили
- main.js # логика
</FileTree>
Все действия с картой выполняются после первичной загрузки исходной карты.
Добавление картографических слоёв включает два шага: добавление источника данных `addSource` и добавление слоя `addLayer`. На первом шаге указываем, откуда мы будем брать данные, а на втором, как их оформить. Из одного источника можно создать несколько слоёв.
```js title="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'`). Аналогично добавляем слой линий и слой точек.
```js title="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
}
})
})
```
:::tip
Порядок отрисовки слоёв соответсвует порядку их объявления в коде. Последующие слои перекрывают предыдущие.
:::
В MapLibre слои можно фильтровать и оформлять на основе атрибутов с помощью [выражений](https://maplibre.org/maplibre-style-spec/expressions/).
Например, оставим только города с численностью населения больше 1 000 000
```diff lang="js" title="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`)
```diff lang="js" title="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`
```js title="main.js"
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.on('click', ['cities-layer'], (e) => {
console.log(e)
console.log(e.features)
})
})
```
Закомментируем вывод в консоль и выведем по клику на слой попап.
```js title="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` вернём значение по умолчанию.
```js title="main.js"
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.on('mouseenter', 'cities-layer', () => {
map.getCanvas().style.cursor = 'pointer'
})
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = ''
})
})
```
В качестве завершающего штриха уберём карту подложку и добавим фон. При этом фон добавляем перед всеми слоями, так как все слои должны рисоваться после фона, поверх него.
```diff lang="js" title="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'
+ }
+ })
...
})
```
У нас получилась отличная карта!
При желании посмотрите [полный код](https://github.com/gtitov/geojson-maplibre-map) и [возможный результат](https://gtitov.github.io/geojson-maplibre-map/).
## Что мы получили
Откроем вкладку Сеть в инструментах разработчика и ещё разок проследим поток данных
![geojson-network-tab](../../../assets/geojson-network-tab.png)
1. Пользователь вводит адрес карты в браузере (в клиенте)
1. Клиент выполняет запрос к серверу по введённому адресу
1. Сервер обрабатывает запрос и возвращает разметку (HTML) (1)
1. В разметке содержаться запросы к офомлению (CSS), картографической библиотеке (MapLibre) и программной логике работы (JavaScript) веб-страницы (2)
1. Клиент (браузер), получив все необходимые сведения, отображает веб-страницу
1. Программная логика работы полученной веб-страницы выполняется и в соотстветвии с кодом инициирует запросы к данным (GeoJSON) для составления карты (3)
1. Полученные данные оформляются на веб-карте в рамках описанной разработчиком на языке JavaScript логики с использованием функций библиотеки MapLibre
1. Пользователь получает веб-карту
1. Веб-карта обогащается дополнительной интерактивностью в рамках описанной разработчиком логики
Такая карта удобна, когда немного данных, потому что мы всё переправляем пользователю данные как есть. Когда мы отправляем пользователю данные как есть, почти не требуется серверных мощностей, поэтому для таких карт есть варианты бесплатного размещения в Интернете.
## Упражнения
1. Покрасьте Москву в красный цвет
2. Выведите в попап один из атрибутов стран
3. Добавьте слой с границами озёр, установите им толщину в 2 пикселя
4. Замените курсор на перекрестие (`crosshair`) при расположении поверх стран
---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025.
---
title: Веб-карта
---
import { Card, FileTree, LinkCard } from '@astrojs/starlight/components';
import Question from '../../components/Question.astro';
import MultipleChoice from '../../components/MultipleChoice.astro';
import Option from '../../components/Option.astro';
В этой главе мы рассмотрим
- понятие веб-карты
- типы веб-карт
- клиент-серверную архитектуру
- HTML, CSS, JavaScript
В рамках практической части создадим карту мира на основе GeoJSON-файлов с использованием открытой библиотеки MapLibre GL JS. При желании заранее посмотреть [полный код](https://github.com/gtitov/geojson-maplibre-map) и [возможный результат](https://gtitov.github.io/geojson-maplibre-map/).
## Карты в Интернете
Любую ли карту в Интернете можно назвать веб-картой?
Яндекс.Карты — да!
Скан карты России, который я отправил по почте — пожалуй, нет.
И между ними ещё огромное множество различных вариантов карт в Интернете.
### Определение веб-карты
Веб-карту определяет цель размещения карты в сети. Если карту разместили в Интернете для того, чтобы пользователи могли работать с ней по сети, то это веб-карта. А файлы карт, передаваемые по сети или размещаемые на сайтах, изображения карт, используемые для оформления веб-страницы, веб-картами не являются.
<Card title="Веб-карта —">это карта, предназначенная для использования в сети</Card>
### Типы веб-карт
Веб-карты можно разделить на интерактивные / неинтерактивные и статические / динамически
| | статические | динамические |
| --------------- | ------------------- | --------------------------- |
| неинтерактивные | карты-картинки | генераторы карт-картинок |
| интерактивные | "простые" веб-карты | картографические приложения |
Интерактивность подразумевает возможность пользователя перемещаться по карте, менять масштаб, получать подробную информацию по клику, скрывать слои, менять цвета и так далее.
Неинтерактивные карты лишены этих возможностей. Но неинтерактивная карта будет веб-картой, если предназначена для использования в Интернете.
Статические веб-карты получают данные на клиент точно в том виде, в котором они хранятся на сервере, без какой-либо обработки. Из программного обеспечения требуется только веб-сервер, который будет принимать запросы от клиентов и возвращать в ответ файлы данных с сервера клиенту.
Динамические веб-карты подразумевают серверную обработку данных и позволяют запрашивать данные более гибко. Из программного обеспечения обычно используются база данных, программа для обработки данных и веб-сервер. В общем случае веб-сервер примет запрос и обратится к программе для обработки данных, она извлечёт данные из базы, обработает их и передаст веб-серверу, который отправит подготовленные данные клиенту.
Интерактивность касается интерфейса и клиентской части, а деление на статические и динамические веб-карты связано с обработкой данных в серверной части.
<LinkCard title='Другие продукты веб-картографии' href='/98-extra#кроме-веб-карт' description='Геопорталы, картографические веб-сервисы, веб-атласы, веб-ГИС'/>
## Клиент-серверная архитектура
Рассмотрим пример работы веб-ресурса.
![alt text](../../assets/image-15.png)
Пользователь вводит адрес запрашиваемого веб-ресурса в адресную строку браузера и нажимает клавишу Enter (1). Браузер инициирует и выполняет запрос к серверу на получение данных для создания веб-страницы (2). Сервер возвращает ответ (3). Из этого ответа браузер формирует веб-страницу, которую видит пользователь (4).
<Card title="Короче">
Браузер -- это клиент. Сервер -- это сервер. Клиент обращается к серверу с запросом, сервер возвращает клиенту ответ. Вот суть клиент-серверной архитектуры.
</Card>
В реальности клиент может выполнять множество запросов к серверу, чтобы показать пользователю веб-страницу.
:::tip
Откройте инструменты разработчика, нажав на клавишу F12 или сочетание клавиш Ctrl+Shift+i, перейдите во вкладку Сеть (Network) и обновите страницу, чтобы увидеть, сколько запросов выполняет браузер, чтобы показать вашу любимую веб-страницу.
:::
Первый запрос инициирует пользователь выполняет путём ввода адреса. Дальнейшие действия пользователя на веб-странице могут инициировать последующие запросы. Запрашиваться могут как веб-страницы целиком, так и отдельные данные для обновления содержания текущей веб-страницы. Если мы обновляем только часть содержания веб-страницы данными с сервера, то речь идёт об асинхронном запросе.
![*Запросы, выполняемые с веб-страницы*](../../assets/network-tab.png)
*Запросы, выполняемые с веб-страницы*
Веб-карты тоже строятся на основе клиент-серверной архитектуры. Рассмотрим интерактивную статическую карту (пока воображаемую).
Разметка, стили, логика (программный код) веб-карты, а также наборы пространственных данных веб-карты хранятся на сервере. Пользователь вводит адрес карты. Браузер запрашивает с сервера разметку веб-карты (html). Разметка приходит в браузер. Разметка содержит запросы к стилям и логике веб-карты. Логика веб-карты приходит в браузер и запрашивает наборы пространственных данных для создания веб-карты. Пространственные данные приходят [Взаимодействие с сервером закончено!] и кодом веб-карты превращаются в картографические слои.
<Card title='В клиент-серверной архитектуре браузер это'>
<MultipleChoice>
<Option>
сервер
</Option>
<Option isCorrect>
клиент
</Option>
<Option >
пользователь
</Option>
</MultipleChoice>
<details>
<summary>Развёрнутый ответ</summary>
Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу
</details>
</Card>
## Создание первой веб-карты
Мы рассмотрели интерактивную статическую карту, а сейчас мы её сделаем своими руками!
Выделим нашей первой веб-карте отдельную папку.
> В качестве среды разработки можно использовать [VS Code](https://code.visualstudio.com/), а чтобы запустить сервер -- расширение [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer).
### Инициализация карты
Создадим файл разметки `index.html`.
```html title="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`.
```css title="style.css"
/* Объявляем, что контейнер карты должен занимать всю страницу */
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
```
И файл с логикой карты `main.js`.
```js title="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` и загрузим в неё данные о [странах](https://raw.githubusercontent.com/gtitov/geojson-maplibre-map/refs/heads/master/data/countries.geojson), [городах](https://raw.githubusercontent.com/gtitov/geojson-maplibre-map/refs/heads/master/data/cities.geojson), [реках](https://raw.githubusercontent.com/gtitov/geojson-maplibre-map/refs/heads/master/data/rivers.geojson) и [озёрах](https://raw.githubusercontent.com/gtitov/geojson-maplibre-map/refs/heads/master/data/lakes.geojson).
Должна получится такая структура. HTML отвечает за структуру веб-страницы, CSS за оформление веб-страницы, JavaScript за логику работы веб-страницы. GeoJSON файлы хранят пространственные данные.
<FileTree>
- data/ # данные
- cities.geojson # города
- countries.geojson # страны
- lakes.geojson # озёра
- rivers.geojson # реки
- index.html # разметка
- style.css # стили
- main.js # логика
</FileTree>
Все действия с картой выполняются после первичной загрузки исходной карты.
Добавление картографических слоёв включает два шага: добавление источника данных `addSource` и добавление слоя `addLayer`. На первом шаге указываем, откуда мы будем брать данные, а на втором, как их оформить. Из одного источника можно создать несколько слоёв.
```js title="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'`). Аналогично добавляем слой линий и слой точек.
```js title="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
}
})
})
```
:::tip
Порядок отрисовки слоёв соответсвует порядку их объявления в коде. Последующие слои перекрывают предыдущие.
:::
В MapLibre слои можно фильтровать и оформлять на основе атрибутов с помощью [выражений](https://maplibre.org/maplibre-style-spec/expressions/).
Например, оставим только города с численностью населения больше 1 000 000
```diff lang="js" title="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`)
```diff lang="js" title="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`
```js title="main.js"
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.on('click', ['cities-layer'], (e) => {
console.log(e)
console.log(e.features)
})
})
```
Закомментируем вывод в консоль и выведем по клику на слой попап.
```js title="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` вернём значение по умолчанию.
```js title="main.js"
// Инициализируем карту
...
map.on('load', () => {
// Выполняется после загрузки карты
...
map.on('mouseenter', 'cities-layer', () => {
map.getCanvas().style.cursor = 'pointer'
})
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = ''
})
})
```
В качестве завершающего штриха уберём карту подложку и добавим фон. При этом фон добавляем перед всеми слоями, так как все слои должны рисоваться после фона, поверх него.
```diff lang="js" title="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'
+ }
+ })
...
})
```
У нас получилась отличная карта!
При желании посмотрите [полный код](https://github.com/gtitov/geojson-maplibre-map) и [возможный результат](https://gtitov.github.io/geojson-maplibre-map/).
## Что мы получили
Откроем вкладку Сеть в инструментах разработчика и ещё разок проследим поток данных
![geojson-network-tab](../../assets/geojson-network-tab.png)
1. Пользователь вводит адрес карты в браузере (в клиенте)
1. Клиент выполняет запрос к серверу по введённому адресу
1. Сервер обрабатывает запрос и возвращает разметку (HTML) (1)
1. В разметке содержаться запросы к офомлению (CSS), картографической библиотеке (MapLibre) и программной логике работы (JavaScript) веб-страницы (2)
1. Клиент (браузер), получив все необходимые сведения, отображает веб-страницу
1. Программная логика работы полученной веб-страницы выполняется и в соотстветвии с кодом инициирует запросы к данным (GeoJSON) для составления карты (3)
1. Полученные данные оформляются на веб-карте в рамках описанной разработчиком на языке JavaScript логики с использованием функций библиотеки MapLibre
1. Пользователь получает веб-карту
1. Веб-карта обогащается дополнительной интерактивностью в рамках описанной разработчиком логики
Такая карта удобна, когда немного данных, потому что мы всё переправляем пользователю данные как есть. Когда мы отправляем пользователю данные как есть, почти не требуется серверных мощностей, поэтому для таких карт есть варианты бесплатного размещения в Интернете.
## Упражнения
1. Покрасьте Москву в красный цвет
2. Выведите в попап один из атрибутов стран
3. Добавьте слой с границами озёр, установите им толщину в 2 пикселя
4. Замените курсор на перекрестие (`crosshair`) при расположении поверх стран
5. Офомление карты оставляет желать лучшего. Попробуйте оформить карту в соответствии с собственными представлениями о прекрасном.
## Контрольные вопросы
1. В каком файле описывается разметка веб-страницы?
1. Какой тег содержит ссылку на стили веб-страницы?
1. Что означает `container: 'map'` в коде `new maplibregl.Map({ container: 'map', ... })`?
1. Какой метод библиотеки MapLibre позволяет добавить картографический слой на карту?
1. Какой тип слоя используется для отображения полигонов озёр?
1. Какой текстовый формат пространственных данных может использоваться в качестве источника пространственных данных для библиотеки MapLibre?
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.

@ -1,404 +1,405 @@
---
title: API
---
import { Card, FileTree, LinkCard, TabItem, Tabs } from '@astrojs/starlight/components';
import Question from '../../../components/Question.astro';
import MultipleChoice from '../../../components/MultipleChoice.astro';
import Option from '../../../components/Option.astro';
В этой главе мы рассмотрим
- определение API
- асинхронность JavaScript
- запрос `fetch...then`
В рамках практической части создадим карту вакансий на основе изменяющегося содержания Google-таблицы.
## API
API обычно переводят как прикладной программный интерфейс или программный интерфейс приложений. На практике чаще говорят просто "апи".
Помните формулировку
> ...клиент (браузер) обращается к серверу с запросом, сервер возвращает клиенту ответ
![alt text](../../../assets/image-16.png)
Откуда браузер знает, куда обращаться? Мы написали. А откуда мы знаем? Мы знаем, потому что ожидаем, что при обращении к определённому *адресу* (URL) нам вернётся определённый ответ. Например, при обращении за файлом `style.css` мы получим стили.
<Card title="API">Совокупность *адресов* (URL) и правил обращения к ним называется API. Отдельные адреса называются методами API или, в обиходе, ручками API.</Card>
В идеале API будет иметь интуитивно понятное назначение и описание каждого метода, но это не всегда так.
> Для пространственных и картографических данных существует ряд стандартов для API. Обращаясь к API, который следует стандарту пользователь знает, чего ему ожидать. Мы уже использовали стандртный формат обмена GeoJSON и видели, что MapLibre принимает его без необходимости предварительной обработки. К другим популярным стандартам следует отнести протоколы WMS, WFS и векторные тайлы. Ряд стандартов курирует Open Geospatial Consortium, некоторые приняты как стандарты ISO и ГОСТ.
Подключая источники данных, мы тоже использовали API, просто вызовом необходимых методов занималась библиотека MapLibre.
## Заготовка для карты
Попробуем обратиться к публично доступным методам API Google-таблиц, а именно загрузить данные таблицы в формате CSV.
По аналогии с первым упражнением создадим заготовку для карты из файлов `index.html`, `style.css`, `main.js`.
<Tabs>
<TabItem label="HTML">
```html title="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">
<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>
```
</TabItem>
<TabItem label="CSS">
```css title="style.css"
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
```
</TabItem>
<TabItem label="JavaScript">
```js title="main.js"
const map = new maplibregl.Map({
container: 'map',
style: "https://raw.githubusercontent.com/gtitov/basemaps/refs/heads/master/positron-nolabels.json",
center: [51, 37],
zoom: 4
});
```
</TabItem>
</Tabs>
{/* <Card title={'Строка <code>&lt;div id="map">&lt;/div></code> это'}> */}
{/* <Question answer="контейнер для карты" ballast={['инициализация карты', 'стиль карты']} explanation="Это разметка контейнера карты"/> */}
{/* </Card> */}
<Card title={'Строка <code>&lt;div id="map">&lt;/div></code> это'}>
<MultipleChoice>
<Option isCorrect>
контейнер для карты
</Option>
<Option>
инициализация карты
</Option>
<Option>
стиль карты
</Option>
</MultipleChoice>
<details>
<summary>Развёрнутый ответ</summary>
Этой строкой мы размечаем контейнер для карты в HTML-файле. В JavaScript-файле мы обращаемся к этому контейнеру по `id`
</details>
</Card>
Удостоверимся, что карта отображается на локальном сервере.
## Обращение к API
Выполним запрос и выведем в консоль ответ
```js title="main.js"
map.on("load", () => {
...
const response = fetch("https://docs.google.com/spreadsheets/d/1f0waZduz5CXdNig_WWcJDWWntF-p5gN2-P-CNTLxEa0/export?format=csv")
console.log(response)
})
```
В консоль вывелся *Promise* -- обещание того, что браузер уже занимается нашим запросом. Ответ от сервера не вывелся, хотя во вкладке Сеть мы видим, что данные загружены. Дело в том, что вывод в консоль был выполнен раньше, чем мы получили ответ от сервера.
> Браузер начал исполнять наш код, увидел запрос к внешему ресурсу и подумал: "Здесь можно завязнуть. Ещё неизвестно, сколько этот внешний ресурс будет отвечать. Я верну пока обещание, что когда будет ответ, я его предоставлю, и отправлю запрос. А пока жду ответа буду дальше код выполнять."
Запрос к внешнему ресурсу выполняется асинхронно, то есть изымается из последовательного выполнения программного кода и выполняется отдельно. Поэтому вывод в консоль выполняется раньше того, как данные получены.
Чтобы этого не происходило, мы должны в явном виде указать, что код, использующий ответ на запрос, должен выполняться после выполнения запроса. Для этого используем конструкцию `fetch...then`
```js title="main.js"
map.on("load", () => {
...
fetch("https://docs.google.com/spreadsheets/d/1f0waZduz5CXdNig_WWcJDWWntF-p5gN2-P-CNTLxEa0/export?format=csv")
.then((response) => response.text())
.then((csv) => {
console.log(csv)
})
})
```
<details>
<summary>В первой карте тоже был асинхронный код</summary>
Сама карта создаётся асинхронно, поэтому все действия по добавлению слоёв мы выполняем после загрузки карты `map.on('load', () => {})`. Функция, которая вызывается после успешного завершения события называется callback-функцией. Это ещё один вариант работы с асинхронностью. А ещё асинхронно выполняется добавление источников данных `map.addSource`, они же тоже фактически загружаются с сервера. В этом случае библиотека MapLibre сама отслеживает, что код по добавлению источника должен завершиться, прежде чем мы сможем создавать картографические слои `map.addLayer` из этого источника.
</details>
## Преобразование данных
MapLibre не может работать с форматом CSV. Мы должны преобразовать данные в знакомый формат GeoJSON. Сделаем это!
Подключим библиотеку для чтения CSV данных в JS-объект.
```html title="index.html"
<head>
...
<script src="https://unpkg.com/papaparse@5.4.1/papaparse.min.js"></script>
</head>
```
Выполним чтение CSV данных c использованием подключенной библиотеки и сконструируем GeoJSON-объект.
```js title="main.js"
.then((csv) => {
const rows = Papa.parse(csv, { header: true }) // читаем CSV
// console.log(rows) // любуемся
// Формируем объекты GeoJSON
const geojsonFeatures = rows.data.map((row) => {
return {
type: "Feature",
properties: row,
geometry: {
type: "Point",
coordinates: [row.lon, row.lat],
}
}
})
const geojson = {
type: "FeatureCollection",
features: geojsonFeatures
}
})
```
GeoJSON уже можно использовать в качестве источника для MapLibre.
## Работа над картой
У нас есть заготовка, есть данные, самое время заняться картой!
```js title="main.js"
.then((csv) => {
...
const geojson = {
type: "FeatureCollection",
features: geojsonFeatures
}
map.addSource("vacancies", {
type: "geojson",
data: geojson,
cluster: true, // точки будем объединять в кластеры
clusterRadius: 20, // радиус поиска 20 пикселей
});
map.addLayer({
id: "clusters",
source: "vacancies",
type: "circle",
paint: {
"circle-color": "#7EC8E3",
"circle-stroke-width": 1,
"circle-stroke-color": "#FFFFFF",
"circle-radius": [
"step", ["get", "point_count"],
12, // до 3 точек в кластере
3, // --- первое граничное значение
20, // от 3 точек до 6
6, // --- второе граничное значение
30 // больше 6 точек в кластере
],
},
});
map.addLayer({
id: "clusters-labels",
type: "symbol",
source: "vacancies",
layout: {
"text-field": ["get", "point_count"],
"text-size": 10,
},
});
})
```
## Сопутствующие элементы
Чаще всего карту сопровождают дополнительные элементы веб-страницы. Для этой карты мы приведём список всех вакансий и список вакансий, которые пользователь видит на карте при текущем охвате.
Разметим этим спискам место на веб-странице.
```html title="index.html"
<body>
<div id="map"></div>
<div id="list-selected"><h2>Сейчас на карте</h2></div>
<div id="list-all"><h2>Все вакансии</h2></div>
<script src="main.js"></script>
</body>
```
И зададим оформление.
```css title="style.css"
h2 {
margin: 10px;
}
.list-item {
padding: 10px;
}
#map {
position: absolute;
top: 0;
bottom: 0;
right: 300px;
left: 300px;
}
#list-selected {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 300px;
overflow-y: auto;
}
#list-all {
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 300px;
overflow-y: auto;
}
```
Теперь на нашей веб-странице выделено место под списки. Плюс мы добавили оформление для заголовков второго уровня `h2` и создали класс `.list-item` для будущих элементов списка.
Сначала наполним список всех вакансий. Это нужно сделать единожды.
```js title="main.js"
.then((csv) => {
...
geojson.features.map((f) => {
document.getElementById(
"list-all"
).innerHTML += `<div class="list-item">
<h4>${f.properties["Вакансия"]}</h4>
<a href='#' onclick="map.flyTo({center: [${f.geometry.coordinates}], zoom: 10})">Найти на карте</a>
</div><hr>`;
});
})
```
А список вакансий, которые видит пользователь при заданном охвате карты, надо будет обновлять при каждом перемещении по карте. Мы будем реагировать на окончание перемещения. Ещё одной сложностью является необходимость извлечь из каждого кластера сведения о том, какие объекты в него входят. Со всем этим мы прекрасно справимся.
```js title="main.js"
.then((csv) => {
...
map.on('moveend', () => {
const features = map.queryRenderedFeatures({
layers: ["clusters"]
});
document.getElementById("list-selected").innerHTML = "<h2>Сейчас на карте</h2>"
features.map(f => {
if (f.properties.cluster) {
const clusterId = f.properties.cluster_id;
const pointCount = f.properties.point_count;
map.getSource("vacancies").getClusterLeaves(clusterId, pointCount, 0)
.then((clusterFeatures) => {
clusterFeatures.map((feature) => document.getElementById("list-selected")
.innerHTML += `<div class="list-item">
<h4>${feature.properties["Вакансия"]}</h4>
<a target="blank_" href='${feature.properties["Ссылка на сайте Картетики"]}'>Подробнее</a>
</div><hr>`)
});
} else {
document.getElementById("list-selected")
.innerHTML += `<div class="list-item">
<h4>${f.properties["Вакансия"]}</h4>
<a target="blank_" href='${f.properties["Ссылка на сайте Картетики"]}'>Подробнее</a>
</div><hr>`
}
})
})
})
```
## Пара UX-штрихов
Для удобства пользования картой добавим приближение к карте по клику на объект и изменение курсора при наведении на слой.
```js title="main.js"
map.on("load", () => {
...
map.on("click", "clusters", function (e) {
map.flyTo({ center: e.lngLat, zoom: 8 });
})
map.on("mouseenter", "clusters", function () {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "clusters", function () {
map.getCanvas().style.cursor = "";
});
})
```
## Что мы получили
Проделана прекрасная работа!
При желании посмотрите [полный код](https://github.com/gtitov/sheet-maplibre-map) и [возможный результат](https://gtitov.github.io/sheet-maplibre-map/).
## Упражнения
1. Поменяйте местами списки
2. Сделайте так, чтобы цвет кластера зависел от количества элементов внутри него
3. Сделайте, чтобы до первого перемещения карты список вакансий "Сейчас на карте" тоже был заполнен
<details>
<summary>Подсказка для третьего 🧙‍♂️</summary>
Можно использовать метод <code>map.on("idle", () => \{\})</code>
</details>
## Чтение
1. Что такое API / Дока [ссылка](https://doka.guide/tools/api/)
1. Асинхронность в JavaScript / Дока [ссылка](https://doka.guide/js/async-in-js/)
1. fetch() / Дока [ссылка](https://doka.guide/js/fetch/)
1. Promise / Дока [ссылка](https://doka.guide/js/promise/)
---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025.
---
title: API
draft: true
---
import { Card, FileTree, LinkCard, TabItem, Tabs } from '@astrojs/starlight/components';
import Question from '../../components/Question.astro';
import MultipleChoice from '../../components/MultipleChoice.astro';
import Option from '../../components/Option.astro';
В этой главе мы рассмотрим
- определение API
- асинхронность JavaScript
- запрос `fetch...then`
В рамках практической части создадим карту вакансий на основе изменяющегося содержания Google-таблицы.
## API
API обычно переводят как прикладной программный интерфейс или программный интерфейс приложений. На практике чаще говорят просто "апи".
Помните формулировку
> ...клиент (браузер) обращается к серверу с запросом, сервер возвращает клиенту ответ
![alt text](../../assets/image-16.png)
Откуда браузер знает, куда обращаться? Мы написали. А откуда мы знаем? Мы знаем, потому что ожидаем, что при обращении к определённому *адресу* (URL) нам вернётся определённый ответ. Например, при обращении за файлом `style.css` мы получим стили.
<Card title="API">Совокупность *адресов* (URL) и правил обращения к ним называется API. Отдельные адреса называются методами API или, в обиходе, ручками API.</Card>
В идеале API будет иметь интуитивно понятное назначение и описание каждого метода, но это не всегда так.
> Для пространственных и картографических данных существует ряд стандартов для API. Обращаясь к API, который следует стандарту пользователь знает, чего ему ожидать. Мы уже использовали стандртный формат обмена GeoJSON и видели, что MapLibre принимает его без необходимости предварительной обработки. К другим популярным стандартам следует отнести протоколы WMS, WFS и векторные тайлы. Ряд стандартов курирует Open Geospatial Consortium, некоторые приняты как стандарты ISO и ГОСТ.
Подключая источники данных, мы тоже использовали API, просто вызовом необходимых методов занималась библиотека MapLibre.
## Заготовка для карты
Попробуем обратиться к публично доступным методам API Google-таблиц, а именно загрузить данные таблицы в формате CSV.
По аналогии с первым упражнением создадим заготовку для карты из файлов `index.html`, `style.css`, `main.js`.
<Tabs>
<TabItem label="HTML">
```html title="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">
<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>
```
</TabItem>
<TabItem label="CSS">
```css title="style.css"
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
```
</TabItem>
<TabItem label="JavaScript">
```js title="main.js"
const map = new maplibregl.Map({
container: 'map',
style: "https://raw.githubusercontent.com/gtitov/basemaps/refs/heads/master/positron-nolabels.json",
center: [51, 37],
zoom: 4
});
```
</TabItem>
</Tabs>
{/* <Card title={'Строка <code>&lt;div id="map">&lt;/div></code> это'}> */}
{/* <Question answer="контейнер для карты" ballast={['инициализация карты', 'стиль карты']} explanation="Это разметка контейнера карты"/> */}
{/* </Card> */}
<Card title={'Строка <code>&lt;div id="map">&lt;/div></code> это'}>
<MultipleChoice>
<Option isCorrect>
контейнер для карты
</Option>
<Option>
инициализация карты
</Option>
<Option>
стиль карты
</Option>
</MultipleChoice>
<details>
<summary>Развёрнутый ответ</summary>
Этой строкой мы размечаем контейнер для карты в HTML-файле. В JavaScript-файле мы обращаемся к этому контейнеру по `id`
</details>
</Card>
Удостоверимся, что карта отображается на локальном сервере.
## Обращение к API
Выполним запрос и выведем в консоль ответ
```js title="main.js"
map.on("load", () => {
...
const response = fetch("https://docs.google.com/spreadsheets/d/1f0waZduz5CXdNig_WWcJDWWntF-p5gN2-P-CNTLxEa0/export?format=csv")
console.log(response)
})
```
В консоль вывелся *Promise* -- обещание того, что браузер уже занимается нашим запросом. Ответ от сервера не вывелся, хотя во вкладке Сеть мы видим, что данные загружены. Дело в том, что вывод в консоль был выполнен раньше, чем мы получили ответ от сервера.
> Браузер начал исполнять наш код, увидел запрос к внешему ресурсу и подумал: "Здесь можно завязнуть. Ещё неизвестно, сколько этот внешний ресурс будет отвечать. Я верну пока обещание, что когда будет ответ, я его предоставлю, и отправлю запрос. А пока жду ответа буду дальше код выполнять."
Запрос к внешнему ресурсу выполняется асинхронно, то есть изымается из последовательного выполнения программного кода и выполняется отдельно. Поэтому вывод в консоль выполняется раньше того, как данные получены.
Чтобы этого не происходило, мы должны в явном виде указать, что код, использующий ответ на запрос, должен выполняться после выполнения запроса. Для этого используем конструкцию `fetch...then`
```js title="main.js"
map.on("load", () => {
...
fetch("https://docs.google.com/spreadsheets/d/1f0waZduz5CXdNig_WWcJDWWntF-p5gN2-P-CNTLxEa0/export?format=csv")
.then((response) => response.text())
.then((csv) => {
console.log(csv)
})
})
```
<details>
<summary>В первой карте тоже был асинхронный код</summary>
Сама карта создаётся асинхронно, поэтому все действия по добавлению слоёв мы выполняем после загрузки карты `map.on('load', () => {})`. Функция, которая вызывается после успешного завершения события называется callback-функцией. Это ещё один вариант работы с асинхронностью. А ещё асинхронно выполняется добавление источников данных `map.addSource`, они же тоже фактически загружаются с сервера. В этом случае библиотека MapLibre сама отслеживает, что код по добавлению источника должен завершиться, прежде чем мы сможем создавать картографические слои `map.addLayer` из этого источника.
</details>
## Преобразование данных
MapLibre не может работать с форматом CSV. Мы должны преобразовать данные в знакомый формат GeoJSON. Сделаем это!
Подключим библиотеку для чтения CSV данных в JS-объект.
```html title="index.html"
<head>
...
<script src="https://unpkg.com/papaparse@5.4.1/papaparse.min.js"></script>
</head>
```
Выполним чтение CSV данных c использованием подключенной библиотеки и сконструируем GeoJSON-объект.
```js title="main.js"
.then((csv) => {
const rows = Papa.parse(csv, { header: true }) // читаем CSV
// console.log(rows) // любуемся
// Формируем объекты GeoJSON
const geojsonFeatures = rows.data.map((row) => {
return {
type: "Feature",
properties: row,
geometry: {
type: "Point",
coordinates: [row.lon, row.lat],
}
}
})
const geojson = {
type: "FeatureCollection",
features: geojsonFeatures
}
})
```
GeoJSON уже можно использовать в качестве источника для MapLibre.
## Работа над картой
У нас есть заготовка, есть данные, самое время заняться картой!
```js title="main.js"
.then((csv) => {
...
const geojson = {
type: "FeatureCollection",
features: geojsonFeatures
}
map.addSource("vacancies", {
type: "geojson",
data: geojson,
cluster: true, // точки будем объединять в кластеры
clusterRadius: 20, // радиус поиска 20 пикселей
});
map.addLayer({
id: "clusters",
source: "vacancies",
type: "circle",
paint: {
"circle-color": "#7EC8E3",
"circle-stroke-width": 1,
"circle-stroke-color": "#FFFFFF",
"circle-radius": [
"step", ["get", "point_count"],
12, // до 3 точек в кластере
3, // --- первое граничное значение
20, // от 3 точек до 6
6, // --- второе граничное значение
30 // больше 6 точек в кластере
],
},
});
map.addLayer({
id: "clusters-labels",
type: "symbol",
source: "vacancies",
layout: {
"text-field": ["get", "point_count"],
"text-size": 10,
},
});
})
```
## Сопутствующие элементы
Чаще всего карту сопровождают дополнительные элементы веб-страницы. Для этой карты мы приведём список всех вакансий и список вакансий, которые пользователь видит на карте при текущем охвате.
Разметим этим спискам место на веб-странице.
```html title="index.html"
<body>
<div id="map"></div>
<div id="list-selected"><h2>Сейчас на карте</h2></div>
<div id="list-all"><h2>Все вакансии</h2></div>
<script src="main.js"></script>
</body>
```
И зададим оформление.
```css title="style.css"
h2 {
margin: 10px;
}
.list-item {
padding: 10px;
}
#map {
position: absolute;
top: 0;
bottom: 0;
right: 300px;
left: 300px;
}
#list-selected {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 300px;
overflow-y: auto;
}
#list-all {
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 300px;
overflow-y: auto;
}
```
Теперь на нашей веб-странице выделено место под списки. Плюс мы добавили оформление для заголовков второго уровня `h2` и создали класс `.list-item` для будущих элементов списка.
Сначала наполним список всех вакансий. Это нужно сделать единожды.
```js title="main.js"
.then((csv) => {
...
geojson.features.map((f) => {
document.getElementById(
"list-all"
).innerHTML += `<div class="list-item">
<h4>${f.properties["Вакансия"]}</h4>
<a href='#' onclick="map.flyTo({center: [${f.geometry.coordinates}], zoom: 10})">Найти на карте</a>
</div><hr>`;
});
})
```
А список вакансий, которые видит пользователь при заданном охвате карты, надо будет обновлять при каждом перемещении по карте. Мы будем реагировать на окончание перемещения. Ещё одной сложностью является необходимость извлечь из каждого кластера сведения о том, какие объекты в него входят. Со всем этим мы прекрасно справимся.
```js title="main.js"
.then((csv) => {
...
map.on('moveend', () => {
const features = map.queryRenderedFeatures({
layers: ["clusters"]
});
document.getElementById("list-selected").innerHTML = "<h2>Сейчас на карте</h2>"
features.map(f => {
if (f.properties.cluster) {
const clusterId = f.properties.cluster_id;
const pointCount = f.properties.point_count;
map.getSource("vacancies").getClusterLeaves(clusterId, pointCount, 0)
.then((clusterFeatures) => {
clusterFeatures.map((feature) => document.getElementById("list-selected")
.innerHTML += `<div class="list-item">
<h4>${feature.properties["Вакансия"]}</h4>
<a target="blank_" href='${feature.properties["Ссылка на сайте Картетики"]}'>Подробнее</a>
</div><hr>`)
});
} else {
document.getElementById("list-selected")
.innerHTML += `<div class="list-item">
<h4>${f.properties["Вакансия"]}</h4>
<a target="blank_" href='${f.properties["Ссылка на сайте Картетики"]}'>Подробнее</a>
</div><hr>`
}
})
})
})
```
## Пара UX-штрихов
Для удобства пользования картой добавим приближение к карте по клику на объект и изменение курсора при наведении на слой.
```js title="main.js"
map.on("load", () => {
...
map.on("click", "clusters", function (e) {
map.flyTo({ center: e.lngLat, zoom: 8 });
})
map.on("mouseenter", "clusters", function () {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "clusters", function () {
map.getCanvas().style.cursor = "";
});
})
```
## Что мы получили
Проделана прекрасная работа!
При желании посмотрите [полный код](https://github.com/gtitov/sheet-maplibre-map) и [возможный результат](https://gtitov.github.io/sheet-maplibre-map/).
## Упражнения
1. Поменяйте местами списки
2. Сделайте так, чтобы цвет кластера зависел от количества элементов внутри него
3. Сделайте, чтобы до первого перемещения карты список вакансий "Сейчас на карте" тоже был заполнен
<details>
<summary>Подсказка для третьего 🧙‍♂️</summary>
Можно использовать метод <code>map.on("idle", () => \{\})</code>
</details>
## Чтение
1. Что такое API / Дока [ссылка](https://doka.guide/tools/api/)
1. Асинхронность в JavaScript / Дока [ссылка](https://doka.guide/js/async-in-js/)
1. fetch() / Дока [ссылка](https://doka.guide/js/fetch/)
1. Promise / Дока [ссылка](https://doka.guide/js/promise/)
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.

@ -1,360 +1,361 @@
---
title: Бэкенд
---
import { Card, FileTree, LinkCard, TabItem, Tabs } from '@astrojs/starlight/components';
В этой главе мы рассмотрим
- понятия бэкенда и фронтенда
- SQL
- Flask
- CORS
В рамках практической части создадим карту индекса качества городской среды по базе данных SQLite с бэкендом на Python.
## Бэкенд
Мы уже знаем, что веб-приложения можно разделить на клиентскую и серверную части. Разработку клиентской части называют фронтендом. Разработку серверной части называют бэкендом. Фронтенд общается с бэкендом через API. Бэкенд предоставляет метода API, а фронтенд к ним обращается.
![alt text](../../../assets/image-17.png)
Когда происходит вызов метода API -- запрос определённого URL -- выполняется соответствующая серверная функция. Для программирования серверных функций могут использоваться различные языки программирования Python, Go, Rust и даже JavaScript (NodeJS).
В предыдущем занятии мы обращались к бэкенду через API, а в этот раз разработаем бэкенд сами. Наш бэкенд мы разработаем на языке Python с использованием библиотеки Flask.
## Подготовка
Создадим папку с заготовкой для нашей карты.
Добавим туда папку `backend`. Загрузим [отсюда](https://github.com/gtitov/flask-maplibre-map/raw/refs/heads/main/backend/cities_index.sqlite) базу данных и положим в созданную папку `backend`. Cоздадим в этой папке файл с нашим бэкендом `app.py`.
<FileTree>
- backend/
- app.py
- cities_index.sqlite
- index.html
- style.css
- main.js
</FileTree>
<details>
<summary>Что это за база данных такая -- SQLite</summary>
SQLite -- встраиваемая база данных, которая хранит все свои данные в одном файле. В одном файле хранятся все таблицы, индексы и другая информация о базе данных, что упрощает управление и резервное копирование. Благодаря этому, она не требует отдельного сервера и легко интегрируется в различные приложения.
</details>
## Разработка бэкенда
### Установка Flask
Установим Python и после установки через терминал загрузим в Python библиотеку Flask.
```sh title=Терминал
pip install Flask
```
Теперь откроем ранее созданный нами файл `backend/app.py`. Подключим необходимые библиотеки и создадим объект `app`, в рамках которого мы будем определять доступные методы API.
```py title="app.py"
from flask import Flask, Response
import sqlite3
import json
import time
app = Flask(__name__)
DB_LOCATION = "cities_index.sqlite"
```
### Список городов по году
Добавим первый метод API. Он возвращает пользователю все города за выбранный год. При обращении к этому методу бэкенд выполняет запрос к базе данных и формирует на основе ответа GeoJSON файл, который мы сможем сразу отправить на карту.
> Мы могли бы отправить на фронтенд и неподготовленный файл. Собрать GeoJSON на клиентской стороне, как в карте вакансий, когда Google возвращал нам CSV. Но то был чужой API. А этот наш. И в нашем мы можем сделать так, как будет удобнее нам!
```py title=app.py
...
@app.route("/cities/<year>") # путь API, к которому обращается пользователь
def cities_by_year(year): # функция, которая будет выполняться при обращении
# start_time = time.time()
db = sqlite3.connect(DB_LOCATION) # подключение к базе данных
db.row_factory = sqlite3.Row # указание, что в строках мы будем сохранять название колонки и значение
cursor = db.execute("SELECT * FROM cities WHERE year = ?", (year,)) # выполняем запрос к базе, подставляя год, введённый пользователем
cities = cursor.fetchall() # забираем результат запроса
cursor.close() # закрываем запрос
db.close() # закрываем подключение
geojson = { # приводим к формату GeoJSON
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [row["longitude"], row["latitude"]],
},
"properties": dict(row),
}
for row in cities
],
}
r = Response( # формируем ответ
json.dumps(geojson, ensure_ascii=False), # ensure_ascii=False, чтобы нормально отображалась кириллица
mimetype="application/json" # указываем тип данных
)
# print("--- %s seconds ---" % (time.time() - start_time))
return r
```
Запускаем бэкенд на локальном сервере для проверки. Открываем терминал в папке `backend` и выполняем команду
```sh title=Терминал
flask run --debug
```
После чего увидим что-то вроде
```sh title=Терминал
flask run --debug
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 106-994-678
```
Теперь мы можем обратиться к нашему бэкенду через API по адресу [`http://127.0.0.1:5000/cities/2020`](http://127.0.0.1:5000/cities/2020).
### Сведения о городе по идентификатору
Перейдём ко второму методу. Получим подробные сведения о городе по его идентификатору.
```py title=app.py
@app.route("/city/<id>")
def city_by_id(id):
start_time = time.time()
db = sqlite3.connect(DB_LOCATION)
db.row_factory = sqlite3.Row
cursor = db.execute("SELECT * FROM cities WHERE id = ?", (id,))
city = cursor.fetchone()
cursor.close()
db.close()
r = Response(
json.dumps(dict(city), ensure_ascii=False),
mimetype="application/json",
)
print("--- %s seconds ---" % (time.time() - start_time))
return r
```
Проверим этот метод [`http://127.0.0.1:5000/city/1000`](http://127.0.0.1:5000/city/1000).
## Разработка фронтенда
### Подключение данных и CORS
Наш бэкенд возвращает данные в формате GeoJSON, поэтому мы можем сразу подключить их в нашу карту.
```js title=main.js
map.on("load", () => {
map.addSource('cities', {
type: 'geojson',
data: "http://localhost:5000/cities/2020" // бэкенд должен быть запущен
});
map.addLayer({
'id': 'cities-layer',
'source': 'cities',
'type': 'circle',
'paint': {
'circle-stroke-width': 1,
'circle-stroke-color': '#FFFFFF',
// SELECT MIN(total_points), MAX(total_points) FROM cities
'circle-color': [
'interpolate', ['linear'],
['get', 'total_points'],
50, '#d7191c',
150, '#ffffbf',
250, '#1a9641'
],
'circle-opacity': 0.8,
// SELECT DISTINCT group_name FROM cities
'circle-radius': [
"match",
['get', 'group_name'],
'Малый город', 3,
'Средний город', 6,
'Большой город', 6,
'Крупный город', 8,
'Крупнейший город', 12,
0 // остальные
]
}
});
})
```
Однако на карте мы не увидим искомых городов. Чтобы узнать почему, проверим вкладку "Сеть" в инструментах разработчика. У запроса к списку городов мы увидим надпись **Ошибка CORS**.
Механизм CORS -- Cross-Origin Resource Sharing -- призван повысить безопасность веб-страницы. Нам, чтобы избежать ошибки CORS, надо указать, что API может отвечать на запросы любых веб-страниц.
> CORS -- это история про *веб-страницы*, поэтому выполняя запросы к API напрямую мы с ней не сталкивались.
```diff lang=py title=app.py
...
@app.route("/cities/<int:year>") # путь API, к которому обращается пользователь
def cities_by_year(year): # функция, которая будет выполняться при обращении
...
r = Response( # формируем ответ
json.dumps(geojson, ensure_ascii=False), # ensure_ascii=False, чтобы нормально отображалась кириллица
mimetype="application/json", # указываем тип данных
+ headers={"Access-Control-Allow-Origin": "*"}
)
# print("--- %s seconds ---" % (time.time() - start_time))
return r
@app.route("/city/<int:id>")
def city_by_id(id):
...
r = Response(
json.dumps(dict(city), ensure_ascii=False),
mimetype="application/json",
+ headers={"Access-Control-Allow-Origin": "*"}
)
# print("--- %s seconds ---" % (time.time() - start_time))
return r
```
После добавления заголовков о том, что API может обслуживать любые веб-страницы, в ответ мы получаем наш список городов в формате GeoJSON.
### Выбор года
Дадим пользователю возможность выбирать год, за который он хочет видеть индекс городов.
Разметим элемент с выпадающем списком.
```diff lang=html title=index.html
<body>
<div id='map'></div>
+ <div>
+ <p>Год</p>
+ <select id="year-selector">
+ <option value="2020" selected>2020</option>
+ <option value="2019">2019</option>
+ <option value="2018">2018</option>
+ </select>
+ </div>
<script src="main.js"></script>
</body>
```
Сделаем так, чтобы он выводился поверх карты.
```diff lang=css title=style.css
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
+ z-index: -1;
}
```
Запрограммируем реакцию карты на выбор года. Асинхронный запрос за нас выполнит картографическая библиотека.
```js title=main.js
map.on("load", () => {
...
document.getElementById("year-selector").addEventListener(
'change',
(e) => {
const year = e.target.value // фиксируем выбранный год
map.getSource('cities').setData(`http://localhost:5000/cities/${year}`) // меняем источник данных
}
)
})
```
### Сведения о городе
Выведем по клику на город подробные сведения о нём. Для отображения информации используем модальное окно, то есть диалоговое окно вспывающее поверх страницы.
```diff lang=html title=index.html
<body>
<div id='map'></div>
...
+ <dialog id="city-details-modal" onmousedown="this.close()"></dialog>
<script src="main.js"></script>
</body>
```
При клике будем выполнять запрос к методу API, который возвращает подробную информацию о городе. Здесь мы работаем с асинхронностью самостоятельно.
```js title=main.js
map.on("load", () => {
...
map.on('click', 'cities-layer', (e) => {
// console.log(e.features[0].properties.id)
fetch(`http://localhost:5000/city/${e.features[0].properties.id}`)
.then(response => response.json())
.then(cityProperties => {
// console.log(cityProperties)
document.getElementById("city-details-modal").innerHTML = `<h1>${cityProperties.name}</h1>
<img src="${cityProperties.emblem_url}" height="200">
<h3>Численность населения</h3><h2>${cityProperties.people_count} тыс. чел</h2>
<h3>Индекс качества городской среды</h3><h2>${cityProperties.total_points} / 360</h2>
<hr>
<h3>Жилье и прилегающие пространства</h3><h2>${cityProperties.house_points} / 60</h2>
<h3>Озелененные пространства</h3><h2>${cityProperties.park_points} / 60</h2>
<h3>Общественно-деловая инфраструктура</h3><h2>${cityProperties.business_points} / 60</h2>
<h3>Социально-досуговая инфраструктура</h3><h2>${cityProperties.social_points} / 60</h2>
<h3>Улично-дорожная</h3><h2>${cityProperties.street_points} / 60</h2>
<h3>Общегородское пространство</h3><h2>${cityProperties.common_points} / 60</h2>`
document.getElementById("city-details-modal").showModal() // showModal() -- встроенный метод элемента <dialog>
})
})
map.on('mouseenter', 'cities-layer', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = '';
});
})
```
> Если вы уже заметили кое-какую нестыковку в наших запросах, посмотрите упражнения в конце главы
## Что мы получили
Наши карты становятся всё лучше!
При желании посмотрите [полный код](https://github.com/gtitov/flask-maplibre-map) и [возможный результат](https://gtitov.github.io/flask-maplibre-map/).
## Упражнения
1. Создайте метод, который вернёт список доступных годов
1. Выведите модальное окно слева и выполните подлёт к точке клика
1. Вы могли заметить, что то, что мы получаем методом запроса деталей о городе уже содержится в полному списке городов: вам предлагается избавиться от этой избыточности
<details>
<summary>Есть два варианта -- подумайте над ними. Это тест на то, что вам ближе, бэкенд или фронтенд. Когда подумаете, можно посмотреть разгадку 👀</summary>
Бэкендер: можно убрать из метода для списка городов лишние атрибуты<br/>Фронтендер: на клик по объекту можно не обращаться к серверу, а использовать данные из атрибутов объекта
</details>
## Чтение
1. Что такое CORS / Дока [ссылка](https://doka.guide/tools/cors/)
1. Безопасность веб-приложений и распространённые атаки / Дока [ссылка](https://doka.guide/tools/web-security/)
---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025.
---
title: Бэкенд
draft: true
---
import { Card, FileTree, LinkCard, TabItem, Tabs } from '@astrojs/starlight/components';
В этой главе мы рассмотрим
- понятия бэкенда и фронтенда
- SQL
- Flask
- CORS
В рамках практической части создадим карту индекса качества городской среды по базе данных SQLite с бэкендом на Python.
## Бэкенд
Мы уже знаем, что веб-приложения можно разделить на клиентскую и серверную части. Разработку клиентской части называют фронтендом. Разработку серверной части называют бэкендом. Фронтенд общается с бэкендом через API. Бэкенд предоставляет метода API, а фронтенд к ним обращается.
![alt text](../../assets/image-17.png)
Когда происходит вызов метода API -- запрос определённого URL -- выполняется соответствующая серверная функция. Для программирования серверных функций могут использоваться различные языки программирования Python, Go, Rust и даже JavaScript (NodeJS).
В предыдущем занятии мы обращались к бэкенду через API, а в этот раз разработаем бэкенд сами. Наш бэкенд мы разработаем на языке Python с использованием библиотеки Flask.
## Подготовка
Создадим папку с заготовкой для нашей карты.
Добавим туда папку `backend`. Загрузим [отсюда](https://github.com/gtitov/flask-maplibre-map/raw/refs/heads/main/backend/cities_index.sqlite) базу данных и положим в созданную папку `backend`. Cоздадим в этой папке файл с нашим бэкендом `app.py`.
<FileTree>
- backend/
- app.py
- cities_index.sqlite
- index.html
- style.css
- main.js
</FileTree>
<details>
<summary>Что это за база данных такая -- SQLite</summary>
SQLite -- встраиваемая база данных, которая хранит все свои данные в одном файле. В одном файле хранятся все таблицы, индексы и другая информация о базе данных, что упрощает управление и резервное копирование. Благодаря этому, она не требует отдельного сервера и легко интегрируется в различные приложения.
</details>
## Разработка бэкенда
### Установка Flask
Установим Python и после установки через терминал загрузим в Python библиотеку Flask.
```sh title=Терминал
pip install Flask
```
Теперь откроем ранее созданный нами файл `backend/app.py`. Подключим необходимые библиотеки и создадим объект `app`, в рамках которого мы будем определять доступные методы API.
```py title="app.py"
from flask import Flask, Response
import sqlite3
import json
import time
app = Flask(__name__)
DB_LOCATION = "cities_index.sqlite"
```
### Список городов по году
Добавим первый метод API. Он возвращает пользователю все города за выбранный год. При обращении к этому методу бэкенд выполняет запрос к базе данных и формирует на основе ответа GeoJSON файл, который мы сможем сразу отправить на карту.
> Мы могли бы отправить на фронтенд и неподготовленный файл. Собрать GeoJSON на клиентской стороне, как в карте вакансий, когда Google возвращал нам CSV. Но то был чужой API. А этот наш. И в нашем мы можем сделать так, как будет удобнее нам!
```py title=app.py
...
@app.route("/cities/<year>") # путь API, к которому обращается пользователь
def cities_by_year(year): # функция, которая будет выполняться при обращении
# start_time = time.time()
db = sqlite3.connect(DB_LOCATION) # подключение к базе данных
db.row_factory = sqlite3.Row # указание, что в строках мы будем сохранять название колонки и значение
cursor = db.execute("SELECT * FROM cities WHERE year = ?", (year,)) # выполняем запрос к базе, подставляя год, введённый пользователем
cities = cursor.fetchall() # забираем результат запроса
cursor.close() # закрываем запрос
db.close() # закрываем подключение
geojson = { # приводим к формату GeoJSON
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [row["longitude"], row["latitude"]],
},
"properties": dict(row),
}
for row in cities
],
}
r = Response( # формируем ответ
json.dumps(geojson, ensure_ascii=False), # ensure_ascii=False, чтобы нормально отображалась кириллица
mimetype="application/json" # указываем тип данных
)
# print("--- %s seconds ---" % (time.time() - start_time))
return r
```
Запускаем бэкенд на локальном сервере для проверки. Открываем терминал в папке `backend` и выполняем команду
```sh title=Терминал
flask run --debug
```
После чего увидим что-то вроде
```sh title=Терминал
flask run --debug
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 106-994-678
```
Теперь мы можем обратиться к нашему бэкенду через API по адресу [`http://127.0.0.1:5000/cities/2020`](http://127.0.0.1:5000/cities/2020).
### Сведения о городе по идентификатору
Перейдём ко второму методу. Получим подробные сведения о городе по его идентификатору.
```py title=app.py
@app.route("/city/<id>")
def city_by_id(id):
start_time = time.time()
db = sqlite3.connect(DB_LOCATION)
db.row_factory = sqlite3.Row
cursor = db.execute("SELECT * FROM cities WHERE id = ?", (id,))
city = cursor.fetchone()
cursor.close()
db.close()
r = Response(
json.dumps(dict(city), ensure_ascii=False),
mimetype="application/json",
)
print("--- %s seconds ---" % (time.time() - start_time))
return r
```
Проверим этот метод [`http://127.0.0.1:5000/city/1000`](http://127.0.0.1:5000/city/1000).
## Разработка фронтенда
### Подключение данных и CORS
Наш бэкенд возвращает данные в формате GeoJSON, поэтому мы можем сразу подключить их в нашу карту.
```js title=main.js
map.on("load", () => {
map.addSource('cities', {
type: 'geojson',
data: "http://localhost:5000/cities/2020" // бэкенд должен быть запущен
});
map.addLayer({
'id': 'cities-layer',
'source': 'cities',
'type': 'circle',
'paint': {
'circle-stroke-width': 1,
'circle-stroke-color': '#FFFFFF',
// SELECT MIN(total_points), MAX(total_points) FROM cities
'circle-color': [
'interpolate', ['linear'],
['get', 'total_points'],
50, '#d7191c',
150, '#ffffbf',
250, '#1a9641'
],
'circle-opacity': 0.8,
// SELECT DISTINCT group_name FROM cities
'circle-radius': [
"match",
['get', 'group_name'],
'Малый город', 3,
'Средний город', 6,
'Большой город', 6,
'Крупный город', 8,
'Крупнейший город', 12,
0 // остальные
]
}
});
})
```
Однако на карте мы не увидим искомых городов. Чтобы узнать почему, проверим вкладку "Сеть" в инструментах разработчика. У запроса к списку городов мы увидим надпись **Ошибка CORS**.
Механизм CORS -- Cross-Origin Resource Sharing -- призван повысить безопасность веб-страницы. Нам, чтобы избежать ошибки CORS, надо указать, что API может отвечать на запросы любых веб-страниц.
> CORS -- это история про *веб-страницы*, поэтому выполняя запросы к API напрямую мы с ней не сталкивались.
```diff lang=py title=app.py
...
@app.route("/cities/<int:year>") # путь API, к которому обращается пользователь
def cities_by_year(year): # функция, которая будет выполняться при обращении
...
r = Response( # формируем ответ
json.dumps(geojson, ensure_ascii=False), # ensure_ascii=False, чтобы нормально отображалась кириллица
mimetype="application/json", # указываем тип данных
+ headers={"Access-Control-Allow-Origin": "*"}
)
# print("--- %s seconds ---" % (time.time() - start_time))
return r
@app.route("/city/<int:id>")
def city_by_id(id):
...
r = Response(
json.dumps(dict(city), ensure_ascii=False),
mimetype="application/json",
+ headers={"Access-Control-Allow-Origin": "*"}
)
# print("--- %s seconds ---" % (time.time() - start_time))
return r
```
После добавления заголовков о том, что API может обслуживать любые веб-страницы, в ответ мы получаем наш список городов в формате GeoJSON.
### Выбор года
Дадим пользователю возможность выбирать год, за который он хочет видеть индекс городов.
Разметим элемент с выпадающем списком.
```diff lang=html title=index.html
<body>
<div id='map'></div>
+ <div>
+ <p>Год</p>
+ <select id="year-selector">
+ <option value="2020" selected>2020</option>
+ <option value="2019">2019</option>
+ <option value="2018">2018</option>
+ </select>
+ </div>
<script src="main.js"></script>
</body>
```
Сделаем так, чтобы он выводился поверх карты.
```diff lang=css title=style.css
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
+ z-index: -1;
}
```
Запрограммируем реакцию карты на выбор года. Асинхронный запрос за нас выполнит картографическая библиотека.
```js title=main.js
map.on("load", () => {
...
document.getElementById("year-selector").addEventListener(
'change',
(e) => {
const year = e.target.value // фиксируем выбранный год
map.getSource('cities').setData(`http://localhost:5000/cities/${year}`) // меняем источник данных
}
)
})
```
### Сведения о городе
Выведем по клику на город подробные сведения о нём. Для отображения информации используем модальное окно, то есть диалоговое окно вспывающее поверх страницы.
```diff lang=html title=index.html
<body>
<div id='map'></div>
...
+ <dialog id="city-details-modal" onmousedown="this.close()"></dialog>
<script src="main.js"></script>
</body>
```
При клике будем выполнять запрос к методу API, который возвращает подробную информацию о городе. Здесь мы работаем с асинхронностью самостоятельно.
```js title=main.js
map.on("load", () => {
...
map.on('click', 'cities-layer', (e) => {
// console.log(e.features[0].properties.id)
fetch(`http://localhost:5000/city/${e.features[0].properties.id}`)
.then(response => response.json())
.then(cityProperties => {
// console.log(cityProperties)
document.getElementById("city-details-modal").innerHTML = `<h1>${cityProperties.name}</h1>
<img src="${cityProperties.emblem_url}" height="200">
<h3>Численность населения</h3><h2>${cityProperties.people_count} тыс. чел</h2>
<h3>Индекс качества городской среды</h3><h2>${cityProperties.total_points} / 360</h2>
<hr>
<h3>Жилье и прилегающие пространства</h3><h2>${cityProperties.house_points} / 60</h2>
<h3>Озелененные пространства</h3><h2>${cityProperties.park_points} / 60</h2>
<h3>Общественно-деловая инфраструктура</h3><h2>${cityProperties.business_points} / 60</h2>
<h3>Социально-досуговая инфраструктура</h3><h2>${cityProperties.social_points} / 60</h2>
<h3>Улично-дорожная</h3><h2>${cityProperties.street_points} / 60</h2>
<h3>Общегородское пространство</h3><h2>${cityProperties.common_points} / 60</h2>`
document.getElementById("city-details-modal").showModal() // showModal() -- встроенный метод элемента <dialog>
})
})
map.on('mouseenter', 'cities-layer', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'cities-layer', () => {
map.getCanvas().style.cursor = '';
});
})
```
> Если вы уже заметили кое-какую нестыковку в наших запросах, посмотрите упражнения в конце главы
## Что мы получили
Наши карты становятся всё лучше!
При желании посмотрите [полный код](https://github.com/gtitov/flask-maplibre-map) и [возможный результат](https://gtitov.github.io/flask-maplibre-map/).
## Упражнения
1. Создайте метод, который вернёт список доступных годов
1. Выведите модальное окно слева и выполните подлёт к точке клика
1. Вы могли заметить, что то, что мы получаем методом запроса деталей о городе уже содержится в полному списке городов: вам предлагается избавиться от этой избыточности
<details>
<summary>Есть два варианта -- подумайте над ними. Это тест на то, что вам ближе, бэкенд или фронтенд. Когда подумаете, можно посмотреть разгадку 👀</summary>
Бэкендер: можно убрать из метода для списка городов лишние атрибуты<br/>Фронтендер: на клик по объекту можно не обращаться к серверу, а использовать данные из атрибутов объекта
</details>
## Чтение
1. Что такое CORS / Дока [ссылка](https://doka.guide/tools/cors/)
1. Безопасность веб-приложений и распространённые атаки / Дока [ссылка](https://doka.guide/tools/web-security/)
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.

@ -1,25 +1,22 @@
---
title: Заключение
tableOfContents: false
draft: true
---
<div style="text-align: right; font-style: italic; line-height: 0.5rem">
<p>Вы не устали? Я бы мог</p>
<p>Вести вас дальше тропкой строк</p>
<p>И много новых букв открыть.</p>
<p>Но погодим. Умерим прыть.</p>
<br/>
<p>Доктор Сьюз в переводe </p>
<p>Григория Кружкова</p>
</div>
<br/><br/><br/>
Сквозь призму практических упражнений мы рассмотрели основные принципы функционирования картографических веб-приложений. К сожалению, а, скорее, всё же к счастью, сфера веб-картографии крайне разнообразна. Освоенный вами материал станет опорой на любом из путей развития в сфере веб-картографии.
---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025.
---
title: Заключение
tableOfContents: false
draft: true
---
<div style="text-align: right; font-style: italic; line-height: 0.5rem">
<p>Вы не устали? Я бы мог</p>
<p>Вести вас дальше тропкой строк</p>
<p>И много новых букв открыть.</p>
<p>Но погодим. Умерим прыть.</p>
</div>
<br/><br/><br/>
Сквозь призму практических упражнений мы рассмотрели основные принципы функционирования картографических веб-приложений. К сожалению, а, скорее, всё же к счастью, сфера веб-картографии крайне разнообразна. Освоенный вами материал станет опорой на любом из путей развития в сфере веб-картографии.
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.

@ -1,129 +1,129 @@
---
title: Внеклассное чтение
---
В этом разделе содержатся дополнительные материалы
## Кроме веб-карт
Определения для продуктов веб-картографии можно получить, добавляя к традиционному определению ГИС, глобуса, атласа “**предназначенный для использования в Интернете**” или “в компьютерных сетях”.
Тогда отличие между, например, веб-ГИС и веб-атласом становится очевидным.
> Веб-ГИС должна не только визуализировать данные, но и реализовывать функции хранения, передачи, обработки пространственных данных
Отдельно стоит упомянуть геопортал и картографические веб-сервисы. Их мы формируем от родовых IT-терминов приземляя к картографии и пространственным данным.
*Геопортал* — это веб-ресурс, предоставляющий доступ к каталогам пространственных данных, наборам пространственных данных, веб-сервисам, публикующим пространственные данные.
*Картографический веб-сервис* — это веб-ресурс, предоставляющий возможности обращения к пространственным данным или метаданным, в т. ч. по стандартизированным протоколам обмена (WMS, WFS, WCS и т. д.). Картографические веб-сервисы не имеют графического интерфейса, к этим сервисам обращаются программно через API (прикладной программный интерфейс).
Этими терминами злоупотребляют по отношению к любым веб-ресурсам, связанным с пространственными данными и картами. Правильнее объединить картографические продукты, публикуемые в сети, под названием *картографические веб-ресурсы*. Они являются веб-ресурсами, основным назначением которых является предоставление доступа к картографической информации.
## Зум, детальность, масштаб
Вопрос о сопоставлении уровня зума, детальности данных и масштаба карты можно отнести к дискуссионным. Однако в прикладных задачах можно следовать следующим соотношениям.
| Уровень зума | Детальность | Масштаб |
| ------------ | ----------- | ------------- |
| z0 | 10 000 м | 1:320 000 000 |
| z1 | 5000 м | 1:160 000 000 |
| z2 | 2500 м | 1:80 000 000 |
| z3 | 1250 м | 1:40 000 000 |
| z4 | 600 м | 1:20 000 000 |
| z5 | 300 м | 1:10 000 000 |
| z6 | 150 м | 1:5 000 000 |
| z7 | 80 м | 1:2 500 000 |
| z8 | 40 м | 1:1 250 000 |
| z9 | 20 м | 1:640 000 |
| z10 | 10 м | 1:320 000 |
| z11 | 5 м | 1:160 000 |
| z12 | 2 м | 1:80 000 |
| z13 | 1 м | 1:40 000 |
| z14 | 50 см | 1:20 000 |
| z15 | 25 см | 1:10 000 |
| z16 | 15 см | 1:5000 |
| z17 | 8 см | 1:2500 |
| z18 | 4 см | 1:1250 |
| z19 | 2 см | 1:600 |
| z20 | 1 см | 1:300 |
| ... | ... | ... |
<!-- ## Стиль программирования карты -->
## Тайлы векторные и растровые
Нельзя сказать, что векторные тайлы легче, быстрее, производительнее, тем более лучше, чем растровые тайлы. Хотя некоторые авторы этим и грешат. Например, векторные тайлы, отображающие множество объектов с богатой атрибутикой, могут весит значительно больше растровых тайлов, а оформление векторных тайлов, тем более динамическая, может привести к замедлению отрисовки карты в браузере.
![vector-raster](../../../assets/vector-raster.gif)
Тайл привязан к глобальной системе координат одной точкой. Содержание тайла хранится во внутренней системе координат тайла. Для векторных тайлов это приводит к сложностям в объединении объектов, которые попадают в несколько тайлов сразу: их соединение требует использования вычислительно дорогих операций.
Зато дальнейшая работа с векторными тайлами становится более удобной благодаря возможностям прямого доступа к объектам и их атрибутам для оформления картографических слоёв и организации интерактивной работы с объектами на карте.
Изначально картографические тайловые системы или пирадимы тайлов применялись к растровым изображениям. Растровые тайлы делятся на стороне сервера, передаются в браузер, в браузере выглядят как единое изображение той части карты, которую просматривает пользователь.
Для растровых тайлов сложно обеспечить непрерывное изменение масштаба, интерактивное взаимодействие с объектами требует определения того, какому объекту соответствует тот или иной пиксель, серверная стилизация изображения может создать нагрузку, на которым не хватит вычислительных мощностей при наплыве пользователей.
Векторные тайлы нагружают клиентскую часть. Растровые тайлы нагружают серверную часть.
У векторных и растровых тайлов есть свои достоинства и недостатки. Выбор конкретного варианта зависит от отображаемых данных, назначения веб-карты, требований к безопасности данных. Векторные тайлы хорошо подходят для объектно-ориентированного картографирования: изображения точек интереса, дорог, границ. Растровые тайлы незаменимый вариант для отображения спутниковых снимков, непрерывных покрытий, данных без возможности прямого доступа к объектам.
## Производительность векторных тайлов
Формирование тайла представляет собой запрос кусочка из полного набора пространственных данных, его перепроецирование и кодирование в векторных тайл.
Такие популярные серверы векторных тайлов, как Martin, Tegola, pg_tileserv, формируют тайл средствами базы данных PostGIS. Увидеть запросы, которые они используют можно в режиме отладки. Изучив запросы видно, что на производительность формирования тайлов влияет
1. наличие пространственного индекса, так как есть этап запроса кусочка набора данных по границам тайла
2. проекция исходного набора данных, так как есть этап перепроецирования
Проверим это на запросах к линейному набору пространственных данных
```sql
-- EPSG:4326 без индекса
SELECT ((SELECT ST_AsMVT(q,'lines',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(6, 37, 20)) as geom, fid FROM lines WHERE geom && ST_TileEnvelope(6, 37, 20)) AS q)) AS data;
-- EPSG:4326 с индексом
SELECT ((SELECT ST_AsMVT(q,'lines_index',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(6, 37, 20)) as geom, fid FROM lines_index WHERE geom && ST_TileEnvelope(6, 37, 20)) AS q)) AS data;
-- EPSG:3857 без индекса
SELECT ((SELECT ST_AsMVT(q,'lines3857',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(6, 37, 20)) as geom, fid FROM lines3857 WHERE geom && ST_TileEnvelope(6, 37, 20)) AS q)) AS data;
-- EPSG:3857 с индексом
SELECT ((SELECT ST_AsMVT(q,'lines3857_index',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(6, 37, 20)) as geom, fid FROM lines3857_index WHERE geom && ST_TileEnvelope(6, 37, 20)) AS q)) AS data;
```
| | без индекса | с индексом |
| ------------------------- | ----------- | ---------- |
| другая проекция (4326) | 389,4 мс | 3,5 мс |
| проекция веб-карты (3857) | 13,5 мс | 0,7 мс |
При запросе тайла на весь мир (фактически всех данных) индекс становится менее существенным.
```sql
-- EPSG:4326 без индекса
SELECT ((SELECT ST_AsMVT(q,'lines',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(0, 0, 0)) as geom, fid FROM lines WHERE geom && ST_TileEnvelope(0, 0, 0)) AS q)) AS data;
-- EPSG:4326 с индексом
SELECT ((SELECT ST_AsMVT(q,'lines_index',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(0, 0, 0)) as geom, fid FROM lines_index WHERE geom && ST_TileEnvelope(0, 0, 0)) AS q)) AS data;
-- EPSG:3857 без индекса
SELECT ((SELECT ST_AsMVT(q,'lines3857',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(0, 0, 0)) as geom, fid FROM lines3857 WHERE geom && ST_TileEnvelope(0, 0, 0)) AS q)) AS data;
-- EPSG:3857 с индексом
SELECT ((SELECT ST_AsMVT(q,'lines3857_index',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(0, 0, 0)) as geom, fid FROM lines3857_index WHERE geom && ST_TileEnvelope(0, 0, 0)) AS q)) AS data;
```
| | без индекса | с индексом |
| ------------------------- | ----------- | ---------- |
| другая проекция (4326) | 544,7 мс | 519,3 мс |
| проекция веб-карты (3857) | 44,1 мс | 41,2 мс |
Проекция и пространственный индекс позволяют ускорить формирование тайлов, однако наибольший прирост производительности даёт *кэширование*, то есть сохранение результатов запросов.
Если данные меняются редко можно предварительно рассчитать тайлы. Для хранения используются форматы MBTiles и PMTiles.
---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025.
---
title: Внеклассное чтение
---
В этом разделе содержатся дополнительные материалы
## Кроме веб-карт
Определения для продуктов веб-картографии можно получить, добавляя к традиционному определению ГИС, глобуса, атласа “**предназначенный для использования в Интернете**” или “в компьютерных сетях”.
Тогда отличие между, например, веб-ГИС и веб-атласом становится очевидным.
> Веб-ГИС должна не только визуализировать данные, но и реализовывать функции хранения, передачи, обработки пространственных данных
Отдельно стоит упомянуть геопортал и картографические веб-сервисы. Их мы формируем от родовых IT-терминов приземляя к картографии и пространственным данным.
*Геопортал* — это веб-ресурс, предоставляющий доступ к каталогам пространственных данных, наборам пространственных данных, веб-сервисам, публикующим пространственные данные.
*Картографический веб-сервис* — это веб-ресурс, предоставляющий возможности обращения к пространственным данным или метаданным, в т. ч. по стандартизированным протоколам обмена (WMS, WFS, WCS и т. д.). Картографические веб-сервисы не имеют графического интерфейса, к этим сервисам обращаются программно через API (прикладной программный интерфейс).
Этими терминами злоупотребляют по отношению к любым веб-ресурсам, связанным с пространственными данными и картами. Правильнее объединить картографические продукты, публикуемые в сети, под названием *картографические веб-ресурсы*. Они являются веб-ресурсами, основным назначением которых является предоставление доступа к картографической информации.
## Зум, детальность, масштаб
Вопрос о сопоставлении уровня зума, детальности данных и масштаба карты можно отнести к дискуссионным. Однако в прикладных задачах можно следовать следующим соотношениям.
| Уровень зума | Детальность | Масштаб |
| ------------ | ----------- | ------------- |
| z0 | 10 000 м | 1:320 000 000 |
| z1 | 5000 м | 1:160 000 000 |
| z2 | 2500 м | 1:80 000 000 |
| z3 | 1250 м | 1:40 000 000 |
| z4 | 600 м | 1:20 000 000 |
| z5 | 300 м | 1:10 000 000 |
| z6 | 150 м | 1:5 000 000 |
| z7 | 80 м | 1:2 500 000 |
| z8 | 40 м | 1:1 250 000 |
| z9 | 20 м | 1:640 000 |
| z10 | 10 м | 1:320 000 |
| z11 | 5 м | 1:160 000 |
| z12 | 2 м | 1:80 000 |
| z13 | 1 м | 1:40 000 |
| z14 | 50 см | 1:20 000 |
| z15 | 25 см | 1:10 000 |
| z16 | 15 см | 1:5000 |
| z17 | 8 см | 1:2500 |
| z18 | 4 см | 1:1250 |
| z19 | 2 см | 1:600 |
| z20 | 1 см | 1:300 |
| ... | ... | ... |
<!-- ## Стиль программирования карты -->
## Тайлы векторные и растровые
Нельзя сказать, что векторные тайлы легче, быстрее, производительнее, тем более лучше, чем растровые тайлы. Хотя некоторые авторы этим и грешат. Например, векторные тайлы, отображающие множество объектов с богатой атрибутикой, могут весит значительно больше растровых тайлов, а оформление векторных тайлов, тем более динамическое, может привести к замедлению отрисовки карты в браузере.
![vector-raster](../../assets/vector-raster.gif)
Тайл привязан к глобальной системе координат одной точкой. Содержание тайла хранится во внутренней системе координат тайла. Для векторных тайлов это приводит к сложностям в объединении объектов, которые попадают в несколько тайлов сразу: их соединение требует использования вычислительно дорогих операций.
Зато дальнейшая работа с векторными тайлами становится более удобной благодаря возможностям прямого доступа к объектам и их атрибутам для оформления картографических слоёв и организации интерактивной работы с объектами на карте.
Изначально картографические тайловые системы или пирадимы тайлов применялись к растровым изображениям. Растровые тайлы делятся на стороне сервера, передаются в браузер, в браузере выглядят как единое изображение той части карты, которую просматривает пользователь.
Для растровых тайлов сложно обеспечить непрерывное изменение масштаба, интерактивное взаимодействие с объектами требует определения того, какому объекту соответствует тот или иной пиксель, серверная стилизация изображения может создать нагрузку, на которым не хватит вычислительных мощностей при наплыве пользователей.
Векторные тайлы нагружают клиентскую часть. Растровые тайлы нагружают серверную часть.
У векторных и растровых тайлов есть свои достоинства и недостатки. Выбор конкретного варианта зависит от отображаемых данных, назначения веб-карты, требований к безопасности данных. Векторные тайлы хорошо подходят для объектно-ориентированного картографирования: изображения точек интереса, дорог, границ. Растровые тайлы незаменимый вариант для отображения спутниковых снимков, непрерывных покрытий, данных без возможности прямого доступа к объектам.
## Производительность векторных тайлов
Формирование тайла представляет собой запрос кусочка из полного набора пространственных данных, его перепроецирование и кодирование в векторных тайл.
Такие популярные серверы векторных тайлов, как Martin, Tegola, pg_tileserv, формируют тайл средствами базы данных PostGIS. Увидеть запросы, которые они используют можно в режиме отладки. Изучив запросы видно, что на производительность формирования тайлов влияет
1. наличие пространственного индекса, так как есть этап запроса кусочка набора данных по границам тайла
2. проекция исходного набора данных, так как есть этап перепроецирования
Проверим это на запросах к линейному набору пространственных данных
```sql
-- EPSG:4326 без индекса
SELECT ((SELECT ST_AsMVT(q,'lines',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(6, 37, 20)) as geom, fid FROM lines WHERE geom && ST_TileEnvelope(6, 37, 20)) AS q)) AS data;
-- EPSG:4326 с индексом
SELECT ((SELECT ST_AsMVT(q,'lines_index',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(6, 37, 20)) as geom, fid FROM lines_index WHERE geom && ST_TileEnvelope(6, 37, 20)) AS q)) AS data;
-- EPSG:3857 без индекса
SELECT ((SELECT ST_AsMVT(q,'lines3857',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(6, 37, 20)) as geom, fid FROM lines3857 WHERE geom && ST_TileEnvelope(6, 37, 20)) AS q)) AS data;
-- EPSG:3857 с индексом
SELECT ((SELECT ST_AsMVT(q,'lines3857_index',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(6, 37, 20)) as geom, fid FROM lines3857_index WHERE geom && ST_TileEnvelope(6, 37, 20)) AS q)) AS data;
```
| | без индекса | с индексом |
| ------------------------- | ----------- | ---------- |
| другая проекция (4326) | 389,4 мс | 3,5 мс |
| проекция веб-карты (3857) | 13,5 мс | 0,7 мс |
При запросе тайла на весь мир (фактически всех данных) индекс становится менее существенным.
```sql
-- EPSG:4326 без индекса
SELECT ((SELECT ST_AsMVT(q,'lines',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(0, 0, 0)) as geom, fid FROM lines WHERE geom && ST_TileEnvelope(0, 0, 0)) AS q)) AS data;
-- EPSG:4326 с индексом
SELECT ((SELECT ST_AsMVT(q,'lines_index',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(ST_Transform(geom, 3857), ST_TileEnvelope(0, 0, 0)) as geom, fid FROM lines_index WHERE geom && ST_TileEnvelope(0, 0, 0)) AS q)) AS data;
-- EPSG:3857 без индекса
SELECT ((SELECT ST_AsMVT(q,'lines3857',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(0, 0, 0)) as geom, fid FROM lines3857 WHERE geom && ST_TileEnvelope(0, 0, 0)) AS q)) AS data;
-- EPSG:3857 с индексом
SELECT ((SELECT ST_AsMVT(q,'lines3857_index',4096,'geom','fid') AS data FROM (SELECT ST_AsMVTGeom(geom, ST_TileEnvelope(0, 0, 0)) as geom, fid FROM lines3857_index WHERE geom && ST_TileEnvelope(0, 0, 0)) AS q)) AS data;
```
| | без индекса | с индексом |
| ------------------------- | ----------- | ---------- |
| другая проекция (4326) | 544,7 мс | 519,3 мс |
| проекция веб-карты (3857) | 44,1 мс | 41,2 мс |
Проекция и пространственный индекс позволяют ускорить формирование тайлов, однако наибольший прирост производительности даёт *кэширование*, то есть сохранение результатов запросов.
Если данные меняются редко можно предварительно рассчитать тайлы. Для хранения используются форматы MBTiles и PMTiles.
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.

@ -1,21 +1,21 @@
---
title: Литература
tableOfContents: false
---
1. **Аляутдинов А. Р., Лурье И. К., Ушакова Л. А.** Основные принципы функционирования геоинформационных ресурсов // Известия высших учебных заведений. Геодезия и аэрофотосъемка. — 2016. — Т. 60, № 5. — С. 123128 [🔎&nbsp;🌐](https://elibrary.ru/item.asp?id=27224121)
1. **Абдуллин Р. К., Пономарчук А. И.** Технологии интернет-картографирования: учебное пособие / Пермский государственный национальный исследовательский университет. - Пермь, 2020. 132 с.: ил. [🔎&nbsp;🌐](https://gis.psu.ru/publications/технологии-интернет-картографирован/)
1. **Берлянт А. М.** Геоинформационное Картографирование. М.: Моск. гос. Ун-т им. М. В. Ломоносова, Рос. акад. естеств. наук, 1997. [🔎&nbsp;🌐](https://rusneb.ru/catalog/000199_000009_000611203/)
1. **Каргашин П. Е.** Основы цифровой картографии: Учебное пособие для бакалавров. 5-е изд., перераб. — Москва: Издательско-торговая корпорация Дашков и К, 2023. — 106 с. [🔎&nbsp;🌐](https://istina.msu.ru/publications/book/557759518/)
1. **Лурье И. К.**, Самсонов Т.Е. Структура и содержание базы пространственных данных для мультимасштабного картографирования // Геодезия и картография. 2010. № 11. С. 17-23. [🔎&nbsp;🌐](https://istina.msu.ru/publications/article/427465/)
1. **Титов Г. С., Прасолова А. И., Каргашин П. Е.** Веб-картографирование ресурсов солнечной энергии Якутии // ИнтерКарто. ИнтерГИС. — 2021. — Т. 27, № 3. — С. 210220. [🔎&nbsp;🌐](https://istina.msu.ru/publications/article/412375618/)
1. **Титов Г. С.** Текущие проблемы терминологического аппарата отечественной веб-картографии // Геодезия, картография, геоинформатика и кадастры. Производство и образование : Сб. материалов IV Всероссийской науч.-практ. конф. — СПб Политехника: 2021. — С. 317323. [🔎&nbsp;🌐](https://istina.msu.ru/publications/article/716105082/)
1. **Kraak M. J.** Web Cartography: Developments and Prospects. Edited by M. J. Kraak and Allan Brown. New York: Taylor & Francis, 2001. [🔎&nbsp;🌐](https://doi.org/10.1201/9781482289237)
1. **Muehlenhaus I.** Web Cartography: Map Design for Interactive and Mobile Devices. Boca Raton, FL: CRC Press, 2014. [🔎&nbsp;🌐](https://doi.org/10.1201/b16229)
1. **Neumann A.** Web Mapping and Web Cartography. In Springer Handbook of Geographic Information, edited by Wolfgang Kresse and David M. Danko, 27387. Berlin, Heidelberg: Springer Berlin Heidelberg, 2011. [🔎&nbsp;🌐](https://doi.org/10.1007/978-3-540-72680-7_14)
1. **Myslenkov S., Samsonov T., Shurygina A. et al.** Wind waves web atlas of the russian seas // Water. — 2023. — Vol. 15, no. 11. — P. 2036. [🔎&nbsp;🌐](http://dx.doi.org/10.3390/w15112036)
1. ГОСТ Р 58570-2019. Инфраструктура Пространственных Данных. Общие Требования // Стандартинформ, 2019. [🔎&nbsp;🌐](https://docs.cntd.ru/document/1200168445)
---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025.
---
title: Литература
tableOfContents: false
---
1. **Аляутдинов А. Р., Лурье И. К., Ушакова Л. А.** Основные принципы функционирования геоинформационных ресурсов // Известия высших учебных заведений. Геодезия и аэрофотосъемка. — 2016. — Т. 60, № 5. — С. 123128 [🔎&nbsp;🌐](https://elibrary.ru/item.asp?id=27224121)
1. **Абдуллин Р. К., Пономарчук А. И.** Технологии интернет-картографирования: учебное пособие / Пермский государственный национальный исследовательский университет. - Пермь, 2020. 132 с.: ил. [🔎&nbsp;🌐](https://gis.psu.ru/publications/технологии-интернет-картографирован/)
1. **Берлянт А. М.** Геоинформационное Картографирование. М.: Моск. гос. Ун-т им. М. В. Ломоносова, Рос. акад. естеств. наук, 1997. [🔎&nbsp;🌐](https://rusneb.ru/catalog/000199_000009_000611203/)
1. **Каргашин П. Е.** Основы цифровой картографии: Учебное пособие для бакалавров. 5-е изд., перераб. — Москва: Издательско-торговая корпорация Дашков и К, 2023. — 106 с. [🔎&nbsp;🌐](https://istina.msu.ru/publications/book/557759518/)
1. **Лурье И. К.**, Самсонов Т.Е. Структура и содержание базы пространственных данных для мультимасштабного картографирования // Геодезия и картография. 2010. № 11. С. 17-23. [🔎&nbsp;🌐](https://istina.msu.ru/publications/article/427465/)
1. **Титов Г. С., Прасолова А. И., Каргашин П. Е.** Веб-картографирование ресурсов солнечной энергии Якутии // ИнтерКарто. ИнтерГИС. — 2021. — Т. 27, № 3. — С. 210220. [🔎&nbsp;🌐](https://istina.msu.ru/publications/article/412375618/)
1. **Титов Г. С.** Текущие проблемы терминологического аппарата отечественной веб-картографии // Геодезия, картография, геоинформатика и кадастры. Производство и образование : Сб. материалов IV Всероссийской науч.-практ. конф. — СПб Политехника: 2021. — С. 317323. [🔎&nbsp;🌐](https://istina.msu.ru/publications/article/716105082/)
1. **Kraak M. J.** Web Cartography: Developments and Prospects. Edited by M. J. Kraak and Allan Brown. New York: Taylor & Francis, 2001. [🔎&nbsp;🌐](https://doi.org/10.1201/9781482289237)
1. **Muehlenhaus I.** Web Cartography: Map Design for Interactive and Mobile Devices. Boca Raton, FL: CRC Press, 2014. [🔎&nbsp;🌐](https://doi.org/10.1201/b16229)
1. **Neumann A.** Web Mapping and Web Cartography. In Springer Handbook of Geographic Information, edited by Wolfgang Kresse and David M. Danko, 27387. Berlin, Heidelberg: Springer Berlin Heidelberg, 2011. [🔎&nbsp;🌐](https://doi.org/10.1007/978-3-540-72680-7_14)
1. **Myslenkov S., Samsonov T., Shurygina A. et al.** Wind waves web atlas of the russian seas // Water. — 2023. — Vol. 15, no. 11. — P. 2036. [🔎&nbsp;🌐](http://dx.doi.org/10.3390/w15112036)
1. ГОСТ Р 58570-2019. Инфраструктура Пространственных Данных. Общие Требования // Стандартинформ, 2019. [🔎&nbsp;🌐](https://docs.cntd.ru/document/1200168445)
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.

@ -1,93 +0,0 @@
---
title: Веб-картографирование
draft: true
---
import { Card, LinkCard } from '@astrojs/starlight/components';
import Question from '../../../components/Question.astro';
import MultipleChoice from '../../../components/MultipleChoice.astro';
import Option from '../../../components/Option.astro';
В этой главе мы совершим краткий *теоретический обзор* сферы веб-картографии
<LinkCard title='Руки чешутся' href='/chapters/2-webmap#создание-первой-веб-карты' description='Сразу перейти к практическому упражнению'/>
## Определение веб-картографирования
Под веб-картографированием понимается создание карт для их распространения и использования во Всемирной сети. Веб-картографирование является одним из направлений геоинформационного картографирования. Суть веб-картографирования заключается в интеграции геоинформационных подходов к картографированию и сетевых технологий.
> Можно встретить и такие варианты употребления как «Интернет-картографирование», «WWW-картографирование», «телекоммуникационное картографирование», «web-картографирование». Однако в словаре их бы одарили меткой *устар.*
Появление большого числа картографических веб-ресурсов свидетельствует, что сетевые технологии являются одним из ключевых драйверов эволюции геоинформационной картографии.
## Особенности веб-картографирования
Веб-картографирование наследует особенности геоинформационного картографирования и обладает собственными характерными чертами, к которым относятся:
1. Сетевая среда распространения
2. Интерактивность
3. Мультимасштабность
**Сетевая среда распространения** меняет подход к публикации картографических произведений. Карта генерируется для пользователя в момент запроса, поэтому обновление картографического содержания можно выполнять непрерывно без дополнительных действий пользователя. Например, изменение границ или переименование улиц не требует приобретения пользователем нового издания картографического произведения: если данные в картографическом произведении обновились, пользователь получит их при новом запросе.
Лучшие практики веб-разработки стимулируют активное использование **интерактивности** в картографическом произведении. Хотя возможности введения диалогового взаимодействия с картой появились на этапе компьютеризации картографии, по-настоящему доступной для широкого круга пользователей интерактивность карт стала именно с развитием Интернет-технологий. Пользователь по умолчанию ожидает возможности перемещения по карте, изменения масштаба, определения координат точки и получения дополнительной информации об объекте по клику. Часто веб-карта предоставляет и более широкие возможности, например, интерактивное изменение содержания карты, оформления слоёв, проекции, компоновки.
Одним из ключевых элементов интерактивности является функция изменения масштаба картографического изображения. Это требует от автора карты формирования **мультимасштабного** содержания или явного обозначения масштабов, которые соответствуют содержанию карты. Работа с мультимасштабным содержанием веб-карт должна начинаться на этапе проектирования.
Легко заметить соподчинённость выделенных особенностей. Веб-технологии способствуют вводу интерактивности в употребление. Активное использование интерактивности обуславливает мультимасштабность картографического содержания.
Кроме того веб-картографирование характеризуют
4. Общедоступность
5. Сходство с программным обеспечением
6. Расширение выразительности
Веб-ресурсы во Всемирной сети обычно являются **общедоступными**: обратиться к ним может любой пользователь, имеющий выход в Интернет. Возможно ограничение доступа к веб-ресурсу, например с помощью системы авторизации, однако сохраняется принципиальная доступность при наличии соответствующих прав. Картографические произведения в Интернете не исключение. Коммерциализация веб-карт базируется не на платном доступе к содержанию, а на широком доступе аудитории к содержанию карты, которое можно монетизировать, например, с помощью рекламы.
На этапе проектирования картографического произведения проявляются черты, характерные для разработки **программного обеспечения** и информационных систем; составление карт сводится к написанию программного кода, который генерирует экземпляр карты для каждого пользователя; изданием картографического произведения становится его публикация на веб-сайте.
Веб-технологии расширяют круг **выразительных средств**: появляется возможность использовать мультимедийные материалы (фото, аудио, видео), давать ссылки на другие веб-страницы, создавать интерактивные картографические анимации. Технически это реализуемо и на электронных картах, локально размещаемых на компьютере пользователя, однако активное использование новых выразительных средств характерно именно для веб-карт. Карта в веб-среде становится динамичной интерактивной моделью.
{/* <Card title='Главная особенность веб-картографрования это'>
<Question answer="cетевая среда распространения" ballast={['интерактивность', 'использование стандартов']} explanation="Особенности веб-картографирования проистекают из сетевой среды распространения картографических материалов"/>
</Card> */}
<Card title='Главная особенность веб-картографрования это'>
<MultipleChoice>
<Option>
интерактивность
</Option>
<Option>
использование стандартов
</Option>
<Option isCorrect>
cетевая среда распространения
</Option>
</MultipleChoice>
<details>
<summary>Развёрнутый ответ</summary>
Особенности веб-картографирования проистекают из сетевой среды распространения картографических материалов. Другими словами, сетевая среда распространения является ключевой особенностью веб-картографирования, а остальные производными от неё.
</details>
</Card>
## Веб-картография
Наличие характерных особенностей позволяет говорить о веб-картографировании как о специфической деятельности, требующей рассмотрения в рамках отдельного направления — веб-картографии.
Реальная практика показывает, что сфера интересов веб-картографии не ограничивается изучаением процесса веб-картографирования. Это направление на пересечении картографии, геоинформатики и сетевых технологий.
![alt text](../../../assets/image-19.png)
Веб-картография изучает особенности оборота пространственных данных в сетевой среде. Так как эти особенности проявляются на всех этапах жизненного цикла веб-карты, то и в область интересов веб-картографии входит вся система создания-использования карт от сбора данных до чтения веб-карты пользователем. Внимание уделяется хранению, кодированию, передаче, обработке пространственных данных в сетевой среде, каталогизации и организации поиска пространственных данных, методологии разработки картографических веб-приложений, методам визуализации и интерактивного взаимодействия.
<Card title="Короче">
Веб-картографирование — это создание карт для распространения и использования в Интернете. Оно характеризуется сетевой средой распространения и производными от этого особенностями, в частности интерактивностью и мультимасштабностью.
Веб-картография формируется на пересечении сетевых технологий, картографии и геоинформатики изучает особенности оборота пространственных данных в сетевой среде, включая хранение, кодирование, передачу, обработку данных, разработку веб-приложений и другие аспекты.
</Card>
---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025.

@ -1,21 +0,0 @@
---
title: Введение
tableOfContents: false
draft: true
---
Введение в веб-картографию призвано дать читателю общую картину с акцентом на принципах работы и ключевых компонентах веб-карт.
```sh title="Пишем код"
Главный способ донесения информации -- практика
```
Мы создадим 4 функциональные веб-карты. Каждая из них является прекрасным шаблоном для решения собственных задач. Каждая карта сопровождается необходимым объёмом теоретических сведений для осознанного освоения практических навыков.
Освоение материала обеспечит надёжных фундамент для дальнейшего роста в сфере веб-картографии.
<!-- Опираясь на полученный опыт, мы зафиксируем походы к самостоятельной разработке веб-карт и пути дальнейшего развития в сфере веб-картографии. -->
---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 MiB

@ -0,0 +1,26 @@
---
title: Вступление
tableOfContents: false
---
Данное пособие призвано ввести читателя в сферу веб-картографию через практику. Мы будем акцентировать внимание на принципах работы веб-карт. Это позволит в короткие сроки освоить ключевые навыки, необходимые для веб-картографирования.
Книга будет полезна специалистам в области картографии, которые хотят выяснить, как работают карты в Интернете, и веб-разработчикам, которые хотят понять особенности картографических интерфейсов и пространственных данных.
Пособие базируется на курсе по веб-картографии, который регулярно читается в Картетике, и многолетнем опыте работы на пересечении картографии и веб-разработки.
```sh title="Пишем код"
Главный способ донесения информации --- практика
```
Основу пособия составляют практические упражнения. В каждом упражнении мы создадим функциональную веб-карту.
Каждая позволит на практике закрепить базовые принципы веб-картографирования.
Каждая познакомит с устоявшимися подходами к созданию веб-карт.
Каждая является практичной заготовкой для решения собственных задач.
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.
Loading…
Cancel
Save