mahal_map 1.7.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,15 +7,52 @@ Mahal Map - JavaScript/TypeScript SDK для работы с картой Mahal
7
7
  ## Установка
8
8
 
9
9
  ```sh
10
- npm install mahal_map maplibre-gl
10
+ npm install mahal_map maplibre-gl @grammaps/maps3d-web
11
11
  ```
12
12
 
13
- `maplibre-gl` является peer dependency. Его нужно установить в приложении или подключить отдельным browser script перед SDK.
13
+ Обе зависимости — `peerDependencies`, в бандл `mahal_map` они не входят. Библиотека их не импортирует: MapLibre и `Maps3D` приходят снаружи, аргументами `create()` либо через `window`.
14
14
 
15
- `@grammaps/maps3d-web` объявлен peer dependency пакета (в `package.json` помечен как `optional` без него `engine: "legacy"` работает как обычно). Для `engine: "3d"` он обязателен в рантайме: установите его явно.
15
+ - `maplibre-gl` (3–6) — обязателен всегда.
16
+ - `@grammaps/maps3d-web` (>=0.5.0) — даёт стили, тайлы, объём, пробки, рельеф, планы этажей, клик по объектам. Помечен `optional`: без него карта поднимется на запасном векторном стиле, но 3D и слоёв платформы на ней не будет.
16
17
 
17
- ```sh
18
- npm i mahal_map maplibre-gl @grammaps/maps3d-web
18
+ MapLibre можно не класть в свою сборку вовсе — платформа отдаёт согласованную версию вместе с веб-воркером:
19
+
20
+ ```ts
21
+ const maplibregl = await Maps3D.maplibre({ base: "https://navi.gram.tj" });
22
+ ```
23
+
24
+ ## Миграция с 1.x на 2.0
25
+
26
+ Собственных URL стилей у `mahal_map` больше нет — их целиком отдаёт `@grammaps/maps3d-web`.
27
+
28
+ | 1.x | 2.0 |
29
+ | ---------------------------------------------- | -------------------------------------------------------------- |
30
+ | `engine: "legacy"` (по умолчанию) | Удалён. Единственный путь — платформа через `Maps3D`. |
31
+ | `engine: "3d"` | Больше не нужен, опция игнорируется. |
32
+ | `autoAddVectorSource: true` | Удалён. Тот же векторный стиль применяется сам, когда нет `Maps3D`. |
33
+ | `preset: "standard-night"` | `theme: "dark"`, либо полный URL в `style`. |
34
+ | `preset: "road-urban-lab-v2"` | `theme: "light"`, либо полный URL в `style`. |
35
+ | `lang` менял URL стиля | Стиль не трогает — язык подписей приходит из самого стиля. |
36
+ | `layer.setBuildingsEnabled(...)` напрямую | `map.toggle3DBuildings(...)` или `map.setLayer("buildings", ...)`. |
37
+
38
+ `engine` и `autoAddVectorSource` оставлены в типах как `@deprecated`, чтобы не ломать компиляцию, но на поведение не влияют.
39
+
40
+ Что появилось: реестр слоёв (`setLayer`/`getLayers`/`onLayers`), выделение зданий, клик по дорогам, рельеф, планы этажей, перекрытия, семейства стилей `navigator`/`mobile` и автоматический `antialias`.
41
+
42
+ Минимальный диф:
43
+
44
+ ```diff
45
+ const map = MahalMap.create(
46
+ {
47
+ container: "map",
48
+ - engine: "3d",
49
+ theme: "dark",
50
+ - preset: "standard-night",
51
+ enable3D: true,
52
+ },
53
+ maplibregl,
54
+ Maps3D,
55
+ );
19
56
  ```
20
57
 
21
58
  ## Быстрый старт через NPM
@@ -23,6 +60,7 @@ npm i mahal_map maplibre-gl @grammaps/maps3d-web
23
60
  ```ts
24
61
  import maplibregl from "maplibre-gl";
25
62
  import "maplibre-gl/dist/maplibre-gl.css";
63
+ import { Maps3D } from "@grammaps/maps3d-web";
26
64
  import { MahalMap, keyUtils } from "mahal_map";
27
65
 
28
66
  keyUtils.saveKey("YOUR_MAP_API_KEY");
@@ -30,11 +68,12 @@ keyUtils.saveKey("YOUR_MAP_API_KEY");
30
68
  const map = MahalMap.create(
31
69
  {
32
70
  container: "map",
33
- center: [69.624024, 40.279687],
34
- zoom: 12,
71
+ center: [68.787, 38.573],
72
+ zoom: 16.6,
35
73
  theme: "light",
36
74
  },
37
75
  maplibregl,
76
+ Maps3D,
38
77
  );
39
78
  ```
40
79
 
@@ -44,6 +83,8 @@ const map = MahalMap.create(
44
83
  <div id="map" style="width: 100%; height: 500px"></div>
45
84
  ```
46
85
 
86
+ `Maps3D` — третий, необязательный аргумент: не передан — SDK возьмёт его из `window.Maps3D`. Стиль, `transformRequest` с ключом, сглаживание и подключение 3D библиотека делает сама.
87
+
47
88
  ## Быстрый старт через Browser SDK
48
89
 
49
90
  Сначала подключите MapLibre, затем `mahal_map.sdk.js`. Для browser SDK параметр `apikey` обязателен: без него карта не инициализируется.
