mahal_map 1.7.2 → 1.7.3
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 +1200 -993
- package/dist/index.d.mts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/dist/mahal_map.sdk.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,993 +1,1200 @@
|
|
|
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
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
`maplibre-gl` является peer dependency. Его нужно установить в приложении или подключить отдельным browser script перед SDK.
|
|
14
|
-
|
|
15
|
-
`@grammaps/maps3d-web` объявлен peer dependency пакета (в `package.json` помечен как `optional` — без него `engine: "legacy"` работает как обычно). Для `engine: "3d"` он обязателен в рантайме: установите его явно.
|
|
16
|
-
|
|
17
|
-
```sh
|
|
18
|
-
npm i mahal_map maplibre-gl @grammaps/maps3d-web
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Быстрый старт через NPM
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
import maplibregl from "maplibre-gl";
|
|
25
|
-
import "maplibre-gl/dist/maplibre-gl.css";
|
|
26
|
-
import { MahalMap, keyUtils } from "mahal_map";
|
|
27
|
-
|
|
28
|
-
keyUtils.saveKey("YOUR_MAP_API_KEY");
|
|
29
|
-
|
|
30
|
-
const map = MahalMap.create(
|
|
31
|
-
{
|
|
32
|
-
container: "map",
|
|
33
|
-
center: [69.624024, 40.279687],
|
|
34
|
-
zoom: 12,
|
|
35
|
-
theme: "light",
|
|
36
|
-
},
|
|
37
|
-
maplibregl,
|
|
38
|
-
);
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Контейнер должен существовать в HTML:
|
|
42
|
-
|
|
43
|
-
```html
|
|
44
|
-
<div id="map" style="width: 100%; height: 500px"></div>
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Быстрый старт через Browser SDK
|
|
48
|
-
|
|
49
|
-
Сначала подключите MapLibre, затем `mahal_map.sdk.js`. Для browser SDK параметр `apikey` обязателен: без него карта не инициализируется.
|
|
50
|
-
|
|
51
|
-
```html
|
|
52
|
-
<link
|
|
53
|
-
rel="stylesheet"
|
|
54
|
-
href="https://unpkg.com/maplibre-gl@5.3.0/dist/maplibre-gl.css"
|
|
55
|
-
/>
|
|
56
|
-
<script src="https://unpkg.com/maplibre-gl@5.3.0/dist/maplibre-gl.js"></script>
|
|
57
|
-
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY"></script>
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
После этого глобальный объект `MahalMap` доступен в `window`:
|
|
61
|
-
|
|
62
|
-
```html
|
|
63
|
-
<div id="map" style="width: 100%; height: 500px"></div>
|
|
64
|
-
|
|
65
|
-
<script>
|
|
66
|
-
const map = MahalMap.create({
|
|
67
|
-
container: "map",
|
|
68
|
-
center: [69.624024, 40.279687],
|
|
69
|
-
zoom: 12,
|
|
70
|
-
});
|
|
71
|
-
</script>
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Через NPM язык можно передать при создании карты:
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
const map = MahalMap.create(
|
|
78
|
-
{
|
|
79
|
-
container: "map",
|
|
80
|
-
lang: "ru",
|
|
81
|
-
theme: "dark",
|
|
82
|
-
},
|
|
83
|
-
maplibregl,
|
|
84
|
-
);
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Через browser SDK язык можно передать в URL скрипта:
|
|
88
|
-
|
|
89
|
-
```html
|
|
90
|
-
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY&lang=ru"></script>
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Если `lang` не передан или передан `lang=tj`, SDK добавляет только `token`.
|
|
94
|
-
|
|
95
|
-
## Параметры создания карты
|
|
96
|
-
|
|
97
|
-
`MahalMap.create(options, maplibreObject?, maps3dCtor?)`
|
|
98
|
-
|
|
99
|
-
`maps3dCtor` — импортированный конструктор `Maps3D` (третий, необязательный аргумент). Если не передан, SDK ищет его в `window.Maps3D`.
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
import type { IMaps3DLayerOptions } from "mahal_map";
|
|
103
|
-
|
|
104
|
-
interface IMahalMapOptions {
|
|
105
|
-
container?: string | HTMLElement;
|
|
106
|
-
style?: string;
|
|
107
|
-
theme?: "dark" | "light";
|
|
108
|
-
lang?: "tj" | "ru";
|
|
109
|
-
center?: [number, number];
|
|
110
|
-
zoom?: number;
|
|
111
|
-
pitch?: number;
|
|
112
|
-
bearing?: number;
|
|
113
|
-
autoAddVectorSource?: boolean;
|
|
114
|
-
engine?: "legacy" | "3d";
|
|
115
|
-
enable3D?: boolean;
|
|
116
|
-
base?: string;
|
|
117
|
-
preset?: string;
|
|
118
|
-
maps3d?: Omit<IMaps3DLayerOptions, "apiKey" | "base" | "buildings">;
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
| Параметр | Тип | Описание |
|
|
123
|
-
| --------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
-
| `container` | `string \| HTMLElement` | ID контейнера или DOM-элемент. Если не передан, используется `"map"`. |
|
|
125
|
-
| `style` | `string` | Пользовательский URL стиля MapLibre. Если передан, `theme`, `lang` и map token не меняют URL стиля. |
|
|
126
|
-
| `theme` | `"dark" \| "light"` | Тема стандартного стиля. По умолчанию используется `light`. |
|
|
127
|
-
| `lang` | `"tj" \| "ru"` | Язык стандартного стиля. `tj` оставляет URL только с `token`, `ru` добавляет `lang=ru`. |
|
|
128
|
-
| `center` | `[number, number]` | Центр карты в формате `[lng, lat]`. |
|
|
129
|
-
| `zoom` | `number` | Начальный zoom. |
|
|
130
|
-
| `pitch` | `number` | Начальный наклон камеры (нужен для 3D-вида). Если не задан и `enable3D` включен — авто `58` (при `pitch: 0` экструзия зданий не видна, камера смотрит строго сверху). |
|
|
131
|
-
| `bearing` | `number` | Начальный поворот камеры. |
|
|
132
|
-
| `autoAddVectorSource` | `boolean` | Использует встроенный vector style и блокирует смену стандартного стиля через `setStyle`. |
|
|
133
|
-
| `engine` | `"legacy" \| "3d"` | Переключатель движка карты. `"legacy"` (по умолчанию) — старые стили mtile.gram.tj. `"3d"` — новая платформа GramMaps (navi.gram.tj) с пресетами стиля и Maps3D. |
|
|
134
|
-
| `enable3D` | `boolean` | Подключает детальные 3D-здания (Maps3D). Работает только при `engine: "3d"`. По умолчанию `true` для `engine: "3d"` — передайте `false`, чтобы отключить. |
|
|
135
|
-
| `base` | `string` | Домен платформы GramMaps для `engine: "3d"`. По умолчанию `https://navi.gram.tj`. |
|
|
136
|
-
| `preset` | `string` | Имя пресета стиля GramMaps (напр. `"standard-night"`) для `engine: "3d"`. По умолчанию `road-urban-lab-v2`/`standard-night` в зависимости от `theme`. |
|
|
137
|
-
| `maps3d` | `object` | Доп. опции Maps3D слоя: `traffic`, `minZoom`, `lodBias`, `memoryBudget`, `maskReplaced`, `typeReplacements`. |
|
|
138
|
-
|
|
139
|
-
### Новый 3D-движок (GramMaps / Maps3D)
|
|
140
|
-
|
|
141
|
-
Токен передается как обычно, через `keyUtils.saveKey()` — отдельно ключ для Maps3D передавать не нужно.
|
|
142
|
-
|
|
143
|
-
```ts
|
|
144
|
-
import maplibregl from "maplibre-gl";
|
|
145
|
-
import "maplibre-gl/dist/maplibre-gl.css";
|
|
146
|
-
import { Maps3D } from "@grammaps/maps3d-web";
|
|
147
|
-
import { MahalMap, keyUtils } from "mahal_map";
|
|
148
|
-
|
|
149
|
-
keyUtils.saveKey("YOUR_MAP_API_KEY");
|
|
150
|
-
|
|
151
|
-
const map = MahalMap.create(
|
|
152
|
-
{
|
|
153
|
-
container: "map",
|
|
154
|
-
center: [68.78, 38.56],
|
|
155
|
-
zoom: 16.6,
|
|
156
|
-
pitch: 58,
|
|
157
|
-
theme: "dark",
|
|
158
|
-
engine: "3d",
|
|
159
|
-
enable3D: true,
|
|
160
|
-
},
|
|
161
|
-
maplibregl,
|
|
162
|
-
Maps3D,
|
|
163
|
-
);
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
`Maps3D` (третий аргумент `create()`) — опционален: если не передан, SDK попробует взять его из `window.Maps3D`. `@grammaps/maps3d-web` — необязательный peer dependency, ставится только если используется `engine: "3d"`.
|
|
167
|
-
|
|
168
|
-
Получить слой Maps3D после создания карты:
|
|
169
|
-
|
|
170
|
-
```ts
|
|
171
|
-
const layer = map.getMaps3DLayer();
|
|
172
|
-
// или
|
|
173
|
-
MahalMap.getMaps3DLayer(map);
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
`getMaps3DLayer()` возвращает реальный инстанс `Maps3D` (не обёртку) — типы `mahal_map` описывают всю его публичную поверхность, так что подключающему сервису не нужно ставить или типизировать `@grammaps/maps3d-web` отдельно.
|
|
177
|
-
|
|
178
|
-
### Опции `maps3d` (расширенные)
|
|
179
|
-
|
|
180
|
-
Передаются в `MahalMap.create({ maps3d: {...} })` при `engine: "3d"`:
|
|
181
|
-
|
|
182
|
-
| Опция | Тип | По умолч. | Описание |
|
|
183
|
-
| ------------------ | ------------------------------------------------ | --------- | ------------------------------------------------------------------------- |
|
|
184
|
-
| `traffic` | `boolean \| { raster?, rasterMaxZoom?, graph? }` | `false` | Слой пробок. `raster: true` — картинкой с сервера вместо векторного слоя. |
|
|
185
|
-
| `minZoom` | `number` | `16` | Зум появления детальных 3D. |
|
|
186
|
-
| `lodBias` | `number` | `1` | `0` — всегда lod0 (детальный), `1` — lod1 на дальних зумах. |
|
|
187
|
-
| `memoryBudget` | `number` | `30` | Сколько моделей держать в сцене одновременно. |
|
|
188
|
-
| `maskReplaced` | `boolean` | `true` | Прятать заменённые OSM-объекты (`anchor=replace`). |
|
|
189
|
-
| `typeReplacements` | `boolean` | `true` | Рисовать замены по типу (`natural=tree` → 3D-дерево и т.п.). |
|
|
190
|
-
|
|
191
|
-
`buildings: true` включается автоматически при `enable3D: true` — переопределять не нужно, если только не требуется передать сам объект опций.
|
|
192
|
-
|
|
193
|
-
### 3D-здания
|
|
194
|
-
|
|
195
|
-
`Maps3D` рисует процедурные 3D-здания (three.js) вместо плоской `fill-extrusion` стиля: фаска кромок, вертикальный градиент и базовый цвет берутся из стиля, окна — из `metadata` пресета. При `engine: "3d"` слой создаётся и `attach`-ится к карте автоматически (`enable3D` по умолчанию `true`) — вручную поднимать `new Maps3D(...)` не нужно, только если требуется отдельный кастомный инстанс.
|
|
196
|
-
|
|
197
|
-
**Ручной `new Maps3D(...)` — отдельный сценарий.** Карту при этом создавайте с `enable3D: false`, иначе на неё повиснут два слоя Maps3D сразу (автоматический от `MahalMap` + ваш ручной) — дублирование зданий и лишний расход ресурсов:
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
const apiKey = "YOUR_MAP_API_KEY";
|
|
201
|
-
const base = "https://navi.gram.tj";
|
|
202
|
-
|
|
203
|
-
const map = MahalMap.create(
|
|
204
|
-
{ container: "map", engine: "3d", enable3D: false },
|
|
205
|
-
maplibregl,
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const layer = new Maps3D({ apiKey, base, buildings: true });
|
|
209
|
-
await layer.attach(map.getMap()); // attach ждёт нативную карту MapLibre, не обёртку MahalMap
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Тема (окна/свет) приходит из `metadata` пресета стиля и применяется автоматически при `map.setStyle()` — пересоздавать слой не нужно. Ручные сеттеры перебивают её.
|
|
213
|
-
|
|
214
|
-
Это работает только для встроенных пресетов **без** явного `options.preset`: `setStyle()` меняет пресет по теме (`light`/`dark` → `GRAM_PRESETS`), а при заданном `preset` считает стиль зафиксированным и ничего не делает (см. [`MahalMap.ts`](src/core/MahalMap.ts:363)). Чтобы сменить пресет/тему в этом случае — пересоздайте карту с другим `preset` или вызовите `map.getMap().setStyle(...)` напрямую.
|
|
215
|
-
|
|
216
|
-
```ts
|
|
217
|
-
const layer = map.getMaps3DLayer();
|
|
218
|
-
|
|
219
|
-
const b = layer?.buildings;
|
|
220
|
-
b?.setWindowStyle(7); // тип окна 0..9 (сетка, лента, curtain wall, ...)
|
|
221
|
-
b?.setWindowDepth(0.85); // глубина ниши окна 0..1 (реальная геометрия вблизи)
|
|
222
|
-
b?.setWindowColor("#6b9ed1");
|
|
223
|
-
b?.setWindowFrameColor("#f2f2f4");
|
|
224
|
-
b?.setEdgeRadius(1.2); // скругление кромок, м
|
|
225
|
-
// Свет обычно НЕ задают руками — его несёт metadata стиля, сеттеры её перебивают
|
|
226
|
-
b?.setSunIntensity(3.2);
|
|
227
|
-
b?.setAmbient(0.76);
|
|
228
|
-
b?.setSky(0.91);
|
|
229
|
-
b?.setExposure(1.5);
|
|
230
|
-
|
|
231
|
-
layer?.onBuildingClick((info) => {
|
|
232
|
-
if (!info) return;
|
|
233
|
-
console.log(info.id, info.height, info.props);
|
|
234
|
-
});
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
#### Вкл/выкл 3D-здания на лету
|
|
238
|
-
|
|
239
|
-
Переключение 3D-здания ⇄ штатные здания стиля, без пересоздания карты — через обёртку `MahalMap`. Она же умеет пересоздать слой, если его не было (`enable3D: false` при создании):
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
const map = MahalMap.getInstance("map");
|
|
243
|
-
|
|
244
|
-
map.toggle3DBuildings(false); // выкл
|
|
245
|
-
map.toggle3DBuildings(true); // вкл обратно
|
|
246
|
-
|
|
247
|
-
// статик-версия и SDK-фасад (mahal_map/sdk) работают так же
|
|
248
|
-
MahalMap.toggle3DBuildings(map, false);
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
`layer?.setBuildingsEnabled(false)` напрямую через слой **не используйте** — он не знает про штатный слой стиля `building-3d`, который `MahalMap` прячет при включённом 3D. Вызов только слоя оставит эти штатные здания скрытыми и одновременно выключит процедурные — в итоге зданий не будет видно вообще, до следующей перезагрузки стиля.
|
|
252
|
-
|
|
253
|
-
#### `map.whenMaps3DReady()`
|
|
254
|
-
|
|
255
|
-
`attach()` слоя асинхронный: сразу после `create()` слой уже есть, но `layer.buildings` (окна, свет, кромки) появляется только после attach. Чтобы не гадать — дождитесь готовности:
|
|
256
|
-
|
|
257
|
-
```ts
|
|
258
|
-
const layer = await map.whenMaps3DReady();
|
|
259
|
-
|
|
260
|
-
layer?.buildings?.setWindowStyle(4);
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Промис резолвится в `undefined`, если движок не `"3d"`, слой выключен (`enable3D: false`) или attach упал — ошибка при этом уходит в `console.error`, а карта остаётся живой со штатными зданиями стиля.
|
|
264
|
-
|
|
265
|
-
### Подключение и выключение 3D-слоя: полный пример (Vue 3)
|
|
266
|
-
|
|
267
|
-
Кнопка-переключатель «3D ⇄ контуры», тонкая настройка окон и корректная очистка при размонтировании. Слой `Maps3D` поднимает и цепляет сама библиотека — вручную `new Maps3D(...)`, `transformRequest` и `attach()` писать не нужно.
|
|
268
|
-
|
|
269
|
-
```vue
|
|
270
|
-
<script setup lang="ts">
|
|
271
|
-
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
|
|
272
|
-
import maplibregl from "maplibre-gl";
|
|
273
|
-
import "maplibre-gl/dist/maplibre-gl.css";
|
|
274
|
-
import { Maps3D } from "@grammaps/maps3d-web";
|
|
275
|
-
import { MahalMap, keyUtils } from "mahal_map";
|
|
276
|
-
|
|
277
|
-
const API_KEY = "YOUR_MAP_API_KEY";
|
|
278
|
-
|
|
279
|
-
const mahalMap = shallowRef<MahalMap | null>(null);
|
|
280
|
-
const is3dEnabled = ref(true);
|
|
281
|
-
const isLayerReady = ref(false);
|
|
282
|
-
|
|
283
|
-
const buildingModeText = computed(() =>
|
|
284
|
-
is3dEnabled.value ? "3D включено" : "Контуры",
|
|
285
|
-
);
|
|
286
|
-
const buildingToggleText = computed(() =>
|
|
287
|
-
is3dEnabled.value ? "Выключить 3D" : "Включить 3D",
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
function toggle3dBuildings() {
|
|
291
|
-
is3dEnabled.value = !is3dEnabled.value;
|
|
292
|
-
// Вкл/выкл детальных 3D-зданий: библиотека сама вернёт/спрячет плоские здания стиля.
|
|
293
|
-
mahalMap.value?.toggle3DBuildings(is3dEnabled.value);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
onMounted(async () => {
|
|
297
|
-
// Токен — один на всё (стиль, тайлы, Maps3D). Отдельный apiKey слою передавать не нужно.
|
|
298
|
-
keyUtils.saveKey(API_KEY);
|
|
299
|
-
|
|
300
|
-
const map = MahalMap.create(
|
|
301
|
-
{
|
|
302
|
-
container: "map",
|
|
303
|
-
engine: "3d", // платформа GramMaps вместо legacy-стилей
|
|
304
|
-
theme: "dark", // preset standard-night; "light" → road-urban-lab-v2
|
|
305
|
-
center: [68.787, 38.573],
|
|
306
|
-
zoom: 16.6,
|
|
307
|
-
pitch: 58, // без наклона экструзия не видна
|
|
308
|
-
bearing: -20,
|
|
309
|
-
enable3D: true, // значение по умолчанию для engine: "3d"
|
|
310
|
-
maps3d: { minZoom: 16, lodBias: 0 },
|
|
311
|
-
},
|
|
312
|
-
maplibregl,
|
|
313
|
-
Maps3D,
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
mahalMap.value = map;
|
|
317
|
-
|
|
318
|
-
// Дожидаемся attach(): до него layer.buildings ещё нет.
|
|
319
|
-
const layer = await map.whenMaps3DReady();
|
|
320
|
-
const buildings = layer?.buildings;
|
|
321
|
-
|
|
322
|
-
if (!buildings) {
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
buildings.setWindowMinZoom?.(16);
|
|
327
|
-
buildings.setWindowStyle(4);
|
|
328
|
-
buildings.setWindowDepth(0.85);
|
|
329
|
-
buildings.setWindowColor("#6b9ed1");
|
|
330
|
-
buildings.setWindowFrameColor("#f2f2f4");
|
|
331
|
-
buildings.setWindowGlow?.(0.22);
|
|
332
|
-
buildings.setEdgeRadius(1.2);
|
|
333
|
-
|
|
334
|
-
isLayerReady.value = true;
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
onBeforeUnmount(() => {
|
|
338
|
-
isLayerReady.value = false;
|
|
339
|
-
// destroy() сам снимает слой Maps3D и удаляет карту MapLibre.
|
|
340
|
-
mahalMap.value?.destroy();
|
|
341
|
-
mahalMap.value = null;
|
|
342
|
-
});
|
|
343
|
-
</script>
|
|
344
|
-
|
|
345
|
-
<template>
|
|
346
|
-
<main class="map-page">
|
|
347
|
-
<div id="map" class="map" />
|
|
348
|
-
|
|
349
|
-
<section class="panel" aria-label="GramMaps 3D">
|
|
350
|
-
<span class="mode-label">{{ buildingModeText }}</span>
|
|
351
|
-
<button
|
|
352
|
-
type="button"
|
|
353
|
-
:aria-pressed="is3dEnabled"
|
|
354
|
-
:disabled="!isLayerReady"
|
|
355
|
-
@click="toggle3dBuildings"
|
|
356
|
-
>
|
|
357
|
-
{{ buildingToggleText }}
|
|
358
|
-
</button>
|
|
359
|
-
</section>
|
|
360
|
-
</main>
|
|
361
|
-
</template>
|
|
362
|
-
|
|
363
|
-
<style>
|
|
364
|
-
.map-page,
|
|
365
|
-
.map {
|
|
366
|
-
position: absolute;
|
|
367
|
-
inset: 0;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
.panel {
|
|
371
|
-
position: absolute;
|
|
372
|
-
top: 12px;
|
|
373
|
-
left: 12px;
|
|
374
|
-
z-index: 2;
|
|
375
|
-
}
|
|
376
|
-
</style>
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
Что библиотека делает за вас против ручного подключения `@grammaps/maps3d-web`:
|
|
380
|
-
|
|
381
|
-
| Ручной код | Через `mahal_map` |
|
|
382
|
-
| ----------------------------------------------- | ----------------------------------------------------------------- |
|
|
383
|
-
| `style: base + "/maps/standard-night.json"` | `engine: "3d"` + `theme` (или `preset` / `base` явно) |
|
|
384
|
-
| `transformRequest: Maps3D.transformRequest(..)` | ставится автоматически (и без Maps3D — своим фолбэком с `?key=`) |
|
|
385
|
-
| `new Maps3D({...}); await layer.attach(map)` | `enable3D: true` + `maps3d: {...}`, `await map.whenMaps3DReady()` |
|
|
386
|
-
| `setBuildingsEnabled` + `buildings.setWindows` | `map.toggle3DBuildings(enabled)` — оба вызова разом |
|
|
387
|
-
| Плоские здания стиля поверх 3D после `setStyle` | скрываются сами на каждой загрузке стиля |
|
|
388
|
-
| `layer.destroy(); map.remove()` | `map.destroy()` |
|
|
389
|
-
|
|
390
|
-
#### То же самое без сборщика (browser SDK)
|
|
391
|
-
|
|
392
|
-
```html
|
|
393
|
-
<script src="https://unpkg.com/maplibre-gl@5.3.0/dist/maplibre-gl.js"></script>
|
|
394
|
-
<script src="https://cdn.jsdelivr.net/npm/@grammaps/maps3d-web/dist/maps3d.global.js"></script>
|
|
395
|
-
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY"></script>
|
|
396
|
-
|
|
397
|
-
<div id="map" style="width: 100%; height: 500px"></div>
|
|
398
|
-
<button id="toggle3d" type="button">Выключить 3D</button>
|
|
399
|
-
|
|
400
|
-
<script>
|
|
401
|
-
// Maps3D берётся из window.Maps3D — третий аргумент передавать не нужно.
|
|
402
|
-
const map = MahalMap.create({
|
|
403
|
-
container: "map",
|
|
404
|
-
engine: "3d",
|
|
405
|
-
theme: "dark",
|
|
406
|
-
center: [68.787, 38.573],
|
|
407
|
-
zoom: 16.6,
|
|
408
|
-
pitch: 58,
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
let enabled = true;
|
|
412
|
-
|
|
413
|
-
document.getElementById("toggle3d").addEventListener("click", () => {
|
|
414
|
-
enabled = !enabled;
|
|
415
|
-
MahalMap.toggle3DBuildings(map, enabled);
|
|
416
|
-
document.getElementById("toggle3d").textContent = enabled
|
|
417
|
-
? "Выключить 3D"
|
|
418
|
-
: "Включить 3D";
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
MahalMap.whenMaps3DReady(map).then((layer) => {
|
|
422
|
-
layer?.buildings?.setWindowStyle(4);
|
|
423
|
-
});
|
|
424
|
-
</script>
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Пробки
|
|
428
|
-
|
|
429
|
-
```ts
|
|
430
|
-
const layer = map.getMaps3DLayer();
|
|
431
|
-
|
|
432
|
-
layer?.setTraffic(true);
|
|
433
|
-
layer?.setTrafficOpacity(0.85);
|
|
434
|
-
layer?.setTrafficClicks(true); // попап скорости по клику
|
|
435
|
-
layer?.refreshTraffic();
|
|
436
|
-
|
|
437
|
-
// растровый вариант (картинка с сервера, без клика по дороге)
|
|
438
|
-
layer?.setTrafficRaster(true);
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
### Жизненный цикл слоя
|
|
442
|
-
|
|
443
|
-
```ts
|
|
444
|
-
const layer = map.getMaps3DLayer();
|
|
445
|
-
|
|
446
|
-
layer?.setMinZoom(15);
|
|
447
|
-
layer?.setObjectsLight({ sun: 1.8, ambient: 0.45, sky: 1.1, exposure: 1.15 });
|
|
448
|
-
await layer?.refresh(); // перечитать модели после правок
|
|
449
|
-
await layer?.clearCache(); // сбросить IndexedDB-кеш ассетов
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
Для полной остановки карты используйте только `map.destroy()` / `MahalMap.destroy(map)` — они сами вызывают `destroy()`/`remove()` у Maps3D слоя. **Не вызывайте `layer.destroy()`/`layer.remove()` напрямую**: `MahalMap` не узнает об этом и продолжит считать слой активным (внутренний `maps3dLayer`, `buildingsEnabled`, видимость штатных зданий стиля разойдутся с реальностью). Нужно временно выключить только 3D-здания — используйте `map.toggle3DBuildings(false)` (см. выше).
|
|
453
|
-
|
|
454
|
-
## MahalMap
|
|
455
|
-
|
|
456
|
-
`MahalMap` - основной класс карты. Конструктор закрыт, карту нужно создавать через `MahalMap.create()`.
|
|
457
|
-
|
|
458
|
-
### `MahalMap.create(options, maplibreObject?, maps3dCtor?)`
|
|
459
|
-
|
|
460
|
-
Создает новый инстанс карты и сохраняет его по ключу `container`. Для стандартных стилей перед созданием карты должен быть сохранен map token через `keyUtils.saveKey()`. В browser SDK token читается из обязательного URL-параметра `apikey`. Третий аргумент — необязательный конструктор `Maps3D`; без него SDK использует `window.Maps3D`.
|
|
461
|
-
|
|
462
|
-
```ts
|
|
463
|
-
const map = MahalMap.create(
|
|
464
|
-
{
|
|
465
|
-
container: "map",
|
|
466
|
-
center: [69.624024, 40.279687],
|
|
467
|
-
zoom: 12,
|
|
468
|
-
theme: "light",
|
|
469
|
-
lang: "tj",
|
|
470
|
-
},
|
|
471
|
-
maplibregl,
|
|
472
|
-
);
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
В NPM-версии второй аргумент `maplibreObject` рекомендуется передавать явно. В browser SDK он берется из `window.maplibregl`.
|
|
476
|
-
|
|
477
|
-
### `MahalMap.
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
```ts
|
|
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
|
-
```ts
|
|
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
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
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
|
-
|
|
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
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`maplibre-gl` является peer dependency. Его нужно установить в приложении или подключить отдельным browser script перед SDK.
|
|
14
|
+
|
|
15
|
+
`@grammaps/maps3d-web` объявлен peer dependency пакета (в `package.json` помечен как `optional` — без него `engine: "legacy"` работает как обычно). Для `engine: "3d"` он обязателен в рантайме: установите его явно.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm i mahal_map maplibre-gl @grammaps/maps3d-web
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Быстрый старт через NPM
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import maplibregl from "maplibre-gl";
|
|
25
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
26
|
+
import { MahalMap, keyUtils } from "mahal_map";
|
|
27
|
+
|
|
28
|
+
keyUtils.saveKey("YOUR_MAP_API_KEY");
|
|
29
|
+
|
|
30
|
+
const map = MahalMap.create(
|
|
31
|
+
{
|
|
32
|
+
container: "map",
|
|
33
|
+
center: [69.624024, 40.279687],
|
|
34
|
+
zoom: 12,
|
|
35
|
+
theme: "light",
|
|
36
|
+
},
|
|
37
|
+
maplibregl,
|
|
38
|
+
);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Контейнер должен существовать в HTML:
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<div id="map" style="width: 100%; height: 500px"></div>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Быстрый старт через Browser SDK
|
|
48
|
+
|
|
49
|
+
Сначала подключите MapLibre, затем `mahal_map.sdk.js`. Для browser SDK параметр `apikey` обязателен: без него карта не инициализируется.
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<link
|
|
53
|
+
rel="stylesheet"
|
|
54
|
+
href="https://unpkg.com/maplibre-gl@5.3.0/dist/maplibre-gl.css"
|
|
55
|
+
/>
|
|
56
|
+
<script src="https://unpkg.com/maplibre-gl@5.3.0/dist/maplibre-gl.js"></script>
|
|
57
|
+
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY"></script>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
После этого глобальный объект `MahalMap` доступен в `window`:
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<div id="map" style="width: 100%; height: 500px"></div>
|
|
64
|
+
|
|
65
|
+
<script>
|
|
66
|
+
const map = MahalMap.create({
|
|
67
|
+
container: "map",
|
|
68
|
+
center: [69.624024, 40.279687],
|
|
69
|
+
zoom: 12,
|
|
70
|
+
});
|
|
71
|
+
</script>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Через NPM язык можно передать при создании карты:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const map = MahalMap.create(
|
|
78
|
+
{
|
|
79
|
+
container: "map",
|
|
80
|
+
lang: "ru",
|
|
81
|
+
theme: "dark",
|
|
82
|
+
},
|
|
83
|
+
maplibregl,
|
|
84
|
+
);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Через browser SDK язык можно передать в URL скрипта:
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY&lang=ru"></script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Если `lang` не передан или передан `lang=tj`, SDK добавляет только `token`.
|
|
94
|
+
|
|
95
|
+
## Параметры создания карты
|
|
96
|
+
|
|
97
|
+
`MahalMap.create(options, maplibreObject?, maps3dCtor?)`
|
|
98
|
+
|
|
99
|
+
`maps3dCtor` — импортированный конструктор `Maps3D` (третий, необязательный аргумент). Если не передан, SDK ищет его в `window.Maps3D`.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import type { IMaps3DLayerOptions } from "mahal_map";
|
|
103
|
+
|
|
104
|
+
interface IMahalMapOptions {
|
|
105
|
+
container?: string | HTMLElement;
|
|
106
|
+
style?: string;
|
|
107
|
+
theme?: "dark" | "light";
|
|
108
|
+
lang?: "tj" | "ru";
|
|
109
|
+
center?: [number, number];
|
|
110
|
+
zoom?: number;
|
|
111
|
+
pitch?: number;
|
|
112
|
+
bearing?: number;
|
|
113
|
+
autoAddVectorSource?: boolean;
|
|
114
|
+
engine?: "legacy" | "3d";
|
|
115
|
+
enable3D?: boolean;
|
|
116
|
+
base?: string;
|
|
117
|
+
preset?: string;
|
|
118
|
+
maps3d?: Omit<IMaps3DLayerOptions, "apiKey" | "base" | "buildings">;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Параметр | Тип | Описание |
|
|
123
|
+
| --------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| `container` | `string \| HTMLElement` | ID контейнера или DOM-элемент. Если не передан, используется `"map"`. |
|
|
125
|
+
| `style` | `string` | Пользовательский URL стиля MapLibre. Если передан, `theme`, `lang` и map token не меняют URL стиля. |
|
|
126
|
+
| `theme` | `"dark" \| "light"` | Тема стандартного стиля. По умолчанию используется `light`. |
|
|
127
|
+
| `lang` | `"tj" \| "ru"` | Язык стандартного стиля. `tj` оставляет URL только с `token`, `ru` добавляет `lang=ru`. |
|
|
128
|
+
| `center` | `[number, number]` | Центр карты в формате `[lng, lat]`. |
|
|
129
|
+
| `zoom` | `number` | Начальный zoom. |
|
|
130
|
+
| `pitch` | `number` | Начальный наклон камеры (нужен для 3D-вида). Если не задан и `enable3D` включен — авто `58` (при `pitch: 0` экструзия зданий не видна, камера смотрит строго сверху). |
|
|
131
|
+
| `bearing` | `number` | Начальный поворот камеры. |
|
|
132
|
+
| `autoAddVectorSource` | `boolean` | Использует встроенный vector style и блокирует смену стандартного стиля через `setStyle`. |
|
|
133
|
+
| `engine` | `"legacy" \| "3d"` | Переключатель движка карты. `"legacy"` (по умолчанию) — старые стили mtile.gram.tj. `"3d"` — новая платформа GramMaps (navi.gram.tj) с пресетами стиля и Maps3D. |
|
|
134
|
+
| `enable3D` | `boolean` | Подключает детальные 3D-здания (Maps3D). Работает только при `engine: "3d"`. По умолчанию `true` для `engine: "3d"` — передайте `false`, чтобы отключить. |
|
|
135
|
+
| `base` | `string` | Домен платформы GramMaps для `engine: "3d"`. По умолчанию `https://navi.gram.tj`. |
|
|
136
|
+
| `preset` | `string` | Имя пресета стиля GramMaps (напр. `"standard-night"`) для `engine: "3d"`. По умолчанию `road-urban-lab-v2`/`standard-night` в зависимости от `theme`. |
|
|
137
|
+
| `maps3d` | `object` | Доп. опции Maps3D слоя: `traffic`, `minZoom`, `lodBias`, `memoryBudget`, `maskReplaced`, `typeReplacements`. |
|
|
138
|
+
|
|
139
|
+
### Новый 3D-движок (GramMaps / Maps3D)
|
|
140
|
+
|
|
141
|
+
Токен передается как обычно, через `keyUtils.saveKey()` — отдельно ключ для Maps3D передавать не нужно.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import maplibregl from "maplibre-gl";
|
|
145
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
146
|
+
import { Maps3D } from "@grammaps/maps3d-web";
|
|
147
|
+
import { MahalMap, keyUtils } from "mahal_map";
|
|
148
|
+
|
|
149
|
+
keyUtils.saveKey("YOUR_MAP_API_KEY");
|
|
150
|
+
|
|
151
|
+
const map = MahalMap.create(
|
|
152
|
+
{
|
|
153
|
+
container: "map",
|
|
154
|
+
center: [68.78, 38.56],
|
|
155
|
+
zoom: 16.6,
|
|
156
|
+
pitch: 58,
|
|
157
|
+
theme: "dark",
|
|
158
|
+
engine: "3d",
|
|
159
|
+
enable3D: true,
|
|
160
|
+
},
|
|
161
|
+
maplibregl,
|
|
162
|
+
Maps3D,
|
|
163
|
+
);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`Maps3D` (третий аргумент `create()`) — опционален: если не передан, SDK попробует взять его из `window.Maps3D`. `@grammaps/maps3d-web` — необязательный peer dependency, ставится только если используется `engine: "3d"`.
|
|
167
|
+
|
|
168
|
+
Получить слой Maps3D после создания карты:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const layer = map.getMaps3DLayer();
|
|
172
|
+
// или
|
|
173
|
+
MahalMap.getMaps3DLayer(map);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`getMaps3DLayer()` возвращает реальный инстанс `Maps3D` (не обёртку) — типы `mahal_map` описывают всю его публичную поверхность, так что подключающему сервису не нужно ставить или типизировать `@grammaps/maps3d-web` отдельно.
|
|
177
|
+
|
|
178
|
+
### Опции `maps3d` (расширенные)
|
|
179
|
+
|
|
180
|
+
Передаются в `MahalMap.create({ maps3d: {...} })` при `engine: "3d"`:
|
|
181
|
+
|
|
182
|
+
| Опция | Тип | По умолч. | Описание |
|
|
183
|
+
| ------------------ | ------------------------------------------------ | --------- | ------------------------------------------------------------------------- |
|
|
184
|
+
| `traffic` | `boolean \| { raster?, rasterMaxZoom?, graph? }` | `false` | Слой пробок. `raster: true` — картинкой с сервера вместо векторного слоя. |
|
|
185
|
+
| `minZoom` | `number` | `16` | Зум появления детальных 3D. |
|
|
186
|
+
| `lodBias` | `number` | `1` | `0` — всегда lod0 (детальный), `1` — lod1 на дальних зумах. |
|
|
187
|
+
| `memoryBudget` | `number` | `30` | Сколько моделей держать в сцене одновременно. |
|
|
188
|
+
| `maskReplaced` | `boolean` | `true` | Прятать заменённые OSM-объекты (`anchor=replace`). |
|
|
189
|
+
| `typeReplacements` | `boolean` | `true` | Рисовать замены по типу (`natural=tree` → 3D-дерево и т.п.). |
|
|
190
|
+
|
|
191
|
+
`buildings: true` включается автоматически при `enable3D: true` — переопределять не нужно, если только не требуется передать сам объект опций.
|
|
192
|
+
|
|
193
|
+
### 3D-здания
|
|
194
|
+
|
|
195
|
+
`Maps3D` рисует процедурные 3D-здания (three.js) вместо плоской `fill-extrusion` стиля: фаска кромок, вертикальный градиент и базовый цвет берутся из стиля, окна — из `metadata` пресета. При `engine: "3d"` слой создаётся и `attach`-ится к карте автоматически (`enable3D` по умолчанию `true`) — вручную поднимать `new Maps3D(...)` не нужно, только если требуется отдельный кастомный инстанс.
|
|
196
|
+
|
|
197
|
+
**Ручной `new Maps3D(...)` — отдельный сценарий.** Карту при этом создавайте с `enable3D: false`, иначе на неё повиснут два слоя Maps3D сразу (автоматический от `MahalMap` + ваш ручной) — дублирование зданий и лишний расход ресурсов:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const apiKey = "YOUR_MAP_API_KEY";
|
|
201
|
+
const base = "https://navi.gram.tj";
|
|
202
|
+
|
|
203
|
+
const map = MahalMap.create(
|
|
204
|
+
{ container: "map", engine: "3d", enable3D: false },
|
|
205
|
+
maplibregl,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const layer = new Maps3D({ apiKey, base, buildings: true });
|
|
209
|
+
await layer.attach(map.getMap()); // attach ждёт нативную карту MapLibre, не обёртку MahalMap
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Тема (окна/свет) приходит из `metadata` пресета стиля и применяется автоматически при `map.setStyle()` — пересоздавать слой не нужно. Ручные сеттеры перебивают её.
|
|
213
|
+
|
|
214
|
+
Это работает только для встроенных пресетов **без** явного `options.preset`: `setStyle()` меняет пресет по теме (`light`/`dark` → `GRAM_PRESETS`), а при заданном `preset` считает стиль зафиксированным и ничего не делает (см. [`MahalMap.ts`](src/core/MahalMap.ts:363)). Чтобы сменить пресет/тему в этом случае — пересоздайте карту с другим `preset` или вызовите `map.getMap().setStyle(...)` напрямую.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const layer = map.getMaps3DLayer();
|
|
218
|
+
|
|
219
|
+
const b = layer?.buildings;
|
|
220
|
+
b?.setWindowStyle(7); // тип окна 0..9 (сетка, лента, curtain wall, ...)
|
|
221
|
+
b?.setWindowDepth(0.85); // глубина ниши окна 0..1 (реальная геометрия вблизи)
|
|
222
|
+
b?.setWindowColor("#6b9ed1");
|
|
223
|
+
b?.setWindowFrameColor("#f2f2f4");
|
|
224
|
+
b?.setEdgeRadius(1.2); // скругление кромок, м
|
|
225
|
+
// Свет обычно НЕ задают руками — его несёт metadata стиля, сеттеры её перебивают
|
|
226
|
+
b?.setSunIntensity(3.2);
|
|
227
|
+
b?.setAmbient(0.76);
|
|
228
|
+
b?.setSky(0.91);
|
|
229
|
+
b?.setExposure(1.5);
|
|
230
|
+
|
|
231
|
+
layer?.onBuildingClick((info) => {
|
|
232
|
+
if (!info) return;
|
|
233
|
+
console.log(info.id, info.height, info.props);
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Вкл/выкл 3D-здания на лету
|
|
238
|
+
|
|
239
|
+
Переключение 3D-здания ⇄ штатные здания стиля, без пересоздания карты — через обёртку `MahalMap`. Она же умеет пересоздать слой, если его не было (`enable3D: false` при создании):
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
const map = MahalMap.getInstance("map");
|
|
243
|
+
|
|
244
|
+
map.toggle3DBuildings(false); // выкл
|
|
245
|
+
map.toggle3DBuildings(true); // вкл обратно
|
|
246
|
+
|
|
247
|
+
// статик-версия и SDK-фасад (mahal_map/sdk) работают так же
|
|
248
|
+
MahalMap.toggle3DBuildings(map, false);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`layer?.setBuildingsEnabled(false)` напрямую через слой **не используйте** — он не знает про штатный слой стиля `building-3d`, который `MahalMap` прячет при включённом 3D. Вызов только слоя оставит эти штатные здания скрытыми и одновременно выключит процедурные — в итоге зданий не будет видно вообще, до следующей перезагрузки стиля.
|
|
252
|
+
|
|
253
|
+
#### `map.whenMaps3DReady()`
|
|
254
|
+
|
|
255
|
+
`attach()` слоя асинхронный: сразу после `create()` слой уже есть, но `layer.buildings` (окна, свет, кромки) появляется только после attach. Чтобы не гадать — дождитесь готовности:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const layer = await map.whenMaps3DReady();
|
|
259
|
+
|
|
260
|
+
layer?.buildings?.setWindowStyle(4);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Промис резолвится в `undefined`, если движок не `"3d"`, слой выключен (`enable3D: false`) или attach упал — ошибка при этом уходит в `console.error`, а карта остаётся живой со штатными зданиями стиля.
|
|
264
|
+
|
|
265
|
+
### Подключение и выключение 3D-слоя: полный пример (Vue 3)
|
|
266
|
+
|
|
267
|
+
Кнопка-переключатель «3D ⇄ контуры», тонкая настройка окон и корректная очистка при размонтировании. Слой `Maps3D` поднимает и цепляет сама библиотека — вручную `new Maps3D(...)`, `transformRequest` и `attach()` писать не нужно.
|
|
268
|
+
|
|
269
|
+
```vue
|
|
270
|
+
<script setup lang="ts">
|
|
271
|
+
import { computed, onBeforeUnmount, onMounted, ref, shallowRef } from "vue";
|
|
272
|
+
import maplibregl from "maplibre-gl";
|
|
273
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
274
|
+
import { Maps3D } from "@grammaps/maps3d-web";
|
|
275
|
+
import { MahalMap, keyUtils } from "mahal_map";
|
|
276
|
+
|
|
277
|
+
const API_KEY = "YOUR_MAP_API_KEY";
|
|
278
|
+
|
|
279
|
+
const mahalMap = shallowRef<MahalMap | null>(null);
|
|
280
|
+
const is3dEnabled = ref(true);
|
|
281
|
+
const isLayerReady = ref(false);
|
|
282
|
+
|
|
283
|
+
const buildingModeText = computed(() =>
|
|
284
|
+
is3dEnabled.value ? "3D включено" : "Контуры",
|
|
285
|
+
);
|
|
286
|
+
const buildingToggleText = computed(() =>
|
|
287
|
+
is3dEnabled.value ? "Выключить 3D" : "Включить 3D",
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
function toggle3dBuildings() {
|
|
291
|
+
is3dEnabled.value = !is3dEnabled.value;
|
|
292
|
+
// Вкл/выкл детальных 3D-зданий: библиотека сама вернёт/спрячет плоские здания стиля.
|
|
293
|
+
mahalMap.value?.toggle3DBuildings(is3dEnabled.value);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
onMounted(async () => {
|
|
297
|
+
// Токен — один на всё (стиль, тайлы, Maps3D). Отдельный apiKey слою передавать не нужно.
|
|
298
|
+
keyUtils.saveKey(API_KEY);
|
|
299
|
+
|
|
300
|
+
const map = MahalMap.create(
|
|
301
|
+
{
|
|
302
|
+
container: "map",
|
|
303
|
+
engine: "3d", // платформа GramMaps вместо legacy-стилей
|
|
304
|
+
theme: "dark", // preset standard-night; "light" → road-urban-lab-v2
|
|
305
|
+
center: [68.787, 38.573],
|
|
306
|
+
zoom: 16.6,
|
|
307
|
+
pitch: 58, // без наклона экструзия не видна
|
|
308
|
+
bearing: -20,
|
|
309
|
+
enable3D: true, // значение по умолчанию для engine: "3d"
|
|
310
|
+
maps3d: { minZoom: 16, lodBias: 0 },
|
|
311
|
+
},
|
|
312
|
+
maplibregl,
|
|
313
|
+
Maps3D,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
mahalMap.value = map;
|
|
317
|
+
|
|
318
|
+
// Дожидаемся attach(): до него layer.buildings ещё нет.
|
|
319
|
+
const layer = await map.whenMaps3DReady();
|
|
320
|
+
const buildings = layer?.buildings;
|
|
321
|
+
|
|
322
|
+
if (!buildings) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
buildings.setWindowMinZoom?.(16);
|
|
327
|
+
buildings.setWindowStyle(4);
|
|
328
|
+
buildings.setWindowDepth(0.85);
|
|
329
|
+
buildings.setWindowColor("#6b9ed1");
|
|
330
|
+
buildings.setWindowFrameColor("#f2f2f4");
|
|
331
|
+
buildings.setWindowGlow?.(0.22);
|
|
332
|
+
buildings.setEdgeRadius(1.2);
|
|
333
|
+
|
|
334
|
+
isLayerReady.value = true;
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
onBeforeUnmount(() => {
|
|
338
|
+
isLayerReady.value = false;
|
|
339
|
+
// destroy() сам снимает слой Maps3D и удаляет карту MapLibre.
|
|
340
|
+
mahalMap.value?.destroy();
|
|
341
|
+
mahalMap.value = null;
|
|
342
|
+
});
|
|
343
|
+
</script>
|
|
344
|
+
|
|
345
|
+
<template>
|
|
346
|
+
<main class="map-page">
|
|
347
|
+
<div id="map" class="map" />
|
|
348
|
+
|
|
349
|
+
<section class="panel" aria-label="GramMaps 3D">
|
|
350
|
+
<span class="mode-label">{{ buildingModeText }}</span>
|
|
351
|
+
<button
|
|
352
|
+
type="button"
|
|
353
|
+
:aria-pressed="is3dEnabled"
|
|
354
|
+
:disabled="!isLayerReady"
|
|
355
|
+
@click="toggle3dBuildings"
|
|
356
|
+
>
|
|
357
|
+
{{ buildingToggleText }}
|
|
358
|
+
</button>
|
|
359
|
+
</section>
|
|
360
|
+
</main>
|
|
361
|
+
</template>
|
|
362
|
+
|
|
363
|
+
<style>
|
|
364
|
+
.map-page,
|
|
365
|
+
.map {
|
|
366
|
+
position: absolute;
|
|
367
|
+
inset: 0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.panel {
|
|
371
|
+
position: absolute;
|
|
372
|
+
top: 12px;
|
|
373
|
+
left: 12px;
|
|
374
|
+
z-index: 2;
|
|
375
|
+
}
|
|
376
|
+
</style>
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Что библиотека делает за вас против ручного подключения `@grammaps/maps3d-web`:
|
|
380
|
+
|
|
381
|
+
| Ручной код | Через `mahal_map` |
|
|
382
|
+
| ----------------------------------------------- | ----------------------------------------------------------------- |
|
|
383
|
+
| `style: base + "/maps/standard-night.json"` | `engine: "3d"` + `theme` (или `preset` / `base` явно) |
|
|
384
|
+
| `transformRequest: Maps3D.transformRequest(..)` | ставится автоматически (и без Maps3D — своим фолбэком с `?key=`) |
|
|
385
|
+
| `new Maps3D({...}); await layer.attach(map)` | `enable3D: true` + `maps3d: {...}`, `await map.whenMaps3DReady()` |
|
|
386
|
+
| `setBuildingsEnabled` + `buildings.setWindows` | `map.toggle3DBuildings(enabled)` — оба вызова разом |
|
|
387
|
+
| Плоские здания стиля поверх 3D после `setStyle` | скрываются сами на каждой загрузке стиля |
|
|
388
|
+
| `layer.destroy(); map.remove()` | `map.destroy()` |
|
|
389
|
+
|
|
390
|
+
#### То же самое без сборщика (browser SDK)
|
|
391
|
+
|
|
392
|
+
```html
|
|
393
|
+
<script src="https://unpkg.com/maplibre-gl@5.3.0/dist/maplibre-gl.js"></script>
|
|
394
|
+
<script src="https://cdn.jsdelivr.net/npm/@grammaps/maps3d-web/dist/maps3d.global.js"></script>
|
|
395
|
+
<script src="https://cp.mahal.tj/sdk/mahal_map.sdk.js?apikey=YOUR_MAP_API_KEY"></script>
|
|
396
|
+
|
|
397
|
+
<div id="map" style="width: 100%; height: 500px"></div>
|
|
398
|
+
<button id="toggle3d" type="button">Выключить 3D</button>
|
|
399
|
+
|
|
400
|
+
<script>
|
|
401
|
+
// Maps3D берётся из window.Maps3D — третий аргумент передавать не нужно.
|
|
402
|
+
const map = MahalMap.create({
|
|
403
|
+
container: "map",
|
|
404
|
+
engine: "3d",
|
|
405
|
+
theme: "dark",
|
|
406
|
+
center: [68.787, 38.573],
|
|
407
|
+
zoom: 16.6,
|
|
408
|
+
pitch: 58,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
let enabled = true;
|
|
412
|
+
|
|
413
|
+
document.getElementById("toggle3d").addEventListener("click", () => {
|
|
414
|
+
enabled = !enabled;
|
|
415
|
+
MahalMap.toggle3DBuildings(map, enabled);
|
|
416
|
+
document.getElementById("toggle3d").textContent = enabled
|
|
417
|
+
? "Выключить 3D"
|
|
418
|
+
: "Включить 3D";
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
MahalMap.whenMaps3DReady(map).then((layer) => {
|
|
422
|
+
layer?.buildings?.setWindowStyle(4);
|
|
423
|
+
});
|
|
424
|
+
</script>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Пробки
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
const layer = map.getMaps3DLayer();
|
|
431
|
+
|
|
432
|
+
layer?.setTraffic(true);
|
|
433
|
+
layer?.setTrafficOpacity(0.85);
|
|
434
|
+
layer?.setTrafficClicks(true); // попап скорости по клику
|
|
435
|
+
layer?.refreshTraffic();
|
|
436
|
+
|
|
437
|
+
// растровый вариант (картинка с сервера, без клика по дороге)
|
|
438
|
+
layer?.setTrafficRaster(true);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Жизненный цикл слоя
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
const layer = map.getMaps3DLayer();
|
|
445
|
+
|
|
446
|
+
layer?.setMinZoom(15);
|
|
447
|
+
layer?.setObjectsLight({ sun: 1.8, ambient: 0.45, sky: 1.1, exposure: 1.15 });
|
|
448
|
+
await layer?.refresh(); // перечитать модели после правок
|
|
449
|
+
await layer?.clearCache(); // сбросить IndexedDB-кеш ассетов
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Для полной остановки карты используйте только `map.destroy()` / `MahalMap.destroy(map)` — они сами вызывают `destroy()`/`remove()` у Maps3D слоя. **Не вызывайте `layer.destroy()`/`layer.remove()` напрямую**: `MahalMap` не узнает об этом и продолжит считать слой активным (внутренний `maps3dLayer`, `buildingsEnabled`, видимость штатных зданий стиля разойдутся с реальностью). Нужно временно выключить только 3D-здания — используйте `map.toggle3DBuildings(false)` (см. выше).
|
|
453
|
+
|
|
454
|
+
## MahalMap
|
|
455
|
+
|
|
456
|
+
`MahalMap` - основной класс карты. Конструктор закрыт, карту нужно создавать через `MahalMap.create()`.
|
|
457
|
+
|
|
458
|
+
### `MahalMap.create(options, maplibreObject?, maps3dCtor?)`
|
|
459
|
+
|
|
460
|
+
Создает новый инстанс карты и сохраняет его по ключу `container`. Для стандартных стилей перед созданием карты должен быть сохранен map token через `keyUtils.saveKey()`. В browser SDK token читается из обязательного URL-параметра `apikey`. Третий аргумент — необязательный конструктор `Maps3D`; без него SDK использует `window.Maps3D`.
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
const map = MahalMap.create(
|
|
464
|
+
{
|
|
465
|
+
container: "map",
|
|
466
|
+
center: [69.624024, 40.279687],
|
|
467
|
+
zoom: 12,
|
|
468
|
+
theme: "light",
|
|
469
|
+
lang: "tj",
|
|
470
|
+
},
|
|
471
|
+
maplibregl,
|
|
472
|
+
);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
В NPM-версии второй аргумент `maplibreObject` рекомендуется передавать явно. В browser SDK он берется из `window.maplibregl`.
|
|
476
|
+
|
|
477
|
+
### `MahalMap.createAsync(options, maplibreObject?, maps3dCtor?)`
|
|
478
|
+
|
|
479
|
+
Асинхронный вариант `create()`. Перед созданием карты проверяет подписку JSApi по map token и, если подписки нет, карту не создаёт вообще: MapLibre-инстанс не строится, промис отклоняется с ошибкой.
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
try {
|
|
483
|
+
const map = await MahalMap.createAsync(
|
|
484
|
+
{
|
|
485
|
+
container: "map",
|
|
486
|
+
center: [69.624024, 40.279687],
|
|
487
|
+
zoom: 12,
|
|
488
|
+
theme: "light",
|
|
489
|
+
},
|
|
490
|
+
maplibregl,
|
|
491
|
+
);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
// подписки нет — показать своё сообщение вместо карты
|
|
494
|
+
console.error(error);
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Правила проверки:
|
|
499
|
+
|
|
500
|
+
| Условие | Поведение |
|
|
501
|
+
| ------- | --------- |
|
|
502
|
+
| `engine: "legacy"` (по умолчанию), сервис ответил `success: true` | Карта создаётся. |
|
|
503
|
+
| `engine: "legacy"`, сервис ответил `success: false` | Карта **не** создаётся, промис отклоняется: `[MahalMap SDK] JSApi subscription is not active for this key: <message>`. |
|
|
504
|
+
| `engine: "legacy"`, проверка не дошла (сеть, CORS, таймаут) | Fail-open: `console.warn` и карта создаётся. Падение сервиса проверки не гасит карты. |
|
|
505
|
+
| `engine: "3d"` (GramMaps) | Проверка **пропускается**, запрос не отправляется. |
|
|
506
|
+
| Map token не сохранён | Проверка пропускается, дальше срабатывает обычная ошибка про `apikey`. |
|
|
507
|
+
|
|
508
|
+
`engine: "3d"` через `createAsync()` ведёт себя ровно как `create()` — доступ к платформе GramMaps контролируется параметром `?key=` на стороне самой платформы, отдельная подписка JSApi к ней отношения не имеет.
|
|
509
|
+
|
|
510
|
+
Синхронный `MahalMap.create()` проверку не выполняет и работает как раньше.
|
|
511
|
+
|
|
512
|
+
### Vue / Nuxt (ClientOnly, container как ref элемента)
|
|
513
|
+
|
|
514
|
+
`container` принимает и `id` строкой, и сам DOM-элемент. Ниже рабочий вариант с проверкой подписки: карта строится только после `createAsync()`, поэтому при отсутствии подписки в контейнере не останется пустой карты.
|
|
515
|
+
|
|
516
|
+
```vue
|
|
517
|
+
<template>
|
|
518
|
+
<ClientOnly>
|
|
519
|
+
<div class="overflow-hidden rounded-2xl border">
|
|
520
|
+
<div ref="mapElement" class="h-[360px] w-full" />
|
|
521
|
+
</div>
|
|
522
|
+
<template #fallback>
|
|
523
|
+
<div class="flex h-[360px] items-center justify-center">{{ loadingLabel }}</div>
|
|
524
|
+
</template>
|
|
525
|
+
</ClientOnly>
|
|
526
|
+
</template>
|
|
527
|
+
|
|
528
|
+
<script setup lang="ts">
|
|
529
|
+
import maplibregl from "maplibre-gl";
|
|
530
|
+
import "maplibre-gl/dist/maplibre-gl.css";
|
|
531
|
+
import type { MahalMap as MahalMapInstance } from "mahal_map";
|
|
532
|
+
import { onBeforeUnmount, onMounted, ref } from "vue";
|
|
533
|
+
|
|
534
|
+
const DUSHANBE_CENTER: [number, number] = [68.759965, 38.572419];
|
|
535
|
+
|
|
536
|
+
const mapElement = ref<HTMLElement | null>(null);
|
|
537
|
+
let map: MahalMapInstance | null = null;
|
|
538
|
+
// onMounted асинхронный: компонент может размонтироваться, пока идёт проверка подписки.
|
|
539
|
+
// Без флага карта создастся уже после unmount и останется висеть в памяти.
|
|
540
|
+
let disposed = false;
|
|
541
|
+
|
|
542
|
+
onMounted(async () => {
|
|
543
|
+
const { MahalMap, keyUtils } = await import("mahal_map");
|
|
544
|
+
|
|
545
|
+
keyUtils.saveKey(import.meta.env.VITE_MAHAL_API_KEY_TILE);
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
const instance = await MahalMap.createAsync(
|
|
549
|
+
{
|
|
550
|
+
container: mapElement.value,
|
|
551
|
+
center: DUSHANBE_CENTER,
|
|
552
|
+
zoom: 11,
|
|
553
|
+
},
|
|
554
|
+
maplibregl,
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
if (disposed) {
|
|
558
|
+
instance.destroy();
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
map = instance;
|
|
563
|
+
} catch (error) {
|
|
564
|
+
// подписки JSApi нет — показать своё сообщение вместо карты
|
|
565
|
+
console.error(error);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
onBeforeUnmount(() => {
|
|
570
|
+
disposed = true;
|
|
571
|
+
map?.destroy();
|
|
572
|
+
map = null;
|
|
573
|
+
});
|
|
574
|
+
</script>
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Замечания по этому паттерну:
|
|
578
|
+
|
|
579
|
+
- Импорт `mahal_map` внутри `onMounted` обязателен в SSR-окружении: пакет работает с `window`/`document`.
|
|
580
|
+
- `ClientOnly` (Nuxt) или эквивалент нужен по той же причине.
|
|
581
|
+
- Для карты с `container` в виде элемента инстанс регистрируется под ключом по умолчанию `"map"`. Для нескольких карт на странице передавайте `container` строкой с разными `id`, иначе `getInstance()` вернёт не тот инстанс.
|
|
582
|
+
- `map.destroy()` снимает карту, логотип и запись из реестра инстансов.
|
|
583
|
+
- Синхронный `MahalMap.create()` в этом же коде работает без изменений — если проверка подписки не нужна, замените `await MahalMap.createAsync(...)` на `MahalMap.create(...)`.
|
|
584
|
+
|
|
585
|
+
### `MahalMap.onReady(container, callback)`
|
|
586
|
+
|
|
587
|
+
Вызывает `callback`, когда карта создана и MapLibre завершил загрузку.
|
|
588
|
+
|
|
589
|
+
```ts
|
|
590
|
+
MahalMap.onReady("map", (maplibreMap) => {
|
|
591
|
+
maplibreMap.resize();
|
|
592
|
+
});
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
`callback` получает нативный `Map` объект из MapLibre GL JS.
|
|
596
|
+
|
|
597
|
+
### `MahalMap.getInstance(container)`
|
|
598
|
+
|
|
599
|
+
Возвращает ранее созданный инстанс карты по ключу контейнера.
|
|
600
|
+
|
|
601
|
+
```ts
|
|
602
|
+
const map = MahalMap.getInstance("map");
|
|
603
|
+
map.setZoom(14);
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
Если инстанс не найден, будет выброшена ошибка.
|
|
607
|
+
|
|
608
|
+
### `MahalMap.hasInstance(container)`
|
|
609
|
+
|
|
610
|
+
Проверяет, существует ли карта с таким ключом контейнера.
|
|
611
|
+
|
|
612
|
+
```ts
|
|
613
|
+
if (MahalMap.hasInstance("map")) {
|
|
614
|
+
const map = MahalMap.getInstance("map");
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### `MahalMap.removeInstance(container)`
|
|
619
|
+
|
|
620
|
+
Удаляет инстанс из внутреннего реестра и возвращает `boolean`.
|
|
621
|
+
|
|
622
|
+
```ts
|
|
623
|
+
const removed = MahalMap.removeInstance("map");
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Метод удаляет только запись из реестра. Для полного удаления карты используйте `destroy()`.
|
|
627
|
+
|
|
628
|
+
### `MahalMap.setDefaultLanguage(lang)`
|
|
629
|
+
|
|
630
|
+
Задает язык по умолчанию для новых карт.
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
MahalMap.setDefaultLanguage("ru");
|
|
634
|
+
keyUtils.saveKey("YOUR_MAP_API_KEY");
|
|
635
|
+
|
|
636
|
+
const map = MahalMap.create({ container: "map" }, maplibregl);
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
После этого новые карты без `options.lang` будут использовать русский стандартный стиль. Для `tj` или пустого значения стандартные стили будут только с `token`.
|
|
640
|
+
|
|
641
|
+
## Методы инстанса карты
|
|
642
|
+
|
|
643
|
+
### `map.getMap()`
|
|
644
|
+
|
|
645
|
+
Возвращает нативный MapLibre `Map`.
|
|
646
|
+
|
|
647
|
+
```ts
|
|
648
|
+
const maplibreMap = map.getMap();
|
|
649
|
+
maplibreMap.resize();
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Используйте этот метод, если нужна функция MapLibre, которой нет в Mahal Map SDK.
|
|
653
|
+
|
|
654
|
+
### `map.getCamera()`
|
|
655
|
+
|
|
656
|
+
Возвращает `CameraController` для управления камерой.
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
const camera = map.getCamera();
|
|
660
|
+
camera.flyTo({
|
|
661
|
+
center: [69.624024, 40.279687],
|
|
662
|
+
zoom: 14,
|
|
663
|
+
});
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### `map.setStyle(theme)`
|
|
667
|
+
|
|
668
|
+
Переключает стандартную тему карты.
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
map.setStyle("dark");
|
|
672
|
+
map.setStyle("light");
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
Если текущий язык `ru`, при переключении темы стиль будет загружен с `token=...&lang=ru`. Если язык `tj`, URL будет только с `token=...`.
|
|
676
|
+
|
|
677
|
+
Метод не меняет стиль, если карта создана с `autoAddVectorSource: true`. Если карта создана с пользовательским `style`, SDK не переписывает этот URL.
|
|
678
|
+
|
|
679
|
+
### `map.setLanguage(lang)`
|
|
680
|
+
|
|
681
|
+
Переключает язык стандартного стиля карты.
|
|
682
|
+
|
|
683
|
+
```ts
|
|
684
|
+
map.setLanguage("ru");
|
|
685
|
+
map.setLanguage("tj");
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
`ru` добавляет `lang=ru`, `tj` возвращает стандартный URL только с `token=...`. Метод не переписывает пользовательский `options.style` и не меняет vector style при `autoAddVectorSource: true`.
|
|
689
|
+
|
|
690
|
+
### `map.setCenter(center)`
|
|
691
|
+
|
|
692
|
+
Меняет центр карты.
|
|
693
|
+
|
|
694
|
+
```ts
|
|
695
|
+
map.setCenter([69.624024, 40.279687]);
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
Формат координат: `[lng, lat]`.
|
|
699
|
+
|
|
700
|
+
### `map.setZoom(zoom)`
|
|
701
|
+
|
|
702
|
+
Меняет zoom карты.
|
|
703
|
+
|
|
704
|
+
```ts
|
|
705
|
+
map.setZoom(13);
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### `map.addMarker(marker)`
|
|
709
|
+
|
|
710
|
+
Добавляет маркер на карту.
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
import { MahalMapDefaultMarker } from "mahal_map";
|
|
714
|
+
|
|
715
|
+
const marker = new MahalMapDefaultMarker({
|
|
716
|
+
coordinates: [69.624024, 40.279687],
|
|
717
|
+
color: "#278960",
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
map.addMarker(marker);
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
Маркер должен реализовать интерфейс:
|
|
724
|
+
|
|
725
|
+
```ts
|
|
726
|
+
interface IMapMarker {
|
|
727
|
+
getElement(): HTMLElement;
|
|
728
|
+
getCoordinates(): [number, number];
|
|
729
|
+
isDraggable?(): boolean;
|
|
730
|
+
getAnchor?():
|
|
731
|
+
| "center"
|
|
732
|
+
| "top"
|
|
733
|
+
| "bottom"
|
|
734
|
+
| "left"
|
|
735
|
+
| "right"
|
|
736
|
+
| "top-left"
|
|
737
|
+
| "top-right"
|
|
738
|
+
| "bottom-left"
|
|
739
|
+
| "bottom-right";
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### `map.destroy()`
|
|
744
|
+
|
|
745
|
+
Удаляет логотип SDK, вызывает `remove()` у MapLibre карты и удаляет инстанс из внутреннего реестра.
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
map.destroy();
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
Используйте при размонтировании страницы или компонента.
|
|
752
|
+
|
|
753
|
+
## Статические методы-обертки
|
|
754
|
+
|
|
755
|
+
Для browser SDK и случаев, когда удобнее работать с функциями, доступны статические методы:
|
|
756
|
+
|
|
757
|
+
```ts
|
|
758
|
+
MahalMap.getMap(map);
|
|
759
|
+
MahalMap.getCamera(map);
|
|
760
|
+
MahalMap.setStyle(map, "dark");
|
|
761
|
+
MahalMap.setLanguage(map, "ru");
|
|
762
|
+
MahalMap.setCenter(map, [69.624024, 40.279687]);
|
|
763
|
+
MahalMap.setZoom(map, 14);
|
|
764
|
+
MahalMap.addMarker(map, marker);
|
|
765
|
+
MahalMap.getMaps3DLayer(map);
|
|
766
|
+
MahalMap.whenMaps3DReady(map);
|
|
767
|
+
MahalMap.toggle3DBuildings(map, false);
|
|
768
|
+
MahalMap.destroy(map);
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
Эти методы вызывают соответствующие методы переданного инстанса.
|
|
772
|
+
|
|
773
|
+
## Browser SDK функции
|
|
774
|
+
|
|
775
|
+
При подключении `mahal_map.sdk.js` функции доступны на глобальном объекте `MahalMap`.
|
|
776
|
+
|
|
777
|
+
```js
|
|
778
|
+
const map = MahalMap.create({
|
|
779
|
+
container: "map",
|
|
780
|
+
center: [69.624024, 40.279687],
|
|
781
|
+
zoom: 12,
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
MahalMap.setStyle(map, "dark");
|
|
785
|
+
MahalMap.setLanguage(map, "ru");
|
|
786
|
+
MahalMap.setZoom(map, 14);
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
Доступные функции карты в browser SDK:
|
|
790
|
+
|
|
791
|
+
| Функция | Описание |
|
|
792
|
+
| -------------------------------------- | ------------------------------------------------------------- |
|
|
793
|
+
| `create(options)` | Создает карту. Требует `apikey` в URL SDK скрипта. |
|
|
794
|
+
| `createAsync(options)` | Создает карту после проверки подписки JSApi (только legacy). |
|
|
795
|
+
| `onReady(container, callback)` | Выполняет callback после загрузки карты. |
|
|
796
|
+
| `getInstance(container)` | Возвращает инстанс карты. |
|
|
797
|
+
| `hasInstance(container)` | Проверяет наличие инстанса. |
|
|
798
|
+
| `removeInstance(container)` | Удаляет инстанс из реестра. |
|
|
799
|
+
| `getMap(instance)` | Возвращает нативный MapLibre Map. |
|
|
800
|
+
| `getCamera(instance)` | Возвращает CameraController. |
|
|
801
|
+
| `setStyle(instance, theme)` | Переключает тему стандартного стиля. |
|
|
802
|
+
| `setLanguage(instance, lang)` | Переключает язык стандартного стиля. |
|
|
803
|
+
| `setCenter(instance, center)` | Меняет центр карты. |
|
|
804
|
+
| `setZoom(instance, zoom)` | Меняет zoom карты. |
|
|
805
|
+
| `addMarker(instance, marker)` | Добавляет маркер. |
|
|
806
|
+
| `getMaps3DLayer(instance)` | Возвращает инстанс слоя Maps3D (только `engine: "3d"`). |
|
|
807
|
+
| `whenMaps3DReady(instance)` | Промис слоя Maps3D после `attach()` (готов `layer.buildings`). |
|
|
808
|
+
| `toggle3DBuildings(instance, enabled)` | Вкл/выкл детальные 3D-здания на лету (только `engine: "3d"`). |
|
|
809
|
+
| `destroy(instance)` | Полностью удаляет карту. |
|
|
810
|
+
| `loadKeyFromScriptUrl()` | Читает `apikey` из URL SDK скрипта. |
|
|
811
|
+
| `loadLanguageFromScriptUrl()` | Читает `lang` из URL SDK скрипта. |
|
|
812
|
+
|
|
813
|
+
## CameraController
|
|
814
|
+
|
|
815
|
+
`CameraController` доступен через `map.getCamera()` или `MahalMap.getCamera(map)`.
|
|
816
|
+
|
|
817
|
+
### `camera.setZoom(zoom, smooth?)`
|
|
818
|
+
|
|
819
|
+
Меняет zoom. Если `smooth` не передан, используется плавная анимация.
|
|
820
|
+
|
|
821
|
+
```ts
|
|
822
|
+
camera.setZoom(14);
|
|
823
|
+
camera.setZoom(10, false);
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### `camera.setBearing(bearing, smooth?)`
|
|
827
|
+
|
|
828
|
+
Меняет поворот карты.
|
|
829
|
+
|
|
830
|
+
```ts
|
|
831
|
+
camera.setBearing(45);
|
|
832
|
+
camera.setBearing(0, false);
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### `camera.setPitch(pitch, smooth?)`
|
|
836
|
+
|
|
837
|
+
Меняет наклон карты.
|
|
838
|
+
|
|
839
|
+
```ts
|
|
840
|
+
camera.setPitch(60);
|
|
841
|
+
camera.setPitch(0, false);
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
### `camera.toggle3D(is3D)`
|
|
845
|
+
|
|
846
|
+
Включает или выключает 3D-вид.
|
|
847
|
+
|
|
848
|
+
```ts
|
|
849
|
+
camera.toggle3D(true);
|
|
850
|
+
camera.toggle3D(false);
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
При включении задается `pitch: 60`, при выключении `pitch: 0` и `bearing: 0`.
|
|
854
|
+
|
|
855
|
+
### `camera.resetNorth()`
|
|
856
|
+
|
|
857
|
+
Возвращает карту на север и сбрасывает наклон.
|
|
858
|
+
|
|
859
|
+
```ts
|
|
860
|
+
camera.resetNorth();
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
### `camera.flyTo(options)`
|
|
864
|
+
|
|
865
|
+
Выполняет плавный перелет камеры. Принимает `FlyToOptions` из MapLibre GL JS.
|
|
866
|
+
|
|
867
|
+
```ts
|
|
868
|
+
camera.flyTo({
|
|
869
|
+
center: [69.624024, 40.279687],
|
|
870
|
+
zoom: 15,
|
|
871
|
+
});
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
SDK добавляет стандартные значения `speed`, `curve` и `essential`, но переданные значения могут их переопределить.
|
|
875
|
+
|
|
876
|
+
### `camera.getPitch()`
|
|
877
|
+
|
|
878
|
+
Возвращает текущий наклон карты.
|
|
879
|
+
|
|
880
|
+
```ts
|
|
881
|
+
const pitch = camera.getPitch();
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
## MahalMapDefaultMarker
|
|
885
|
+
|
|
886
|
+
`MahalMapDefaultMarker` - готовый маркер, который можно использовать с `map.addMarker()`.
|
|
887
|
+
|
|
888
|
+
```ts
|
|
889
|
+
import { MahalMapDefaultMarker } from "mahal_map";
|
|
890
|
+
|
|
891
|
+
const marker = new MahalMapDefaultMarker({
|
|
892
|
+
coordinates: [69.624024, 40.279687],
|
|
893
|
+
color: "#278960",
|
|
894
|
+
draggable: true,
|
|
895
|
+
anchor: "bottom",
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
map.addMarker(marker);
|
|
899
|
+
```
|
|
900
|
+
|
|
901
|
+
Параметры:
|
|
902
|
+
|
|
903
|
+
| Параметр | Тип | Описание |
|
|
904
|
+
| ------------- | ------------------ | ----------------------------------------------------------------------- |
|
|
905
|
+
| `coordinates` | `[number, number]` | Координаты маркера в формате `[lng, lat]`. |
|
|
906
|
+
| `draggable` | `boolean` | Делает HTML-элемент маркера draggable. |
|
|
907
|
+
| `anchor` | `string` | Anchor MapLibre маркера. |
|
|
908
|
+
| `color` | `string` | Цвет стандартного SVG маркера или замена `fill` в пользовательском SVG. |
|
|
909
|
+
| `svg` | `string` | Полностью пользовательский SVG. |
|
|
910
|
+
| `innerSvg` | `string` | SVG внутри стандартного маркера. |
|
|
911
|
+
| `innerUrl` | `string` | URL изображения внутри стандартного маркера. |
|
|
912
|
+
|
|
913
|
+
Методы маркера:
|
|
914
|
+
|
|
915
|
+
```ts
|
|
916
|
+
marker.getElement();
|
|
917
|
+
marker.getCoordinates();
|
|
918
|
+
marker.isDraggable();
|
|
919
|
+
marker.getAnchor();
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
## Несколько карт
|
|
923
|
+
|
|
924
|
+
Каждая карта сохраняется по ключу `container`.
|
|
925
|
+
|
|
926
|
+
```ts
|
|
927
|
+
const mainMap = MahalMap.create({ container: "main" }, maplibregl);
|
|
928
|
+
const miniMap = MahalMap.create({ container: "mini" }, maplibregl);
|
|
929
|
+
|
|
930
|
+
MahalMap.getInstance("main").setZoom(14);
|
|
931
|
+
MahalMap.getInstance("mini").setStyle("dark");
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
Если `container` не передан, ключом будет `"map"`. Для нескольких карт всегда указывайте разные контейнеры.
|
|
935
|
+
|
|
936
|
+
## Пользовательский стиль
|
|
937
|
+
|
|
938
|
+
Можно передать любой MapLibre style URL:
|
|
939
|
+
|
|
940
|
+
```ts
|
|
941
|
+
const map = MahalMap.create(
|
|
942
|
+
{
|
|
943
|
+
container: "map",
|
|
944
|
+
style: "https://example.com/custom-style.json",
|
|
945
|
+
},
|
|
946
|
+
maplibregl,
|
|
947
|
+
);
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
Когда передан `style`, SDK не добавляет `token` или `lang=ru` и не подменяет URL при `setStyle()` или `setLanguage()`.
|
|
951
|
+
|
|
952
|
+
## Жизненный цикл
|
|
953
|
+
|
|
954
|
+
Рекомендуемый порядок работы:
|
|
955
|
+
|
|
956
|
+
1. Создать DOM-контейнер.
|
|
957
|
+
2. Сохранить map token через `keyUtils.saveKey()` или передать `apikey` в URL browser SDK.
|
|
958
|
+
3. Создать карту через `MahalMap.create()`.
|
|
959
|
+
4. Дождаться загрузки через `MahalMap.onReady()`, если нужен доступ к загруженной MapLibre карте.
|
|
960
|
+
5. Добавлять маркеры, менять камеру, тему или язык.
|
|
961
|
+
6. Вызвать `destroy()` при удалении страницы или компонента.
|
|
962
|
+
|
|
963
|
+
```ts
|
|
964
|
+
const map = MahalMap.create({ container: "map" }, maplibregl);
|
|
965
|
+
|
|
966
|
+
MahalMap.onReady("map", () => {
|
|
967
|
+
map.setZoom(13);
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// При размонтировании:
|
|
971
|
+
map.destroy();
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
## MeasureTool (линейка и планиметр)
|
|
975
|
+
|
|
976
|
+
`MeasureTool` — инструмент измерения расстояния и площади прямо на карте (линейка + планиметр, как в Яндекс.Картах). Полностью самодостаточен: сам рисует точки, линии, полигон и подписи поверх MapLibre, сам обрабатывает клики/drag/удаление точек. Приложение только передает стили (цвета, иконки, подписи единиц) и слушает `onChange`.
|
|
977
|
+
|
|
978
|
+
```ts
|
|
979
|
+
import { MeasureTool } from "mahal_map";
|
|
980
|
+
|
|
981
|
+
const map = MahalMap.getInstance("map").getMap();
|
|
982
|
+
|
|
983
|
+
const measureTool = new MeasureTool(map, {
|
|
984
|
+
mode: "distance",
|
|
985
|
+
style: {
|
|
986
|
+
lineColor: "#278960",
|
|
987
|
+
pointColor: "#FFFFFF",
|
|
988
|
+
pointStrokeColor: "#278960",
|
|
989
|
+
fillColor: "#278960",
|
|
990
|
+
fillOpacity: 0.15,
|
|
991
|
+
},
|
|
992
|
+
labels: {
|
|
993
|
+
meters: "м",
|
|
994
|
+
kilometers: "км",
|
|
995
|
+
squareMeters: "м²",
|
|
996
|
+
squareKilometers: "км²",
|
|
997
|
+
},
|
|
998
|
+
onChange: (state) => {
|
|
999
|
+
console.log(state.mode, state.draft, state.shapes);
|
|
1000
|
+
},
|
|
1001
|
+
onCloseRequest: () => {
|
|
1002
|
+
measureTool.stop();
|
|
1003
|
+
},
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
measureTool.start("distance");
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### Важно: цвета — реальные, не CSS-переменные
|
|
1010
|
+
|
|
1011
|
+
MapLibre GL проверяет `paint`-свойства слоя и не понимает `var(--primary)` — только hex/rgb. Если в приложении цвета живут в CSS-переменных (тема light/dark), резолвьте их в реальное значение перед передачей в `style`:
|
|
1012
|
+
|
|
1013
|
+
```ts
|
|
1014
|
+
const primary =
|
|
1015
|
+
getComputedStyle(document.documentElement)
|
|
1016
|
+
.getPropertyValue("--primary")
|
|
1017
|
+
.trim() || "#278960";
|
|
1018
|
+
|
|
1019
|
+
const measureTool = new MeasureTool(map, {
|
|
1020
|
+
style: { lineColor: primary, pointStrokeColor: primary, fillColor: primary },
|
|
1021
|
+
});
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
Значения `badgeBackground`, `badgeTextColor` и другие DOM-стили бейджа — обычный CSS, туда `var(--x)` передавать можно.
|
|
1025
|
+
|
|
1026
|
+
### Конструктор: `new MeasureTool(map, options?)`
|
|
1027
|
+
|
|
1028
|
+
| Опция | Тип | Описание |
|
|
1029
|
+
| ---------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1030
|
+
| `mode` | `"distance" \| "area"` | Режим по умолчанию. По умолчанию `"distance"`. |
|
|
1031
|
+
| `sourceIdPrefix` | `string` | Префикс id source/layer на карте. По умолчанию генерируется уникальный (`"mahal-measure-1"`, `"mahal-measure-2"`, ...) — так несколько инструментов на одной карте не конфликтуют. Задайте явно, если нужен предсказуемый id. |
|
|
1032
|
+
| `style` | `MeasureStyleOptions` | Цвета и размеры точек/линий/заливки/бейджа. |
|
|
1033
|
+
| `icons` | `MeasureIcons` | SVG-иконки `trash` / `close` / `check` для бейджей. |
|
|
1034
|
+
| `labels` | `MeasureLabels` | Подписи единиц: `meters`, `kilometers`, `squareMeters`, `squareKilometers`. |
|
|
1035
|
+
| `onChange` | `(state: MeasureState) => void` | Вызывается при любом изменении: новая точка, drag, смена режима и т.д. |
|
|
1036
|
+
| `onCloseRequest` | `() => void` | Вызывается по клику на ✕ в бейджике активной фигуры — решение "выключить инструмент" остается за приложением. |
|
|
1037
|
+
|
|
1038
|
+
### Методы
|
|
1039
|
+
|
|
1040
|
+
| Метод | Описание |
|
|
1041
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
1042
|
+
| `start(mode?)` | Включает инструмент и начинает/продолжает рисование в указанном режиме. |
|
|
1043
|
+
| `stop()` | Выключает инструмент, прячет активный бейдж. Сохраненные фигуры остаются на карте. |
|
|
1044
|
+
| `setMode(mode)` | Переключает режим. Если фигура уже рисуется — её точки сохраняются, меняется только тип (линия ⇄ полигон), как в Яндекс.Картах. |
|
|
1045
|
+
| `finishDraft()` | Завершает текущую фигуру (если валидна — от 2 точек для линии, от 3 для полигона) и начинает новую. |
|
|
1046
|
+
| `removeShape(shapeId)` | Удаляет фигуру (черновик или уже сохраненную) целиком. |
|
|
1047
|
+
| `removePoint(shapeId, pointId)` | Удаляет одну точку фигуры. |
|
|
1048
|
+
| `clearAll()` | Удаляет все фигуры и черновик. |
|
|
1049
|
+
| `getState()` | Возвращает текущий `MeasureState` (снимок, без подписки). |
|
|
1050
|
+
| `setStyleOptions(style)` | Обновляет палитру (частично, `Partial<MeasureStyleOptions>`) без пересоздания инструмента: перекрашивает существующие слои и бейджи. Нужен при смене темы карты. |
|
|
1051
|
+
| `refresh()` | Пересоздает источники/слои и перерисовывает фигуры. Инструмент делает это сам после `setStyle()`; метод оставлен как страховка. |
|
|
1052
|
+
| `destroy()` | Полностью снимает слои, обработчики и DOM-бейджи. Вызывать при размонтировании. Повторный вызов безопасен. |
|
|
1053
|
+
|
|
1054
|
+
### Смена стиля карты (тема, язык)
|
|
1055
|
+
|
|
1056
|
+
`map.setStyle()` — а значит и `mahalMap.setStyle('dark')`, и смена языка — применяется MapLibre диффом: все слои, добавленные в рантайме, удаляются как отсутствующие в новом стиле, и событие `style.load` при этом не эмитится. `MeasureTool` переживает это сам: он слушает `styledata` и восстанавливает источники/слои с теми же id, а `render()` создает недостающие слои при каждой отрисовке. Фигуры, черновик и `getState()` не меняются, лишних `onChange` не будет.
|
|
1057
|
+
|
|
1058
|
+
Хосту делать ничего не нужно — обходы вида `map.fire('style.load')` после `setStyle()` можно убирать. Цвета за темой карты не следуют автоматически: после переключения вызовите `setStyleOptions()` с новой палитрой.
|
|
1059
|
+
|
|
1060
|
+
```ts
|
|
1061
|
+
mahalMap.setStyle("dark");
|
|
1062
|
+
measureTool.setStyleOptions({
|
|
1063
|
+
lineColor: "#4ADE80",
|
|
1064
|
+
pointStrokeColor: "#4ADE80",
|
|
1065
|
+
fillColor: "#4ADE80",
|
|
1066
|
+
badgeBackground: "#19191A",
|
|
1067
|
+
badgeTextColor: "#FFFFFF",
|
|
1068
|
+
});
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
### Взаимодействие на карте
|
|
1072
|
+
|
|
1073
|
+
- клик по карте — добавляет точку в текущую фигуру;
|
|
1074
|
+
- перетаскивание существующей точки — двигает её, расстояние/площадь пересчитываются на лету;
|
|
1075
|
+
- правый клик по точке — удаляет её;
|
|
1076
|
+
- наведение на линию/ребро полигона — показывает точку-призрак прямо под курсором; зажатие мыши вставляет в этом месте новую точку и сразу тянет её (как вставка узла в Яндекс.Картах);
|
|
1077
|
+
- бейдж активной (незавершенной) фигуры — показывает значение и три кнопки: ✓ (завершить фигуру), 🗑 (удалить), ✕ (вызывает `onCloseRequest`);
|
|
1078
|
+
- у уже сохраненных фигур — постоянный мини-бейдж: только значение и 🗑 (удалить), не пропадает при рисовании следующей фигуры.
|
|
1079
|
+
|
|
1080
|
+
### `MeasureState`
|
|
1081
|
+
|
|
1082
|
+
```ts
|
|
1083
|
+
interface MeasureState {
|
|
1084
|
+
active: boolean;
|
|
1085
|
+
mode: "distance" | "area";
|
|
1086
|
+
draft: MeasureShape | null;
|
|
1087
|
+
shapes: MeasureShape[];
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
interface MeasureShape {
|
|
1091
|
+
id: string;
|
|
1092
|
+
mode: "distance" | "area";
|
|
1093
|
+
points: { id: string; lngLat: [number, number] }[];
|
|
1094
|
+
closed: boolean;
|
|
1095
|
+
distance: number; // метры
|
|
1096
|
+
area: number; // квадратные метры, 0 для линии
|
|
1097
|
+
}
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
## Сервисы поиска и маршрутов
|
|
1101
|
+
|
|
1102
|
+
Сервисы работают независимо от карты: их можно вызывать без `MahalMap.create()`. Токен передаётся аргументом в каждый вызов — сохранённый через `keyUtils.saveKey()` map token для них не используется.
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
import { Search, SearchPoi, SearchByLocation, CheckJSApi, Router } from "mahal_map";
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
### `Search(text, token, additionalParam?)`
|
|
1109
|
+
|
|
1110
|
+
Поиск адресов (геокодер). Вызовы дебаунсятся на 500 мс: при вводе по символу уходит один запрос.
|
|
1111
|
+
|
|
1112
|
+
```ts
|
|
1113
|
+
const results = await Search("Рудаки 33", token, {
|
|
1114
|
+
lat: "38.5598",
|
|
1115
|
+
lng: "68.7870",
|
|
1116
|
+
limit: 10,
|
|
1117
|
+
});
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
| Параметр | Тип | Описание |
|
|
1121
|
+
| -------- | --- | -------- |
|
|
1122
|
+
| `text` | `string` | Строка поиска. |
|
|
1123
|
+
| `token` | `string` | Токен сервиса. Обязателен, иначе `[MahalMap SDK] Search token is required`. |
|
|
1124
|
+
| `additionalParam.lat` / `.lng` | `string` | Точка для сортировки результатов по удалённости. |
|
|
1125
|
+
| `additionalParam.limit` | `number` | Максимум результатов. |
|
|
1126
|
+
| `additionalParam.type` | `string` | Фильтр по типу объекта. |
|
|
1127
|
+
|
|
1128
|
+
Возвращает `ISearchResponse[]`.
|
|
1129
|
+
|
|
1130
|
+
### `SearchPoi(text, token, additionalParam?)`
|
|
1131
|
+
|
|
1132
|
+
Поиск POI (организации, объекты). Сигнатура и дебаунс те же, что у `Search`, таймер отдельный — параллельный ввод в двух полях не перебивает запросы друг друга.
|
|
1133
|
+
|
|
1134
|
+
```ts
|
|
1135
|
+
const places = await SearchPoi("кафе", token, { lat: "38.5598", lng: "68.7870", limit: 20 });
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
Возвращает `ISearchResponse[]`.
|
|
1139
|
+
|
|
1140
|
+
### `SearchByLocation(params)`
|
|
1141
|
+
|
|
1142
|
+
Обратный геокодинг: адреса и POI по координатам. Без дебаунса.
|
|
1143
|
+
|
|
1144
|
+
```ts
|
|
1145
|
+
const res = await SearchByLocation({
|
|
1146
|
+
lat: 38.5598,
|
|
1147
|
+
lng: 68.787,
|
|
1148
|
+
token,
|
|
1149
|
+
});
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
| Поле | Тип | Обязательное |
|
|
1153
|
+
| ---- | --- | ------------ |
|
|
1154
|
+
| `lat` | `string \| number` | да |
|
|
1155
|
+
| `lng` | `string \| number` | да |
|
|
1156
|
+
| `token` | `string` | да |
|
|
1157
|
+
| `type` | `string` | нет |
|
|
1158
|
+
|
|
1159
|
+
### `CheckJSApi(token)`
|
|
1160
|
+
|
|
1161
|
+
Проверяет, активна ли подписка JSApi у токена.
|
|
1162
|
+
|
|
1163
|
+
```ts
|
|
1164
|
+
const { success, message } = await CheckJSApi(token);
|
|
1165
|
+
|
|
1166
|
+
if (!success) {
|
|
1167
|
+
console.warn("Подписка не активна:", message);
|
|
1168
|
+
}
|
|
1169
|
+
```
|
|
1170
|
+
|
|
1171
|
+
Промис резолвится и при отрицательном ответе — `success: false` это результат проверки, а не сбой. Исключение бросается только если вызов не дошёл до сервиса (сеть, CORS, таймаут) или токен пустой.
|
|
1172
|
+
|
|
1173
|
+
Этот же вызов используется внутри [`MahalMap.createAsync()`](#mahalmapcreateasyncoptions-maplibreobject-maps3dctor) для legacy-карты.
|
|
1174
|
+
|
|
1175
|
+
### `Router(points, typeData, token)`
|
|
1176
|
+
|
|
1177
|
+
Маршрут между точками.
|
|
1178
|
+
|
|
1179
|
+
```ts
|
|
1180
|
+
const routes = await Router(
|
|
1181
|
+
[
|
|
1182
|
+
[68.787, 38.5598],
|
|
1183
|
+
[68.809, 38.561],
|
|
1184
|
+
],
|
|
1185
|
+
"geojson",
|
|
1186
|
+
token,
|
|
1187
|
+
);
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
| Параметр | Тип | Описание |
|
|
1191
|
+
| -------- | --- | -------- |
|
|
1192
|
+
| `points` | `number[][]` | Точки в формате `[lng, lat]`. |
|
|
1193
|
+
| `typeData` | `string` | `"geojson"` — декодирует polyline в массив координат. Другое значение оставляет `geometry` строкой polyline. |
|
|
1194
|
+
| `token` | `string` | Токен сервиса. |
|
|
1195
|
+
|
|
1196
|
+
Возвращает `IRoute[]`.
|
|
1197
|
+
|
|
1198
|
+
## License
|
|
1199
|
+
|
|
1200
|
+
ISC
|