mahal_map 2.2.2 → 2.3.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 +1808 -1754
- package/dist/index.d.mts +139 -50
- package/dist/index.d.ts +139 -50
- package/dist/index.js +1 -1
- package/dist/index.mjs +5 -5
- package/dist/mahal_map.sdk.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,1754 +1,1808 @@
|
|
|
1
|
-
# Mahal Map
|
|
2
|
-
|
|
3
|
-
Mahal Map - JavaScript/TypeScript SDK для работы с картой Mahal поверх MapLibre GL JS.
|
|
4
|
-
|
|
5
|
-
Документация ниже описывает только открытые функции карты: создание карты, управление инстансами, стили, язык, камера, маркеры и browser SDK.
|
|
6
|
-
|
|
7
|
-
## Установка
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
npm install mahal_map maplibre-gl @grammaps/maps3d-web
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Обе зависимости — `peerDependencies`, в бандл `mahal_map` они не входят. Библиотека их не импортирует: MapLibre и `Maps3D` приходят снаружи, аргументами `create()` либо через `window`.
|
|
14
|
-
|
|
15
|
-
- `maplibre-gl` (`^6.10.0`) — обязателен всегда.
|
|
16
|
-
- `@grammaps/maps3d-web` (>=0.
|
|
17
|
-
|
|
18
|
-
MapLibre можно не класть в свою сборку вовсе — платформа отдаёт согласованную версию вместе с веб-воркером:
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
const maplibregl = await Maps3D.maplibre({ base: "https://navi.gram.tj" });
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Веб-воркер MapLibre
|
|
25
|
-
|
|
26
|
-
MapLibre разбирает векторные тайлы в веб-воркере и ищет его файл рядом со своим модулем. Сборщик складывает библиотеку в общий бандл, рядом файла не оказывается, и вместо скрипта сервер отдаёт `index.html`.
|
|
27
|
-
|
|
28
|
-
Симптом обманчивый: **карта показывает пустой фон, ошибок про карту нет**. В консоли лежит только `Uncaught SyntaxError: Unexpected token '<'` — ни слова ни про MapLibre, ни про воркер, ни про тайлы.
|
|
29
|
-
|
|
30
|
-
`mahal_map` это распознаёт сам. Если через 8 с после создания карты не разобран ни один векторный тайл, в консоль уходит предупреждение с причиной и обоими решениями:
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
[MahalMap SDK] Карта пуста: ни один векторный тайл не разобран — похоже, не запустился веб-воркер MapLibre.
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Проверка молчит на растровых стилях (им воркер не нужен), в фоновой вкладке (там MapLibre приостановлен) и когда фичи отрисованы. Отключается через `workerCheck: false`, своя задержка — `workerCheck: 12000`.
|
|
37
|
-
|
|
38
|
-
### Решение 1 — взять библиотеку у платформы
|
|
39
|
-
|
|
40
|
-
Версия согласована с Maps3D, веб-воркер приезжает вместе с ней, настраивать нечего:
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
const maplibregl = await Maps3D.maplibre({ base: "https://navi.gram.tj" });
|
|
44
|
-
|
|
45
|
-
const map = MahalMap.create({ container: "map" }, maplibregl, Maps3D);
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Решение 2 — оставить свою сборку
|
|
49
|
-
|
|
50
|
-
Тогда адрес воркера нужно свести с реальным файлом — и в разработке, и в собранной версии:
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
// vite.config.ts — чтобы адрес воркера совпал с файлом во время разработки
|
|
54
|
-
export default defineConfig({ optimizeDeps: { exclude: ["maplibre-gl"] } });
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
// в приложении — чтобы адрес совпал и в собранной версии
|
|
59
|
-
import * as maplibregl from "maplibre-gl";
|
|
60
|
-
import "maplibre-gl/dist/maplibre-gl.css";
|
|
61
|
-
|
|
62
|
-
maplibregl.setWorkerUrl("/maplibre-gl-worker.mjs"); // файл скопирован в public/ из maplibre-gl/dist
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
> `import * as maplibregl` — не случайность: в MapLibre 6 default-экспорта нет, `import maplibregl from "maplibre-gl"` даёт `undefined`.
|
|
66
|
-
|
|
67
|
-
## Миграция с 1.x на 2.0
|
|
68
|
-
|
|
69
|
-
Собственных URL стилей у `mahal_map` больше нет — их целиком отдаёт `@grammaps/maps3d-web`.
|
|
70
|
-
|
|
71
|
-
| 1.x | 2.0 |
|
|
72
|
-
| ----------------------------------------- | ------------------------------------------------------------------- |
|
|
73
|
-
| `engine: "legacy"` (по умолчанию) | Удалён. Единственный путь — платформа через `Maps3D`. |
|
|
74
|
-
| `engine: "3d"` | Больше не нужен, опция игнорируется. |
|
|
75
|
-
| `autoAddVectorSource: true` | Удалён. Тот же векторный стиль применяется сам, когда нет `Maps3D`. |
|
|
76
|
-
| `preset: "standard-night"` | `theme: "dark"`, либо полный URL в `style`. |
|
|
77
|
-
| `preset: "road-urban-lab-v2"` | `theme: "light"`, либо полный URL в `style`. |
|
|
78
|
-
| `lang` менял URL стиля | Стиль не трогает — язык подписей приходит из самого стиля. |
|
|
79
|
-
| `layer.setBuildingsEnabled(...)` напрямую | `map.toggle3DBuildings(...)` или `map.setLayer("buildings", ...)`. |
|
|
80
|
-
|
|
81
|
-
`engine` и `autoAddVectorSource` оставлены в типах как `@deprecated`, чтобы не ломать компиляцию, но на поведение не влияют.
|
|
82
|
-
|
|
83
|
-
Что появилось: реестр слоёв (`setLayer`/`getLayers`/`onLayers`), выделение зданий, клик по дорогам, рельеф, планы этажей, перекрытия, семейства стилей `navigator`/`mobile` и автоматический `antialias`.
|
|
84
|
-
|
|
85
|
-
Минимальный диф:
|
|
86
|
-
|
|
87
|
-
```diff
|
|
88
|
-
const map = MahalMap.create(
|
|
89
|
-
{
|
|
90
|
-
container: "map",
|
|
91
|
-
- engine: "3d",
|
|
92
|
-
theme: "dark",
|
|
93
|
-
- preset: "standard-night",
|
|
94
|
-
enable3D: true,
|
|
95
|
-
},
|
|
96
|
-
maplibregl,
|
|
97
|
-
Maps3D,
|
|
98
|
-
);
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Быстрый старт через NPM
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
import * as maplibregl from "maplibre-gl";
|
|
105
|
-
import "maplibre-gl/dist/maplibre-gl.css";
|
|
106
|
-
import { Maps3D } from "@grammaps/maps3d-web";
|
|
107
|
-
import { MahalMap, keyUtils } from "mahal_map";
|
|
108
|
-
|
|
109
|
-
keyUtils.saveKey("YOUR_MAP_API_KEY");
|
|
110
|
-
|
|
111
|
-
const map = MahalMap.create(
|
|
112
|
-
{
|
|
113
|
-
container: "map",
|
|
114
|
-
center: [68.787, 38.573],
|
|
115
|
-
zoom: 16.6,
|
|
116
|
-
theme: "light",
|
|
117
|
-
},
|
|
118
|
-
maplibregl,
|
|
119
|
-
Maps3D,
|
|
120
|
-
);
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Контейнер должен существовать в HTML:
|
|
124
|
-
|
|
125
|
-
```html
|
|
126
|
-
<div id="map" style="width: 100%; height: 500px"></div>
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
`Maps3D` — третий, необязательный аргумент: не передан — SDK возьмёт его из `window.Maps3D`. Стиль, `transformRequest` с ключом, сглаживание и подключение 3D библиотека делает сама.
|
|
130
|
-
|
|
131
|
-
## Быстрый старт через Browser SDK
|
|
132
|
-
|
|
133
|
-
Для browser SDK параметр `apikey` обязателен: без него карта не инициализируется.
|
|
134
|
-
|
|
135
|
-
**MapLibre 6 поставляется только как ESM** — классической сборки `dist/maplibre-gl.js` для `<script src>` в ней больше нет. Поэтому библиотеку карты подключают модульным скриптом и передают в `create()` вторым аргументом:
|
|
136
|
-
|
|
137
|
-
```html
|
|
138
|
-
<link
|
|
139
|
-
rel="stylesheet"
|
|
140
|
-
href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.10.0/dist/maplibre-gl.css"
|
|
141
|
-
/>
|
|
142
|
-
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY"></script>
|
|
143
|
-
|
|
144
|
-
<div id="map" style="width: 100%; height: 500px"></div>
|
|
145
|
-
|
|
146
|
-
<script type="module">
|
|
147
|
-
import * as maplibregl from "https://cdn.jsdelivr.net/npm/maplibre-gl@6.10.0/+esm";
|
|
148
|
-
|
|
149
|
-
const map = MahalMap.create(
|
|
150
|
-
{
|
|
151
|
-
container: "map",
|
|
152
|
-
center: [69.624024, 40.279687],
|
|
153
|
-
zoom: 12,
|
|
154
|
-
},
|
|
155
|
-
maplibregl,
|
|
156
|
-
);
|
|
157
|
-
</script>
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Через NPM язык можно передать при создании карты:
|
|
161
|
-
|
|
162
|
-
```ts
|
|
163
|
-
const map = MahalMap.create(
|
|
164
|
-
{
|
|
165
|
-
container: "map",
|
|
166
|
-
lang: "ru",
|
|
167
|
-
theme: "dark",
|
|
168
|
-
},
|
|
169
|
-
maplibregl,
|
|
170
|
-
);
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
Через browser SDK язык можно передать в URL скрипта:
|
|
174
|
-
|
|
175
|
-
```html
|
|
176
|
-
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY&lang=ru"></script>
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
`lang` влияет на поиск и роутинг, но не на стиль: подписи на карте приходят из самого стиля платформы.
|
|
180
|
-
|
|
181
|
-
## Параметры создания карты
|
|
182
|
-
|
|
183
|
-
`MahalMap.create(options, maplibreObject?, maps3dCtor?)`
|
|
184
|
-
|
|
185
|
-
`maps3dCtor` — импортированный конструктор `Maps3D` (третий, необязательный аргумент). Если не передан, SDK ищет его в `window.Maps3D`.
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
import type { IMaps3DLayerOptions, Maps3DThemeName } from "mahal_map";
|
|
189
|
-
|
|
190
|
-
interface IMahalMapOptions {
|
|
191
|
-
container?: string | HTMLElement;
|
|
192
|
-
style?: string;
|
|
193
|
-
theme?: "dark" | "light";
|
|
194
|
-
lang?: "tj" | "ru";
|
|
195
|
-
center?: [number, number];
|
|
196
|
-
zoom?: number;
|
|
197
|
-
pitch?: number;
|
|
198
|
-
bearing?: number;
|
|
199
|
-
enable3D?: boolean;
|
|
200
|
-
base?: string;
|
|
201
|
-
family?: "default" | "navigator" | "mobile";
|
|
202
|
-
preset?: Maps3DThemeName | string;
|
|
203
|
-
antialias?: boolean;
|
|
204
|
-
workerCheck?: boolean | number;
|
|
205
|
-
maps3d?: Omit<IMaps3DLayerOptions, "apiKey" | "base">;
|
|
206
|
-
/** @deprecated Оставлена для совместимости, на поведение не влияет. */
|
|
207
|
-
engine?: "legacy" | "3d";
|
|
208
|
-
/** @deprecated Оставлена для совместимости, на поведение не влияет. */
|
|
209
|
-
autoAddVectorSource?: boolean;
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
| Параметр | Тип | Описание |
|
|
214
|
-
| ------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
215
|
-
| `container` | `string \| HTMLElement` | ID контейнера или DOM-элемент. Не передан — берётся `"map"`. |
|
|
216
|
-
| `style` | `string` | Полный URL своего стиля. Задан — стиль зафиксирован, `setStyle()` его не меняет. |
|
|
217
|
-
| `theme` | `"dark" \| "light"` | Светлая/тёмная внутри выбранного `family`. По умолчанию `light`. |
|
|
218
|
-
| `lang` | `"tj" \| "ru"` | Язык для поиска и роутинга. На стиль не влияет — подписи приходят из самого стиля платформы. |
|
|
219
|
-
| `center` | `[number, number]` | Центр карты в формате `[lng, lat]`. |
|
|
220
|
-
| `zoom` | `number` | Начальный zoom. |
|
|
221
|
-
| `pitch` | `number` | Начальный наклон камеры. Не задан и 3D включено — авто `58`: при `pitch: 0` объём зданий не виден, камера смотрит строго сверху. |
|
|
222
|
-
| `bearing` | `number` | Начальный поворот камеры. |
|
|
223
|
-
| `enable3D` | `boolean` | Подключает Maps3D. По умолчанию `true`, когда `Maps3D` доступен. |
|
|
224
|
-
| `base` | `string` | Домен платформы. По умолчанию `https://navi.gram.tj`. |
|
|
225
|
-
| `family` | `"default" \| "navigator" \| "mobile"` | Семейство стилей. `theme` выбирает внутри него: `default` → `light`/`dark`, `navigator` → `navigator-light`/`navigator-dark`, `mobile` → `mobile-*`. |
|
|
226
|
-
| `preset` | `Maps3DThemeName \| string` | Явное имя темы платформы или полный URL стиля вместо пары `family` + `theme`. Задан — стиль зафиксирован. |
|
|
227
|
-
| `antialias` | `boolean` | Сглаживание сцены. По умолчанию `true` при включённом 3D: без него тонкая геометрия (перила, мачты, ряды сидений) на отдалении рассыпается в рябь. |
|
|
228
|
-
| `workerCheck` | `boolean \| number` | Диагностика неработающего веб-воркера MapLibre через 8 с после создания карты. `false` отключает, число задаёт свою задержку в мс. См. [Веб-воркер MapLibre](#веб-воркер-maplibre). |
|
|
229
|
-
| `maps3d` | `object` | Опции Maps3D: `buildings`, `traffic`, `indoor`, `closures`, `places`, `minZoom`, `lodBias`, `memoryBudget`, `maskReplaced`, `typeReplacements`. |
|
|
230
|
-
|
|
231
|
-
### Стили и темы
|
|
232
|
-
|
|
233
|
-
Стили целиком приходят из `@grammaps/maps3d-web` — своего списка URL у `mahal_map` больше нет. Словарь тем один и тот же у SDK и у библиотеки:
|
|
234
|
-
|
|
235
|
-
| `family` | `theme: "light"` | `theme: "dark"` |
|
|
236
|
-
| ------------- | ----------------- | ---------------- |
|
|
237
|
-
| `"default"` | `light` | `dark` |
|
|
238
|
-
| `"navigator"` | `navigator-light` | `navigator-dark` |
|
|
239
|
-
| `"mobile"` | `mobile-light` | `mobile-dark` |
|
|
240
|
-
|
|
241
|
-
Имя темы уходит в `Maps3D.styleUrl()`, адрес строит сам SDK. Неизвестное имя — явная ошибка с префиксом `[MahalMap SDK]`, а не пустая карта.
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
// Навигаторная тёмная тема
|
|
245
|
-
MahalMap.create(
|
|
246
|
-
{ container: "map", family: "navigator", theme: "dark" },
|
|
247
|
-
maplibregl,
|
|
248
|
-
Maps3D,
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
// Смена темы внутри того же семейства
|
|
252
|
-
map.setStyle("light"); // → navigator-light
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
> **Миграция с 1.x.** Имена пресетов прежнего поколения (`road-urban-lab-v2`, `standard-night`) больше не подставляются по умолчанию — словарь тем теперь один, платформенный. Если старый стиль всё ещё нужен, передайте его полным URL:
|
|
256
|
-
>
|
|
257
|
-
> ```ts
|
|
258
|
-
> MahalMap.create(
|
|
259
|
-
> {
|
|
260
|
-
> container: "map",
|
|
261
|
-
> style: "https://navi.gram.tj/maps/standard-night.json",
|
|
262
|
-
> },
|
|
263
|
-
> maplibregl,
|
|
264
|
-
> Maps3D,
|
|
265
|
-
> );
|
|
266
|
-
> ```
|
|
267
|
-
|
|
268
|
-
### Без `@grammaps/maps3d-web`
|
|
269
|
-
|
|
270
|
-
Библиотека не установлена и в `window.Maps3D` ничего нет — карта всё равно поднимется: используется запасной векторный стиль `mtile.gram.tj` с подписью `?token=`, в консоль уходит предупреждение. На такой карте нет объёма, объектов, пробок, рельефа и реестра слоёв; `setStyle()` и `setLanguage()` стиль не меняют, методы реестра возвращают пустые значения (`false`, `null`, `[]`).
|
|
271
|
-
|
|
272
|
-
Этот путь — единственный, где ещё проверяется подписка JSApi: `createAsync()` не создаст карту, если подписки нет. С переданным `Maps3D` проверка пропускается — доступ гейтит сама платформа по `?key=`.
|
|
273
|
-
|
|
274
|
-
### Опции `maps3d` (расширенные)
|
|
275
|
-
|
|
276
|
-
Передаются в `MahalMap.create({ maps3d: {...} })` и уходят в `Maps3D` как есть:
|
|
277
|
-
|
|
278
|
-
| Опция | Тип | По умолч. | Описание |
|
|
279
|
-
| ------------------ | ------------------------------------------------------------------- | --------- | -------------------------------------------------------------------- |
|
|
280
|
-
| `buildings` | `boolean \| { detail?: footprint\|volume\|roofs\|facade }` | `true` | Объёмные здания; объектом — их облик. |
|
|
281
|
-
| `traffic` | `boolean \| { raster?, rasterMaxZoom?, graph?, opacity?, arrows? }` | `false` | Слой пробок. `raster: true` — картинкой вместо векторного слоя. |
|
|
282
|
-
| `indoor` | `boolean \| { level? }` | `false` | Планы этажей. |
|
|
283
|
-
| `closures` | `boolean \| object` | `false` | Перекрытия дорог. |
|
|
284
|
-
| `places` | `object` | — | Парковки, заправки, зарядки: `highlight`, `paid`, `free`, `unknown`, `fillOpacity` (0.10.0+). |
|
|
285
|
-
| `minZoom` | `number` | `16` | Зум появления объёма. |
|
|
286
|
-
| `lodBias` | `number` | `1` | `0` — всегда детальная геометрия, `1` — упрощённая вдали. |
|
|
287
|
-
| `memoryBudget` | `number` | `30` | Сколько 3D-моделей держать в памяти. |
|
|
288
|
-
| `maskReplaced` | `boolean` | `true` | Прятать заменённые OSM-объекты (`anchor=replace`). |
|
|
289
|
-
| `typeReplacements` | `boolean` | `true` | Рисовать замены по типу (`natural=tree` → 3D-дерево и т.п.). |
|
|
290
|
-
|
|
291
|
-
`apiKey` и `base` в `maps3d` передавать не нужно — их подставляет сам `MahalMap` из сохранённого ключа и `options.base`.
|
|
292
|
-
|
|
293
|
-
### Реестр слоёв
|
|
294
|
-
|
|
295
|
-
Единая дверь ко всем слоям платформы. Идентификаторы и параметры типизированы: опечатка в имени слоя или в значении параметра — ошибка компиляции, а не тихий `false` в рантайме.
|
|
296
|
-
|
|
297
|
-
```ts
|
|
298
|
-
map.setLayer("terrain", true, { mode: "on" });
|
|
299
|
-
map.setLayer("traffic", true);
|
|
300
|
-
map.setLayer("indoor", true, { level: 2 });
|
|
301
|
-
map.setLayer("buildings", true, { detail: "facade" });
|
|
302
|
-
|
|
303
|
-
map.getLayerState("terrain"); // снимок одного слоя или null
|
|
304
|
-
map.getLayers(); // снимок всех — по нему рисуется панель слоёв
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
| Слой | Параметры | По умолчанию | Откуда умолчание |
|
|
308
|
-
| ------------------ | ------------------------------------------ | ---------------------------------------------------------------------- | ----------------------- |
|
|
309
|
-
| `buildings` | `detail: footprint\|volume\|roofs\|facade` | включён | зашито |
|
|
310
|
-
| `objects` | — | включён | зашито |
|
|
311
|
-
| `terrain` | `mode: auto\|on\|off` | включён, `mode: "auto"` | зашито |
|
|
312
|
-
| `parking` | `highlight`, `paid`, `free`, `unknown` | включён, `{ highlight: false, paid: true, free: true, unknown: true }` | зашито |
|
|
313
|
-
| `fuel`, `charging` | — | включены | зашито |
|
|
314
|
-
| `traffic` | — | выключен | `maps3d.traffic` |
|
|
315
|
-
| `trafficRaster` | — | выключен | `maps3d.traffic.raster` |
|
|
316
|
-
| `indoor` | `level` | выключен | `maps3d.indoor` |
|
|
317
|
-
| `closures` | — | выключен | `maps3d.closures` |
|
|
318
|
-
|
|
319
|
-
У нижних четырёх умолчание не зашито — реестр берёт его из опций `maps3d`, переданных при создании карты. Поэтому «выключен» в таблице значит «выключен, пока вы не передали опцию», а не «выключен всегда».
|
|
320
|
-
|
|
321
|
-
Стартовое состояние можно задать сразу при создании карты — тогда первый кадр уже правильный, без моргания:
|
|
322
|
-
|
|
323
|
-
```ts
|
|
324
|
-
MahalMap.create(
|
|
325
|
-
{ container: "map", maps3d: { traffic: true, closures: true } },
|
|
326
|
-
maplibregl,
|
|
327
|
-
Maps3D,
|
|
328
|
-
);
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
Полный список — в разделе [Опции `maps3d`](#опции-maps3d-расширенные).
|
|
332
|
-
|
|
333
|
-
#### `wanted`, `available`, `active` — три разных вопроса
|
|
334
|
-
|
|
335
|
-
Поля независимы, и это главная ловушка реестра:
|
|
336
|
-
|
|
337
|
-
| Поле | Вопрос |
|
|
338
|
-
| ----------- | ------------------------------------------------- |
|
|
339
|
-
| `wanted` | чего хочет приложение |
|
|
340
|
-
| `available` | что позволяют стиль и данные |
|
|
341
|
-
| `active` | что нарисовано прямо сейчас (память SDK) |
|
|
342
|
-
| `rendered` | факт со стороны карты: слой есть в стиле и не погашен |
|
|
343
|
-
|
|
344
|
-
`rendered` появилось в Maps3D 0.8.0; у сборок постарше его нет (`undefined`). Расхождение `active !== rendered` означает, что внутренний признак SDK разошёлся с картой — это видно одной проверкой, без чтения минифицированной сборки. Сравнивать поля можно только когда `rendered` есть — иначе на старой сборке любой активный слой покажется расхождением:
|
|
345
|
-
|
|
346
|
-
```ts
|
|
347
|
-
const state = map.getLayerState("terrain");
|
|
348
|
-
|
|
349
|
-
// rendered === undefined — Maps3D < 0.8.0, проверка недоступна
|
|
350
|
-
if (state?.rendered !== undefined && state.active !== state.rendered) {
|
|
351
|
-
reportLayerMismatch(state);
|
|
352
|
-
}
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
Слой может быть включён и при этом не нарисован — и это не ошибка:
|
|
356
|
-
|
|
357
|
-
```ts
|
|
358
|
-
map.setLayer("terrain", true); // wanted: true
|
|
359
|
-
const state = map.getLayerState("terrain");
|
|
360
|
-
|
|
361
|
-
state?.available; // false — в стиле нет ключа maps3d:terrain
|
|
362
|
-
state?.active; // false — либо режим auto, а зум слишком близкий
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
Поэтому галочку в интерфейсе рисуют по `wanted`, а пометку «сейчас не видно» — по `active`. Проверять сразу после `setLayer()` бесполезно: тайлы ещё едут. Правильный способ — подписка.
|
|
366
|
-
|
|
367
|
-
#### Панель слоёв: подписка и отписка
|
|
368
|
-
|
|
369
|
-
`onLayers()` возвращает функцию отписки. Звать её обязательно — иначе колбэк переживёт компонент и будет дёргать размонтированное состояние:
|
|
370
|
-
|
|
371
|
-
```ts
|
|
372
|
-
const unsubscribe = map.onLayers((state) => {
|
|
373
|
-
console.log(state.id, state.wanted, state.available, state.active);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// при размонтировании компонента
|
|
377
|
-
unsubscribe();
|
|
378
|
-
```
|
|
379
|
-
|
|
380
|
-
`map.destroy()` снимает слой Maps3D целиком, так что после него подписка всё равно мертва — но до него отписываться нужно самим.
|
|
381
|
-
|
|
382
|
-
#### Парковки, заправки, зарядки
|
|
383
|
-
|
|
384
|
-
`Maps3D` рисует их сам, забирая эти классы у POI-слоёв стиля. Поэтому выключение слоя убирает объекты с карты полностью, а не оставляет значок стиля.
|
|
385
|
-
|
|
386
|
-
Подтип парковки берётся из атрибута `fee`:
|
|
387
|
-
|
|
388
|
-
| Параметр | Условие |
|
|
389
|
-
| --------- | ------------ |
|
|
390
|
-
| `paid` | `fee=yes` |
|
|
391
|
-
| `free` | `fee=no` |
|
|
392
|
-
| `unknown` | атрибута нет |
|
|
393
|
-
|
|
394
|
-
```ts
|
|
395
|
-
// только платные, с подсветкой
|
|
396
|
-
map.setLayer("parking", true, {
|
|
397
|
-
paid: true,
|
|
398
|
-
free: false,
|
|
399
|
-
unknown: false,
|
|
400
|
-
highlight: true,
|
|
401
|
-
});
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
Площадные парковки рисуются тремя слоями по одной геометрии: заливка в цвете подтипа, мягкая внутренняя кромка (с z14) и волосяной кант. Цвета: янтарь — платно, зелёный — бесплатно, сланцевый — неизвестно. Размеры заданы в экранных пикселях и от зума плитки не зависят.
|
|
405
|
-
|
|
406
|
-
Заливку включает только режим поиска — `highlight: true`. Без него подключение Maps3D облик карты не меняет. Постоянную плотность заливки вместо кривой по зуму задаёт `maps3d.places.fillOpacity`.
|
|
407
|
-
|
|
408
|
-
Наведение, клик и справка по участку — в разделе [Парковки: наведение, клик, справка](#парковки-наведение-клик-справка).
|
|
409
|
-
|
|
410
|
-
#### Рельеф
|
|
411
|
-
|
|
412
|
-
`auto` показывает рельеф на обзорных зумах, `on` — всегда. Режим `on` заметно дороже по трафику и времени кадра, чем `auto`.
|
|
413
|
-
|
|
414
|
-
```ts
|
|
415
|
-
map.setLayer("terrain", true, { mode: "auto" });
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
#### Планы этажей
|
|
419
|
-
|
|
420
|
-
Слой включается реестром, но одного этого мало — без переключения этажа он бесполезен:
|
|
421
|
-
|
|
422
|
-
```ts
|
|
423
|
-
map.setLayer("indoor", true);
|
|
424
|
-
|
|
425
|
-
const levels = map.getIndoorLevels(); // этажи в текущем виде карты, [] — данных нет
|
|
426
|
-
map.setIndoorLevel(1); // нумерация OSM: 0 — первый наземный
|
|
427
|
-
map.refreshIndoor(); // перечитать после правки картографом
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
```ts
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
b?.
|
|
467
|
-
b?.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
)
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
<
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
map.
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
layer?.
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
map
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
```
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
```
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
```ts
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
MahalMap.
|
|
1268
|
-
MahalMap.
|
|
1269
|
-
MahalMap.
|
|
1270
|
-
MahalMap.
|
|
1271
|
-
MahalMap.
|
|
1272
|
-
MahalMap.
|
|
1273
|
-
MahalMap.
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
MahalMap.
|
|
1278
|
-
MahalMap.
|
|
1279
|
-
MahalMap.
|
|
1280
|
-
MahalMap.
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
MahalMap.
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
|
1340
|
-
|
|
|
1341
|
-
| `
|
|
1342
|
-
| `
|
|
1343
|
-
| `
|
|
1344
|
-
| `
|
|
1345
|
-
| `
|
|
1346
|
-
| `
|
|
1347
|
-
| `
|
|
1348
|
-
| `
|
|
1349
|
-
| `
|
|
1350
|
-
| `
|
|
1351
|
-
| `
|
|
1352
|
-
| `
|
|
1353
|
-
| `
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
`
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
camera.
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
const
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
});
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
const measureTool = new MeasureTool(map, {
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
```ts
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
```ts
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
### `
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
```ts
|
|
1699
|
-
const
|
|
1700
|
-
lat: 38.5598,
|
|
1701
|
-
lng: 68.
|
|
1702
|
-
|
|
1703
|
-
});
|
|
1704
|
-
```
|
|
1705
|
-
|
|
1706
|
-
|
|
|
1707
|
-
|
|
|
1708
|
-
| `
|
|
1709
|
-
| `
|
|
1710
|
-
| `
|
|
1711
|
-
| `
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
|
1745
|
-
|
|
|
1746
|
-
| `
|
|
1747
|
-
| `
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1
|
+
# Mahal Map
|
|
2
|
+
|
|
3
|
+
Mahal Map - JavaScript/TypeScript SDK для работы с картой Mahal поверх MapLibre GL JS.
|
|
4
|
+
|
|
5
|
+
Документация ниже описывает только открытые функции карты: создание карты, управление инстансами, стили, язык, камера, маркеры и browser SDK.
|
|
6
|
+
|
|
7
|
+
## Установка
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install mahal_map maplibre-gl @grammaps/maps3d-web
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Обе зависимости — `peerDependencies`, в бандл `mahal_map` они не входят. Библиотека их не импортирует: MapLibre и `Maps3D` приходят снаружи, аргументами `create()` либо через `window`.
|
|
14
|
+
|
|
15
|
+
- `maplibre-gl` (`^6.10.0`) — обязателен всегда.
|
|
16
|
+
- `@grammaps/maps3d-web` (>=0.11.0 — в ней отписки у подписок, `selectedBuildingKind()`, этажи в состоянии слоя и починенный клик по зданию сверху; на сборках постарше карта работает через запасные пути, но отписка и `selectedBuildingKind` не гарантированы) — даёт стили, тайлы, объём, пробки, рельеф, планы этажей, клик по объектам. Помечен `optional`: без него карта поднимется на запасном векторном стиле, но 3D и слоёв платформы на ней не будет.
|
|
17
|
+
|
|
18
|
+
MapLibre можно не класть в свою сборку вовсе — платформа отдаёт согласованную версию вместе с веб-воркером:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const maplibregl = await Maps3D.maplibre({ base: "https://navi.gram.tj" });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Веб-воркер MapLibre
|
|
25
|
+
|
|
26
|
+
MapLibre разбирает векторные тайлы в веб-воркере и ищет его файл рядом со своим модулем. Сборщик складывает библиотеку в общий бандл, рядом файла не оказывается, и вместо скрипта сервер отдаёт `index.html`.
|
|
27
|
+
|
|
28
|
+
Симптом обманчивый: **карта показывает пустой фон, ошибок про карту нет**. В консоли лежит только `Uncaught SyntaxError: Unexpected token '<'` — ни слова ни про MapLibre, ни про воркер, ни про тайлы.
|
|
29
|
+
|
|
30
|
+
`mahal_map` это распознаёт сам. Если через 8 с после создания карты не разобран ни один векторный тайл, в консоль уходит предупреждение с причиной и обоими решениями:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
[MahalMap SDK] Карта пуста: ни один векторный тайл не разобран — похоже, не запустился веб-воркер MapLibre.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Проверка молчит на растровых стилях (им воркер не нужен), в фоновой вкладке (там MapLibre приостановлен) и когда фичи отрисованы. Отключается через `workerCheck: false`, своя задержка — `workerCheck: 12000`.
|
|
37
|
+
|
|
38
|
+
### Решение 1 — взять библиотеку у платформы
|
|
39
|
+
|
|
40
|
+
Версия согласована с Maps3D, веб-воркер приезжает вместе с ней, настраивать нечего:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const maplibregl = await Maps3D.maplibre({ base: "https://navi.gram.tj" });
|
|
44
|
+
|
|
45
|
+
const map = MahalMap.create({ container: "map" }, maplibregl, Maps3D);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Решение 2 — оставить свою сборку
|
|
49
|
+
|
|
50
|
+
Тогда адрес воркера нужно свести с реальным файлом — и в разработке, и в собранной версии:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// vite.config.ts — чтобы адрес воркера совпал с файлом во время разработки
|
|
54
|
+
export default defineConfig({ optimizeDeps: { exclude: ["maplibre-gl"] } });
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// в приложении — чтобы адрес совпал и в собранной версии
|
|
59
|
+
import * as maplibregl from "maplibre-gl";
|
|
60
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
61
|
+
|
|
62
|
+
maplibregl.setWorkerUrl("/maplibre-gl-worker.mjs"); // файл скопирован в public/ из maplibre-gl/dist
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> `import * as maplibregl` — не случайность: в MapLibre 6 default-экспорта нет, `import maplibregl from "maplibre-gl"` даёт `undefined`.
|
|
66
|
+
|
|
67
|
+
## Миграция с 1.x на 2.0
|
|
68
|
+
|
|
69
|
+
Собственных URL стилей у `mahal_map` больше нет — их целиком отдаёт `@grammaps/maps3d-web`.
|
|
70
|
+
|
|
71
|
+
| 1.x | 2.0 |
|
|
72
|
+
| ----------------------------------------- | ------------------------------------------------------------------- |
|
|
73
|
+
| `engine: "legacy"` (по умолчанию) | Удалён. Единственный путь — платформа через `Maps3D`. |
|
|
74
|
+
| `engine: "3d"` | Больше не нужен, опция игнорируется. |
|
|
75
|
+
| `autoAddVectorSource: true` | Удалён. Тот же векторный стиль применяется сам, когда нет `Maps3D`. |
|
|
76
|
+
| `preset: "standard-night"` | `theme: "dark"`, либо полный URL в `style`. |
|
|
77
|
+
| `preset: "road-urban-lab-v2"` | `theme: "light"`, либо полный URL в `style`. |
|
|
78
|
+
| `lang` менял URL стиля | Стиль не трогает — язык подписей приходит из самого стиля. |
|
|
79
|
+
| `layer.setBuildingsEnabled(...)` напрямую | `map.toggle3DBuildings(...)` или `map.setLayer("buildings", ...)`. |
|
|
80
|
+
|
|
81
|
+
`engine` и `autoAddVectorSource` оставлены в типах как `@deprecated`, чтобы не ломать компиляцию, но на поведение не влияют.
|
|
82
|
+
|
|
83
|
+
Что появилось: реестр слоёв (`setLayer`/`getLayers`/`onLayers`), выделение зданий, клик по дорогам, рельеф, планы этажей, перекрытия, семейства стилей `navigator`/`mobile` и автоматический `antialias`.
|
|
84
|
+
|
|
85
|
+
Минимальный диф:
|
|
86
|
+
|
|
87
|
+
```diff
|
|
88
|
+
const map = MahalMap.create(
|
|
89
|
+
{
|
|
90
|
+
container: "map",
|
|
91
|
+
- engine: "3d",
|
|
92
|
+
theme: "dark",
|
|
93
|
+
- preset: "standard-night",
|
|
94
|
+
enable3D: true,
|
|
95
|
+
},
|
|
96
|
+
maplibregl,
|
|
97
|
+
Maps3D,
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Быстрый старт через NPM
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import * as maplibregl from "maplibre-gl";
|
|
105
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
106
|
+
import { Maps3D } from "@grammaps/maps3d-web";
|
|
107
|
+
import { MahalMap, keyUtils } from "mahal_map";
|
|
108
|
+
|
|
109
|
+
keyUtils.saveKey("YOUR_MAP_API_KEY");
|
|
110
|
+
|
|
111
|
+
const map = MahalMap.create(
|
|
112
|
+
{
|
|
113
|
+
container: "map",
|
|
114
|
+
center: [68.787, 38.573],
|
|
115
|
+
zoom: 16.6,
|
|
116
|
+
theme: "light",
|
|
117
|
+
},
|
|
118
|
+
maplibregl,
|
|
119
|
+
Maps3D,
|
|
120
|
+
);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Контейнер должен существовать в HTML:
|
|
124
|
+
|
|
125
|
+
```html
|
|
126
|
+
<div id="map" style="width: 100%; height: 500px"></div>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`Maps3D` — третий, необязательный аргумент: не передан — SDK возьмёт его из `window.Maps3D`. Стиль, `transformRequest` с ключом, сглаживание и подключение 3D библиотека делает сама.
|
|
130
|
+
|
|
131
|
+
## Быстрый старт через Browser SDK
|
|
132
|
+
|
|
133
|
+
Для browser SDK параметр `apikey` обязателен: без него карта не инициализируется.
|
|
134
|
+
|
|
135
|
+
**MapLibre 6 поставляется только как ESM** — классической сборки `dist/maplibre-gl.js` для `<script src>` в ней больше нет. Поэтому библиотеку карты подключают модульным скриптом и передают в `create()` вторым аргументом:
|
|
136
|
+
|
|
137
|
+
```html
|
|
138
|
+
<link
|
|
139
|
+
rel="stylesheet"
|
|
140
|
+
href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.10.0/dist/maplibre-gl.css"
|
|
141
|
+
/>
|
|
142
|
+
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY"></script>
|
|
143
|
+
|
|
144
|
+
<div id="map" style="width: 100%; height: 500px"></div>
|
|
145
|
+
|
|
146
|
+
<script type="module">
|
|
147
|
+
import * as maplibregl from "https://cdn.jsdelivr.net/npm/maplibre-gl@6.10.0/+esm";
|
|
148
|
+
|
|
149
|
+
const map = MahalMap.create(
|
|
150
|
+
{
|
|
151
|
+
container: "map",
|
|
152
|
+
center: [69.624024, 40.279687],
|
|
153
|
+
zoom: 12,
|
|
154
|
+
},
|
|
155
|
+
maplibregl,
|
|
156
|
+
);
|
|
157
|
+
</script>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Через NPM язык можно передать при создании карты:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const map = MahalMap.create(
|
|
164
|
+
{
|
|
165
|
+
container: "map",
|
|
166
|
+
lang: "ru",
|
|
167
|
+
theme: "dark",
|
|
168
|
+
},
|
|
169
|
+
maplibregl,
|
|
170
|
+
);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Через browser SDK язык можно передать в URL скрипта:
|
|
174
|
+
|
|
175
|
+
```html
|
|
176
|
+
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY&lang=ru"></script>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`lang` влияет на поиск и роутинг, но не на стиль: подписи на карте приходят из самого стиля платформы.
|
|
180
|
+
|
|
181
|
+
## Параметры создания карты
|
|
182
|
+
|
|
183
|
+
`MahalMap.create(options, maplibreObject?, maps3dCtor?)`
|
|
184
|
+
|
|
185
|
+
`maps3dCtor` — импортированный конструктор `Maps3D` (третий, необязательный аргумент). Если не передан, SDK ищет его в `window.Maps3D`.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import type { IMaps3DLayerOptions, Maps3DThemeName } from "mahal_map";
|
|
189
|
+
|
|
190
|
+
interface IMahalMapOptions {
|
|
191
|
+
container?: string | HTMLElement;
|
|
192
|
+
style?: string;
|
|
193
|
+
theme?: "dark" | "light";
|
|
194
|
+
lang?: "tj" | "ru";
|
|
195
|
+
center?: [number, number];
|
|
196
|
+
zoom?: number;
|
|
197
|
+
pitch?: number;
|
|
198
|
+
bearing?: number;
|
|
199
|
+
enable3D?: boolean;
|
|
200
|
+
base?: string;
|
|
201
|
+
family?: "default" | "navigator" | "mobile";
|
|
202
|
+
preset?: Maps3DThemeName | string;
|
|
203
|
+
antialias?: boolean;
|
|
204
|
+
workerCheck?: boolean | number;
|
|
205
|
+
maps3d?: Omit<IMaps3DLayerOptions, "apiKey" | "base">;
|
|
206
|
+
/** @deprecated Оставлена для совместимости, на поведение не влияет. */
|
|
207
|
+
engine?: "legacy" | "3d";
|
|
208
|
+
/** @deprecated Оставлена для совместимости, на поведение не влияет. */
|
|
209
|
+
autoAddVectorSource?: boolean;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
| Параметр | Тип | Описание |
|
|
214
|
+
| ------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
215
|
+
| `container` | `string \| HTMLElement` | ID контейнера или DOM-элемент. Не передан — берётся `"map"`. |
|
|
216
|
+
| `style` | `string` | Полный URL своего стиля. Задан — стиль зафиксирован, `setStyle()` его не меняет. |
|
|
217
|
+
| `theme` | `"dark" \| "light"` | Светлая/тёмная внутри выбранного `family`. По умолчанию `light`. |
|
|
218
|
+
| `lang` | `"tj" \| "ru"` | Язык для поиска и роутинга. На стиль не влияет — подписи приходят из самого стиля платформы. |
|
|
219
|
+
| `center` | `[number, number]` | Центр карты в формате `[lng, lat]`. |
|
|
220
|
+
| `zoom` | `number` | Начальный zoom. |
|
|
221
|
+
| `pitch` | `number` | Начальный наклон камеры. Не задан и 3D включено — авто `58`: при `pitch: 0` объём зданий не виден, камера смотрит строго сверху. |
|
|
222
|
+
| `bearing` | `number` | Начальный поворот камеры. |
|
|
223
|
+
| `enable3D` | `boolean` | Подключает Maps3D. По умолчанию `true`, когда `Maps3D` доступен. |
|
|
224
|
+
| `base` | `string` | Домен платформы. По умолчанию `https://navi.gram.tj`. |
|
|
225
|
+
| `family` | `"default" \| "navigator" \| "mobile"` | Семейство стилей. `theme` выбирает внутри него: `default` → `light`/`dark`, `navigator` → `navigator-light`/`navigator-dark`, `mobile` → `mobile-*`. |
|
|
226
|
+
| `preset` | `Maps3DThemeName \| string` | Явное имя темы платформы или полный URL стиля вместо пары `family` + `theme`. Задан — стиль зафиксирован. |
|
|
227
|
+
| `antialias` | `boolean` | Сглаживание сцены. По умолчанию `true` при включённом 3D: без него тонкая геометрия (перила, мачты, ряды сидений) на отдалении рассыпается в рябь. |
|
|
228
|
+
| `workerCheck` | `boolean \| number` | Диагностика неработающего веб-воркера MapLibre через 8 с после создания карты. `false` отключает, число задаёт свою задержку в мс. См. [Веб-воркер MapLibre](#веб-воркер-maplibre). |
|
|
229
|
+
| `maps3d` | `object` | Опции Maps3D: `buildings`, `traffic`, `indoor`, `closures`, `places`, `minZoom`, `lodBias`, `memoryBudget`, `maskReplaced`, `typeReplacements`. |
|
|
230
|
+
|
|
231
|
+
### Стили и темы
|
|
232
|
+
|
|
233
|
+
Стили целиком приходят из `@grammaps/maps3d-web` — своего списка URL у `mahal_map` больше нет. Словарь тем один и тот же у SDK и у библиотеки:
|
|
234
|
+
|
|
235
|
+
| `family` | `theme: "light"` | `theme: "dark"` |
|
|
236
|
+
| ------------- | ----------------- | ---------------- |
|
|
237
|
+
| `"default"` | `light` | `dark` |
|
|
238
|
+
| `"navigator"` | `navigator-light` | `navigator-dark` |
|
|
239
|
+
| `"mobile"` | `mobile-light` | `mobile-dark` |
|
|
240
|
+
|
|
241
|
+
Имя темы уходит в `Maps3D.styleUrl()`, адрес строит сам SDK. Неизвестное имя — явная ошибка с префиксом `[MahalMap SDK]`, а не пустая карта.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
// Навигаторная тёмная тема
|
|
245
|
+
MahalMap.create(
|
|
246
|
+
{ container: "map", family: "navigator", theme: "dark" },
|
|
247
|
+
maplibregl,
|
|
248
|
+
Maps3D,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Смена темы внутри того же семейства
|
|
252
|
+
map.setStyle("light"); // → navigator-light
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
> **Миграция с 1.x.** Имена пресетов прежнего поколения (`road-urban-lab-v2`, `standard-night`) больше не подставляются по умолчанию — словарь тем теперь один, платформенный. Если старый стиль всё ещё нужен, передайте его полным URL:
|
|
256
|
+
>
|
|
257
|
+
> ```ts
|
|
258
|
+
> MahalMap.create(
|
|
259
|
+
> {
|
|
260
|
+
> container: "map",
|
|
261
|
+
> style: "https://navi.gram.tj/maps/standard-night.json",
|
|
262
|
+
> },
|
|
263
|
+
> maplibregl,
|
|
264
|
+
> Maps3D,
|
|
265
|
+
> );
|
|
266
|
+
> ```
|
|
267
|
+
|
|
268
|
+
### Без `@grammaps/maps3d-web`
|
|
269
|
+
|
|
270
|
+
Библиотека не установлена и в `window.Maps3D` ничего нет — карта всё равно поднимется: используется запасной векторный стиль `mtile.gram.tj` с подписью `?token=`, в консоль уходит предупреждение. На такой карте нет объёма, объектов, пробок, рельефа и реестра слоёв; `setStyle()` и `setLanguage()` стиль не меняют, методы реестра возвращают пустые значения (`false`, `null`, `[]`).
|
|
271
|
+
|
|
272
|
+
Этот путь — единственный, где ещё проверяется подписка JSApi: `createAsync()` не создаст карту, если подписки нет. С переданным `Maps3D` проверка пропускается — доступ гейтит сама платформа по `?key=`.
|
|
273
|
+
|
|
274
|
+
### Опции `maps3d` (расширенные)
|
|
275
|
+
|
|
276
|
+
Передаются в `MahalMap.create({ maps3d: {...} })` и уходят в `Maps3D` как есть:
|
|
277
|
+
|
|
278
|
+
| Опция | Тип | По умолч. | Описание |
|
|
279
|
+
| ------------------ | ------------------------------------------------------------------- | --------- | -------------------------------------------------------------------- |
|
|
280
|
+
| `buildings` | `boolean \| { detail?: footprint\|volume\|roofs\|facade }` | `true` | Объёмные здания; объектом — их облик. |
|
|
281
|
+
| `traffic` | `boolean \| { raster?, rasterMaxZoom?, graph?, opacity?, arrows? }` | `false` | Слой пробок. `raster: true` — картинкой вместо векторного слоя. |
|
|
282
|
+
| `indoor` | `boolean \| { level? }` | `false` | Планы этажей. |
|
|
283
|
+
| `closures` | `boolean \| object` | `false` | Перекрытия дорог. |
|
|
284
|
+
| `places` | `object` | — | Парковки, заправки, зарядки: `highlight`, `paid`, `free`, `unknown`, `fillOpacity` (0.10.0+). |
|
|
285
|
+
| `minZoom` | `number` | `16` | Зум появления объёма. |
|
|
286
|
+
| `lodBias` | `number` | `1` | `0` — всегда детальная геометрия, `1` — упрощённая вдали. |
|
|
287
|
+
| `memoryBudget` | `number` | `30` | Сколько 3D-моделей держать в памяти. |
|
|
288
|
+
| `maskReplaced` | `boolean` | `true` | Прятать заменённые OSM-объекты (`anchor=replace`). |
|
|
289
|
+
| `typeReplacements` | `boolean` | `true` | Рисовать замены по типу (`natural=tree` → 3D-дерево и т.п.). |
|
|
290
|
+
|
|
291
|
+
`apiKey` и `base` в `maps3d` передавать не нужно — их подставляет сам `MahalMap` из сохранённого ключа и `options.base`.
|
|
292
|
+
|
|
293
|
+
### Реестр слоёв
|
|
294
|
+
|
|
295
|
+
Единая дверь ко всем слоям платформы. Идентификаторы и параметры типизированы: опечатка в имени слоя или в значении параметра — ошибка компиляции, а не тихий `false` в рантайме.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
map.setLayer("terrain", true, { mode: "on" });
|
|
299
|
+
map.setLayer("traffic", true);
|
|
300
|
+
map.setLayer("indoor", true, { level: 2 });
|
|
301
|
+
map.setLayer("buildings", true, { detail: "facade" });
|
|
302
|
+
|
|
303
|
+
map.getLayerState("terrain"); // снимок одного слоя или null
|
|
304
|
+
map.getLayers(); // снимок всех — по нему рисуется панель слоёв
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
| Слой | Параметры | По умолчанию | Откуда умолчание |
|
|
308
|
+
| ------------------ | ------------------------------------------ | ---------------------------------------------------------------------- | ----------------------- |
|
|
309
|
+
| `buildings` | `detail: footprint\|volume\|roofs\|facade` | включён | зашито |
|
|
310
|
+
| `objects` | — | включён | зашито |
|
|
311
|
+
| `terrain` | `mode: auto\|on\|off` | включён, `mode: "auto"` | зашито |
|
|
312
|
+
| `parking` | `highlight`, `paid`, `free`, `unknown` | включён, `{ highlight: false, paid: true, free: true, unknown: true }` | зашито |
|
|
313
|
+
| `fuel`, `charging` | — | включены | зашито |
|
|
314
|
+
| `traffic` | — | выключен | `maps3d.traffic` |
|
|
315
|
+
| `trafficRaster` | — | выключен | `maps3d.traffic.raster` |
|
|
316
|
+
| `indoor` | `level` | выключен | `maps3d.indoor` |
|
|
317
|
+
| `closures` | — | выключен | `maps3d.closures` |
|
|
318
|
+
|
|
319
|
+
У нижних четырёх умолчание не зашито — реестр берёт его из опций `maps3d`, переданных при создании карты. Поэтому «выключен» в таблице значит «выключен, пока вы не передали опцию», а не «выключен всегда».
|
|
320
|
+
|
|
321
|
+
Стартовое состояние можно задать сразу при создании карты — тогда первый кадр уже правильный, без моргания:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
MahalMap.create(
|
|
325
|
+
{ container: "map", maps3d: { traffic: true, closures: true } },
|
|
326
|
+
maplibregl,
|
|
327
|
+
Maps3D,
|
|
328
|
+
);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Полный список — в разделе [Опции `maps3d`](#опции-maps3d-расширенные).
|
|
332
|
+
|
|
333
|
+
#### `wanted`, `available`, `active` — три разных вопроса
|
|
334
|
+
|
|
335
|
+
Поля независимы, и это главная ловушка реестра:
|
|
336
|
+
|
|
337
|
+
| Поле | Вопрос |
|
|
338
|
+
| ----------- | ------------------------------------------------- |
|
|
339
|
+
| `wanted` | чего хочет приложение |
|
|
340
|
+
| `available` | что позволяют стиль и данные |
|
|
341
|
+
| `active` | что нарисовано прямо сейчас (память SDK) |
|
|
342
|
+
| `rendered` | факт со стороны карты: слой есть в стиле и не погашен |
|
|
343
|
+
|
|
344
|
+
`rendered` появилось в Maps3D 0.8.0; у сборок постарше его нет (`undefined`). Расхождение `active !== rendered` означает, что внутренний признак SDK разошёлся с картой — это видно одной проверкой, без чтения минифицированной сборки. Сравнивать поля можно только когда `rendered` есть — иначе на старой сборке любой активный слой покажется расхождением:
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
const state = map.getLayerState("terrain");
|
|
348
|
+
|
|
349
|
+
// rendered === undefined — Maps3D < 0.8.0, проверка недоступна
|
|
350
|
+
if (state?.rendered !== undefined && state.active !== state.rendered) {
|
|
351
|
+
reportLayerMismatch(state);
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Слой может быть включён и при этом не нарисован — и это не ошибка:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
map.setLayer("terrain", true); // wanted: true
|
|
359
|
+
const state = map.getLayerState("terrain");
|
|
360
|
+
|
|
361
|
+
state?.available; // false — в стиле нет ключа maps3d:terrain
|
|
362
|
+
state?.active; // false — либо режим auto, а зум слишком близкий
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Поэтому галочку в интерфейсе рисуют по `wanted`, а пометку «сейчас не видно» — по `active`. Проверять сразу после `setLayer()` бесполезно: тайлы ещё едут. Правильный способ — подписка.
|
|
366
|
+
|
|
367
|
+
#### Панель слоёв: подписка и отписка
|
|
368
|
+
|
|
369
|
+
`onLayers()` возвращает функцию отписки. Звать её обязательно — иначе колбэк переживёт компонент и будет дёргать размонтированное состояние:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
const unsubscribe = map.onLayers((state) => {
|
|
373
|
+
console.log(state.id, state.wanted, state.available, state.active);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// при размонтировании компонента
|
|
377
|
+
unsubscribe();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
`map.destroy()` снимает слой Maps3D целиком, так что после него подписка всё равно мертва — но до него отписываться нужно самим.
|
|
381
|
+
|
|
382
|
+
#### Парковки, заправки, зарядки
|
|
383
|
+
|
|
384
|
+
`Maps3D` рисует их сам, забирая эти классы у POI-слоёв стиля. Поэтому выключение слоя убирает объекты с карты полностью, а не оставляет значок стиля.
|
|
385
|
+
|
|
386
|
+
Подтип парковки берётся из атрибута `fee`:
|
|
387
|
+
|
|
388
|
+
| Параметр | Условие |
|
|
389
|
+
| --------- | ------------ |
|
|
390
|
+
| `paid` | `fee=yes` |
|
|
391
|
+
| `free` | `fee=no` |
|
|
392
|
+
| `unknown` | атрибута нет |
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
// только платные, с подсветкой
|
|
396
|
+
map.setLayer("parking", true, {
|
|
397
|
+
paid: true,
|
|
398
|
+
free: false,
|
|
399
|
+
unknown: false,
|
|
400
|
+
highlight: true,
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Площадные парковки рисуются тремя слоями по одной геометрии: заливка в цвете подтипа, мягкая внутренняя кромка (с z14) и волосяной кант. Цвета: янтарь — платно, зелёный — бесплатно, сланцевый — неизвестно. Размеры заданы в экранных пикселях и от зума плитки не зависят.
|
|
405
|
+
|
|
406
|
+
Заливку включает только режим поиска — `highlight: true`. Без него подключение Maps3D облик карты не меняет. Постоянную плотность заливки вместо кривой по зуму задаёт `maps3d.places.fillOpacity`.
|
|
407
|
+
|
|
408
|
+
Наведение, клик и справка по участку — в разделе [Парковки: наведение, клик, справка](#парковки-наведение-клик-справка).
|
|
409
|
+
|
|
410
|
+
#### Рельеф
|
|
411
|
+
|
|
412
|
+
`auto` показывает рельеф на обзорных зумах, `on` — всегда. Режим `on` заметно дороже по трафику и времени кадра, чем `auto`.
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
map.setLayer("terrain", true, { mode: "auto" });
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
#### Планы этажей
|
|
419
|
+
|
|
420
|
+
Слой включается реестром, но одного этого мало — без переключения этажа он бесполезен:
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
map.setLayer("indoor", true);
|
|
424
|
+
|
|
425
|
+
const levels = map.getIndoorLevels(); // этажи в текущем виде карты, [] — данных нет
|
|
426
|
+
map.setIndoorLevel(1); // нумерация OSM: 0 — первый наземный
|
|
427
|
+
map.refreshIndoor(); // перечитать после правки картографом
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
С Maps3D 0.11.0 список этажей приходит в состоянии слоя — опрашивать `getIndoorLevels()` на `idle`/`moveend` не нужно:
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
map.onLayers((state) => {
|
|
434
|
+
if (state.id !== "indoor") return;
|
|
435
|
+
renderLevelPicker(state.params.levels ?? [], state.params.level);
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
После поиска показать найденный объект вместе с его этажом:
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
// level приходит у объектов внутри зданий; без него откроется первый этаж
|
|
443
|
+
// и метка окажется в чужом зале
|
|
444
|
+
map.showPlace(68.787, 38.573, place.level, 18);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### Слой, которого нет в списке
|
|
448
|
+
|
|
449
|
+
`setLayer()` принимает только известные идентификаторы. Если в новой сборке Maps3D появится слой, которого ещё нет в типах, — он доступен напрямую через слой, там `id` остаётся строкой:
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
map.getMaps3DLayer()?.setLayer?.("новый-слой", true);
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
`setLayer()` возвращает `false`, если такого слоя в подключённой сборке Maps3D нет или Maps3D не передан вовсе. После полной смены стиля волю клиента возвращает `map.refreshLayers()` — при `setStyle()` библиотека вызывает его сама.
|
|
456
|
+
|
|
457
|
+
### 3D-здания
|
|
458
|
+
|
|
459
|
+
`Maps3D` рисует процедурные 3D-здания (three.js) вместо плоской `fill-extrusion` стиля: фаска кромок, вертикальный градиент и базовый цвет берутся из стиля, окна — из `metadata` темы. Слой создаётся и подключается автоматически (`enable3D` по умолчанию `true`) — вручную поднимать `new Maps3D(...)` не нужно.
|
|
460
|
+
|
|
461
|
+
Тонкая настройка облика — через сам слой, после готовности:
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
const layer = await map.whenMaps3DReady();
|
|
465
|
+
|
|
466
|
+
const b = layer?.buildings;
|
|
467
|
+
b?.setWindowStyle(7); // тип окна 0..9 (сетка, лента, curtain wall, ...)
|
|
468
|
+
b?.setWindowDepth(0.85); // глубина ниши окна 0..1 (реальная геометрия вблизи)
|
|
469
|
+
b?.setWindowColor("#6b9ed1");
|
|
470
|
+
b?.setWindowFrameColor("#f2f2f4");
|
|
471
|
+
b?.setEdgeRadius(1.2); // скругление кромок, м
|
|
472
|
+
// Свет обычно НЕ задают руками — его несёт metadata стиля, сеттеры её перебивают
|
|
473
|
+
b?.setSunIntensity(3.2);
|
|
474
|
+
b?.setAmbient(0.76);
|
|
475
|
+
b?.setSky(0.91);
|
|
476
|
+
b?.setExposure(1.5);
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Тема (окна/свет) приходит из `metadata` стиля и применяется автоматически при смене стиля — пересоздавать слой не нужно. Ручные сеттеры её перебивают.
|
|
480
|
+
|
|
481
|
+
**Ручной `Maps3D.enhance(...)` — отдельный сценарий.** Карту при этом создавайте с `enable3D: false`: второй экземпляр на занятой карте `Maps3D` отклоняет.
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
const map = MahalMap.create(
|
|
485
|
+
{ container: "map", enable3D: false },
|
|
486
|
+
maplibregl,
|
|
487
|
+
Maps3D,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
const maps3d = Maps3D.enhance(map.getMap(), {
|
|
491
|
+
apiKey: "YOUR_MAP_API_KEY",
|
|
492
|
+
base: "https://navi.gram.tj",
|
|
493
|
+
});
|
|
494
|
+
await maps3d.ready; // enhance() ждёт нативную карту MapLibre, не обёртку MahalMap
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Выделение зданий и клик по дорогам
|
|
498
|
+
|
|
499
|
+
Клик по зданию и клик по дороге приходят независимо: одна точка может попасть и туда, и туда — что важнее, решает приложение.
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
map.setSelectionStyle({ color: "#e23b2f", opacity: 0.55, durationMs: 300 });
|
|
503
|
+
|
|
504
|
+
const stopBuildingClick = map.onBuildingClick((building) => {
|
|
505
|
+
if (!building) return;
|
|
506
|
+
// SDK уже подсветил его. index >= 0 — объёмный дом сцены, -1 — дом из слоя стиля
|
|
507
|
+
console.log(building.props?.osm_id, building.height, building.index);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const stopRoadClick = map.onRoadClick((road) => {
|
|
511
|
+
if (!road) return;
|
|
512
|
+
console.log(road.nameRu ?? road.name, road.class);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Выделить здание по osm_id — например, после поиска
|
|
516
|
+
map.selectBuilding(123456789); // false, если здания нет в загруженных данных
|
|
517
|
+
map.selectedBuilding(); // текущий id или null
|
|
518
|
+
map.selectedBuildingKind(); // 'scene' — объём, 'flat' — слой стиля, null — выделения нет
|
|
519
|
+
map.clearSelection();
|
|
520
|
+
|
|
521
|
+
// При размонтировании
|
|
522
|
+
stopBuildingClick();
|
|
523
|
+
stopRoadClick();
|
|
524
|
+
|
|
525
|
+
// Дорога под точкой холста, допуск по умолчанию 12 px
|
|
526
|
+
map.roadAt({ x: 320, y: 240 });
|
|
527
|
+
map.roadsNamed; // есть ли в текущем стиле названия дорог
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
Подписываться можно сразу после `create()` — даже когда слой ещё не поднят (`enable3D: false`). Подписка и облик выделения запоминаются и переигрываются, как только слой появится:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
const map = MahalMap.create({ container: "map", enable3D: false }, maplibregl, Maps3D);
|
|
534
|
+
|
|
535
|
+
map.onBuildingClick((b) => console.log(b?.props.osm_id));
|
|
536
|
+
map.setSelectionStyle({ color: "#e23b2f" });
|
|
537
|
+
|
|
538
|
+
map.toggle3DBuildings(true); // подписка и стиль выделения уже применены к новому слою
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
#### Отписка
|
|
542
|
+
|
|
543
|
+
`onBuildingClick`, `onBuildingHover`, `onRoadClick`, `onParkingHover` и `onParkingClick` возвращают функцию отписки и принимают `null`. Повторный вызов заменяет обработчик. Отписка снимает **только свою** подписку: если обработчик уже заменён следующим вызовом, она ничего не делает — отложенный вызов прежнего владельца (например, cleanup размонтированного компонента) не погасит обработчик нового. Работает и для подписки, сделанной до подъёма слоя: снятая до подъёма, она на слой не попадёт.
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
useEffect(() => map.onBuildingClick(openCard), [map]); // отписка уходит в cleanup
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
#### Порог зума объёма
|
|
550
|
+
|
|
551
|
+
`getBuildingsMinZoom()` — зум, с которого поднимается объём: `maps3d.minZoom`, который `mahal_map` передал в Maps3D, либо умолчание Maps3D (`16`). В отличие от `rendered` в состоянии слоя, ответ не зависит от текущего зума.
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
const willBe3D = targetZoom >= map.getBuildingsMinZoom();
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Наведение на здание
|
|
558
|
+
|
|
559
|
+
Требует `@grammaps/maps3d-web` **0.8.0** и старше. Подсветку и курсор рисует сам SDK — приложение задаёт облик и получает уведомления.
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
map.setHoverStyle({ color: "#6b9ed1", opacity: 0.3, cursor: "pointer" });
|
|
563
|
+
|
|
564
|
+
map.onBuildingHover((building) => {
|
|
565
|
+
if (!building) return; // курсор ушёл с домов
|
|
566
|
+
console.log(building.props.osm_id, building.height);
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Колбэк приходит **только в момент смены** дома под курсором, а не на каждое движение мыши.
|
|
571
|
+
|
|
572
|
+
Слежение включается самой подпиской: пока `onBuildingHover` не вызван, SDK курсор не трогает и сцену не разбирает. `onBuildingHover(null)` или возвращённая отписка снимает подписку и гасит подсветку.
|
|
573
|
+
|
|
574
|
+
| Метод | Описание |
|
|
575
|
+
| --- | --- |
|
|
576
|
+
| `onBuildingHover(cb \| null)` | Подписка на наведение; возвращает отписку, `null` отписывает и гасит подсветку. |
|
|
577
|
+
| `setHoverStyle(s)` | Облик: `color`, `opacity`, `glow`, `durationMs`, `easing`, `cursor`. `cursor: null` — не трогать курсор. |
|
|
578
|
+
| `setHoverEnabled(on)` | Вкл/выкл наведение, не трогая подписку. |
|
|
579
|
+
| `hoverEnabled` | Включено ли наведение сейчас. |
|
|
580
|
+
| `hoverBuilding(id \| null)` | Навести программно — например, с наведения на строку списка. `false` — дома нет на карте. |
|
|
581
|
+
| `hoveredBuilding()` | Идентификатор дома под наведением или `null`. |
|
|
582
|
+
|
|
583
|
+
Слот наведения свой, отдельный от выделения: **выбор всегда главнее**, и на выбранном доме наведение не рисуется. Занимать `setSelectionStyle` ради подсветки под курсором, как приходилось до 0.8.0, больше не нужно.
|
|
584
|
+
|
|
585
|
+
#### Наведение с элементов интерфейса
|
|
586
|
+
|
|
587
|
+
Курсор стоит на строке списка, а не на карте:
|
|
588
|
+
|
|
589
|
+
```ts
|
|
590
|
+
row.addEventListener("mouseenter", () => map.hoverBuilding(row.dataset.osmId!));
|
|
591
|
+
row.addEventListener("mouseleave", () => map.hoverBuilding(null));
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Следующее движение мыши по карте перебивает программное наведение — слежение владеет слотом, пока включено.
|
|
595
|
+
|
|
596
|
+
#### Где наведение выключают
|
|
597
|
+
|
|
598
|
+
Там, где подсветка мешает: режим рисования на карте, открытая карточка объекта, тач-устройство без курсора.
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
map.setHoverEnabled(false); // подписка сохраняется
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Цену ограничивает сам SDK: разбор не чаще раза в кадр, пропускается во время движения камеры — и от жестов, и от `jumpTo`/`easeTo` из приложения (с 0.11.0) — и не трогает сцену, пока курсор на том же доме. С 0.11.0 наведение читает пиксель из GPU асинхронно и главный поток не держит; гасить наведение на время движения карты снаружи больше не нужно.
|
|
605
|
+
|
|
606
|
+
### Парковки: наведение, клик, справка
|
|
607
|
+
|
|
608
|
+
> Нужен `@grammaps/maps3d-web` **0.10.0+**. На более старой сборке методы ничего не делают и возвращают пустые значения (`false`, `null`).
|
|
609
|
+
>
|
|
610
|
+
> Подписки `onParkingHover`/`onParkingClick` можно делать до подъёма слоя — они запоминаются и переигрываются, как у зданий. Обе возвращают отписку.
|
|
611
|
+
|
|
612
|
+
Устроено как у зданий: приложение задаёт облик и получает уведомления, подсветку и курсор рисует SDK.
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
// Режим поиска: заливка по подтипу
|
|
616
|
+
map.setLayer("parking", true, { highlight: true });
|
|
617
|
+
|
|
618
|
+
map.setParkingHoverStyle({ opacity: 0.6, outlineWidth: 2.4, cursor: "pointer" });
|
|
619
|
+
|
|
620
|
+
map.onParkingHover((p) => {
|
|
621
|
+
tooltip.textContent = p ? p.subtype : "";
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
map.onParkingClick((p) => {
|
|
625
|
+
if (!p) return;
|
|
626
|
+
openCard(p); // { id, subtype, fee, access, kind, capacity, props }
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// Подсветить со строки списка и погасить
|
|
630
|
+
row.onmouseenter = () => map.hoverParking(osmId);
|
|
631
|
+
row.onmouseleave = () => map.hoverParking(null);
|
|
632
|
+
|
|
633
|
+
// Справка по точке холста (e.point события карты), допуск 4 px
|
|
634
|
+
map.getMap().on("contextmenu", (e) => console.log(map.parkingAt(e.point, 4)));
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
| Метод | Описание |
|
|
638
|
+
| ---------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
639
|
+
| `onParkingHover(callback \| null)` | Участок под курсором либо `null`. Зовётся **только при смене участка**. Возвращает отписку; `null` — отписаться и погасить. |
|
|
640
|
+
| `setParkingHoverStyle(style)` | Облик подсветки: `color`, `opacity`, `outlineColor`, `outlineWidth`, `cursor`. |
|
|
641
|
+
| `setParkingHoverEnabled(enabled)` | Выключить/включить наведение, не теряя подписку. |
|
|
642
|
+
| `parkingHoverEnabled` | Геттер: включено ли наведение. |
|
|
643
|
+
| `hoverParking(id \| null)` | Подсветить участок программно по `osm_id`; `null` — погасить. `false` — участка нет в данных. |
|
|
644
|
+
| `hoveredParking()` | `osm_id` подсвеченного участка или `null`. |
|
|
645
|
+
| `onParkingClick(callback \| null)` | Клик по участку: справка либо `null`. Возвращает отписку; `null` вместо колбэка — отписаться. |
|
|
646
|
+
| `parkingAt(point, tolPx?)` | Справка по точке холста. Допуск по умолчанию `0` — строго внутрь участка. |
|
|
647
|
+
|
|
648
|
+
Облик подсветки (`IMaps3DParkingHoverStyle`):
|
|
649
|
+
|
|
650
|
+
| Поле | По умолч. | Описание |
|
|
651
|
+
| -------------- | ------------ | ------------------------------------------------------------------------------------------ |
|
|
652
|
+
| `color` | цвет подтипа | Цвет заливки под курсором. По умолчанию не меняется намеренно — подтип уже покрашен смыслом. |
|
|
653
|
+
| `opacity` | `0.55` | Непрозрачность заливки под курсором. |
|
|
654
|
+
| `outlineColor` | кант подтипа | Цвет канта под курсором. |
|
|
655
|
+
| `outlineWidth` | `2.2` | Во сколько раз толще кант под курсором. |
|
|
656
|
+
| `cursor` | `"pointer"` | CSS-курсор над парковкой; `null` — не трогать. |
|
|
657
|
+
|
|
658
|
+
Справка (`IMaps3DParkingInfo`) — ровно то, что лежит в тайле. Отсутствующий атрибут — `null`.
|
|
659
|
+
|
|
660
|
+
| Поле | Тип | Описание |
|
|
661
|
+
| ---------- | -------------------------------- | --------------------------------------------------------------------- |
|
|
662
|
+
| `id` | `number \| string` | `osm_id` участка — тот же, что принимает `hoverParking()`. |
|
|
663
|
+
| `subtype` | `"paid" \| "free" \| "unknown"` | Платность. `unknown` значит «не указано», а **не** «бесплатно». |
|
|
664
|
+
| `fee` | `string \| null` | Исходное `fee` (`yes`/`no`). |
|
|
665
|
+
| `access` | `string \| null` | Кому можно: `residents`, `private`, `customers`, `permissive`… |
|
|
666
|
+
| `kind` | `string \| null` | OSM `parking=*`: `surface`, `multi-storey`, `underground`, `rooftop`… |
|
|
667
|
+
| `capacity` | `number \| null` | Число мест. |
|
|
668
|
+
| `props` | `Record<string, unknown>` | Все свойства фичи как есть. |
|
|
669
|
+
|
|
670
|
+
Поведение:
|
|
671
|
+
|
|
672
|
+
- Попадание определяется по границе полигона, а не по расстоянию до центра.
|
|
673
|
+
- Колбэк наведения вызывается при смене участка, не на каждое движение мыши. Разбор идёт не чаще раза в кадр и пропускается, пока карта движется.
|
|
674
|
+
- Подписка на наведение или клик поднимает слой заливки, даже когда режим поиска выключен, — по нему идёт опрос. Красить в этом случае слой ничего не будет.
|
|
675
|
+
|
|
676
|
+
Ограничения:
|
|
677
|
+
|
|
678
|
+
- Графика работы нет: `opening_hours` в слое `landuse` отсутствует.
|
|
679
|
+
- Названия у площадных парковок нет — оно бывает только у точек POI.
|
|
680
|
+
|
|
681
|
+
#### Вкл/выкл 3D-здания на лету
|
|
682
|
+
|
|
683
|
+
Переключение объём ⇄ штатные здания стиля, без пересоздания карты. Умеет поднять слой, если его не было (`enable3D: false` при создании):
|
|
684
|
+
|
|
685
|
+
```ts
|
|
686
|
+
const map = MahalMap.getInstance("map");
|
|
687
|
+
|
|
688
|
+
map.toggle3DBuildings(false); // выкл
|
|
689
|
+
map.toggle3DBuildings(true); // вкл обратно
|
|
690
|
+
|
|
691
|
+
// статик-версия и SDK-фасад (mahal_map/sdk) работают так же
|
|
692
|
+
MahalMap.toggle3DBuildings(map, false);
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Штатные здания стиля прячет и возвращает сам `Maps3D` — `mahal_map` их видимость не трогает, поэтому спорить за один слой некому.
|
|
696
|
+
|
|
697
|
+
Для переключения пользуйтесь именно `toggle3DBuildings()`: он поднимет слой, если его ещё не было (`enable3D: false` при создании), а на сборках Maps3D без реестра слоёв откатится на `setBuildingsEnabled()`.
|
|
698
|
+
|
|
699
|
+
```ts
|
|
700
|
+
map.toggle3DBuildings(false); // совместимо со всеми сборками
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
`map.setLayer("buildings", false)` — не замена: он ходит только в реестр и ничего не создаёт. Годится, когда вы точно знаете, что на странице Maps3D с реестром слоёв (0.7.x), и слой уже подключён.
|
|
704
|
+
|
|
705
|
+
#### `map.whenMaps3DReady()`
|
|
706
|
+
|
|
707
|
+
Подключение слоя асинхронное: сразу после `create()` слой уже есть, но `layer.buildings` (окна, свет, кромки) появляется только после него. Чтобы не гадать — дождитесь готовности:
|
|
708
|
+
|
|
709
|
+
```ts
|
|
710
|
+
const layer = await map.whenMaps3DReady();
|
|
711
|
+
|
|
712
|
+
layer?.buildings?.setWindowStyle(4);
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
Промис резолвится в `undefined`, если `Maps3D` не передан, слой выключен (`enable3D: false`) или подключение упало — ошибка при этом уходит в `console.error`, а карта остаётся живой на штатных зданиях стиля.
|
|
716
|
+
|
|
717
|
+
### Подключение и выключение 3D-слоя: полный пример (Vue 3)
|
|
718
|
+
|
|
719
|
+
Кнопка-переключатель «3D ⇄ контуры», панель слоёв на живом состоянии реестра и корректная очистка при размонтировании. Слой `Maps3D` поднимает и цепляет сама библиотека — вручную `Maps3D.enhance(...)`, `transformRequest` и `attach()` писать не нужно.
|
|
720
|
+
|
|
721
|
+
```vue
|
|
722
|
+
<script setup lang="ts">
|
|
723
|
+
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
|
|
724
|
+
import * as maplibregl from "maplibre-gl";
|
|
725
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
726
|
+
import { Maps3D } from "@grammaps/maps3d-web";
|
|
727
|
+
import { MahalMap, keyUtils } from "mahal_map";
|
|
728
|
+
import type { IMaps3DLayerState, Maps3DLayerId } from "mahal_map";
|
|
729
|
+
|
|
730
|
+
const API_KEY = "YOUR_MAP_API_KEY";
|
|
731
|
+
|
|
732
|
+
const mahalMap = shallowRef<MahalMap | null>(null);
|
|
733
|
+
const is3dEnabled = ref(true);
|
|
734
|
+
const isLayerReady = ref(false);
|
|
735
|
+
|
|
736
|
+
// Панель слоёв рисуется по живому снимку реестра
|
|
737
|
+
const layers = ref<IMaps3DLayerState[]>([]);
|
|
738
|
+
let unsubscribeLayers: (() => void) | null = null;
|
|
739
|
+
|
|
740
|
+
function toggleLayer(id: Maps3DLayerId, on: boolean) {
|
|
741
|
+
// Опечатка в id или в параметрах не скомпилируется
|
|
742
|
+
mahalMap.value?.setLayer(id, on);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const buildingModeText = computed(() =>
|
|
746
|
+
is3dEnabled.value ? "3D включено" : "Контуры",
|
|
747
|
+
);
|
|
748
|
+
const buildingToggleText = computed(() =>
|
|
749
|
+
is3dEnabled.value ? "Выключить 3D" : "Включить 3D",
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
function toggle3dBuildings() {
|
|
753
|
+
is3dEnabled.value = !is3dEnabled.value;
|
|
754
|
+
// Вкл/выкл детальных 3D-зданий: библиотека сама вернёт/спрячет плоские здания стиля.
|
|
755
|
+
mahalMap.value?.toggle3DBuildings(is3dEnabled.value);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
onMounted(async () => {
|
|
759
|
+
// Токен — один на всё (стиль, тайлы, Maps3D). Отдельный apiKey слою передавать не нужно.
|
|
760
|
+
keyUtils.saveKey(API_KEY);
|
|
761
|
+
|
|
762
|
+
const map = MahalMap.create(
|
|
763
|
+
{
|
|
764
|
+
container: "map",
|
|
765
|
+
theme: "dark", // тема dark; "light" → тема light
|
|
766
|
+
center: [68.787, 38.573],
|
|
767
|
+
zoom: 16.6,
|
|
768
|
+
pitch: 58, // без наклона объём не виден
|
|
769
|
+
bearing: -20,
|
|
770
|
+
enable3D: true, // значение по умолчанию, когда Maps3D передан
|
|
771
|
+
maps3d: { minZoom: 16, lodBias: 0 },
|
|
772
|
+
},
|
|
773
|
+
maplibregl,
|
|
774
|
+
Maps3D,
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
mahalMap.value = map;
|
|
778
|
+
|
|
779
|
+
// Дожидаемся attach(): до него layer.buildings ещё нет.
|
|
780
|
+
const layer = await map.whenMaps3DReady();
|
|
781
|
+
const buildings = layer?.buildings;
|
|
782
|
+
|
|
783
|
+
if (!buildings) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
buildings.setWindowMinZoom?.(16);
|
|
788
|
+
buildings.setWindowStyle(4);
|
|
789
|
+
buildings.setWindowDepth(0.85);
|
|
790
|
+
buildings.setWindowColor("#6b9ed1");
|
|
791
|
+
buildings.setWindowFrameColor("#f2f2f4");
|
|
792
|
+
buildings.setWindowGlow?.(0.22);
|
|
793
|
+
buildings.setEdgeRadius(1.2);
|
|
794
|
+
|
|
795
|
+
// Слои: стартовый снимок плюс подписка на изменения.
|
|
796
|
+
// Состояние приходит асинхронно — сразу после setLayer() проверять бесполезно.
|
|
797
|
+
layers.value = map.getLayers();
|
|
798
|
+
unsubscribeLayers = map.onLayers(() => {
|
|
799
|
+
layers.value = map.getLayers();
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
isLayerReady.value = true;
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
onBeforeUnmount(() => {
|
|
806
|
+
isLayerReady.value = false;
|
|
807
|
+
// Отписку снимаем сами: иначе колбэк переживёт компонент.
|
|
808
|
+
unsubscribeLayers?.();
|
|
809
|
+
unsubscribeLayers = null;
|
|
810
|
+
// destroy() сам снимает слой Maps3D и удаляет карту MapLibre.
|
|
811
|
+
mahalMap.value?.destroy();
|
|
812
|
+
mahalMap.value = null;
|
|
813
|
+
});
|
|
814
|
+
</script>
|
|
815
|
+
|
|
816
|
+
<template>
|
|
817
|
+
<main class="map-page">
|
|
818
|
+
<div id="map" class="map" />
|
|
819
|
+
|
|
820
|
+
<section class="panel" aria-label="GramMaps 3D">
|
|
821
|
+
<span class="mode-label">{{ buildingModeText }}</span>
|
|
822
|
+
<button
|
|
823
|
+
type="button"
|
|
824
|
+
:aria-pressed="is3dEnabled"
|
|
825
|
+
:disabled="!isLayerReady"
|
|
826
|
+
@click="toggle3dBuildings"
|
|
827
|
+
>
|
|
828
|
+
{{ buildingToggleText }}
|
|
829
|
+
</button>
|
|
830
|
+
|
|
831
|
+
<ul class="layers">
|
|
832
|
+
<li v-for="layer in layers" :key="layer.id">
|
|
833
|
+
<label>
|
|
834
|
+
<!-- галочка по wanted: это воля приложения -->
|
|
835
|
+
<input
|
|
836
|
+
type="checkbox"
|
|
837
|
+
:checked="layer.wanted"
|
|
838
|
+
:disabled="!layer.available"
|
|
839
|
+
@change="toggleLayer(layer.id as Maps3DLayerId, !layer.wanted)"
|
|
840
|
+
/>
|
|
841
|
+
{{ layer.id }}
|
|
842
|
+
</label>
|
|
843
|
+
<!-- слой включён, но не нарисован — это норма, а не ошибка -->
|
|
844
|
+
<small v-if="layer.wanted && !layer.active">сейчас не видно</small>
|
|
845
|
+
<small v-else-if="!layer.available">нет в стиле</small>
|
|
846
|
+
</li>
|
|
847
|
+
</ul>
|
|
848
|
+
</section>
|
|
849
|
+
</main>
|
|
850
|
+
</template>
|
|
851
|
+
|
|
852
|
+
<style>
|
|
853
|
+
.map-page,
|
|
854
|
+
.map {
|
|
855
|
+
position: absolute;
|
|
856
|
+
inset: 0;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.panel {
|
|
860
|
+
position: absolute;
|
|
861
|
+
top: 12px;
|
|
862
|
+
left: 12px;
|
|
863
|
+
z-index: 2;
|
|
864
|
+
}
|
|
865
|
+
</style>
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
Что библиотека делает за вас против ручного подключения `@grammaps/maps3d-web`:
|
|
869
|
+
|
|
870
|
+
| Ручной код | Через `mahal_map` |
|
|
871
|
+
| ----------------------------------------------- | ----------------------------------------------------------------- |
|
|
872
|
+
| `...Maps3D.mapOptions({ base, apiKey, style })` | `theme` + `family` (или `preset` / `style` / `base` явно) |
|
|
873
|
+
| `antialias: true` не забыть | ставится сам при включённом 3D |
|
|
874
|
+
| `Maps3D.enhance(map, opts); await maps3d.ready` | `enable3D: true` + `maps3d: {...}`, `await map.whenMaps3DReady()` |
|
|
875
|
+
| `map.setStyle(Maps3D.styleUrl("dark", base))` | `map.setStyle("dark")` — внутри выбранного семейства |
|
|
876
|
+
| `maps3d.refreshLayers()` после смены стиля | вызывается сам на `style.load` |
|
|
877
|
+
| `maps3d.destroy(); map.remove()` | `map.destroy()` |
|
|
878
|
+
| ключ руками в каждый вызов | один `keyUtils.saveKey()` на всё |
|
|
879
|
+
|
|
880
|
+
#### То же самое без сборщика (browser SDK)
|
|
881
|
+
|
|
882
|
+
Библиотеку карты проще взять у платформы: версия согласована с Maps3D, веб-воркер приезжает вместе с ней, про ESM-сборку думать не нужно.
|
|
883
|
+
|
|
884
|
+
```html
|
|
885
|
+
<script src="https://cdn.jsdelivr.net/npm/@grammaps/maps3d-web/dist/maps3d.global.js"></script>
|
|
886
|
+
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY"></script>
|
|
887
|
+
|
|
888
|
+
<div id="map" style="width: 100%; height: 500px"></div>
|
|
889
|
+
<button id="toggle3d" type="button">Выключить 3D</button>
|
|
890
|
+
|
|
891
|
+
<script type="module">
|
|
892
|
+
const maplibregl = await Maps3D.maplibre({ base: "https://navi.gram.tj" });
|
|
893
|
+
|
|
894
|
+
// maplibregl обязателен вторым аргументом: window.maplibregl здесь не появляется.
|
|
895
|
+
// Maps3D третьим можно не передавать — SDK возьмёт его из window.Maps3D.
|
|
896
|
+
const map = MahalMap.create(
|
|
897
|
+
{
|
|
898
|
+
container: "map",
|
|
899
|
+
theme: "dark",
|
|
900
|
+
center: [68.787, 38.573],
|
|
901
|
+
zoom: 16.6,
|
|
902
|
+
pitch: 58,
|
|
903
|
+
},
|
|
904
|
+
maplibregl,
|
|
905
|
+
);
|
|
906
|
+
|
|
907
|
+
let enabled = true;
|
|
908
|
+
|
|
909
|
+
document.getElementById("toggle3d").addEventListener("click", () => {
|
|
910
|
+
enabled = !enabled;
|
|
911
|
+
MahalMap.toggle3DBuildings(map, enabled);
|
|
912
|
+
document.getElementById("toggle3d").textContent = enabled
|
|
913
|
+
? "Выключить 3D"
|
|
914
|
+
: "Включить 3D";
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
MahalMap.whenMaps3DReady(map).then((layer) => {
|
|
918
|
+
layer?.buildings?.setWindowStyle(4);
|
|
919
|
+
});
|
|
920
|
+
</script>
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
### Пробки
|
|
924
|
+
|
|
925
|
+
Через реестр слоёв:
|
|
926
|
+
|
|
927
|
+
```ts
|
|
928
|
+
map.setLayer("traffic", true);
|
|
929
|
+
map.setLayer("trafficRaster", true); // растровый вариант, без клика по дороге
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
Тонкие настройки — через сам слой:
|
|
933
|
+
|
|
934
|
+
```ts
|
|
935
|
+
const layer = map.getMaps3DLayer();
|
|
936
|
+
|
|
937
|
+
layer?.setTrafficOpacity?.(0.85);
|
|
938
|
+
layer?.setTrafficClicks?.(true); // попап скорости по клику
|
|
939
|
+
layer?.setTrafficGraph?.("yandex"); // osm | yandex | gis2 | mahal
|
|
940
|
+
layer?.refreshTraffic?.();
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
Либо сразу при создании карты: `maps3d: { traffic: { raster: true, opacity: 0.85 } }`.
|
|
944
|
+
|
|
945
|
+
### Жизненный цикл слоя
|
|
946
|
+
|
|
947
|
+
```ts
|
|
948
|
+
const layer = map.getMaps3DLayer();
|
|
949
|
+
|
|
950
|
+
layer?.setMinZoom?.(15);
|
|
951
|
+
layer?.setObjectsLight?.({ sun: 1.8, ambient: 0.45, sky: 1.1, exposure: 1.15 });
|
|
952
|
+
await layer?.refresh?.(); // перечитать объекты в кадре
|
|
953
|
+
await layer?.clearCache?.(); // сбросить IndexedDB-кеш моделей
|
|
954
|
+
layer?.diagnostics?.(); // рельеф, потеря контекста WebGL, счётчики зданий
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
Для полной остановки карты используйте только `map.destroy()` / `MahalMap.destroy(map)` — они сами вызывают `destroy()`/`remove()` у Maps3D слоя. **Не вызывайте `layer.destroy()`/`layer.remove()` напрямую**: `MahalMap` не узнает об этом и продолжит считать слой активным (внутренний `maps3dLayer` и `buildingsEnabled` разойдутся с реальностью). Нужно временно выключить только 3D-здания — используйте `map.toggle3DBuildings(false)` (см. выше).
|
|
958
|
+
|
|
959
|
+
## MahalMap
|
|
960
|
+
|
|
961
|
+
`MahalMap` - основной класс карты. Конструктор закрыт, карту нужно создавать через `MahalMap.create()`.
|
|
962
|
+
|
|
963
|
+
### `MahalMap.create(options, maplibreObject?, maps3dCtor?)`
|
|
964
|
+
|
|
965
|
+
Создает новый инстанс карты и сохраняет его по ключу `container`. Для стандартных стилей перед созданием карты должен быть сохранен map token через `keyUtils.saveKey()`. В browser SDK token читается из обязательного URL-параметра `apikey`. Третий аргумент — необязательный конструктор `Maps3D`; без него SDK использует `window.Maps3D`.
|
|
966
|
+
|
|
967
|
+
```ts
|
|
968
|
+
const map = MahalMap.create(
|
|
969
|
+
{
|
|
970
|
+
container: "map",
|
|
971
|
+
center: [69.624024, 40.279687],
|
|
972
|
+
zoom: 12,
|
|
973
|
+
theme: "light",
|
|
974
|
+
lang: "tj",
|
|
975
|
+
},
|
|
976
|
+
maplibregl,
|
|
977
|
+
);
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
В NPM-версии второй аргумент `maplibreObject` рекомендуется передавать явно. В browser SDK он берется из `window.maplibregl`.
|
|
981
|
+
|
|
982
|
+
### `MahalMap.createAsync(options, maplibreObject?, maps3dCtor?)`
|
|
983
|
+
|
|
984
|
+
Асинхронный вариант `create()`. Перед созданием карты проверяет подписку JSApi по map token и, если подписки нет, карту не создаёт вообще: MapLibre-инстанс не строится, промис отклоняется с ошибкой.
|
|
985
|
+
|
|
986
|
+
```ts
|
|
987
|
+
try {
|
|
988
|
+
const map = await MahalMap.createAsync(
|
|
989
|
+
{
|
|
990
|
+
container: "map",
|
|
991
|
+
center: [69.624024, 40.279687],
|
|
992
|
+
zoom: 12,
|
|
993
|
+
theme: "light",
|
|
994
|
+
},
|
|
995
|
+
maplibregl,
|
|
996
|
+
);
|
|
997
|
+
} catch (error) {
|
|
998
|
+
// подписки нет — показать своё сообщение вместо карты
|
|
999
|
+
console.error(error);
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
Правила проверки:
|
|
1004
|
+
|
|
1005
|
+
| Условие | Поведение |
|
|
1006
|
+
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
|
|
1007
|
+
| `Maps3D` не передан, сервис ответил `success: true` | Карта создаётся на запасном стиле. |
|
|
1008
|
+
| `Maps3D` не передан, сервис ответил `success: false` | Карта **не** создаётся, промис отклоняется: `[MahalMap SDK] JSApi subscription is not active for this key: <message>`. |
|
|
1009
|
+
| `Maps3D` не передан, проверка не дошла (сеть, CORS, таймаут) | Fail-open: `console.warn` и карта создаётся. Падение сервиса проверки не гасит карты. |
|
|
1010
|
+
| `Maps3D` передан | Проверка **пропускается**, запрос не отправляется. |
|
|
1011
|
+
| Map token не сохранён | Проверка пропускается, дальше срабатывает обычная ошибка про `apikey`. |
|
|
1012
|
+
|
|
1013
|
+
С переданным `Maps3D` вызов `createAsync()` ведёт себя ровно как `create()` — доступ к платформе контролируется параметром `?key=` на её стороне, отдельная подписка JSApi к ней отношения не имеет.
|
|
1014
|
+
|
|
1015
|
+
Синхронный `MahalMap.create()` проверку не выполняет и работает как раньше.
|
|
1016
|
+
|
|
1017
|
+
### Vue / Nuxt (ClientOnly, container как ref элемента)
|
|
1018
|
+
|
|
1019
|
+
`container` принимает и `id` строкой, и сам DOM-элемент. Ниже рабочий вариант с проверкой подписки: карта строится только после `createAsync()`, поэтому при отсутствии подписки в контейнере не останется пустой карты.
|
|
1020
|
+
|
|
1021
|
+
```vue
|
|
1022
|
+
<template>
|
|
1023
|
+
<ClientOnly>
|
|
1024
|
+
<div class="overflow-hidden rounded-2xl border">
|
|
1025
|
+
<div ref="mapElement" class="h-[360px] w-full" />
|
|
1026
|
+
</div>
|
|
1027
|
+
<template #fallback>
|
|
1028
|
+
<div class="flex h-[360px] items-center justify-center">
|
|
1029
|
+
{{ loadingLabel }}
|
|
1030
|
+
</div>
|
|
1031
|
+
</template>
|
|
1032
|
+
</ClientOnly>
|
|
1033
|
+
</template>
|
|
1034
|
+
|
|
1035
|
+
<script setup lang="ts">
|
|
1036
|
+
import * as maplibregl from "maplibre-gl";
|
|
1037
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
1038
|
+
import type { MahalMap as MahalMapInstance } from "mahal_map";
|
|
1039
|
+
import { onBeforeUnmount, onMounted, ref } from "vue";
|
|
1040
|
+
|
|
1041
|
+
const DUSHANBE_CENTER: [number, number] = [68.759965, 38.572419];
|
|
1042
|
+
|
|
1043
|
+
const mapElement = ref<HTMLElement | null>(null);
|
|
1044
|
+
let map: MahalMapInstance | null = null;
|
|
1045
|
+
// onMounted асинхронный: компонент может размонтироваться, пока идёт проверка подписки.
|
|
1046
|
+
// Без флага карта создастся уже после unmount и останется висеть в памяти.
|
|
1047
|
+
let disposed = false;
|
|
1048
|
+
|
|
1049
|
+
onMounted(async () => {
|
|
1050
|
+
const { MahalMap, keyUtils } = await import("mahal_map");
|
|
1051
|
+
|
|
1052
|
+
keyUtils.saveKey(import.meta.env.VITE_MAHAL_API_KEY_TILE);
|
|
1053
|
+
|
|
1054
|
+
try {
|
|
1055
|
+
const instance = await MahalMap.createAsync(
|
|
1056
|
+
{
|
|
1057
|
+
container: mapElement.value,
|
|
1058
|
+
center: DUSHANBE_CENTER,
|
|
1059
|
+
zoom: 11,
|
|
1060
|
+
},
|
|
1061
|
+
maplibregl,
|
|
1062
|
+
);
|
|
1063
|
+
|
|
1064
|
+
if (disposed) {
|
|
1065
|
+
instance.destroy();
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
map = instance;
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
// подписки JSApi нет — показать своё сообщение вместо карты
|
|
1072
|
+
console.error(error);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
onBeforeUnmount(() => {
|
|
1077
|
+
disposed = true;
|
|
1078
|
+
map?.destroy();
|
|
1079
|
+
map = null;
|
|
1080
|
+
});
|
|
1081
|
+
</script>
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
Замечания по этому паттерну:
|
|
1085
|
+
|
|
1086
|
+
- Импорт `mahal_map` внутри `onMounted` обязателен в SSR-окружении: пакет работает с `window`/`document`.
|
|
1087
|
+
- `ClientOnly` (Nuxt) или эквивалент нужен по той же причине.
|
|
1088
|
+
- Для карты с `container` в виде элемента инстанс регистрируется под ключом по умолчанию `"map"`. Для нескольких карт на странице передавайте `container` строкой с разными `id`, иначе `getInstance()` вернёт не тот инстанс.
|
|
1089
|
+
- `map.destroy()` снимает карту, логотип и запись из реестра инстансов.
|
|
1090
|
+
- Синхронный `MahalMap.create()` в этом же коде работает без изменений — если проверка подписки не нужна, замените `await MahalMap.createAsync(...)` на `MahalMap.create(...)`.
|
|
1091
|
+
|
|
1092
|
+
### `MahalMap.onReady(container, callback)`
|
|
1093
|
+
|
|
1094
|
+
Вызывает `callback`, когда карта создана и MapLibre завершил загрузку.
|
|
1095
|
+
|
|
1096
|
+
```ts
|
|
1097
|
+
MahalMap.onReady("map", (maplibreMap) => {
|
|
1098
|
+
maplibreMap.resize();
|
|
1099
|
+
});
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
`callback` получает нативный `Map` объект из MapLibre GL JS.
|
|
1103
|
+
|
|
1104
|
+
### `MahalMap.getInstance(container)`
|
|
1105
|
+
|
|
1106
|
+
Возвращает ранее созданный инстанс карты по ключу контейнера.
|
|
1107
|
+
|
|
1108
|
+
```ts
|
|
1109
|
+
const map = MahalMap.getInstance("map");
|
|
1110
|
+
map.setZoom(14);
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
Если инстанс не найден, будет выброшена ошибка.
|
|
1114
|
+
|
|
1115
|
+
### `MahalMap.hasInstance(container)`
|
|
1116
|
+
|
|
1117
|
+
Проверяет, существует ли карта с таким ключом контейнера.
|
|
1118
|
+
|
|
1119
|
+
```ts
|
|
1120
|
+
if (MahalMap.hasInstance("map")) {
|
|
1121
|
+
const map = MahalMap.getInstance("map");
|
|
1122
|
+
}
|
|
1123
|
+
```
|
|
1124
|
+
|
|
1125
|
+
### `MahalMap.removeInstance(container)`
|
|
1126
|
+
|
|
1127
|
+
Удаляет инстанс из внутреннего реестра и возвращает `boolean`.
|
|
1128
|
+
|
|
1129
|
+
```ts
|
|
1130
|
+
const removed = MahalMap.removeInstance("map");
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
Метод удаляет только запись из реестра. Для полного удаления карты используйте `destroy()`.
|
|
1134
|
+
|
|
1135
|
+
### `MahalMap.setDefaultLanguage(lang)`
|
|
1136
|
+
|
|
1137
|
+
Задает язык по умолчанию для новых карт.
|
|
1138
|
+
|
|
1139
|
+
```ts
|
|
1140
|
+
MahalMap.setDefaultLanguage("ru");
|
|
1141
|
+
keyUtils.saveKey("YOUR_MAP_API_KEY");
|
|
1142
|
+
|
|
1143
|
+
const map = MahalMap.create({ container: "map" }, maplibregl);
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
После этого новые карты без `options.lang` будут использовать русский стандартный стиль. Для `tj` или пустого значения стандартные стили будут только с `token`.
|
|
1147
|
+
|
|
1148
|
+
## Методы инстанса карты
|
|
1149
|
+
|
|
1150
|
+
### `map.getMap()`
|
|
1151
|
+
|
|
1152
|
+
Возвращает нативный MapLibre `Map`.
|
|
1153
|
+
|
|
1154
|
+
```ts
|
|
1155
|
+
const maplibreMap = map.getMap();
|
|
1156
|
+
maplibreMap.resize();
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
Используйте этот метод, если нужна функция MapLibre, которой нет в Mahal Map SDK.
|
|
1160
|
+
|
|
1161
|
+
### `map.getCamera()`
|
|
1162
|
+
|
|
1163
|
+
Возвращает `CameraController` для управления камерой.
|
|
1164
|
+
|
|
1165
|
+
```ts
|
|
1166
|
+
const camera = map.getCamera();
|
|
1167
|
+
camera.flyTo({
|
|
1168
|
+
center: [69.624024, 40.279687],
|
|
1169
|
+
zoom: 14,
|
|
1170
|
+
});
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
### `map.setStyle(theme)`
|
|
1174
|
+
|
|
1175
|
+
Переключает светлую/тёмную тему внутри выбранного `family`.
|
|
1176
|
+
|
|
1177
|
+
```ts
|
|
1178
|
+
map.setStyle("dark"); // family: "navigator" → navigator-dark
|
|
1179
|
+
map.setStyle("light"); // → navigator-light
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
Адрес стиля строит `Maps3D.styleUrl()`. После загрузки нового стиля библиотека сама зовёт `refreshLayers()` — состояние слоёв переживает смену темы.
|
|
1183
|
+
|
|
1184
|
+
Метод ничего не делает, если карта создана с явным `style` или `preset` (стиль зафиксирован), либо если `Maps3D` не передан — у запасного стиля вариантов по теме нет.
|
|
1185
|
+
|
|
1186
|
+
### `map.setLanguage(lang)`
|
|
1187
|
+
|
|
1188
|
+
Запоминает язык для поиска и роутинга.
|
|
1189
|
+
|
|
1190
|
+
```ts
|
|
1191
|
+
map.setLanguage("ru");
|
|
1192
|
+
map.setLanguage("tj");
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
Стиль метод не трогает: подписи приходят из самого стиля платформы, отдельных URL по языкам больше нет.
|
|
1196
|
+
|
|
1197
|
+
### `map.setCenter(center)`
|
|
1198
|
+
|
|
1199
|
+
Меняет центр карты.
|
|
1200
|
+
|
|
1201
|
+
```ts
|
|
1202
|
+
map.setCenter([69.624024, 40.279687]);
|
|
1203
|
+
```
|
|
1204
|
+
|
|
1205
|
+
Формат координат: `[lng, lat]`.
|
|
1206
|
+
|
|
1207
|
+
### `map.setZoom(zoom)`
|
|
1208
|
+
|
|
1209
|
+
Меняет zoom карты.
|
|
1210
|
+
|
|
1211
|
+
```ts
|
|
1212
|
+
map.setZoom(13);
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
### `map.addMarker(marker)`
|
|
1216
|
+
|
|
1217
|
+
Добавляет маркер на карту.
|
|
1218
|
+
|
|
1219
|
+
```ts
|
|
1220
|
+
import { MahalMapDefaultMarker } from "mahal_map";
|
|
1221
|
+
|
|
1222
|
+
const marker = new MahalMapDefaultMarker({
|
|
1223
|
+
coordinates: [69.624024, 40.279687],
|
|
1224
|
+
color: "#278960",
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
map.addMarker(marker);
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
Маркер должен реализовать интерфейс:
|
|
1231
|
+
|
|
1232
|
+
```ts
|
|
1233
|
+
interface IMapMarker {
|
|
1234
|
+
getElement(): HTMLElement;
|
|
1235
|
+
getCoordinates(): [number, number];
|
|
1236
|
+
isDraggable?(): boolean;
|
|
1237
|
+
getAnchor?():
|
|
1238
|
+
| "center"
|
|
1239
|
+
| "top"
|
|
1240
|
+
| "bottom"
|
|
1241
|
+
| "left"
|
|
1242
|
+
| "right"
|
|
1243
|
+
| "top-left"
|
|
1244
|
+
| "top-right"
|
|
1245
|
+
| "bottom-left"
|
|
1246
|
+
| "bottom-right";
|
|
1247
|
+
}
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
### `map.destroy()`
|
|
1251
|
+
|
|
1252
|
+
Удаляет логотип SDK, вызывает `remove()` у MapLibre карты и удаляет инстанс из внутреннего реестра.
|
|
1253
|
+
|
|
1254
|
+
```ts
|
|
1255
|
+
map.destroy();
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
Используйте при размонтировании страницы или компонента.
|
|
1259
|
+
|
|
1260
|
+
## Статические методы-обертки
|
|
1261
|
+
|
|
1262
|
+
Для browser SDK и случаев, когда удобнее работать с функциями, доступны статические методы:
|
|
1263
|
+
|
|
1264
|
+
```ts
|
|
1265
|
+
MahalMap.getMap(map);
|
|
1266
|
+
MahalMap.getCamera(map);
|
|
1267
|
+
MahalMap.setStyle(map, "dark");
|
|
1268
|
+
MahalMap.setLanguage(map, "ru");
|
|
1269
|
+
MahalMap.setCenter(map, [69.624024, 40.279687]);
|
|
1270
|
+
MahalMap.setZoom(map, 14);
|
|
1271
|
+
MahalMap.addMarker(map, marker);
|
|
1272
|
+
MahalMap.getMaps3DLayer(map);
|
|
1273
|
+
MahalMap.whenMaps3DReady(map);
|
|
1274
|
+
MahalMap.toggle3DBuildings(map, false);
|
|
1275
|
+
|
|
1276
|
+
// Реестр слоёв
|
|
1277
|
+
MahalMap.setLayer(map, "terrain", true, { mode: "on" });
|
|
1278
|
+
MahalMap.getLayerState(map, "terrain");
|
|
1279
|
+
MahalMap.getLayers(map);
|
|
1280
|
+
MahalMap.onLayers(map, (state) => console.log(state.id, state.active));
|
|
1281
|
+
MahalMap.refreshLayers(map);
|
|
1282
|
+
|
|
1283
|
+
// Планы этажей
|
|
1284
|
+
MahalMap.getIndoorLevels(map);
|
|
1285
|
+
MahalMap.setIndoorLevel(map, 1);
|
|
1286
|
+
MahalMap.refreshIndoor(map);
|
|
1287
|
+
MahalMap.showPlace(map, 68.787, 38.573, 2, 18);
|
|
1288
|
+
|
|
1289
|
+
// Выделение зданий и клики
|
|
1290
|
+
MahalMap.onBuildingClick(map, (building) => console.log(building?.id));
|
|
1291
|
+
MahalMap.selectBuilding(map, 123456789);
|
|
1292
|
+
MahalMap.selectedBuilding(map);
|
|
1293
|
+
MahalMap.selectedBuildingKind(map);
|
|
1294
|
+
MahalMap.getBuildingsMinZoom(map);
|
|
1295
|
+
MahalMap.setSelectionStyle(map, { color: "#e23b2f" });
|
|
1296
|
+
MahalMap.clearSelection(map);
|
|
1297
|
+
MahalMap.onRoadClick(map, (road) => console.log(road?.name));
|
|
1298
|
+
MahalMap.roadAt(map, { x: 320, y: 240 });
|
|
1299
|
+
|
|
1300
|
+
// Парковки
|
|
1301
|
+
MahalMap.setParkingHoverStyle(map, { opacity: 0.6, outlineWidth: 2.4 });
|
|
1302
|
+
MahalMap.onParkingHover(map, (p) => console.log(p?.subtype));
|
|
1303
|
+
MahalMap.setParkingHoverEnabled(map, false);
|
|
1304
|
+
MahalMap.hoverParking(map, 123456789);
|
|
1305
|
+
MahalMap.hoveredParking(map);
|
|
1306
|
+
MahalMap.onParkingClick(map, (p) => console.log(p?.id));
|
|
1307
|
+
MahalMap.parkingAt(map, { x: 320, y: 240 }, 4);
|
|
1308
|
+
|
|
1309
|
+
// Наведение на здание (Maps3D 0.8.0)
|
|
1310
|
+
MahalMap.onBuildingHover(map, (b) => console.log(b?.id));
|
|
1311
|
+
MahalMap.setHoverStyle(map, { color: "#6b9ed1", cursor: "pointer" });
|
|
1312
|
+
MahalMap.setHoverEnabled(map, false);
|
|
1313
|
+
MahalMap.hoverBuilding(map, 123456789);
|
|
1314
|
+
MahalMap.hoveredBuilding(map);
|
|
1315
|
+
|
|
1316
|
+
MahalMap.destroy(map);
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
Эти методы вызывают соответствующие методы переданного инстанса.
|
|
1320
|
+
|
|
1321
|
+
## Browser SDK функции
|
|
1322
|
+
|
|
1323
|
+
При подключении `mahal_map.sdk.js` функции доступны на глобальном объекте `MahalMap`.
|
|
1324
|
+
|
|
1325
|
+
```js
|
|
1326
|
+
const map = MahalMap.create({
|
|
1327
|
+
container: "map",
|
|
1328
|
+
center: [69.624024, 40.279687],
|
|
1329
|
+
zoom: 12,
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
MahalMap.setStyle(map, "dark");
|
|
1333
|
+
MahalMap.setLanguage(map, "ru");
|
|
1334
|
+
MahalMap.setZoom(map, 14);
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
Доступные функции карты в browser SDK:
|
|
1338
|
+
|
|
1339
|
+
| Функция | Описание |
|
|
1340
|
+
| ---------------------------------------------- | --------------------------------------------------------------------- |
|
|
1341
|
+
| `create(options)` | Создает карту. Требует `apikey` в URL SDK скрипта. |
|
|
1342
|
+
| `createAsync(options)` | Создает карту после проверки подписки JSApi (только без Maps3D). |
|
|
1343
|
+
| `onReady(container, callback)` | Выполняет callback после загрузки карты. |
|
|
1344
|
+
| `getInstance(container)` | Возвращает инстанс карты. |
|
|
1345
|
+
| `hasInstance(container)` | Проверяет наличие инстанса. |
|
|
1346
|
+
| `removeInstance(container)` | Удаляет инстанс из реестра. |
|
|
1347
|
+
| `getMap(instance)` | Возвращает нативный MapLibre Map. |
|
|
1348
|
+
| `getCamera(instance)` | Возвращает CameraController. |
|
|
1349
|
+
| `setStyle(instance, theme)` | Переключает тему стандартного стиля. |
|
|
1350
|
+
| `setLanguage(instance, lang)` | Переключает язык стандартного стиля. |
|
|
1351
|
+
| `setCenter(instance, center)` | Меняет центр карты. |
|
|
1352
|
+
| `setZoom(instance, zoom)` | Меняет zoom карты. |
|
|
1353
|
+
| `addMarker(instance, marker)` | Добавляет маркер. |
|
|
1354
|
+
| `getMaps3DLayer(instance)` | Возвращает инстанс слоя Maps3D (`undefined`, если Maps3D не передан). |
|
|
1355
|
+
| `whenMaps3DReady(instance)` | Промис слоя Maps3D после `attach()` (готов `layer.buildings`). |
|
|
1356
|
+
| `toggle3DBuildings(instance, enabled)` | Вкл/выкл объёмные здания на лету. |
|
|
1357
|
+
| `setLayer(instance, id, on, params?)` | Включить/выключить слой платформы. |
|
|
1358
|
+
| `getLayerState(instance, id)` | Снимок состояния одного слоя. |
|
|
1359
|
+
| `getLayers(instance)` | Снимок всех слоёв. |
|
|
1360
|
+
| `onLayers(instance, callback)` | Подписка на изменения слоёв; возвращает отписку. |
|
|
1361
|
+
| `refreshLayers(instance)` | Пере-применить волю клиента ко всем слоям. |
|
|
1362
|
+
| `getIndoorLevels(instance)` | Этажи в текущем виде карты (с 0.11.0 — из `params.levels` слоя). |
|
|
1363
|
+
| `setIndoorLevel(instance, level)` | Переключить этаж (0 — первый наземный). |
|
|
1364
|
+
| `refreshIndoor(instance)` | Перечитать планы этажей. |
|
|
1365
|
+
| `showPlace(instance, lon, lat, level?, zoom?)` | Показать объект и открыть его этаж. |
|
|
1366
|
+
| `onBuildingClick(instance, callback \| null)` | Клик по зданию; возвращает отписку. |
|
|
1367
|
+
| `selectBuilding(instance, id, style?)` | Выделить здание по `osm_id`. |
|
|
1368
|
+
| `clearSelection(instance)` | Снять выделение. |
|
|
1369
|
+
| `selectedBuilding(instance)` | Идентификатор выделенного здания или `null`. |
|
|
1370
|
+
| `selectedBuildingKind(instance)` | `'scene'` / `'flat'` / `null` — чем нарисовано выделение. |
|
|
1371
|
+
| `getBuildingsMinZoom(instance)` | Зум появления объёма, переданный в Maps3D (по умолчанию `16`). |
|
|
1372
|
+
| `setSelectionStyle(instance, style)` | Облик выделения. |
|
|
1373
|
+
| `onRoadClick(instance, callback \| null)` | Клик по дороге; возвращает отписку. |
|
|
1374
|
+
| `roadAt(instance, point, tolPx?)` | Дорога под точкой холста. |
|
|
1375
|
+
| `onParkingHover(instance, callback \| null)` | Парковка под курсором (только при смене участка). |
|
|
1376
|
+
| `setParkingHoverStyle(instance, style)` | Облик подсветки парковки. |
|
|
1377
|
+
| `setParkingHoverEnabled(instance, enabled)` | Вкл/выкл наведение на парковки, не теряя подписку. |
|
|
1378
|
+
| `hoverParking(instance, id \| null)` | Подсветить парковку программно по `osm_id`. |
|
|
1379
|
+
| `hoveredParking(instance)` | `osm_id` подсвеченной парковки или `null`. |
|
|
1380
|
+
| `onParkingClick(instance, callback \| null)` | Клик по парковке. |
|
|
1381
|
+
| `parkingAt(instance, point, tolPx?)` | Справка о парковке под точкой холста. |
|
|
1382
|
+
| `onBuildingHover(instance, cb)` | Подписка на наведение (Maps3D 0.8.0); `null` отписывает. |
|
|
1383
|
+
| `setHoverStyle(instance, style)` | Облик наведения, включая CSS-курсор. |
|
|
1384
|
+
| `setHoverEnabled(instance, on)` | Вкл/выкл наведение, не трогая подписку. |
|
|
1385
|
+
| `hoverBuilding(instance, id)` | Навести на здание программно; `null` снимает. |
|
|
1386
|
+
| `hoveredBuilding(instance)` | Дом под наведением или `null`. |
|
|
1387
|
+
| `destroy(instance)` | Полностью удаляет карту. |
|
|
1388
|
+
| `loadKeyFromScriptUrl()` | Читает `apikey` из URL SDK скрипта. |
|
|
1389
|
+
| `loadLanguageFromScriptUrl()` | Читает `lang` из URL SDK скрипта. |
|
|
1390
|
+
|
|
1391
|
+
## CameraController
|
|
1392
|
+
|
|
1393
|
+
`CameraController` доступен через `map.getCamera()` или `MahalMap.getCamera(map)`.
|
|
1394
|
+
|
|
1395
|
+
### `camera.setZoom(zoom, smooth?)`
|
|
1396
|
+
|
|
1397
|
+
Меняет zoom. Если `smooth` не передан, используется плавная анимация.
|
|
1398
|
+
|
|
1399
|
+
```ts
|
|
1400
|
+
camera.setZoom(14);
|
|
1401
|
+
camera.setZoom(10, false);
|
|
1402
|
+
```
|
|
1403
|
+
|
|
1404
|
+
### `camera.setBearing(bearing, smooth?)`
|
|
1405
|
+
|
|
1406
|
+
Меняет поворот карты.
|
|
1407
|
+
|
|
1408
|
+
```ts
|
|
1409
|
+
camera.setBearing(45);
|
|
1410
|
+
camera.setBearing(0, false);
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
### `camera.setPitch(pitch, smooth?)`
|
|
1414
|
+
|
|
1415
|
+
Меняет наклон карты.
|
|
1416
|
+
|
|
1417
|
+
```ts
|
|
1418
|
+
camera.setPitch(60);
|
|
1419
|
+
camera.setPitch(0, false);
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
### `camera.toggle3D(is3D)`
|
|
1423
|
+
|
|
1424
|
+
Включает или выключает 3D-вид.
|
|
1425
|
+
|
|
1426
|
+
```ts
|
|
1427
|
+
camera.toggle3D(true);
|
|
1428
|
+
camera.toggle3D(false);
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
При включении задается `pitch: 60`, при выключении `pitch: 0` и `bearing: 0`.
|
|
1432
|
+
|
|
1433
|
+
### `camera.resetNorth()`
|
|
1434
|
+
|
|
1435
|
+
Возвращает карту на север и сбрасывает наклон.
|
|
1436
|
+
|
|
1437
|
+
```ts
|
|
1438
|
+
camera.resetNorth();
|
|
1439
|
+
```
|
|
1440
|
+
|
|
1441
|
+
### `camera.flyTo(options)`
|
|
1442
|
+
|
|
1443
|
+
Выполняет плавный перелет камеры. Принимает `FlyToOptions` из MapLibre GL JS.
|
|
1444
|
+
|
|
1445
|
+
```ts
|
|
1446
|
+
camera.flyTo({
|
|
1447
|
+
center: [69.624024, 40.279687],
|
|
1448
|
+
zoom: 15,
|
|
1449
|
+
});
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
SDK добавляет стандартные значения `speed`, `curve` и `essential`, но переданные значения могут их переопределить.
|
|
1453
|
+
|
|
1454
|
+
### `camera.getPitch()`
|
|
1455
|
+
|
|
1456
|
+
Возвращает текущий наклон карты.
|
|
1457
|
+
|
|
1458
|
+
```ts
|
|
1459
|
+
const pitch = camera.getPitch();
|
|
1460
|
+
```
|
|
1461
|
+
|
|
1462
|
+
## MahalMapDefaultMarker
|
|
1463
|
+
|
|
1464
|
+
`MahalMapDefaultMarker` - готовый маркер, который можно использовать с `map.addMarker()`.
|
|
1465
|
+
|
|
1466
|
+
```ts
|
|
1467
|
+
import { MahalMapDefaultMarker } from "mahal_map";
|
|
1468
|
+
|
|
1469
|
+
const marker = new MahalMapDefaultMarker({
|
|
1470
|
+
coordinates: [69.624024, 40.279687],
|
|
1471
|
+
color: "#278960",
|
|
1472
|
+
draggable: true,
|
|
1473
|
+
anchor: "bottom",
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
map.addMarker(marker);
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
Параметры:
|
|
1480
|
+
|
|
1481
|
+
| Параметр | Тип | Описание |
|
|
1482
|
+
| ------------- | ------------------ | ----------------------------------------------------------------------- |
|
|
1483
|
+
| `coordinates` | `[number, number]` | Координаты маркера в формате `[lng, lat]`. |
|
|
1484
|
+
| `draggable` | `boolean` | Маркер можно перетаскивать на карте (слушайте `dragend` у результата `addMarker()`). |
|
|
1485
|
+
| `anchor` | `string` | Anchor MapLibre маркера. |
|
|
1486
|
+
| `color` | `string` | Цвет стандартного SVG маркера или замена `fill` в пользовательском SVG. |
|
|
1487
|
+
| `svg` | `string` | Полностью пользовательский SVG. |
|
|
1488
|
+
| `innerSvg` | `string` | SVG внутри стандартного маркера. |
|
|
1489
|
+
| `innerUrl` | `string` | URL изображения внутри стандартного маркера. |
|
|
1490
|
+
|
|
1491
|
+
Методы маркера:
|
|
1492
|
+
|
|
1493
|
+
```ts
|
|
1494
|
+
marker.getElement();
|
|
1495
|
+
marker.getCoordinates();
|
|
1496
|
+
marker.isDraggable();
|
|
1497
|
+
marker.getAnchor();
|
|
1498
|
+
```
|
|
1499
|
+
|
|
1500
|
+
## Несколько карт
|
|
1501
|
+
|
|
1502
|
+
Каждая карта сохраняется по ключу `container`.
|
|
1503
|
+
|
|
1504
|
+
```ts
|
|
1505
|
+
const mainMap = MahalMap.create({ container: "main" }, maplibregl);
|
|
1506
|
+
const miniMap = MahalMap.create({ container: "mini" }, maplibregl);
|
|
1507
|
+
|
|
1508
|
+
MahalMap.getInstance("main").setZoom(14);
|
|
1509
|
+
MahalMap.getInstance("mini").setStyle("dark");
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
Если `container` не передан, ключом будет `"map"`. Для нескольких карт всегда указывайте разные контейнеры.
|
|
1513
|
+
|
|
1514
|
+
## Пользовательский стиль
|
|
1515
|
+
|
|
1516
|
+
Можно передать любой MapLibre style URL:
|
|
1517
|
+
|
|
1518
|
+
```ts
|
|
1519
|
+
const map = MahalMap.create(
|
|
1520
|
+
{
|
|
1521
|
+
container: "map",
|
|
1522
|
+
style: "https://example.com/custom-style.json",
|
|
1523
|
+
},
|
|
1524
|
+
maplibregl,
|
|
1525
|
+
);
|
|
1526
|
+
```
|
|
1527
|
+
|
|
1528
|
+
Стиль при этом считается зафиксированным: `setStyle()` и `setLanguage()` его не подменяют.
|
|
1529
|
+
|
|
1530
|
+
С переданным `Maps3D` URL всё равно проходит через `Maps3D.mapOptions()`, поэтому `transformRequest` с ключом на месте — тайлы и шрифты платформы внутри своего стиля продолжают работать.
|
|
1531
|
+
|
|
1532
|
+
## Жизненный цикл
|
|
1533
|
+
|
|
1534
|
+
Рекомендуемый порядок работы:
|
|
1535
|
+
|
|
1536
|
+
1. Создать DOM-контейнер.
|
|
1537
|
+
2. Сохранить map token через `keyUtils.saveKey()` или передать `apikey` в URL browser SDK.
|
|
1538
|
+
3. Создать карту через `MahalMap.create()`.
|
|
1539
|
+
4. Дождаться загрузки через `MahalMap.onReady()`, если нужен доступ к загруженной MapLibre карте.
|
|
1540
|
+
5. Добавлять маркеры, менять камеру, тему или язык.
|
|
1541
|
+
6. Вызвать `destroy()` при удалении страницы или компонента.
|
|
1542
|
+
|
|
1543
|
+
```ts
|
|
1544
|
+
const map = MahalMap.create({ container: "map" }, maplibregl);
|
|
1545
|
+
|
|
1546
|
+
MahalMap.onReady("map", () => {
|
|
1547
|
+
map.setZoom(13);
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
// При размонтировании:
|
|
1551
|
+
map.destroy();
|
|
1552
|
+
```
|
|
1553
|
+
|
|
1554
|
+
## MeasureTool (линейка и планиметр)
|
|
1555
|
+
|
|
1556
|
+
`MeasureTool` — инструмент измерения расстояния и площади прямо на карте (линейка + планиметр, как в Яндекс.Картах). Полностью самодостаточен: сам рисует точки, линии, полигон и подписи поверх MapLibre, сам обрабатывает клики/drag/удаление точек. Приложение только передает стили (цвета, иконки, подписи единиц) и слушает `onChange`.
|
|
1557
|
+
|
|
1558
|
+
```ts
|
|
1559
|
+
import { MeasureTool } from "mahal_map";
|
|
1560
|
+
|
|
1561
|
+
const map = MahalMap.getInstance("map").getMap();
|
|
1562
|
+
|
|
1563
|
+
const measureTool = new MeasureTool(map, {
|
|
1564
|
+
mode: "distance",
|
|
1565
|
+
style: {
|
|
1566
|
+
lineColor: "#278960",
|
|
1567
|
+
pointColor: "#FFFFFF",
|
|
1568
|
+
pointStrokeColor: "#278960",
|
|
1569
|
+
fillColor: "#278960",
|
|
1570
|
+
fillOpacity: 0.15,
|
|
1571
|
+
},
|
|
1572
|
+
labels: {
|
|
1573
|
+
meters: "м",
|
|
1574
|
+
kilometers: "км",
|
|
1575
|
+
squareMeters: "м²",
|
|
1576
|
+
squareKilometers: "км²",
|
|
1577
|
+
},
|
|
1578
|
+
onChange: (state) => {
|
|
1579
|
+
console.log(state.mode, state.draft, state.shapes);
|
|
1580
|
+
},
|
|
1581
|
+
onCloseRequest: () => {
|
|
1582
|
+
measureTool.stop();
|
|
1583
|
+
},
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
measureTool.start("distance");
|
|
1587
|
+
```
|
|
1588
|
+
|
|
1589
|
+
### Важно: цвета — реальные, не CSS-переменные
|
|
1590
|
+
|
|
1591
|
+
MapLibre GL проверяет `paint`-свойства слоя и не понимает `var(--primary)` — только hex/rgb. Если в приложении цвета живут в CSS-переменных (тема light/dark), резолвьте их в реальное значение перед передачей в `style`:
|
|
1592
|
+
|
|
1593
|
+
```ts
|
|
1594
|
+
const primary =
|
|
1595
|
+
getComputedStyle(document.documentElement)
|
|
1596
|
+
.getPropertyValue("--primary")
|
|
1597
|
+
.trim() || "#278960";
|
|
1598
|
+
|
|
1599
|
+
const measureTool = new MeasureTool(map, {
|
|
1600
|
+
style: { lineColor: primary, pointStrokeColor: primary, fillColor: primary },
|
|
1601
|
+
});
|
|
1602
|
+
```
|
|
1603
|
+
|
|
1604
|
+
Значения `badgeBackground`, `badgeTextColor` и другие DOM-стили бейджа — обычный CSS, туда `var(--x)` передавать можно.
|
|
1605
|
+
|
|
1606
|
+
### Конструктор: `new MeasureTool(map, options?)`
|
|
1607
|
+
|
|
1608
|
+
| Опция | Тип | Описание |
|
|
1609
|
+
| ---------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1610
|
+
| `mode` | `"distance" \| "area"` | Режим по умолчанию. По умолчанию `"distance"`. |
|
|
1611
|
+
| `sourceIdPrefix` | `string` | Префикс id source/layer на карте. По умолчанию генерируется уникальный (`"mahal-measure-1"`, `"mahal-measure-2"`, ...) — так несколько инструментов на одной карте не конфликтуют. Задайте явно, если нужен предсказуемый id. |
|
|
1612
|
+
| `style` | `MeasureStyleOptions` | Цвета и размеры точек/линий/заливки/бейджа. |
|
|
1613
|
+
| `icons` | `MeasureIcons` | SVG-иконки `trash` / `close` / `check` для бейджей. |
|
|
1614
|
+
| `labels` | `MeasureLabels` | Подписи единиц: `meters`, `kilometers`, `squareMeters`, `squareKilometers`. |
|
|
1615
|
+
| `onChange` | `(state: MeasureState) => void` | Вызывается при любом изменении: новая точка, drag, смена режима и т.д. |
|
|
1616
|
+
| `onCloseRequest` | `() => void` | Вызывается по клику на ✕ в бейджике активной фигуры — решение "выключить инструмент" остается за приложением. |
|
|
1617
|
+
|
|
1618
|
+
### Методы
|
|
1619
|
+
|
|
1620
|
+
| Метод | Описание |
|
|
1621
|
+
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1622
|
+
| `start(mode?)` | Включает инструмент и начинает/продолжает рисование в указанном режиме. |
|
|
1623
|
+
| `stop()` | Выключает инструмент, прячет активный бейдж. Сохраненные фигуры остаются на карте. |
|
|
1624
|
+
| `setMode(mode)` | Переключает режим. Если фигура уже рисуется — её точки сохраняются, меняется только тип (линия ⇄ полигон), как в Яндекс.Картах. |
|
|
1625
|
+
| `finishDraft()` | Завершает текущую фигуру (если валидна — от 2 точек для линии, от 3 для полигона) и начинает новую. |
|
|
1626
|
+
| `removeShape(shapeId)` | Удаляет фигуру (черновик или уже сохраненную) целиком. |
|
|
1627
|
+
| `removePoint(shapeId, pointId)` | Удаляет одну точку фигуры. |
|
|
1628
|
+
| `clearAll()` | Удаляет все фигуры и черновик. |
|
|
1629
|
+
| `getState()` | Возвращает текущий `MeasureState` (снимок, без подписки). |
|
|
1630
|
+
| `setStyleOptions(style)` | Обновляет палитру (частично, `Partial<MeasureStyleOptions>`) без пересоздания инструмента: перекрашивает существующие слои и бейджи. Нужен при смене темы карты. |
|
|
1631
|
+
| `refresh()` | Пересоздает источники/слои и перерисовывает фигуры. Инструмент делает это сам после `setStyle()`; метод оставлен как страховка. |
|
|
1632
|
+
| `destroy()` | Полностью снимает слои, обработчики и DOM-бейджи. Вызывать при размонтировании. Повторный вызов безопасен. |
|
|
1633
|
+
|
|
1634
|
+
### Смена стиля карты (тема, язык)
|
|
1635
|
+
|
|
1636
|
+
`map.setStyle()` — а значит и `mahalMap.setStyle('dark')`, и смена языка — применяется MapLibre диффом: все слои, добавленные в рантайме, удаляются как отсутствующие в новом стиле, и событие `style.load` при этом не эмитится. `MeasureTool` переживает это сам: он слушает `styledata` и восстанавливает источники/слои с теми же id, а `render()` создает недостающие слои при каждой отрисовке. Фигуры, черновик и `getState()` не меняются, лишних `onChange` не будет.
|
|
1637
|
+
|
|
1638
|
+
Хосту делать ничего не нужно — обходы вида `map.fire('style.load')` после `setStyle()` можно убирать. Цвета за темой карты не следуют автоматически: после переключения вызовите `setStyleOptions()` с новой палитрой.
|
|
1639
|
+
|
|
1640
|
+
```ts
|
|
1641
|
+
mahalMap.setStyle("dark");
|
|
1642
|
+
measureTool.setStyleOptions({
|
|
1643
|
+
lineColor: "#4ADE80",
|
|
1644
|
+
pointStrokeColor: "#4ADE80",
|
|
1645
|
+
fillColor: "#4ADE80",
|
|
1646
|
+
badgeBackground: "#19191A",
|
|
1647
|
+
badgeTextColor: "#FFFFFF",
|
|
1648
|
+
});
|
|
1649
|
+
```
|
|
1650
|
+
|
|
1651
|
+
### Взаимодействие на карте
|
|
1652
|
+
|
|
1653
|
+
- клик по карте — добавляет точку в текущую фигуру;
|
|
1654
|
+
- перетаскивание существующей точки — двигает её, расстояние/площадь пересчитываются на лету;
|
|
1655
|
+
- правый клик по точке — удаляет её;
|
|
1656
|
+
- наведение на линию/ребро полигона — показывает точку-призрак прямо под курсором; зажатие мыши вставляет в этом месте новую точку и сразу тянет её (как вставка узла в Яндекс.Картах);
|
|
1657
|
+
- бейдж активной (незавершенной) фигуры — показывает значение и три кнопки: ✓ (завершить фигуру), 🗑 (удалить), ✕ (вызывает `onCloseRequest`);
|
|
1658
|
+
- у уже сохраненных фигур — постоянный мини-бейдж: только значение и 🗑 (удалить), не пропадает при рисовании следующей фигуры.
|
|
1659
|
+
|
|
1660
|
+
### `MeasureState`
|
|
1661
|
+
|
|
1662
|
+
```ts
|
|
1663
|
+
interface MeasureState {
|
|
1664
|
+
active: boolean;
|
|
1665
|
+
mode: "distance" | "area";
|
|
1666
|
+
draft: MeasureShape | null;
|
|
1667
|
+
shapes: MeasureShape[];
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
interface MeasureShape {
|
|
1671
|
+
id: string;
|
|
1672
|
+
mode: "distance" | "area";
|
|
1673
|
+
points: { id: string; lngLat: [number, number] }[];
|
|
1674
|
+
closed: boolean;
|
|
1675
|
+
distance: number; // метры
|
|
1676
|
+
area: number; // квадратные метры, 0 для линии
|
|
1677
|
+
}
|
|
1678
|
+
```
|
|
1679
|
+
|
|
1680
|
+
## Сервисы поиска и маршрутов
|
|
1681
|
+
|
|
1682
|
+
Сервисы работают независимо от карты: их можно вызывать без `MahalMap.create()`. Токен передаётся аргументом в каждый вызов — сохранённый через `keyUtils.saveKey()` map token для них не используется.
|
|
1683
|
+
|
|
1684
|
+
```ts
|
|
1685
|
+
import {
|
|
1686
|
+
Search,
|
|
1687
|
+
SearchPoi,
|
|
1688
|
+
SearchByLocation,
|
|
1689
|
+
CheckJSApi,
|
|
1690
|
+
Router,
|
|
1691
|
+
} from "mahal_map";
|
|
1692
|
+
```
|
|
1693
|
+
|
|
1694
|
+
### `Search(text, token, additionalParam?)`
|
|
1695
|
+
|
|
1696
|
+
Поиск адресов (геокодер). Вызовы дебаунсятся на 500 мс: при вводе по символу уходит один запрос.
|
|
1697
|
+
|
|
1698
|
+
```ts
|
|
1699
|
+
const results = await Search("Рудаки 33", token, {
|
|
1700
|
+
lat: "38.5598",
|
|
1701
|
+
lng: "68.7870",
|
|
1702
|
+
limit: 10,
|
|
1703
|
+
});
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
| Параметр | Тип | Описание |
|
|
1707
|
+
| ------------------------------ | -------- | --------------------------------------------------------------------------- |
|
|
1708
|
+
| `text` | `string` | Строка поиска. |
|
|
1709
|
+
| `token` | `string` | Токен сервиса. Обязателен, иначе `[MahalMap SDK] Search token is required`. |
|
|
1710
|
+
| `additionalParam.lat` / `.lng` | `string` | Точка для сортировки результатов по удалённости. |
|
|
1711
|
+
| `additionalParam.limit` | `number` | Максимум результатов. |
|
|
1712
|
+
| `additionalParam.type` | `string` | Фильтр по типу объекта. |
|
|
1713
|
+
|
|
1714
|
+
Возвращает `ISearchResponse[]`.
|
|
1715
|
+
|
|
1716
|
+
### `SearchPoi(text, token, additionalParam?)`
|
|
1717
|
+
|
|
1718
|
+
Поиск POI (организации, объекты). Сигнатура и дебаунс те же, что у `Search`, таймер отдельный — параллельный ввод в двух полях не перебивает запросы друг друга.
|
|
1719
|
+
|
|
1720
|
+
```ts
|
|
1721
|
+
const places = await SearchPoi("кафе", token, {
|
|
1722
|
+
lat: "38.5598",
|
|
1723
|
+
lng: "68.7870",
|
|
1724
|
+
limit: 20,
|
|
1725
|
+
});
|
|
1726
|
+
```
|
|
1727
|
+
|
|
1728
|
+
Возвращает `ISearchResponse[]`.
|
|
1729
|
+
|
|
1730
|
+
### `SearchByLocation(params)`
|
|
1731
|
+
|
|
1732
|
+
Обратный геокодинг: адреса и POI по координатам. Без дебаунса.
|
|
1733
|
+
|
|
1734
|
+
```ts
|
|
1735
|
+
const res = await SearchByLocation({
|
|
1736
|
+
lat: 38.5598,
|
|
1737
|
+
lng: 68.787,
|
|
1738
|
+
token,
|
|
1739
|
+
});
|
|
1740
|
+
```
|
|
1741
|
+
|
|
1742
|
+
| Поле | Тип | Обязательное |
|
|
1743
|
+
| ------- | ------------------ | ------------ |
|
|
1744
|
+
| `lat` | `string \| number` | да |
|
|
1745
|
+
| `lng` | `string \| number` | да |
|
|
1746
|
+
| `token` | `string` | да |
|
|
1747
|
+
| `type` | `string` | нет |
|
|
1748
|
+
|
|
1749
|
+
Возвращает тело ответа целиком — `IResponse<ISearchResponse[]>`: объекты рядом с точкой
|
|
1750
|
+
(адреса и POI), по возрастанию `distance` в метрах.
|
|
1751
|
+
|
|
1752
|
+
```ts
|
|
1753
|
+
const { data } = await SearchByLocation({ lat: 40.2808, lng: 69.6248, token });
|
|
1754
|
+
|
|
1755
|
+
const house = data.find((item) => item.type.toLowerCase() === "address");
|
|
1756
|
+
if (house && !Array.isArray(house.detailAddress)) {
|
|
1757
|
+
const { city, street_type, street_name, number } = house.detailAddress;
|
|
1758
|
+
// "Хуҷанд", "кӯчаи", "Камоли Хуҷандӣ", "162/1"
|
|
1759
|
+
}
|
|
1760
|
+
```
|
|
1761
|
+
|
|
1762
|
+
- `type` приходит в разном регистре (`"Address"`, `"poi"`) — сравнивайте через `toLowerCase()`.
|
|
1763
|
+
- Город — в `detailAddress.city` (`city_type`: `шаҳр`). Района в ответе обратного геокодинга нет.
|
|
1764
|
+
- Любое поле `detailAddress` может отсутствовать, в том числе `number`.
|
|
1765
|
+
- Пустой `detailAddress` приходит массивом `[]`.
|
|
1766
|
+
|
|
1767
|
+
### `CheckJSApi(token)`
|
|
1768
|
+
|
|
1769
|
+
Проверяет, активна ли подписка JSApi у токена.
|
|
1770
|
+
|
|
1771
|
+
```ts
|
|
1772
|
+
const { success, message } = await CheckJSApi(token);
|
|
1773
|
+
|
|
1774
|
+
if (!success) {
|
|
1775
|
+
console.warn("Подписка не активна:", message);
|
|
1776
|
+
}
|
|
1777
|
+
```
|
|
1778
|
+
|
|
1779
|
+
Промис резолвится и при отрицательном ответе — `success: false` это результат проверки, а не сбой. Исключение бросается только если вызов не дошёл до сервиса (сеть, CORS, таймаут) или токен пустой.
|
|
1780
|
+
|
|
1781
|
+
Этот же вызов используется внутри [`MahalMap.createAsync()`](#mahalmapcreateasyncoptions-maplibreobject-maps3dctor), когда `Maps3D` не передан и карта поднимается на запасном стиле.
|
|
1782
|
+
|
|
1783
|
+
### `Router(points, typeData, token)`
|
|
1784
|
+
|
|
1785
|
+
Маршрут между точками.
|
|
1786
|
+
|
|
1787
|
+
```ts
|
|
1788
|
+
const routes = await Router(
|
|
1789
|
+
[
|
|
1790
|
+
[68.787, 38.5598],
|
|
1791
|
+
[68.809, 38.561],
|
|
1792
|
+
],
|
|
1793
|
+
"geojson",
|
|
1794
|
+
token,
|
|
1795
|
+
);
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
| Параметр | Тип | Описание |
|
|
1799
|
+
| ---------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
1800
|
+
| `points` | `number[][]` | Точки в формате `[lng, lat]`. |
|
|
1801
|
+
| `typeData` | `string` | `"geojson"` — декодирует polyline в массив координат. Другое значение оставляет `geometry` строкой polyline. |
|
|
1802
|
+
| `token` | `string` | Токен сервиса. |
|
|
1803
|
+
|
|
1804
|
+
Возвращает `IRoute[]`.
|
|
1805
|
+
|
|
1806
|
+
## License
|
|
1807
|
+
|
|
1808
|
+
ISC
|