master
gman 6 months ago
parent 53c1d0e0e1
commit 15621cd4e8

@ -1,42 +1,33 @@
// @ts-check // @ts-check
import starlight from '@astrojs/starlight'; import starlight from '@astrojs/starlight';
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import react from '@astrojs/react'; import react from '@astrojs/react';
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
redirects: { integrations: [
'/': '/chapters', starlight({
}, title: 'Практическое введение в веб-картографию',
integrations: [ description: 'Веб-картография и веб-картографирование: практическое пособие',
starlight({ head: [
title: 'Веб-картография', {
description: 'Веб-картография и веб-картографирование: практическое пособие', tag: 'script',
head: [ attrs: {
{ src: "//gc.zgo.at/count.js",
tag: 'script', 'data-goatcounter': "https://webcartography.goatcounter.com/count",
attrs: { async: true
src: "//gc.zgo.at/count.js", }
'data-goatcounter': "https://webcartography.goatcounter.com/count", }
async: true ],
} locales: {
} root: {
], label: 'Русский',
locales: { lang: 'ru',
root: { }
label: 'Русский', },
lang: 'ru', components: {}
} }),
}, react()
sidebar: [ ],
{
label: 'Введение в веб-картографию',
autogenerate: { directory: 'chapters' },
}
],
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: Веб-карта title: Веб-карта
--- ---
import { Card, FileTree, LinkCard } from '@astrojs/starlight/components'; import { Card, FileTree, LinkCard } from '@astrojs/starlight/components';
import Question from '../../../components/Question.astro'; import Question from '../../components/Question.astro';
import MultipleChoice from '../../../components/MultipleChoice.astro'; import MultipleChoice from '../../components/MultipleChoice.astro';
import Option from '../../../components/Option.astro'; import Option from '../../components/Option.astro';
В этой главе мы рассмотрим В этой главе мы рассмотрим
- понятие веб-карты - понятие веб-карты
- типы веб-карт - типы веб-карт
- клиент-серверную архитектуру - клиент-серверную архитектуру
- HTML, CSS, JavaScript - HTML, CSS, JavaScript
В рамках практической части создадим карту мира на основе статических GeoJSON-файлов с использованием открытой библиотеки MapLibre. В рамках практической части создадим карту мира на основе GeoJSON-файлов с использованием открытой библиотеки MapLibre GL JS. При желании заранее посмотреть [полный код](https://github.com/gtitov/geojson-maplibre-map) и [возможный результат](https://gtitov.github.io/geojson-maplibre-map/).
## Карты в Интернете ## Карты в Интернете
Любую ли карту в Интернете можно назвать веб-картой? Любую ли карту в Интернете можно назвать веб-картой?
> Яндекс.Карты — конечно, да! Яндекс.Карты — да!
>
> Скан карты России, который я отправил по почте — пожалуй, нет. Скан карты России, который я отправил по почте — пожалуй, нет.
>
> И между ними ещё огромное множество различных вариантов карт в Интернете. И между ними ещё огромное множество различных вариантов карт в Интернете.
### Определение веб-карты ### Определение веб-карты
Цель размещения карты в сети определяет веб-карту. Веб-карту определяет цель размещения карты в сети. Если карту разместили в Интернете для того, чтобы пользователи могли работать с ней по сети, то это веб-карта. А файлы карт, передаваемые по сети или размещаемые на сайтах, изображения карт, используемые для оформления веб-страницы, веб-картами не являются.
Файлам карт, передаваемым по сети, или размещаемым на сайтах, изображениям карт для печати, загрузки, иллюстрации, украшения будет отказано в праве называться веб-картой. А вот если карту разместили в Интернете для того, чтобы пользователи могли работать с ней по сети, то это веб-карта. <Card title="Веб-карта —">это карта, предназначенная для использования в сети</Card>
<Card title="Веб-карта —">это карта, предназначенная для использования в сети</Card> ### Типы веб-карт
### Типы веб-карт Веб-карты можно разделить на интерактивные / неинтерактивные и статические / динамически
Веб-карты можно разделить на интерактивные / неинтерактивные и статические / динамически | | статические | динамические |
| --------------- | ------------------- | --------------------------- |
| | статические | динамические | | неинтерактивные | карты-картинки | генераторы карт-картинок |
| --------------- | ------------------- | --------------------------- | | интерактивные | "простые" веб-карты | картографические приложения |
| неинтерактивные | карты-картинки | генераторы карт-картинок |
| интерактивные | "простые" веб-карты | картографические приложения | Интерактивность подразумевает возможность пользователя перемещаться по карте, менять масштаб, получать подробную информацию по клику, скрывать слои, менять цвета и так далее.
Инерактивность подразумевает возможность пользователя перемещаться по карте, менять масштаб, получать подробную информацию по клику, скрывать слои, менять цвета и так далее. Неинтерактивные карты лишены этих возможностей. Но неинтерактивная карта будет веб-картой, если предназначена для использования в Интернете. Неинтерактивные карты лишены этих возможностей. Но неинтерактивная карта будет веб-картой, если предназначена для использования в Интернете.
Статические веб-карты получают данные на клиент точно в том виде, в котором они хранятся на сервере, без какой-либо обработки. Из программного обеспечения требуется только веб-сервер, который будет принимать запросы от клиентов и возвращать в ответ файлы данных с сервера клиенту. Статические веб-карты получают данные на клиент точно в том виде, в котором они хранятся на сервере, без какой-либо обработки. Из программного обеспечения требуется только веб-сервер, который будет принимать запросы от клиентов и возвращать в ответ файлы данных с сервера клиенту.
Динамические веб-карты подразумевают серверную обработку данных и позволяют запрашивать данные более гибко. Из программного обеспечения обычно используются база данных, программа для обработки данных и веб-сервер. В общем случае веб-сервер примет запрос и обратится к программе для обработки данных, она извлечёт данные из базы, обработает их и передаст веб-серверу, который отправит подготовленные данные клиенту. Динамические веб-карты подразумевают серверную обработку данных и позволяют запрашивать данные более гибко. Из программного обеспечения обычно используются база данных, программа для обработки данных и веб-сервер. В общем случае веб-сервер примет запрос и обратится к программе для обработки данных, она извлечёт данные из базы, обработает их и передаст веб-серверу, который отправит подготовленные данные клиенту.
Интерактивность касается интерфейса и клиентской части, а деление на статические и динамические веб-карты связано с обработкой данных в серверной части. Интерактивность касается интерфейса и клиентской части, а деление на статические и динамические веб-карты связано с обработкой данных в серверной части.
<LinkCard title='Другие продукты веб-картографии' href='/chapters/7-extra#кроме-веб-карт' description='Геопорталы, картографические веб-сервисы, веб-атласы, веб-ГИС'/> <LinkCard title='Другие продукты веб-картографии' href='/98-extra#кроме-веб-карт' description='Геопорталы, картографические веб-сервисы, веб-атласы, веб-ГИС'/>
## Клиент-серверная архитектура ## Клиент-серверная архитектура
Рассмотрим пример работы простого веб-ресурса. Рассмотрим пример работы веб-ресурса.
![alt text](../../../assets/image-15.png) ![alt text](../../assets/image-15.png)
Пользователь вводит адрес запрашиваемого веб-ресурса в адресную строку браузера и нажимает клавишу Enter (1). Браузер инициирует и выполняет запрос к серверу на получение данных для создания веб-страницы (2). Сервер возвращает ответ (3). Из этого ответа браузер формирует веб-страницу, которую видит пользователь (4). Пользователь вводит адрес запрашиваемого веб-ресурса в адресную строку браузера и нажимает клавишу Enter (1). Браузер инициирует и выполняет запрос к серверу на получение данных для создания веб-страницы (2). Сервер возвращает ответ (3). Из этого ответа браузер формирует веб-страницу, которую видит пользователь (4).
<Card title="Короче"> <Card title="Короче">
Браузер -- это клиент. Сервер -- это сервер. Клиент обращается к серверу с запросом, сервер возвращает клиенту ответ. Вот суть клиент-серверной архитектуры. Браузер -- это клиент. Сервер -- это сервер. Клиент обращается к серверу с запросом, сервер возвращает клиенту ответ. Вот суть клиент-серверной архитектуры.
</Card> </Card>
В реальности клиент может выполнять множество запросов к серверу, чтобы показать пользователю веб-страницу. В реальности клиент может выполнять множество запросов к серверу, чтобы показать пользователю веб-страницу.
:::tip :::tip
Откройте инструменты разработчика, нажав на клавишу F12 или сочетание клавиш Ctrl+Shift+i, перейдите во вкладку Сеть (Network) и обновите страницу, чтобы увидеть, сколько запросов выполняет браузер, чтобы показать вашу любимую веб-страницу. Откройте инструменты разработчика, нажав на клавишу F12 или сочетание клавиш Ctrl+Shift+i, перейдите во вкладку Сеть (Network) и обновите страницу, чтобы увидеть, сколько запросов выполняет браузер, чтобы показать вашу любимую веб-страницу.
::: :::
Первый запрос инициирует пользователь выполняет путём ввода адреса. Дальнейшие действия пользователя на веб-странице могут инициировать последующие запросы. Запрашиваться могут как веб-страницы целиком, так и отдельные данные для обновления содержания текущей веб-страницы. Если мы обновляем только часть содержания веб-страницы данными с сервера, то речь идёт об асинхронном запросе. Первый запрос инициирует пользователь выполняет путём ввода адреса. Дальнейшие действия пользователя на веб-странице могут инициировать последующие запросы. Запрашиваться могут как веб-страницы целиком, так и отдельные данные для обновления содержания текущей веб-страницы. Если мы обновляем только часть содержания веб-страницы данными с сервера, то речь идёт об асинхронном запросе.
![*Запросы, выполняемые с веб-страницы*](../../../assets/network-tab.png) ![*Запросы, выполняемые с веб-страницы*](../../assets/network-tab.png)
*Запросы, выполняемые с веб-страницы* *Запросы, выполняемые с веб-страницы*
Веб-карты тоже строятся на основе клиент-серверной архитектуры. Рассмотрим интерактивную статическую карту. Веб-карты тоже строятся на основе клиент-серверной архитектуры. Рассмотрим интерактивную статическую карту (пока воображаемую).
Разметка, стили, логика (программный код) веб-карты, а также наборы пространственных данных веб-карты хранятся на сервере. Пользователь вводит адрес карты. Браузер запрашивает с сервера разметку веб-карты (html). Разметка приходит в браузер. Разметра содержит запросы к стилям и логике веб-карты. Логика веб-карты приходит в браузер и запрашивает наборы пространственных данных для создания веб-карты. Пространственные данные приходят [Взаимодействие с сервером закончено!] и кодом веб-карты превращаются в картографические слои. Разметка, стили, логика (программный код) веб-карты, а также наборы пространственных данных веб-карты хранятся на сервере. Пользователь вводит адрес карты. Браузер запрашивает с сервера разметку веб-карты (html). Разметка приходит в браузер. Разметка содержит запросы к стилям и логике веб-карты. Логика веб-карты приходит в браузер и запрашивает наборы пространственных данных для создания веб-карты. Пространственные данные приходят [Взаимодействие с сервером закончено!] и кодом веб-карты превращаются в картографические слои.
{/* <Card title='В клиент-серверной архитектуре браузер это'> <Card title='В клиент-серверной архитектуре браузер это'>
<Question answer="клиент" ballast={['сервер', 'пользователь']} explanation="Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу"/> <MultipleChoice>
</Card> */} <Option>
сервер
<Card title='В клиент-серверной архитектуре браузер это'> </Option>
<MultipleChoice> <Option isCorrect>
<Option> клиент
сервер </Option>
</Option> <Option >
<Option isCorrect> пользователь
клиент </Option>
</Option> </MultipleChoice>
<Option >
пользователь <details>
</Option> <summary>Развёрнутый ответ</summary>
</MultipleChoice> Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу
</details>
<details> </Card>
<summary>Развёрнутый ответ</summary>
Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу ## Создание первой веб-карты
</details>
</Card> Мы рассмотрели интерактивную статическую карту, а сейчас мы её сделаем своими руками!
## Создание первой веб-карты Выделим нашей первой веб-карте отдельную папку.
Мы рассмотрели интерактивную статическую карту, а сейчас мы её сделаем своими руками! > В качестве среды разработки можно использовать [VS Code](https://code.visualstudio.com/), а чтобы запустить сервер -- расширение [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer).
Выделим нашей первой веб-карте отдельную папку. ### Инициализация карты
> В качестве среды разработки можно использовать [VS Code](https://code.visualstudio.com/), а чтобы запустить сервер -- расширение [Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer). Создадим файл разметки `index.html`.
### Инициализация карты ```html title="index.html"
<!DOCTYPE html>
Создадим файл разметки `index.html`. <html lang="en">
```html title="index.html" <head>
<!DOCTYPE html> <meta charset="UTF-8">
<html lang="en"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Население мира</title>
<head> <!-- Запрашиваем стили 👇 -->
<meta charset="UTF-8"> <link rel="stylesheet" href="style.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Запрашиваем библиотеку Maplibre 👇 -->
<title>Население мира</title> <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" />
<link rel="stylesheet" href="style.css"> </head>
<!-- Запрашиваем библиотеку Maplibre 👇 -->
<script src="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js"></script> <body>
<link href="https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css" rel="stylesheet" /> <!-- Размечаем контейнер для карты 👇 -->
</head> <div id="map"></div>
<!-- Запрашиваем логику карты 👇 -->
<body> <script src="main.js"></script>
<!-- Размечаем контейнер для карты 👇 --> </body>
<div id="map"></div>
<!-- Запрашиваем логику карты 👇 --> </html>
<script src="main.js"></script> ```
</body>
Библиотеку Maplibre мы запрашиваем из внешнего ресурса, а вот стили и логику карты нам нужно создать.
</html>
``` Создадим файл стилей `style.css`.
Библиотеку Maplibre мы запрашиваем из внешнего ресурса, а вот стили и логику карты нам нужно создать. ```css title="style.css"
/* Объявляем, что контейнер карты должен занимать всю страницу */
Создадим файл стилей `style.css`. #map {
position: absolute;
```css title="style.css" top: 0;
/* Объявляем, что контейнер карты должен занимать всю страницу */ bottom: 0;
#map { left: 0;
position: absolute; right: 0;
top: 0; }
bottom: 0; ```
left: 0;
right: 0; И файл с логикой карты `main.js`.
}
``` ```js title="main.js"
// Инициализируем карту
И файл с логикой карты `main.js`. const map = new maplibregl.Map({
container: 'map',
```js title="main.js" style: "https://raw.githubusercontent.com/gtitov/basemaps/refs/heads/master/positron-nolabels.json",
// Инициализируем карту center: [51, 0],
const map = new maplibregl.Map({ zoom: 4
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.
> Наименование файла `index.html` важно тем, что именно страница `index.html` загружается при обращении к корневому URL. Наименования файлов CSS и JavaScript особой роли не играют. После этого запустим Live Server, перейдём по адресу локального сервера и увидим карту.
>
> Страница HTML является корневой. Ей необходимо дать информацию о том, какие внешние библиотеки и файлы будут использоваться. Например, `style.css` и `main.js` являются внешними файлами, а MapLibre является внешней библиотекой. Находящиеся на сервере файлы необходимо подключать по URL. > Live Server обычно запускается по адресу `127.0.0.1:5500`. `127.0.0.1` или `localhost` -- это внутренний адрес сервера на нашем компьютере. Он будет одним и тем же у всех компьютеров. И он недоступен для запросов снаружи. На одном веб-сервере может быть запущено несколько приложений. Для их разграничения используется порт, в нашем случае `5500`.
После этого запустим 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 файлы хранят пространственные данные.
Создадим подпапку `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). <FileTree>
- data/ # данные
Должна получится такая структура. HTML отвечает за структуру веб-страницы, CSS за оформление веб-страницы, JavaScript за логику работы веб-страницы. GeoJSON файлы хранят пространственные данные. - cities.geojson # города
- countries.geojson # страны
<FileTree> - lakes.geojson # озёра
- data/ # данные - rivers.geojson # реки
- cities.geojson # города - index.html # разметка
- countries.geojson # страны - style.css # стили
- lakes.geojson # озёра - main.js # логика
- rivers.geojson # реки </FileTree>
- index.html # разметка
- style.css # стили Все действия с картой выполняются после первичной загрузки исходной карты.
- main.js # логика
</FileTree> Добавление картографических слоёв включает два шага: добавление источника данных `addSource` и добавление слоя `addLayer`. На первом шаге указываем, откуда мы будем брать данные, а на втором, как их оформить. Из одного источника можно создать несколько слоёв.
Все действия с картой выполняются после первичной загрузки исходной карты. ```js title="main.js"
// Инициализируем карту
Добавление картографических слоёв включает два шага: добавление источника данных `addSource` и добавление слоя `addLayer`. На первом шаге указываем, откуда мы будем брать данные, а на втором, как их оформить. Из одного источника можно создать несколько слоёв. // ... этим многоточием отмечен код, который мы уже написали
// в случае сомнений всегда можно свериться с полным кодом веб-карты
```js title="main.js" // ссылка на него есть в конце упражнения
// Инициализируем карту
... map.on('load', () => {
map.on('load', () => { // Выполняется после загрузки карты
// Выполняется после загрузки карты // Добавление источника данных
// Добавление источника данных map.addSource('countries', {
map.addSource('countries', { type: 'geojson',
type: 'geojson', data: './data/countries.geojson',
data: './data/countries.geojson', attribution: 'Natural Earth'
attribution: 'Natural Earth' })
})
// Добавление слоя
// Добавление слоя map.addLayer({
map.addLayer({ id: 'countries-layer',
id: 'countries-layer', type: 'fill',
type: 'fill', source: 'countries',
source: 'countries', paint: {
paint: { 'fill-color': 'lightgray',
'fill-color': 'lightgray', }
} })
}) })
}) ```
```
Мы добавили полигональный слой (`type: 'fill'`). Аналогично добавляем слой линий и слой точек.
Мы добавили полигональный слой (`type: 'fill'`). Аналогично добавляем слой линий и слой точек.
```js title="main.js"
```js title="main.js" // Инициализируем карту
// Инициализируем карту ...
... map.on('load', () => {
map.on('load', () => { // Выполняется после загрузки карты
// Выполняется после загрузки карты ...
... map.addSource('rivers', {
map.addSource('rivers', { type: 'geojson',
type: 'geojson', data: './data/rivers.geojson'
data: './data/rivers.geojson' })
})
map.addLayer({
map.addLayer({ id: 'rivers-layer',
id: 'rivers-layer', type: 'line',
type: 'line', source: 'rivers',
source: 'rivers', paint: {
paint: { 'line-color': '#00BFFF'
'line-color': '#00BFFF' }
} })
})
map.addSource('lakes', {
map.addSource('lakes', { type: 'geojson',
type: 'geojson', data: './data/lakes.geojson'
data: './data/lakes.geojson' })
})
map.addLayer({
map.addLayer({ id: 'lakes-layer',
id: 'lakes-layer', type: 'fill',
type: 'fill', source: 'lakes',
source: 'lakes', paint: {
paint: { 'fill-color': 'lightblue',
'fill-color': 'lightblue', 'fill-outline-color': '#00BFFF'
'fill-outline-color': '#00BFFF' }
} })
})
map.addSource('cities', {
map.addSource('cities', { type: 'geojson',
type: 'geojson', data: './data/cities.geojson'
data: './data/cities.geojson' })
})
map.addLayer({
map.addLayer({ id: 'cities-layer',
id: 'cities-layer', type: 'circle',
type: 'circle', source: 'cities',
source: 'cities', paint: {
paint: { 'circle-color': 'rgb(123, 12, 234)',
'circle-color': 'rgb(123, 12, 234)', 'circle-radius': 3
'circle-radius': 3 }
} })
}) })
}) ```
```
:::tip
:::tip Порядок отрисовки слоёв соответсвует порядку их объявления в коде. Последующие слои перекрывают предыдущие.
Порядок отрисовки слоёв соответсвует порядку их объявления в коде. Последующие слои перекрывают предыдущие. :::
:::
В MapLibre слои можно фильтровать и оформлять на основе атрибутов с помощью [выражений](https://maplibre.org/maplibre-style-spec/expressions/).
В MapLibre слои можно фильтровать и оформлять на основе атрибутов с помощью [выражений](https://maplibre.org/maplibre-style-spec/expressions/).
Например, оставим только города с численностью населения больше 1 000 000
Например, оставим только города с численностью населения больше 1 000 000
```diff lang="js" title="main.js"
```diff lang="js" title="main.js" // Инициализируем карту
// Инициализируем карту ...
... map.on('load', () => {
map.on('load', () => { // Выполняется после загрузки карты
// Выполняется после загрузки карты ...
... map.addLayer({
map.addLayer({ id: 'cities-layer',
id: 'cities-layer', type: 'circle',
type: 'circle', source: 'cities',
source: 'cities', paint: {
paint: { 'circle-color': 'rgb(123, 12, 234)',
'circle-color': 'rgb(123, 12, 234)', 'circle-radius': 3
'circle-radius': 3 },
}, + filter: ['>', ['get', 'POP_MAX'], 1000000]
+ filter: ['>', ['get', 'POP_MAX'], 1000000] })
}) })
}) ```
```
Изобразим красным (`red`) цветом страны, у которых атрибут `MAPCOLOR7` равен 1, а остальные изобразим светло-серым (`lightgray`)
Изобразим красным (`red`) цветом страны, у которых атрибут `MAPCOLOR7` равен 1, а остальные изобразим светло-серым (`lightgray`)
```diff lang="js" title="main.js"
```diff lang="js" title="main.js" // Инициализируем карту
// Инициализируем карту ...
... map.on('load', () => {
map.on('load', () => { // Выполняется после загрузки карты
// Выполняется после загрузки карты ...
... map.addLayer({
map.addLayer({ id: 'countries-layer',
id: 'countries-layer', type: 'fill',
type: 'fill', source: 'countries',
source: 'countries', paint: {
paint: { - 'fill-color': 'lightgray',
- 'fill-color': 'lightgray', + 'fill-color': ['match', ['get', 'MAPCOLOR7'], 1, 'red', 'lightgray']
+ 'fill-color': ['match', ['get', 'MAPCOLOR7'], 1, 'red', 'lightgray'] }
} })
}) ...
... })
}) ```
```
### Расширение интерактивности
### Расширение интерактивности
Созданная нами карта сразу даёт пользователю возможности перемещения, зума и даже наклона (попробуйте зажать правую кнопку мыши). Однако чтобы, например, выводить атрибутивные сведения о слое по клику, надо указать это в коде.
Созданная нами карта сразу даёт пользователю возможности перемещения, зума и даже наклона (попробуйте зажать правую кнопку мыши). Однако чтобы, например, выводить атрибутивные сведения о слое по клику, надо указать это в коде.
Отследим событие клика по слою `cities-layer`. Назовём событие клика переменной `e`. Посмотрим в консоли браузера, что собой представляет это событие. Если мы отслеживаем событие клика по конкретному слою, а не по всей карте, то мы можем обратиться к набору объектов, по которым был выполнен клик `e.features`
Отследим событие клика по слою `cities-layer`. Назовём событие клика переменной `e`. Посмотрим в консоли браузера, что собой представляет это событие. Если мы отслеживаем событие клика по конкретному слою, а не по всей карте, то мы можем обратиться к набору объектов, по которым был выполнен клик `e.features`
```js title="main.js"
```js title="main.js" // Инициализируем карту
// Инициализируем карту ...
... map.on('load', () => {
map.on('load', () => { // Выполняется после загрузки карты
// Выполняется после загрузки карты ...
... map.on('click', ['cities-layer'], (e) => {
map.on('click', ['cities-layer'], (e) => { console.log(e)
console.log(e) console.log(e.features)
console.log(e.features) })
}) })
}) ```
```
Закомментируем вывод в консоль и выведем по клику на слой попап.
Закомментируем вывод в консоль и выведем по клику на слой попап.
```js title="main.js"
```js title="main.js" // Инициализируем карту
// Инициализируем карту ...
... map.on('load', () => {
map.on('load', () => { // Выполняется после загрузки карты
// Выполняется после загрузки карты ...
... map.on('click', ['cities-layer'], (e) => {
map.on('click', ['cities-layer'], (e) => { // console.log(e)
// console.log(e) // console.log(e.features)
// console.log(e.features) new maplibregl.Popup() // создадим попап
new maplibregl.Popup() // создадим попап .setLngLat(e.features[0].geometry.coordinates) // установим на координатах объекта
.setLngLat(e.features[0].geometry.coordinates) // установим на координатах объекта .setHTML(e.features[0].properties.NAME) // заполним текстом из атрибута с именем объекта
.setHTML(e.features[0].properties.NAME) // заполним текстом из атрибута с именем объекта .addTo(map); // добавим на карту
.addTo(map); // добавим на карту })
}) })
}) ```
```
Попап отображается, но надо показать пользователю, что на объект можно кликать. При попадании мыши на слой `cities-layer` поменяем курсор на pointer, а при покидании слоя `cities-layer` вернём значение по умолчанию.
Попап отображается, но надо показать пользователю, что на объект можно кликать. При попадании мыши на слой `cities-layer` поменяем курсор на pointer, а при покидании слоя `cities-layer` вернём значение по умолчанию.
```js title="main.js"
```js title="main.js" // Инициализируем карту
// Инициализируем карту ...
... map.on('load', () => {
map.on('load', () => { // Выполняется после загрузки карты
// Выполняется после загрузки карты ...
... map.on('mouseenter', 'cities-layer', () => {
map.on('mouseenter', 'cities-layer', () => { map.getCanvas().style.cursor = 'pointer'
map.getCanvas().style.cursor = 'pointer' })
}) map.on('mouseleave', 'cities-layer', () => {
map.on('mouseleave', 'cities-layer', () => { map.getCanvas().style.cursor = ''
map.getCanvas().style.cursor = '' })
}) })
}) ```
```
В качестве завершающего штриха уберём карту подложку и добавим фон. При этом фон добавляем перед всеми слоями, так как все слои должны рисоваться после фона, поверх него.
В качестве завершающего штриха уберём карту подложку и добавим фон. При этом фон добавляем перед всеми слоями, так как все слои должны рисоваться после фона, поверх него.
```diff lang="js" title="main.js"
```diff lang="js" title="main.js" // Инициализируем карту
// Инициализируем карту const map = new maplibregl.Map({
const map = new maplibregl.Map({ container: 'map',
container: 'map', - style: "https://raw.githubusercontent.com/gtitov/basemaps/refs/heads/master/positron-nolabels.json",
- style: "https://raw.githubusercontent.com/gtitov/basemaps/refs/heads/master/positron-nolabels.json", + style: {
+ style: { + "version": 8,
+ "version": 8, + "sources": {},
+ "sources": {}, + "layers": []
+ "layers": [] + },
+ }, center: [51, 0],
center: [51, 0], zoom: 4
zoom: 4 });
});
map.on('load', () => {
map.on('load', () => { // Выполняется после загрузки карты
// Выполняется после загрузки карты + map.addLayer({
+ map.addLayer({ + id: 'background',
+ id: 'background', + type: 'background',
+ type: 'background', + paint: {
+ paint: { + 'background-color': 'lightblue'
+ 'background-color': 'lightblue' + }
+ } + })
+ }) ...
... })
}) ```
```
У нас получилась отличная карта!
У нас получилась отличная карта!
При желании посмотрите [полный код](https://github.com/gtitov/geojson-maplibre-map) и [возможный результат](https://gtitov.github.io/geojson-maplibre-map/).
При желании посмотрите [полный код](https://github.com/gtitov/geojson-maplibre-map) и [возможный результат](https://gtitov.github.io/geojson-maplibre-map/).
## Что мы получили
## Что мы получили
Откроем вкладку Сеть в инструментах разработчика и ещё разок проследим поток данных
Откроем вкладку Сеть в инструментах разработчика и ещё разок проследим поток данных
![geojson-network-tab](../../assets/geojson-network-tab.png)
![geojson-network-tab](../../../assets/geojson-network-tab.png)
1. Пользователь вводит адрес карты в браузере (в клиенте)
1. Пользователь вводит адрес карты в браузере (в клиенте) 1. Клиент выполняет запрос к серверу по введённому адресу
1. Клиент выполняет запрос к серверу по введённому адресу 1. Сервер обрабатывает запрос и возвращает разметку (HTML) (1)
1. Сервер обрабатывает запрос и возвращает разметку (HTML) (1) 1. В разметке содержаться запросы к офомлению (CSS), картографической библиотеке (MapLibre) и программной логике работы (JavaScript) веб-страницы (2)
1. В разметке содержаться запросы к офомлению (CSS), картографической библиотеке (MapLibre) и программной логике работы (JavaScript) веб-страницы (2) 1. Клиент (браузер), получив все необходимые сведения, отображает веб-страницу
1. Клиент (браузер), получив все необходимые сведения, отображает веб-страницу 1. Программная логика работы полученной веб-страницы выполняется и в соотстветвии с кодом инициирует запросы к данным (GeoJSON) для составления карты (3)
1. Программная логика работы полученной веб-страницы выполняется и в соотстветвии с кодом инициирует запросы к данным (GeoJSON) для составления карты (3) 1. Полученные данные оформляются на веб-карте в рамках описанной разработчиком на языке JavaScript логики с использованием функций библиотеки MapLibre
1. Полученные данные оформляются на веб-карте в рамках описанной разработчиком на языке JavaScript логики с использованием функций библиотеки MapLibre 1. Пользователь получает веб-карту
1. Пользователь получает веб-карту 1. Веб-карта обогащается дополнительной интерактивностью в рамках описанной разработчиком логики
1. Веб-карта обогащается дополнительной интерактивностью в рамках описанной разработчиком логики
Такая карта удобна, когда немного данных, потому что мы всё переправляем пользователю данные как есть. Когда мы отправляем пользователю данные как есть, почти не требуется серверных мощностей, поэтому для таких карт есть варианты бесплатного размещения в Интернете.
Такая карта удобна, когда немного данных, потому что мы всё переправляем пользователю данные как есть. Когда мы отправляем пользователю данные как есть, почти не требуется серверных мощностей, поэтому для таких карт есть варианты бесплатного размещения в Интернете.
## Упражнения
## Упражнения
1. Покрасьте Москву в красный цвет
1. Покрасьте Москву в красный цвет 2. Выведите в попап один из атрибутов стран
2. Выведите в попап один из атрибутов стран 3. Добавьте слой с границами озёр, установите им толщину в 2 пикселя
3. Добавьте слой с границами озёр, установите им толщину в 2 пикселя 4. Замените курсор на перекрестие (`crosshair`) при расположении поверх стран
4. Замените курсор на перекрестие (`crosshair`) при расположении поверх стран 5. Офомление карты оставляет желать лучшего. Попробуйте оформить карту в соответствии с собственными представлениями о прекрасном.
--- ## Контрольные вопросы
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025. 1. В каком файле описывается разметка веб-страницы?
1. Какой тег содержит ссылку на стили веб-страницы?
1. Что означает `container: 'map'` в коде `new maplibregl.Map({ container: 'map', ... })`?
1. Какой метод библиотеки MapLibre позволяет добавить картографический слой на карту?
1. Какой тип слоя используется для отображения полигонов озёр?
1. Какой текстовый формат пространственных данных может использоваться в качестве источника пространственных данных для библиотеки MapLibre?
---
Титов Г. С. Практическое введение в веб-картографию. Москва, 2025.

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

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

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

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

@ -1,21 +1,21 @@
--- ---
title: Литература title: Литература
tableOfContents: false tableOfContents: false
--- ---
1. **Аляутдинов А. Р., Лурье И. К., Ушакова Л. А.** Основные принципы функционирования геоинформационных ресурсов // Известия высших учебных заведений. Геодезия и аэрофотосъемка. — 2016. — Т. 60, № 5. — С. 123128 [🔎&nbsp;🌐](https://elibrary.ru/item.asp?id=27224121) 1. **Аляутдинов А. Р., Лурье И. К., Ушакова Л. А.** Основные принципы функционирования геоинформационных ресурсов // Известия высших учебных заведений. Геодезия и аэрофотосъемка. — 2016. — Т. 60, № 5. — С. 123128 [🔎&nbsp;🌐](https://elibrary.ru/item.asp?id=27224121)
1. **Абдуллин Р. К., Пономарчук А. И.** Технологии интернет-картографирования: учебное пособие / Пермский государственный национальный исследовательский университет. - Пермь, 2020. 132 с.: ил. [🔎&nbsp;🌐](https://gis.psu.ru/publications/технологии-интернет-картографирован/) 1. **Абдуллин Р. К., Пономарчук А. И.** Технологии интернет-картографирования: учебное пособие / Пермский государственный национальный исследовательский университет. - Пермь, 2020. 132 с.: ил. [🔎&nbsp;🌐](https://gis.psu.ru/publications/технологии-интернет-картографирован/)
1. **Берлянт А. М.** Геоинформационное Картографирование. М.: Моск. гос. Ун-т им. М. В. Ломоносова, Рос. акад. естеств. наук, 1997. [🔎&nbsp;🌐](https://rusneb.ru/catalog/000199_000009_000611203/) 1. **Берлянт А. М.** Геоинформационное Картографирование. М.: Моск. гос. Ун-т им. М. В. Ломоносова, Рос. акад. естеств. наук, 1997. [🔎&nbsp;🌐](https://rusneb.ru/catalog/000199_000009_000611203/)
1. **Каргашин П. Е.** Основы цифровой картографии: Учебное пособие для бакалавров. 5-е изд., перераб. — Москва: Издательско-торговая корпорация Дашков и К, 2023. — 106 с. [🔎&nbsp;🌐](https://istina.msu.ru/publications/book/557759518/) 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. **Лурье И. К.**, Самсонов Т.Е. Структура и содержание базы пространственных данных для мультимасштабного картографирования // Геодезия и картография. 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. **Титов Г. С., Прасолова А. И., Каргашин П. Е.** Веб-картографирование ресурсов солнечной энергии Якутии // ИнтерКарто. ИнтерГИС. — 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. **Титов Г. С.** Текущие проблемы терминологического аппарата отечественной веб-картографии // Геодезия, картография, геоинформатика и кадастры. Производство и образование : Сб. материалов 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. **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. **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. **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. **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) 1. ГОСТ Р 58570-2019. Инфраструктура Пространственных Данных. Общие Требования // Стандартинформ, 2019. [🔎&nbsp;🌐](https://docs.cntd.ru/document/1200168445)
--- ---
Титов Г. С. Введение в веб-картографию / Веб-картография : учебные материалы. Москва, 2025. Титов Г. С. Практическое введение в веб-картографию. Москва, 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