@@ -99,7 +140,7 @@ const map = MahalMap.create(
99
140
  `maps3dCtor` — импортированный конструктор `Maps3D` (третий, необязательный аргумент). Если не передан, SDK ищет его в `window.Maps3D`.
100
141
 
101
142
  ```ts
102
- import type { IMaps3DLayerOptions } from "mahal_map";
143
+ import type { IMaps3DLayerOptions, Maps3DThemeName } from "mahal_map";
103
144
 
104
145
  interface IMahalMapOptions {
105
146
  container?: string | HTMLElement;
@@ -110,111 +151,130 @@ interface IMahalMapOptions {
110
151
  zoom?: number;
111
152
  pitch?: number;
112
153
  bearing?: number;
113
- autoAddVectorSource?: boolean;
114
- engine?: "legacy" | "3d";
115
154
  enable3D?: boolean;
116
155
  base?: string;
117
- preset?: string;
118
- maps3d?: Omit<IMaps3DLayerOptions, "apiKey" | "base" | "buildings">;
156
+ family?: "default" | "navigator" | "mobile";
157
+ preset?: Maps3DThemeName | string;
158
+ antialias?: boolean;
159
+ maps3d?: Omit<IMaps3DLayerOptions, "apiKey" | "base">;
119
160
  }
120
161
  ```
121
162
 
122
- | Параметр | Тип | Описание |
123
- | --------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
124
- | `container` | `string \| HTMLElement` | ID контейнера или DOM-элемент. Если не передан, используется `"map"`. |
125
- | `style` | `string` | Пользовательский URL стиля MapLibre. Если передан, `theme`, `lang` и map token не меняют URL стиля. |
126
- | `theme` | `"dark" \| "light"` | Тема стандартного стиля. По умолчанию используется `light`. |
127
- | `lang` | `"tj" \| "ru"` | Язык стандартного стиля. `tj` оставляет URL только с `token`, `ru` добавляет `lang=ru`. |
128
- | `center` | `[number, number]` | Центр карты в формате `[lng, lat]`. |
129
- | `zoom` | `number` | Начальный zoom. |
130
- | `pitch` | `number` | Начальный наклон камеры (нужен для 3D-вида). Если не задан и `enable3D` включен — авто `58` (при `pitch: 0` экструзия зданий не видна, камера смотрит строго сверху). |
131
- | `bearing` | `number` | Начальный поворот камеры. |
132
- | `autoAddVectorSource` | `boolean` | Использует встроенный vector style и блокирует смену стандартного стиля через `setStyle`. |
133
- | `engine` | `"legacy" \| "3d"` | Переключатель движка карты. `"legacy"` (по умолчанию) — старые стили mtile.gram.tj. `"3d"` — новая платформа GramMaps (navi.gram.tj) с пресетами стиля и Maps3D. |
134
- | `enable3D` | `boolean` | Подключает детальные 3D-здания (Maps3D). Работает только при `engine: "3d"`. По умолчанию `true` для `engine: "3d"` передайте `false`, чтобы отключить. |
135
- | `base` | `string` | Домен платформы GramMaps для `engine: "3d"`. По умолчанию `https://navi.gram.tj`. |
136
- | `preset` | `string` | Имя пресета стиля GramMaps (напр. `"standard-night"`) для `engine: "3d"`. По умолчанию `road-urban-lab-v2`/`standard-night` в зависимости от `theme`. |
137
- | `maps3d` | `object` | Доп. опции Maps3D слоя: `traffic`, `minZoom`, `lodBias`, `memoryBudget`, `maskReplaced`, `typeReplacements`. |
138
-
139
- ### Новый 3D-движок (GramMaps / Maps3D)
140
-
141
- Токен передается как обычно, через `keyUtils.saveKey()` отдельно ключ для Maps3D передавать не нужно.
163
+ | Параметр | Тип | Описание |
164
+ | ----------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
165
+ | `container` | `string \| HTMLElement` | ID контейнера или DOM-элемент. Не передан берётся `"map"`. |
166
+ | `style` | `string` | Полный URL своего стиля. Задан стиль зафиксирован, `setStyle()` его не меняет. |
167
+ | `theme` | `"dark" \| "light"` | Светлая/тёмная внутри выбранного `family`. По умолчанию `light`. |
168
+ | `lang` | `"tj" \| "ru"` | Язык для поиска и роутинга. На стиль не влияет подписи приходят из самого стиля платформы. |
169
+ | `center` | `[number, number]` | Центр карты в формате `[lng, lat]`. |
170
+ | `zoom` | `number` | Начальный zoom. |
171
+ | `pitch` | `number` | Начальный наклон камеры. Не задан и 3D включено — авто `58`: при `pitch: 0` объём зданий не виден, камера смотрит строго сверху. |
172
+ | `bearing` | `number` | Начальный поворот камеры. |
173
+ | `enable3D` | `boolean` | Подключает Maps3D. По умолчанию `true`, когда `Maps3D` доступен. |
174
+ | `base` | `string` | Домен платформы. По умолчанию `https://navi.gram.tj`. |
175
+ | `family` | `"default" \| "navigator" \| "mobile"` | Семейство стилей. `theme` выбирает внутри него: `default` `light`/`dark`, `navigator` `navigator-light`/`navigator-dark`, `mobile` → `mobile-*`. |
176
+ | `preset` | `Maps3DThemeName \| string` | Явное имя темы платформы или полный URL стиля вместо пары `family` + `theme`. Задан стиль зафиксирован. |
177
+ | `antialias` | `boolean` | Сглаживание сцены. По умолчанию `true` при включённом 3D: без него тонкая геометрия (перила, мачты, ряды сидений) на отдалении рассыпается в рябь. |
178
+ | `maps3d` | `object` | Опции Maps3D: `buildings`, `traffic`, `indoor`, `closures`, `places`, `minZoom`, `lodBias`, `memoryBudget`, `maskReplaced`, `typeReplacements`. |
179
+
180
+ ### Стили и темы
181
+
182
+ Стили целиком приходят из `@grammaps/maps3d-web` — своего списка URL у `mahal_map` больше нет. Словарь тем один и тот же у SDK и у библиотеки:
183
+
184
+ | `family` | `theme: "light"` | `theme: "dark"` |
185
+ | ------------- | ------------------ | ----------------- |
186
+ | `"default"` | `light` | `dark` |
187
+ | `"navigator"` | `navigator-light` | `navigator-dark` |
188
+ | `"mobile"` | `mobile-light` | `mobile-dark` |
189
+
190
+ Имя темы уходит в `Maps3D.styleUrl()`, адрес строит сам SDK. Неизвестное имя — явная ошибка с префиксом `[MahalMap SDK]`, а не пустая карта.
142
191
 
143
192
  ```ts
144
- import maplibregl from "maplibre-gl";
145
- import "maplibre-gl/dist/maplibre-gl.css";
146
- import { Maps3D } from "@grammaps/maps3d-web";
147
- import { MahalMap, keyUtils } from "mahal_map";
148
-
149
- keyUtils.saveKey("YOUR_MAP_API_KEY");
193
+ // Навигаторная тёмная тема
194
+ MahalMap.create({ container: "map", family: "navigator", theme: "dark" }, maplibregl, Maps3D);
150
195
 
151
- const map = MahalMap.create(
152
- {
153
- container: "map",
154
- center: [68.78, 38.56],
155
- zoom: 16.6,
156
- pitch: 58,
157
- theme: "dark",
158
- engine: "3d",
159
- enable3D: true,
160
- },
161
- maplibregl,
162
- Maps3D,
163
- );
196
+ // Смена темы внутри того же семейства
197
+ map.setStyle("light"); // → navigator-light
164
198
  ```
165
199
 
166
- `Maps3D` (третий аргумент `create()`) опционален: если не передан, SDK попробует взять его из `window.Maps3D`. `@grammaps/maps3d-web` необязательный peer dependency, ставится только если используется `engine: "3d"`.
200
+ > **Миграция с 1.x.** Имена пресетов прежнего поколения (`road-urban-lab-v2`, `standard-night`) больше не подставляются по умолчанию словарь тем теперь один, платформенный. Если старый стиль всё ещё нужен, передайте его полным URL:
201
+ >
202
+ > ```ts
203
+ > MahalMap.create(
204
+ > { container: "map", style: "https://navi.gram.tj/maps/standard-night.json" },
205
+ > maplibregl,
206
+ > Maps3D,
207
+ > );
208
+ > ```
167
209
 
168
- Получить слой Maps3D после создания карты:
210
+ ### Без `@grammaps/maps3d-web`
169
211
 
170
- ```ts
171
- const layer = map.getMaps3DLayer();
172
- // или
173
- MahalMap.getMaps3DLayer(map);
174
- ```
212
+ Библиотека не установлена и в `window.Maps3D` ничего нет — карта всё равно поднимется: используется запасной векторный стиль `mtile.gram.tj` с подписью `?token=`, в консоль уходит предупреждение. На такой карте нет объёма, объектов, пробок, рельефа и реестра слоёв; `setStyle()` и `setLanguage()` стиль не меняют, методы реестра возвращают пустые значения (`false`, `null`, `[]`).
175
213
 
176
- `getMaps3DLayer()` возвращает реальный инстанс `Maps3D` (не обёртку) типы `mahal_map` описывают всю его публичную поверхность, так что подключающему сервису не нужно ставить или типизировать `@grammaps/maps3d-web` отдельно.
214
+ Этот путь единственный, где ещё проверяется подписка JSApi: `createAsync()` не создаст карту, если подписки нет. С переданным `Maps3D` проверка пропускается доступ гейтит сама платформа по `?key=`.
177
215
 
178
216
  ### Опции `maps3d` (расширенные)
179
217
 
180
- Передаются в `MahalMap.create({ maps3d: {...} })` при `engine: "3d"`:
181
-
182
- | Опция | Тип | По умолч. | Описание |
183
- | ------------------ | ------------------------------------------------ | --------- | ------------------------------------------------------------------------- |
184
- | `traffic` | `boolean \| { raster?, rasterMaxZoom?, graph? }` | `false` | Слой пробок. `raster: true` — картинкой с сервера вместо векторного слоя. |
185
- | `minZoom` | `number` | `16` | Зум появления детальных 3D. |
186
- | `lodBias` | `number` | `1` | `0` — всегда lod0 (детальный), `1` — lod1 на дальних зумах. |
187
- | `memoryBudget` | `number` | `30` | Сколько моделей держать в сцене одновременно. |
188
- | `maskReplaced` | `boolean` | `true` | Прятать заменённые OSM-объекты (`anchor=replace`). |
189
- | `typeReplacements` | `boolean` | `true` | Рисовать замены по типу (`natural=tree` → 3D-дерево и т.п.). |
218
+ Передаются в `MahalMap.create({ maps3d: {...} })` и уходят в `Maps3D` как есть:
190
219
 
191
- `buildings: true` включается автоматически при `enable3D: true` — переопределять не нужно, если только не требуется передать сам объект опций.
220
+ | Опция | Тип | По умолч. | Описание |
221
+ | ------------------ | ----------------------------------------------------- | --------- | -------------------------------------------------------------------- |
222
+ | `buildings` | `boolean \| { detail?: footprint\|volume\|roofs\|facade }` | `true` | Объёмные здания; объектом — их облик. |
223
+ | `traffic` | `boolean \| { raster?, rasterMaxZoom?, graph?, opacity?, arrows? }` | `false` | Слой пробок. `raster: true` — картинкой вместо векторного слоя. |
224
+ | `indoor` | `boolean \| { level? }` | `false` | Планы этажей. |
225
+ | `closures` | `boolean \| object` | `false` | Перекрытия дорог. |
226
+ | `places` | `object` | — | Парковки, заправки, зарядки: `highlight`, `paid`, `free`, `unknown`. |
227
+ | `minZoom` | `number` | `16` | Зум появления объёма. |
228
+ | `lodBias` | `number` | `1` | `0` — всегда детальная геометрия, `1` — упрощённая вдали. |
229
+ | `memoryBudget` | `number` | `30` | Сколько 3D-моделей держать в памяти. |
230
+ | `maskReplaced` | `boolean` | `true` | Прятать заменённые OSM-объекты (`anchor=replace`). |
231
+ | `typeReplacements` | `boolean` | `true` | Рисовать замены по типу (`natural=tree` → 3D-дерево и т.п.). |
192
232
 
193
- ### 3D-здания
233
+ `apiKey` и `base` в `maps3d` передавать не нужно — их подставляет сам `MahalMap` из сохранённого ключа и `options.base`.
194
234
 
195
- `Maps3D` рисует процедурные 3D-здания (three.js) вместо плоской `fill-extrusion` стиля: фаска кромок, вертикальный градиент и базовый цвет берутся из стиля, окна — из `metadata` пресета. При `engine: "3d"` слой создаётся и `attach`-ится к карте автоматически (`enable3D` по умолчанию `true`) — вручную поднимать `new Maps3D(...)` не нужно, только если требуется отдельный кастомный инстанс.
235
+ ### Реестр слоёв
196
236
 
197
- **Ручной `new Maps3D(...)` отдельный сценарий.** Карту при этом создавайте с `enable3D: false`, иначе на неё повиснут два слоя Maps3D сразу (автоматический от `MahalMap` + ваш ручной) — дублирование зданий и лишний расход ресурсов:
237
+ Единая дверь ко всем слоям платформы. Состояние слоя три независимых поля: `wanted` (чего хочет приложение), `available` (что позволяют стиль и данные), `active` (что нарисовано сейчас).
198
238
 
199
239
  ```ts
200
- const apiKey = "YOUR_MAP_API_KEY";
201
- const base = "https://navi.gram.tj";
240
+ map.setLayer("terrain", true, { mode: "on" });
241
+ map.setLayer("traffic", true);
242
+ map.setLayer("indoor", true, { level: 2 });
202
243
 
203
- const map = MahalMap.create(
204
- { container: "map", engine: "3d", enable3D: false },
205
- maplibregl,
206
- );
244
+ const unsubscribe = map.onLayers((state) => {
245
+ console.log(state.id, state.wanted, state.available, state.active);
246
+ });
207
247
 
208
- const layer = new Maps3D({ apiKey, base, buildings: true });
209
- await layer.attach(map.getMap()); // attach ждёт нативную карту MapLibre, не обёртку MahalMap
248
+ map.getLayerState("terrain"); // снимок одного слоя или null
249
+ map.getLayers(); // снимок всех по нему рисуется панель слоёв
210
250
  ```
211
251
 
212
- Тема (окна/свет) приходит из `metadata` пресета стиля и применяется автоматически при `map.setStyle()` — пересоздавать слой не нужно. Ручные сеттеры перебивают её.
252
+ | Слой | Параметры | По умолчанию |
253
+ | ------------------ | ---------------------------------------- | ------------ |
254
+ | `buildings` | `detail: footprint\|volume\|roofs\|facade` | включён |
255
+ | `objects` | — | включён |
256
+ | `traffic` | — | выключен |
257
+ | `trafficRaster` | — | выключен |
258
+ | `parking` | `highlight`, `paid`, `free`, `unknown` | включён |
259
+ | `fuel`, `charging` | — | включены |
260
+ | `closures` | — | выключен |
261
+ | `indoor` | `level` | выключен |
262
+ | `terrain` | `mode: auto\|on\|off` | `auto` |
213
263
 
214
- Это работает только для встроенных пресетов **без** явного `options.preset`: `setStyle()` меняет пресет по теме (`light`/`dark` `GRAM_PRESETS`), а при заданном `preset` считает стиль зафиксированным и ничего не делает (см. [`MahalMap.ts`](src/core/MahalMap.ts:363)). Чтобы сменить пресет/тему в этом случае пересоздайте карту с другим `preset` или вызовите `map.getMap().setStyle(...)` напрямую.
264
+ Слой может быть включён и при этом не нарисован: рельеф в режиме `auto` появляется на обзорных зумах, перекрытия требуют своего тайлсета, планы этажей данных по зданию. Рельеф в режиме `on` заметно дороже по трафику и времени кадра.
265
+
266
+ Парковки, заправки и зарядки `Maps3D` рисует сам, забирая эти классы у POI-слоёв стиля, — поэтому выключение слоя убирает объекты с карты полностью, а не оставляет значок стиля.
267
+
268
+ `setLayer()` возвращает `false`, если такого слоя в подключённой сборке Maps3D нет (или Maps3D не передан вовсе). После полной смены стиля волю клиента возвращает `map.refreshLayers()` — при `setStyle()` библиотека вызывает его сама.
269
+
270
+ ### 3D-здания
271
+
272
+ `Maps3D` рисует процедурные 3D-здания (three.js) вместо плоской `fill-extrusion` стиля: фаска кромок, вертикальный градиент и базовый цвет берутся из стиля, окна — из `metadata` темы. Слой создаётся и подключается автоматически (`enable3D` по умолчанию `true`) — вручную поднимать `new Maps3D(...)` не нужно.
273
+
274
+ Тонкая настройка облика — через сам слой, после готовности:
215
275
 
216
276
  ```ts
217
- const layer = map.getMaps3DLayer();
277
+ const layer = await map.whenMaps3DReady();
218
278
 
219
279
  const b = layer?.buildings;
220
280
  b?.setWindowStyle(7); // тип окна 0..9 (сетка, лента, curtain wall, ...)
@@ -227,16 +287,59 @@ b?.setSunIntensity(3.2);
227
287
  b?.setAmbient(0.76);
228
288
  b?.setSky(0.91);
229
289
  b?.setExposure(1.5);
290
+ ```
291
+
292
+ Тема (окна/свет) приходит из `metadata` стиля и применяется автоматически при смене стиля — пересоздавать слой не нужно. Ручные сеттеры её перебивают.
293
+
294
+ **Ручной `Maps3D.enhance(...)` — отдельный сценарий.** Карту при этом создавайте с `enable3D: false`: второй экземпляр на занятой карте `Maps3D` отклоняет.
295
+
296
+ ```ts
297
+ const map = MahalMap.create(
298
+ { container: "map", enable3D: false },
299
+ maplibregl,
300
+ Maps3D,
301
+ );
302
+
303
+ const maps3d = Maps3D.enhance(map.getMap(), {
304
+ apiKey: "YOUR_MAP_API_KEY",
305
+ base: "https://navi.gram.tj",
306
+ });
307
+ await maps3d.ready; // enhance() ждёт нативную карту MapLibre, не обёртку MahalMap
308
+ ```
230
309
 
231
- layer?.onBuildingClick((info) => {
232
- if (!info) return;
233
- console.log(info.id, info.height, info.props);
310
+ ### Выделение зданий и клик по дорогам
311
+
312
+ Клик по зданию и клик по дороге приходят независимо: одна точка может попасть и туда, и туда — что важнее, решает приложение.
313
+
314
+ ```ts
315
+ map.setSelectionStyle({ color: "#e23b2f", opacity: 0.55, durationMs: 300 });
316
+
317
+ map.onBuildingClick((building) => {
318
+ if (!building) return;
319
+ // SDK уже подсветил его
320
+ console.log(building.props?.osm_id, building.height);
321
+ });
322
+
323
+ map.onRoadClick((road) => {
324
+ if (!road) return;
325
+ console.log(road.nameRu ?? road.name, road.class);
234
326
  });
327
+
328
+ // Выделить здание по osm_id — например, после поиска
329
+ map.selectBuilding(123456789); // false, если здания нет в загруженных данных
330
+ map.selectedBuilding(); // текущий id или null
331
+ map.clearSelection();
332
+
333
+ // Дорога под точкой холста, допуск по умолчанию 12 px
334
+ map.roadAt({ x: 320, y: 240 });
335
+ map.roadsNamed; // есть ли в текущем стиле названия дорог
235
336
  ```
236
337
 
338
+ Подписываться можно сразу после `create()`, до готовности карты.
339
+
237
340
  #### Вкл/выкл 3D-здания на лету
238
341
 
239
- Переключение 3D-здания ⇄ штатные здания стиля, без пересоздания карты через обёртку `MahalMap`. Она же умеет пересоздать слой, если его не было (`enable3D: false` при создании):
342
+ Переключение объём ⇄ штатные здания стиля, без пересоздания карты. Умеет поднять слой, если его не было (`enable3D: false` при создании):
240
343
 
241
344
  ```ts
242
345
  const map = MahalMap.getInstance("map");
@@ -248,11 +351,11 @@ map.toggle3DBuildings(true); // вкл обратно
248
351
  MahalMap.toggle3DBuildings(map, false);
249
352
  ```
250
353
 
251
- `layer?.setBuildingsEnabled(false)` напрямую через слой **не используйте** он не знает про штатный слой стиля `building-3d`, который `MahalMap` прячет при включённом 3D. Вызов только слоя оставит эти штатные здания скрытыми и одновременно выключит процедурные — в итоге зданий не будет видно вообще, до следующей перезагрузки стиля.
354
+ Под капотом это `setLayer("buildings", enabled)`. Штатные здания стиля прячет и возвращает сам `Maps3D` `mahal_map` их видимость не трогает, поэтому спорить за один слой некому. Эквивалентная запись: `map.setLayer("buildings", false)`.
252
355
 
253
356
  #### `map.whenMaps3DReady()`
254
357
 
255
- `attach()` слоя асинхронный: сразу после `create()` слой уже есть, но `layer.buildings` (окна, свет, кромки) появляется только после attach. Чтобы не гадать — дождитесь готовности:
358
+ Подключение слоя асинхронное: сразу после `create()` слой уже есть, но `layer.buildings` (окна, свет, кромки) появляется только после него. Чтобы не гадать — дождитесь готовности:
256
359
 
257
360
  ```ts
258
361
  const layer = await map.whenMaps3DReady();
@@ -260,7 +363,7 @@ const layer = await map.whenMaps3DReady();
260
363
  layer?.buildings?.setWindowStyle(4);
261
364
  ```
262
365
 
263
- Промис резолвится в `undefined`, если движок не `"3d"`, слой выключен (`enable3D: false`) или attach упал — ошибка при этом уходит в `console.error`, а карта остаётся живой со штатными зданиями стиля.
366
+ Промис резолвится в `undefined`, если `Maps3D` не передан, слой выключен (`enable3D: false`) или подключение упало — ошибка при этом уходит в `console.error`, а карта остаётся живой на штатных зданиях стиля.
264
367
 
265
368
  ### Подключение и выключение 3D-слоя: полный пример (Vue 3)
266
369
 
@@ -300,13 +403,12 @@ onMounted(async () => {
300
403
  const map = MahalMap.create(
301
404
  {
302
405
  container: "map",
303
- engine: "3d", // платформа GramMaps вместо legacy-стилей
304
- theme: "dark", // preset standard-night; "light" → road-urban-lab-v2
406
+ theme: "dark", // тема dark; "light" → тема light
305
407
  center: [68.787, 38.573],
306
408
  zoom: 16.6,
307
- pitch: 58, // без наклона экструзия не видна
409
+ pitch: 58, // без наклона объём не виден
308
410
  bearing: -20,
309
- enable3D: true, // значение по умолчанию для engine: "3d"
411
+ enable3D: true, // значение по умолчанию, когда Maps3D передан
310
412
  maps3d: { minZoom: 16, lodBias: 0 },
311
413
  },
312
414
  maplibregl,
@@ -378,14 +480,15 @@ onBeforeUnmount(() => {
378
480
 
379
481
  Что библиотека делает за вас против ручного подключения `@grammaps/maps3d-web`:
380
482
 
381
- | Ручной код | Через `mahal_map` |
382
- | ----------------------------------------------- | ----------------------------------------------------------------- |
383
- | `style: base + "/maps/standard-night.json"` | `engine: "3d"` + `theme` (или `preset` / `base` явно) |
384
- | `transformRequest: Maps3D.transformRequest(..)` | ставится автоматически без Maps3D — своим фолбэком с `?key=`) |
385
- | `new Maps3D({...}); await layer.attach(map)` | `enable3D: true` + `maps3d: {...}`, `await map.whenMaps3DReady()` |
386
- | `setBuildingsEnabled` + `buildings.setWindows` | `map.toggle3DBuildings(enabled)` — оба вызова разом |
387
- | Плоские здания стиля поверх 3D после `setStyle` | скрываются сами на каждой загрузке стиля |
388
- | `layer.destroy(); map.remove()` | `map.destroy()` |
483
+ | Ручной код | Через `mahal_map` |
484
+ | --------------------------------------------------- | -------------------------------------------------------------- |
485
+ | `...Maps3D.mapOptions({ base, apiKey, style })` | `theme` + `family` (или `preset` / `style` / `base` явно) |
486
+ | `antialias: true` не забыть | ставится сам при включённом 3D |
487
+ | `Maps3D.enhance(map, opts); await maps3d.ready` | `enable3D: true` + `maps3d: {...}`, `await map.whenMaps3DReady()` |
488
+ | `map.setStyle(Maps3D.styleUrl("dark", base))` | `map.setStyle("dark")` — внутри выбранного семейства |
489
+ | `maps3d.refreshLayers()` после смены стиля | вызывается сам на `style.load` |
490
+ | `maps3d.destroy(); map.remove()` | `map.destroy()` |
491
+ | ключ руками в каждый вызов | один `keyUtils.saveKey()` на всё |
389
492
 
390
493
  #### То же самое без сборщика (browser SDK)
391
494
 
@@ -401,7 +504,6 @@ onBeforeUnmount(() => {
401
504
  // Maps3D берётся из window.Maps3D — третий аргумент передавать не нужно.
402
505
  const map = MahalMap.create({
403
506
  container: "map",
404
- engine: "3d",
405
507
  theme: "dark",
406
508
  center: [68.787, 38.573],
407
509
  zoom: 16.6,
@@ -426,30 +528,39 @@ onBeforeUnmount(() => {
426
528
 
427
529
  ### Пробки
428
530
 
531
+ Через реестр слоёв:
532
+
429
533
  ```ts
430
- const layer = map.getMaps3DLayer();
534
+ map.setLayer("traffic", true);
535
+ map.setLayer("trafficRaster", true); // растровый вариант, без клика по дороге
536
+ ```
537
+
538
+ Тонкие настройки — через сам слой:
431
539
 
432
- layer?.setTraffic(true);
433
- layer?.setTrafficOpacity(0.85);
434
- layer?.setTrafficClicks(true); // попап скорости по клику
435
- layer?.refreshTraffic();
540
+ ```ts
541
+ const layer = map.getMaps3DLayer();
436
542
 
437
- // растровый вариант (картинка с сервера, без клика по дороге)
438
- layer?.setTrafficRaster(true);
543
+ layer?.setTrafficOpacity?.(0.85);
544
+ layer?.setTrafficClicks?.(true); // попап скорости по клику
545
+ layer?.setTrafficGraph?.("yandex"); // osm | yandex | gis2 | mahal
546
+ layer?.refreshTraffic?.();
439
547
  ```
440
548
 
549
+ Либо сразу при создании карты: `maps3d: { traffic: { raster: true, opacity: 0.85 } }`.
550
+
441
551
  ### Жизненный цикл слоя
442
552
 
443
553
  ```ts
444
554
  const layer = map.getMaps3DLayer();
445
555
 
446
- layer?.setMinZoom(15);
447
- layer?.setObjectsLight({ sun: 1.8, ambient: 0.45, sky: 1.1, exposure: 1.15 });
448
- await layer?.refresh(); // перечитать модели после правок
449
- await layer?.clearCache(); // сбросить IndexedDB-кеш ассетов
556
+ layer?.setMinZoom?.(15);
557
+ layer?.setObjectsLight?.({ sun: 1.8, ambient: 0.45, sky: 1.1, exposure: 1.15 });
558
+ await layer?.refresh?.(); // перечитать объекты в кадре
559
+ await layer?.clearCache?.(); // сбросить IndexedDB-кеш моделей
560
+ layer?.diagnostics?.(); // рельеф, потеря контекста WebGL, счётчики зданий
450
561
  ```
451
562
 
452
- Для полной остановки карты используйте только `map.destroy()` / `MahalMap.destroy(map)` — они сами вызывают `destroy()`/`remove()` у Maps3D слоя. **Не вызывайте `layer.destroy()`/`layer.remove()` напрямую**: `MahalMap` не узнает об этом и продолжит считать слой активным (внутренний `maps3dLayer`, `buildingsEnabled`, видимость штатных зданий стиля разойдутся с реальностью). Нужно временно выключить только 3D-здания — используйте `map.toggle3DBuildings(false)` (см. выше).
563
+ Для полной остановки карты используйте только `map.destroy()` / `MahalMap.destroy(map)` — они сами вызывают `destroy()`/`remove()` у Maps3D слоя. **Не вызывайте `layer.destroy()`/`layer.remove()` напрямую**: `MahalMap` не узнает об этом и продолжит считать слой активным (внутренний `maps3dLayer` и `buildingsEnabled` разойдутся с реальностью). Нужно временно выключить только 3D-здания — используйте `map.toggle3DBuildings(false)` (см. выше).
453
564
 
454
565
  ## MahalMap
455
566
 
@@ -474,6 +585,114 @@ const map = MahalMap.create(
474
585
 
475
586
  В NPM-версии второй аргумент `maplibreObject` рекомендуется передавать явно. В browser SDK он берется из `window.maplibregl`.
476
587
 
588
+ ### `MahalMap.createAsync(options, maplibreObject?, maps3dCtor?)`
589
+
590
+ Асинхронный вариант `create()`. Перед созданием карты проверяет подписку JSApi по map token и, если подписки нет, карту не создаёт вообще: MapLibre-инстанс не строится, промис отклоняется с ошибкой.
591
+
592
+ ```ts
593
+ try {
594
+ const map = await MahalMap.createAsync(
595
+ {
596
+ container: "map",
597
+ center: [69.624024, 40.279687],
598
+ zoom: 12,
599
+ theme: "light",
600
+ },
601
+ maplibregl,
602
+ );
603
+ } catch (error) {
604
+ // подписки нет — показать своё сообщение вместо карты
605
+ console.error(error);
606
+ }
607
+ ```
608
+
609
+ Правила проверки:
610
+
611
+ | Условие | Поведение |
612
+ | ------- | --------- |
613
+ | `Maps3D` не передан, сервис ответил `success: true` | Карта создаётся на запасном стиле. |
614
+ | `Maps3D` не передан, сервис ответил `success: false` | Карта **не** создаётся, промис отклоняется: `[MahalMap SDK] JSApi subscription is not active for this key: <message>`. |
615
+ | `Maps3D` не передан, проверка не дошла (сеть, CORS, таймаут) | Fail-open: `console.warn` и карта создаётся. Падение сервиса проверки не гасит карты. |
616
+ | `Maps3D` передан | Проверка **пропускается**, запрос не отправляется. |
617
+ | Map token не сохранён | Проверка пропускается, дальше срабатывает обычная ошибка про `apikey`. |
618
+
619
+ С переданным `Maps3D` вызов `createAsync()` ведёт себя ровно как `create()` — доступ к платформе контролируется параметром `?key=` на её стороне, отдельная подписка JSApi к ней отношения не имеет.
620
+
621
+ Синхронный `MahalMap.create()` проверку не выполняет и работает как раньше.
622
+
623
+ ### Vue / Nuxt (ClientOnly, container как ref элемента)
624
+
625
+ `container` принимает и `id` строкой, и сам DOM-элемент. Ниже рабочий вариант с проверкой подписки: карта строится только после `createAsync()`, поэтому при отсутствии подписки в контейнере не останется пустой карты.
626
+
627
+ ```vue
628
+ <template>
629
+ <ClientOnly>
630
+ <div class="overflow-hidden rounded-2xl border">
631
+ <div ref="mapElement" class="h-[360px] w-full" />
632
+ </div>
633
+ <template #fallback>
634
+ <div class="flex h-[360px] items-center justify-center">{{ loadingLabel }}</div>
635
+ </template>
636
+ </ClientOnly>
637
+ </template>
638
+
639
+ <script setup lang="ts">
640
+ import maplibregl from "maplibre-gl";
641
+ import "maplibre-gl/dist/maplibre-gl.css";
642
+ import type { MahalMap as MahalMapInstance } from "mahal_map";
643
+ import { onBeforeUnmount, onMounted, ref } from "vue";
644
+
645
+ const DUSHANBE_CENTER: [number, number] = [68.759965, 38.572419];
646
+
647
+ const mapElement = ref<HTMLElement | null>(null);
648
+ let map: MahalMapInstance | null = null;
649
+ // onMounted асинхронный: компонент может размонтироваться, пока идёт проверка подписки.
650
+ // Без флага карта создастся уже после unmount и останется висеть в памяти.
651
+ let disposed = false;
652
+
653
+ onMounted(async () => {
654
+ const { MahalMap, keyUtils } = await import("mahal_map");
655
+
656
+ keyUtils.saveKey(import.meta.env.VITE_MAHAL_API_KEY_TILE);
657
+
658
+ try {
659
+ const instance = await MahalMap.createAsync(
660
+ {
661
+ container: mapElement.value,
662
+ center: DUSHANBE_CENTER,
663
+ zoom: 11,
664
+ },
665
+ maplibregl,
666
+ );
667
+
668
+ if (disposed) {
669
+ instance.destroy();
670
+ return;
671
+ }
672
+
673
+ map = instance;
674
+ } catch (error) {
675
+ // подписки JSApi нет — показать своё сообщение вместо карты
676
+ console.error(error);
677
+ }
678
+ });
679
+
680
+ onBeforeUnmount(() => {
681
+ disposed = true;
682
+ map?.destroy();
683
+ map = null;
684
+ });
685
+ </script>
686
+ ```
687
+
688
+ Замечания по этому паттерну:
689
+
690
+ - Импорт `mahal_map` внутри `onMounted` обязателен в SSR-окружении: пакет работает с `window`/`document`.
691
+ - `ClientOnly` (Nuxt) или эквивалент нужен по той же причине.
692
+ - Для карты с `container` в виде элемента инстанс регистрируется под ключом по умолчанию `"map"`. Для нескольких карт на странице передавайте `container` строкой с разными `id`, иначе `getInstance()` вернёт не тот инстанс.
693
+ - `map.destroy()` снимает карту, логотип и запись из реестра инстансов.
694
+ - Синхронный `MahalMap.create()` в этом же коде работает без изменений — если проверка подписки не нужна, замените `await MahalMap.createAsync(...)` на `MahalMap.create(...)`.
695
+
477
696
  ### `MahalMap.onReady(container, callback)`
478
697
 
479
698
  Вызывает `callback`, когда карта создана и MapLibre завершил загрузку.
@@ -557,27 +776,27 @@ camera.flyTo({
557
776
 
558
777
  ### `map.setStyle(theme)`
559
778
 
560
- Переключает стандартную тему карты.
779
+ Переключает светлую/тёмную тему внутри выбранного `family`.
561
780
 
562
781
  ```ts
563
- map.setStyle("dark");
564
- map.setStyle("light");
782
+ map.setStyle("dark"); // family: "navigator" → navigator-dark
783
+ map.setStyle("light"); // → navigator-light
565
784
  ```
566
785
 
567
- Если текущий язык `ru`, при переключении темы стиль будет загружен с `token=...&lang=ru`. Если язык `tj`, URL будет только с `token=...`.
786
+ Адрес стиля строит `Maps3D.styleUrl()`. После загрузки нового стиля библиотека сама зовёт `refreshLayers()` состояние слоёв переживает смену темы.
568
787
 
569
- Метод не меняет стиль, если карта создана с `autoAddVectorSource: true`. Если карта создана с пользовательским `style`, SDK не переписывает этот URL.
788
+ Метод ничего не делает, если карта создана с явным `style` или `preset` (стиль зафиксирован), либо если `Maps3D` не передан у запасного стиля вариантов по теме нет.
570
789
 
571
790
  ### `map.setLanguage(lang)`
572
791
 
573
- Переключает язык стандартного стиля карты.
792
+ Запоминает язык для поиска и роутинга.
574
793
 
575
794
  ```ts
576
795
  map.setLanguage("ru");
577
796
  map.setLanguage("tj");
578
797
  ```
579
798
 
580
- `ru` добавляет `lang=ru`, `tj` возвращает стандартный URL только с `token=...`. Метод не переписывает пользовательский `options.style` и не меняет vector style при `autoAddVectorSource: true`.
799
+ Стиль метод не трогает: подписи приходят из самого стиля платформы, отдельных URL по языкам больше нет.
581
800
 
582
801
  ### `map.setCenter(center)`
583
802
 
@@ -657,6 +876,23 @@ MahalMap.addMarker(map, marker);
657
876
  MahalMap.getMaps3DLayer(map);
658
877
  MahalMap.whenMaps3DReady(map);
659
878
  MahalMap.toggle3DBuildings(map, false);
879
+
880
+ // Реестр слоёв
881
+ MahalMap.setLayer(map, "terrain", true, { mode: "on" });
882
+ MahalMap.getLayerState(map, "terrain");
883
+ MahalMap.getLayers(map);
884
+ MahalMap.onLayers(map, (state) => console.log(state.id, state.active));
885
+ MahalMap.refreshLayers(map);
886
+
887
+ // Выделение зданий и клики
888
+ MahalMap.onBuildingClick(map, (building) => console.log(building?.id));
889
+ MahalMap.selectBuilding(map, 123456789);
890
+ MahalMap.selectedBuilding(map);
891
+ MahalMap.setSelectionStyle(map, { color: "#e23b2f" });
892
+ MahalMap.clearSelection(map);
893
+ MahalMap.onRoadClick(map, (road) => console.log(road?.name));
894
+ MahalMap.roadAt(map, { x: 320, y: 240 });
895
+
660
896
  MahalMap.destroy(map);
661
897
  ```
662
898
 
@@ -683,6 +919,7 @@ MahalMap.setZoom(map, 14);
683
919
  | Функция | Описание |
684
920
  | -------------------------------------- | ------------------------------------------------------------- |
685
921
  | `create(options)` | Создает карту. Требует `apikey` в URL SDK скрипта. |
922
+ | `createAsync(options)` | Создает карту после проверки подписки JSApi (только без Maps3D). |
686
923
  | `onReady(container, callback)` | Выполняет callback после загрузки карты. |
687
924
  | `getInstance(container)` | Возвращает инстанс карты. |
688
925
  | `hasInstance(container)` | Проверяет наличие инстанса. |
@@ -694,9 +931,21 @@ MahalMap.setZoom(map, 14);
694
931
  | `setCenter(instance, center)` | Меняет центр карты. |
695
932
  | `setZoom(instance, zoom)` | Меняет zoom карты. |
696
933
  | `addMarker(instance, marker)` | Добавляет маркер. |
697
- | `getMaps3DLayer(instance)` | Возвращает инстанс слоя Maps3D (только `engine: "3d"`). |
934
+ | `getMaps3DLayer(instance)` | Возвращает инстанс слоя Maps3D (`undefined`, если Maps3D не передан). |
698
935
  | `whenMaps3DReady(instance)` | Промис слоя Maps3D после `attach()` (готов `layer.buildings`). |
699
- | `toggle3DBuildings(instance, enabled)` | Вкл/выкл детальные 3D-здания на лету (только `engine: "3d"`). |
936
+ | `toggle3DBuildings(instance, enabled)` | Вкл/выкл объёмные здания на лету. |
937
+ | `setLayer(instance, id, on, params?)` | Включить/выключить слой платформы. |
938
+ | `getLayerState(instance, id)` | Снимок состояния одного слоя. |
939
+ | `getLayers(instance)` | Снимок всех слоёв. |
940
+ | `onLayers(instance, callback)` | Подписка на изменения слоёв; возвращает отписку. |
941
+ | `refreshLayers(instance)` | Пере-применить волю клиента ко всем слоям. |
942
+ | `onBuildingClick(instance, callback)` | Клик по зданию. |
943
+ | `selectBuilding(instance, id, style?)` | Выделить здание по `osm_id`. |
944
+ | `clearSelection(instance)` | Снять выделение. |
945
+ | `selectedBuilding(instance)` | Идентификатор выделенного здания или `null`. |
946
+ | `setSelectionStyle(instance, style)` | Облик выделения. |
947
+ | `onRoadClick(instance, callback)` | Клик по дороге. |
948
+ | `roadAt(instance, point, tolPx?)` | Дорога под точкой холста. |
700
949
  | `destroy(instance)` | Полностью удаляет карту. |
701
950
  | `loadKeyFromScriptUrl()` | Читает `apikey` из URL SDK скрипта. |
702
951
  | `loadLanguageFromScriptUrl()` | Читает `lang` из URL SDK скрипта. |
@@ -838,7 +1087,9 @@ const map = MahalMap.create(
838
1087
  );
839
1088
  ```
840
1089
 
841
- Когда передан `style`, SDK не добавляет `token` или `lang=ru` и не подменяет URL при `setStyle()` или `setLanguage()`.
1090
+ Стиль при этом считается зафиксированным: `setStyle()` и `setLanguage()` его не подменяют.
1091
+
1092
+ С переданным `Maps3D` URL всё равно проходит через `Maps3D.mapOptions()`, поэтому `transformRequest` с ключом на месте — тайлы и шрифты платформы внутри своего стиля продолжают работать.
842
1093
 
843
1094
  ## Жизненный цикл
844
1095
 
@@ -988,6 +1239,104 @@ interface MeasureShape {
988
1239
  }
989
1240
  ```
990
1241
 
1242
+ ## Сервисы поиска и маршрутов
1243
+
1244
+ Сервисы работают независимо от карты: их можно вызывать без `MahalMap.create()`. Токен передаётся аргументом в каждый вызов — сохранённый через `keyUtils.saveKey()` map token для них не используется.
1245
+
1246
+ ```ts
1247
+ import { Search, SearchPoi, SearchByLocation, CheckJSApi, Router } from "mahal_map";
1248
+ ```
1249
+
1250
+ ### `Search(text, token, additionalParam?)`
1251
+
1252
+ Поиск адресов (геокодер). Вызовы дебаунсятся на 500 мс: при вводе по символу уходит один запрос.
1253
+
1254
+ ```ts
1255
+ const results = await Search("Рудаки 33", token, {
1256
+ lat: "38.5598",
1257
+ lng: "68.7870",
1258
+ limit: 10,
1259
+ });
1260
+ ```
1261
+
1262
+ | Параметр | Тип | Описание |
1263
+ | -------- | --- | -------- |
1264
+ | `text` | `string` | Строка поиска. |
1265
+ | `token` | `string` | Токен сервиса. Обязателен, иначе `[MahalMap SDK] Search token is required`. |
1266
+ | `additionalParam.lat` / `.lng` | `string` | Точка для сортировки результатов по удалённости. |
1267
+ | `additionalParam.limit` | `number` | Максимум результатов. |
1268
+ | `additionalParam.type` | `string` | Фильтр по типу объекта. |
1269
+
1270
+ Возвращает `ISearchResponse[]`.
1271
+
1272
+ ### `SearchPoi(text, token, additionalParam?)`
1273
+
1274
+ Поиск POI (организации, объекты). Сигнатура и дебаунс те же, что у `Search`, таймер отдельный — параллельный ввод в двух полях не перебивает запросы друг друга.
1275
+
1276
+ ```ts
1277
+ const places = await SearchPoi("кафе", token, { lat: "38.5598", lng: "68.7870", limit: 20 });
1278
+ ```
1279
+
1280
+ Возвращает `ISearchResponse[]`.
1281
+
1282
+ ### `SearchByLocation(params)`
1283
+
1284
+ Обратный геокодинг: адреса и POI по координатам. Без дебаунса.
1285
+
1286
+ ```ts
1287
+ const res = await SearchByLocation({
1288
+ lat: 38.5598,
1289
+ lng: 68.787,
1290
+ token,
1291
+ });
1292
+ ```
1293
+
1294
+ | Поле | Тип | Обязательное |
1295
+ | ---- | --- | ------------ |
1296
+ | `lat` | `string \| number` | да |
1297
+ | `lng` | `string \| number` | да |
1298
+ | `token` | `string` | да |
1299
+ | `type` | `string` | нет |
1300
+
1301
+ ### `CheckJSApi(token)`
1302
+
1303
+ Проверяет, активна ли подписка JSApi у токена.
1304
+
1305
+ ```ts
1306
+ const { success, message } = await CheckJSApi(token);
1307
+
1308
+ if (!success) {
1309
+ console.warn("Подписка не активна:", message);
1310
+ }
1311
+ ```
1312
+
1313
+ Промис резолвится и при отрицательном ответе — `success: false` это результат проверки, а не сбой. Исключение бросается только если вызов не дошёл до сервиса (сеть, CORS, таймаут) или токен пустой.
1314
+
1315
+ Этот же вызов используется внутри [`MahalMap.createAsync()`](#mahalmapcreateasyncoptions-maplibreobject-maps3dctor), когда `Maps3D` не передан и карта поднимается на запасном стиле.
1316
+
1317
+ ### `Router(points, typeData, token)`
1318
+
1319
+ Маршрут между точками.
1320
+
1321
+ ```ts
1322
+ const routes = await Router(
1323
+ [
1324
+ [68.787, 38.5598],
1325
+ [68.809, 38.561],
1326
+ ],
1327
+ "geojson",
1328
+ token,
1329
+ );
1330
+ ```
1331
+
1332
+ | Параметр | Тип | Описание |
1333
+ | -------- | --- | -------- |
1334
+ | `points` | `number[][]` | Точки в формате `[lng, lat]`. |
1335
+ | `typeData` | `string` | `"geojson"` — декодирует polyline в массив координат. Другое значение оставляет `geometry` строкой polyline. |
1336
+ | `token` | `string` | Токен сервиса. |
1337
+
1338
+ Возвращает `IRoute[]`.
1339
+
991
1340
  ## License
992
1341
 
993
1342
  ISC