# JavaScript SDK RC9

Справочник публичных методов, параметров и событий SDK 1.0.0-rc.9.

## Подключение и типы

SDK поставляется как браузерный ES-модуль. Подключите `https://cdn.mmapps.ru/sdk/1.0.0-rc.9/index.css`, затем импортируйте нужные функции из `https://cdn.mmapps.ru/sdk/1.0.0-rc.9/index.js`. [Готовая HTML-страница](https://mmapps.ru/docs/maps).

Точные типы поставляются вместе с выпуском: [точка входа TypeScript](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/index.d.ts), [экспорты SDK](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/index.d.ts). Ссылки на типы ниже относятся к той же версии. Обозначение `?` в сигнатурах означает необязательный аргумент.

| Экспорт | Назначение |
| --- | --- |
| `createClient` | Карта и клиент сервисного API |
| `createMap` | Отдельная карта |
| `createMapWidget` | Карта с готовыми инструментами |
| `checkedPoint` | Проверка координат |
| `createGeocoder` | Адресный клиент со своим обработчиком запросов |
| `createSearchController` | Состояние поиска и выбор адреса |
| `attachSearchControl` | Форма поиска в DOM-контейнере |
| `attachRouteControl` | Редактор маршрута |
| `attachContextMenu` | Меню действий с точкой |
| `createRouteOverlay` | Отображение рассчитанного маршрута |
| `routeView`, `routeCandidates`, `geocodedRouteView` | Подготовка ответа маршрута для интерфейса |
| `matrixView`, `geocodedMatrixView` | Проверка ответа матрицы |
| `createTimeline`, `attachTimelineControl` | Временные данные и панель воспроизведения |
| `MapSDKError`, `GeocoderError` | Классы ошибок |

## Клиент и сервисные запросы

`createClient(options?) → GeoClient`. Без параметров создаёт клиент бесплатной карты. Параметры `ClientOptions`: `apiBaseUrl`, `apiKey`, `catalogUrl`, `fetch`, `timeoutMs`.

```javascript
import { createClient } from 'https://cdn.mmapps.ru/sdk/1.0.0-rc.9/index.js';

const geo = createClient({
  apiBaseUrl: 'https://mmapps.ru',
  apiKey: 'YOUR_BROWSER_KEY',
});
const operationKey = crypto.randomUUID();
const result = await geo.geocoder.search(
  { q: 'Примерная улица', limit: 5 },
  { operationKey },
);
```

`apiBaseUrl` — адрес API без пути, параметров и фрагмента. Для сервисных вызовов используйте HTTPS и браузерный ключ с нужными правами и разрешённым origin. `YOUR_BROWSER_KEY` — условное обозначение. Серверный ключ в SDK не передавайте.

| Метод | Результат |
| --- | --- |
| `geo.createMap(options)` | `Promise<GeoMap>` |
| `geo.geocoder.search(query, options?)` | Подсказки адресов |
| `geo.geocoder.geocode(query, options?)` | Координаты по адресу |
| `geo.geocoder.reverse(query, options?)` | Адрес по точке |
| `geo.geocoder.resolve(ref, options?)` | Выбранный объект |
| `geo.routes.calculate(request, options?)` | Маршрут по координатам |
| `geo.routes.geocode(request, options?)` | Маршрут по адресам и подтверждённым точкам |
| `geo.routes.matrix(request, options?)` | Матрица по координатам |
| `geo.routes.matrixGeocode(request, options?)` | Матрица по адресам и подтверждённым точкам |
| `geo.on('usage', callback)` | Подписка на учёт операции; возвращает функцию отписки |
| `geo.on('error', callback)` | Подписка на ошибку; возвращает функцию отписки |
| `geo.destroy()` | Завершение работы клиента |

`OperationOptions` содержит `signal?: AbortSignal` и `operationKey?: string`. Для повторов сохраняйте ключ и тело запроса; изменение данных — новая операция. Если ключ не задан, SDK создаёт его сам. Отмена ожидания через `signal` не доказывает, что запрос не успел выполниться.

`usage` передаёт `operationId`, `requestId`, `chargedMicrorub`, `funding`, `retryAfter`, `stage`. При наличии в ответе доступны `quantity`, `freeUnits`, `paidUnits`, `meter`. Суммы и количества передаются строками. [Правила оплаты](https://mmapps.ru/docs/billing), [типы клиента](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/client.d.ts).

## Создание карты

`createMap(options) → Promise<GeoMap>` и `geo.createMap(options) → Promise<GeoMap>`. После создания дождитесь `map.ready`.

| Параметр | Назначение |
| --- | --- |
| `element` | Обязательный DOM-контейнер с заданной высотой |
| `mapId`, `catalogUrl` | Выбор карты и каталога у `geo.createMap`; по умолчанию `ru` и публичный каталог |
| `styleUrl` | URL собственного публичного стиля |
| `basemap` | `{type: 'public', styleUrl, …}` или `{type: 'public_cdn', catalogUrl, mapId}` |
| `view` | Начальные `center`, `zoom`, `bearing`, `pitch` |
| `initialPoint`, `editable` | Исходная точка и возможность её изменения |
| `theme` | `'light'` или `'dark'` |
| `houseNumbers` | `{minZoom?, sizeScale?}` для поддерживаемой подложки |
| `controls`, `interactions` | Инструменты и жесты, описанные ниже |
| `renderMarker` | Отрисовщик маркера исходной точки и совместимых маркеров |
| `workerUrl` | URL worker из того же выпуска |
| `signal`, `timeoutMs` | Отмена и таймаут загрузки |
| `fetch` | Свой fetch у `createMap`; для `geo.createMap` задаётся на клиенте |
| `onReady`, `onLoading`, `onError` | Обратные вызовы состояния |

У публичного `basemap` дополнительно возможны `styleSha256`, `styleBytes`, `assetSizes`. Для обычного подключения достаточно `mapId`. `houseNumbers.minZoom` принимает 0–20, `sizeScale` — 0,5–2; стандартные значения — 16 и 1. Настройки меняют существующие подписи, а не добавляют данные.

Координаты `GeoPoint` имеют вид `{latitude, longitude}`. `checkedPoint(latitude, longitude)` проверяет конечные числа и диапазоны WGS84. GeoJSON использует обратный порядок: `[longitude, latitude]`. `Bounds` — `{west, south, east, north}`, экранная точка — `{x, y}`.

## Состояние и события карты

| Метод или поле | Назначение |
| --- | --- |
| `map.ready` | Ожидание готовности |
| `map.getState()` | `loading`, `ready`, `degraded`, `failed`, `destroyed` |
| `map.waitForIdle({signal?, timeoutMs?}?)` | Ожидание завершения текущей отрисовки |
| `map.resize()` | Обновление размера после изменения контейнера |
| `map.setTheme(theme)` | Смена светлой или тёмной темы |
| `map.setBasemap(basemap)` | Асинхронная смена подложки с сохранением камеры и объектов |
| `map.setPoint(point)` | Установка исходной точки; `null` удаляет её |
| `map.getQueryPoint()` | Исходная точка или `null` |
| `map.focusPoint(point, zoom?)` | Фокус на точке |
| `map.fitPoints(points, options?)`, `map.fitVisible(options?)` | Подбор видимой области |
| `map.on(type, callback)`, `map.once(type, callback)` | Подписка, возвращающая отписку |
| `map.off(type, callback)` | Отмена подписки |
| `map.onPointChange(callback)` | Изменение исходной точки, причина `drag` или `click` |
| `map.onMarkerClick(callback)` | Клик по совместимому маркеру |
| `map.destroy()` | Удаление карты и принадлежащих ей ресурсов |

События указателя: `click`, `dblclick`, `contextmenu`, `mousemove`, `mouseenter`, `mouseleave`, `pointerdown`, `pointerup`, `touchstart`, `touchend`. События камеры: `move`, `movestart`, `moveend`, `zoom`, `zoomend`, `rotate`, `pitch`, `resize`, `idle`. Состояние: `statechange`, `error`, `destroy`.

Событие указателя содержит `point`, `screen`, `objectId`, `originalEvent`, `stopPropagation()` и `preventDefault()`. [Типы карты и событий](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/types.d.ts).

Для существующих интеграций сохранены `setVisits(points)`, `setMarkers(points)`, `setTracks(tracks)`. Они принимают типы `MapPoint` и `Track`; при новом подключении используйте `objects` и `createRouteOverlay`. [Типы совместимых методов](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/frontend-contracts.d.ts).

## Камера и жесты

| `map.camera` | Назначение |
| --- | --- |
| `getState()`, `getCenter()`, `getZoom()`, `getBounds()` | Чтение вида |
| `setView(view, transition?)` | Центр, масштаб, поворот и наклон |
| `panTo(point, transition?)` | Перемещение центра |
| `setZoom(zoom, transition?)`, `zoomIn(transition?)`, `zoomOut(transition?)` | Масштаб |
| `fitBounds(bounds, options?)` | Показать область |
| `fitObjects(ids?, options?)` | Показать объекты |
| `project(point)`, `unproject(screenPoint)` | Перевод между координатами и пикселями |
| `setLimits({minZoom?, maxZoom?, bounds?})` | Ограничение камеры; `bounds: null` снимает границу |
| `stop()` | Остановка анимации |

`transition` задаёт `duration` и `mode: 'ease'`, `'fly'` или `'jump'`. `FitOptions` содержит `maxZoom`, `duration`, `padding` (число или отступы по четырём сторонам), `resetOrientation`.

`map.interactions.getState()`, `set(options)`, `enable()`, `disable()` управляют жестами. Параметры: `dragPan`, `scrollZoom` (boolean или `'cooperative'`), `doubleClickZoom`, `touchZoom`, `touchRotate`, `dragRotate`, `touchPitch`, `keyboard`.

## Объекты и коллекции

`map.objects.add(object)` возвращает `ObjectHandle`. У каждого объекта обязательны уникальный `id` и `kind`.

| `kind` | Геометрия | Сокращённый метод |
| --- | --- | --- |
| `marker` | `point`, необязательные `draggable`, `renderer` | `addMarker(object)` |
| `line` | `points` | `addLine(object)` |
| `polygon` | `rings` | `addPolygon(object)` |
| `circle` | `center`, `radiusM` | `addCircle(object)` |
| `rectangle` | `bounds` | `addRectangle(object)` |
| `geojson` | `geometry` | `add({id, kind: 'geojson', geometry})` |

Сокращённые методы принимают те же поля без `kind`. Общие параметры: `label`, `properties`, `visible`, `selected`, `style`, `collection`, `zIndex`. Стиль: `color`, `fillColor`, `width`, `opacity`, `fillOpacity`, `radius`, `dash`.

`objects` также предоставляет `get(id)`, `getAll()`, `remove(id)`, `clear()`, `replace(objects)`, `getBounds(ids?)`, `toGeoJSON(ids?)`, `importGeoJSON(value, {replace?, collection?}?)`. `batch(changes)` принимает записи `{action: 'upsert', object}` или `{action: 'remove', id}`.

Поддерживаемая GeoJSON-геометрия: `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`, `GeometryCollection`. Используйте строковые ID объектов, JSON-совместимые `properties` и корректный порядок координат.

`objects.createCollection(id)` создаёт группу. Коллекция имеет `id`, `add`, `get`, `getAll`, `remove`, `clear`, `setVisible`, `setFilter(predicate | null)`, `setStyle`, `getBounds`, `on`, `once`, `destroy`. События: `add`, `change`, `remove`, `statechange`, `destroy`.

## Маркеры, карточки и подсказки

`ObjectHandle` содержит `id`, `getData()`, `update(change)`, `setVisible(boolean)`, `setSelected(boolean)`, `getBounds()`, `getPoint()`, `remove()`, `on()`, `once()`, `popup` и `tooltip`. Через `update` нельзя менять `id` и `kind`.

События объекта: `click`, `dblclick`, `contextmenu`, `mouseenter`, `mouseleave`, `dragstart`, `drag`, `dragend`, `change`, `remove`, `popupopen`, `popupclose`.

`popup` и `tooltip` имеют одинаковые методы: `open(content)`, `update(content)`, `load(loader)`, `close()`, `isOpen()`. `content` — `{text?, element?, offset?, maxWidth?}`. В `load` передаётся функция `(signal) => Promise<PopupContent>`; используйте сигнал для отмены своего запроса.

```javascript
const marker = map.objects.addMarker({
  id: 'example',
  point: { latitude: 55.75, longitude: 37.61 },
  label: 'Наша точка',
});
marker.popup.open({ text: 'Карточка места', maxWidth: 270 });
marker.update({ selected: true });
```

Для собственного маркера `renderer(data)` возвращает новый `HTMLElement`, объект `{element, anchor?, offset?, update?, destroy?}` или `null` для стандартного вида. Создавайте отдельный DOM-элемент для каждого маркера. Пользовательский текст вставляйте через `textContent`.

## Кластеры

`map.clusters.create(id, options?) → ClusterGroup`; доступны также `get(id)`, `getAll()`, `clear()`. Параметры группы: `radius`, `maxZoom`, `minPoints`, `zoomOnClick`, `style`. Это группировка Ваших точек в браузере, не адресный поиск.

```javascript
const group = map.clusters.create('places', { zoomOnClick: true });
group.setPoints([
  { id: 'a', point: { latitude: 55.75, longitude: 37.61 } },
  { id: 'b', point: { latitude: 55.76, longitude: 37.62 } },
]);
```

| Метод группы | Назначение |
| --- | --- |
| `getState()` | Состояние, ревизия и количество точек |
| `setPoints(points)`, `getPoint(id)`, `getPoints()`, `clear()` | Данные |
| `batch(changes)` | `{action: 'upsert', point}` или `{action: 'remove', id}` |
| `setVisible(value)`, `setFilter(predicate \| null)`, `setStyle(style)` | Отображение |
| `getBounds()`, `getRendered(limit?)` | Область и отображённые узлы |
| `getChildren(reference, options?)` | Непосредственные дочерние узлы |
| `getLeaves(reference, {limit?, offset?, signal?, timeoutMs?}?)` | Исходные точки кластера |
| `getExpansionZoom(reference, options?)` | Масштаб раскрытия |
| `expand(reference, options?)` | Раскрыть кластер камерой |
| `on(type, callback)`, `once(type, callback)`, `destroy()` | Подписки и удаление |

`ClusterPoint` содержит `id`, `point`, необязательные `label`, `properties`, `selected`. `ClusterSnapshot` — `{nodes, truncated, loading}`. Узел имеет `kind: 'cluster'` с `reference` или `kind: 'point'` с исходной точкой. Reference принадлежит конкретной ревизии группы; после изменения данных получите новый, иначе возможен `STALE_CLUSTER`.

События: `click`, `dblclick`, `contextmenu`, `mouseenter`, `mouseleave`, `change`, `destroy`, `error`. Стиль: `color`, `pointColor`, `selectedColor`, `textColor`, `clusterRadius`, `pointRadius`, `textSize`, `opacity`, `textFont`. Для запросов узлов доступны `signal`, `timeoutMs`, у `getChildren` также `limit`, у `expand` — параметры перехода камеры.

## Инструменты карты

`map.controls.set(id, options)` включает или настраивает встроенный инструмент; `false` выключает его. Общие параметры: `position` (`top-left`, `top-right`, `bottom-left`, `bottom-right`), `visible`, `margin`.

| ID | Дополнительные параметры |
| --- | --- |
| `navigation` | `showZoom`, `showCompass`, `visualizePitch` |
| `scale` | `unit: 'metric' / 'imperial' / 'nautical'`, `maxWidth` |
| `attribution` | `compact`; сохраняйте требуемую атрибуцию данных |
| `fullscreen` | `container`, `pseudo` |
| `geolocation` | `trackUserLocation`, `showUserLocation`, `showAccuracyCircle`, `zoomToUserAccuracy`, `maxZoom`, `positionOptions` |
| `theme` | Общие параметры размещения |

Геолокация использует разрешение браузера. `positionOptions` содержит `enableHighAccuracy`, `timeout`, `maximumAge`.

`controls.add({id, element, position?, visible?, margin?, onAdd?, onRemove?})` добавляет свой инструмент. Также доступны `get(id)`, `getAll()`, `remove(id)`, `clear()`.

`ControlHandle`: `id`, `element`, `getState()`, `setPosition()`, `setVisible()`, `setMargin()`, `focus()`, `remove()`, `destroy()`. [Полные типы инструментов](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/control-types.d.ts).

## Поиск и выбор адреса

`createSearchController({client, map?, filters?, language?, limit?, fitResults?})` создаёт контроллер. `client` — `geo.geocoder` или результат `createGeocoder()`.

В выпуске SDK `1.0.0-rc.11` предусмотрен параметр `mapContext`: при наличии `map` он включён по умолчанию и передаёт снимок центра и области карты. `mapContext: false` оставляет намеренно глобальный поиск. Движение карты само по себе запросов не делает; текстовый Enter использует тот же поиск подсказок, не добавляя геокодинг. Для этой возможности требуется подключить именно этот выпуск после его публикации.

| Метод | Назначение |
| --- | --- |
| `getState()`, `getResponse()` | Состояние интерфейса и последний ответ |
| `search(q, options?)`, `geocode(q, options?)` | Текстовый запрос |
| `reverse(point, radiusM?, options?)` | Обратный поиск |
| `confirm(ref)` | Явный локальный выбор; возвращает `Promise<SearchSelection>` |
| `setSelectionOpen(boolean)` | Открыть или закрыть карточку |
| `useContext(ref)` | Выбрать контекст поиска |
| `setFilters(filters)`, `clear()` | Фильтры и очистка состояния |
| `on(type, callback)`, `destroy()` | Подписки и освобождение ресурсов |

`filters` поддерживает `country_codes` и ссылки `region`, `locality`, `street`. События контроллера: `change`, `confirm`, `response`, `error`. Фазы `SearchState`: `idle`, `loading`, `candidates`, `not_found`, `needs_context`, `outside_coverage`, `selected`, `error`, `destroyed`.

`SearchSelection` содержит `candidate`, `ref`, `point`, `alternative`, `queryPoint`, `userConfirmed: true`. Выбор не вызывает новый HTTP-запрос. `point` может быть `null`; не создавайте из него маршрут.

`map.controls.addSearch(options)` размещает поиск на карте. Передайте либо `controller`, либо `client` с настройками `filters`, `language`, `limit`, `fitResults`. Общие настройки: размещение, `debounceMs`, `placeholder`, `width`, `onSelect`, `onRoute`, `onShare`. Результат `SearchMapControl` дополнительно имеет `controller`, `dockElement`, `openSelection()`, `closeSelection()`.

Для своей вёрстки используйте `attachSearchControl({controller, element, dockElement?, debounceMs?, placeholder?, onSelect?, onRoute?, onShare?})`. Результат: `element`, `dockElement`, `focus()`, `setVisible()`, `openSelection()`, `closeSelection()`, `destroy()`. Самостоятельно созданный контроллер освобождайте отдельно.

`onRoute(selection)` и `onShare(selection)` выполняют действия Вашего приложения; само открытие карточки не рассчитывает маршрут. [Пример подключения](https://mmapps.ru/docs/maps), [типы поиска](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/search.d.ts).

## Свой обработчик запросов геокодера

`createGeocoder({baseUrl, fetch?, timeoutMs?, signal?, credentials?, paths?, requestHeaders?}) → Geocoder` нужен для подключения к своему backend. Методы: `search`, `geocode`, `reverse`, `resolve`.

`paths` задаёт пути этих четырёх операций, `requestHeaders(operation, options?)` — заголовки. `credentials` принимает `'omit'` или `'same-origin'`. Для прямого подключения к API Ммаппс используйте `createClient` с браузерным ключом. В `resolve` первый аргумент — сам `ref`, а не `{ref}`; дополнительные параметры — `language`, `signal`, `operationKey`.

## Расчёт и отображение маршрута

`geo.routes.calculate({start, end, via?, profile?}, options?)` принимает `GeoPoint`, профиль — `'car'`. `geo.routes.geocode()` принимает в каждой позиции `{point}`, `{ref}` или `{q}`. Текст требует подтверждения. [Запросы и результаты](https://mmapps.ru/docs/routes).

`routeView(response)` возвращает `{track, input, snapped, distanceM, durationS, routingVersion}`. `track` может быть `null`. `geocodedRouteView(response)` возвращает `{route, waypoints}`, где каждый waypoint содержит `index`, `point`, `candidates`. `routeCandidates(value)` извлекает кандидатов из результата геокодинга. Эти функции работают с готовым ответом и не отправляют запросов.

```javascript
import { createRouteOverlay } from 'https://cdn.mmapps.ru/sdk/1.0.0-rc.9/index.js';

const overlay = createRouteOverlay(map);
overlay.set(routeResponse); // Ответ calculate() или geocode().
overlay.fit();
// overlay.clear();
// overlay.destroy();
```

`createRouteOverlay(map)` создаёт слой. `set(response | null)`, `clear()`, `fit()`, `destroy()` управляют им. Результат `no_route` не превращается в прямую линию.

## Редактор маршрута

`attachRouteControl(map, service, {recalculate?}?) → RouteControl`. `service` должен предоставлять `calculate`, например `geo.routes`. `recalculate: 'on-change'` пересчитывает после завершённых изменений пользователем; `'manual'` требует явного расчёта.

| Метод | Назначение |
| --- | --- |
| `getState()` | `visible`, `picking`, `phase`, `stops`, `operationKey`, `errorCode` |
| `open(destination?)`, `close()` | Открыть или закрыть редактор; `destination` подтверждает конечную точку |
| `setStops(stops)` | Программно задать точки без расчёта |
| `chooseEndpoint('start' / 'end', stop)` | Выбрать конечную точку с учётом режима пересчёта |
| `acceptSelection(selection)` | Принять явно выбранную точку; возвращает boolean |
| `pick(index)` | Выбрать остановку на карте |
| `calculate(options?)` | Выполнить расчёт |
| `cancel()`, `fit()` | Отменить ожидание или показать маршрут |
| `on(type, callback)`, `destroy()` | Подписки и удаление |

`RouteStop` — `{point, label?}`; в списке остановок допускаются незаполненные `null`. События: `change`, `result`, `error`. Фазы: `editing`, `loading`, `route`, `no_route`, `error`. [Типы редактора](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/route-control.d.ts).

## Матрица расстояний

`geo.routes.matrix({sources, targets, profile?}, options?) → Promise<MatrixResponse>` рассчитывает пары координат. `geo.routes.matrixGeocode()` использует `{point}`, `{ref}` или `{q}` в списках. Профиль — `'car'`, параметры операции — `signal`, `operationKey`.

```javascript
const operationKey = crypto.randomUUID();
const matrix = await geo.routes.matrix({
  sources: [{ latitude: 55.75, longitude: 37.61 }],
  targets: [{ latitude: 55.76, longitude: 37.62 }],
  profile: 'car',
}, { operationKey });
const cell = matrix.cells[0][0];
if (cell.status === 'ok') {
  // cell.distance_m — метры; cell.duration_s — секунды.
}
```

`matrixView(value, expected?)` проверяет готовый ответ, необязательный `expected` — исходный `MatrixRequest`. `geocodedMatrixView(value, expected)` принимает обязательный `GeocodeMatrixRequest`. Обе функции возвращают проверенный ответ и не вызывают API.

До подтверждения текстовых адресов `matrix` равна `null`. Для `no_route` расстояние и время равны `null`. [Ограничения, тариф и подтверждение адресов](https://mmapps.ru/docs/matrix), [типы матрицы](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/matrix.d.ts).

## Таймлайн

`createTimeline(map, {data, time?, markerRenderer?, observationInterpolation?}) → Timeline` отображает временные данные Вашего приложения. Время — Unix в **миллисекундах UTC**.

```javascript
import { createTimeline, attachTimelineControl } from 'https://cdn.mmapps.ru/sdk/1.0.0-rc.9/index.js';

const from = Date.UTC(2026, 0, 1, 9);
const to = from + 60 * 60 * 1000;
const timeline = createTimeline(map, {
  data: {
    version: 1,
    range: { from, to },
    referenceTime: from,
    entities: [{
      id: 'example',
      label: 'Наш объект',
      locations: [{
        id: 'stop', from, to, basis: 'planned',
        point: { latitude: 55.75, longitude: 37.61 },
      }],
    }],
  },
  time: from,
});
const control = attachTimelineControl(map, timeline);
// При удалении: control.destroy(); timeline.destroy();
```

`TimelineData`: `version: 1`, `range: {from, to}`, `referenceTime`, `entities`. Каждый объект имеет `id`, необязательные `label`, `locations`, `observations`. Местоположение: `{id, from, to?, point, basis, label?}`, где `basis` — `'reported'` или `'planned'`; интервал `[from, to)`. `point: null` означает неизвестную координату. Наблюдение: `{at, point, accuracyM?}`.

| Метод | Назначение |
| --- | --- |
| `getSnapshot()` | Время, период, воспроизведение и позиции объектов |
| `setData(data)` | Обновить данные |
| `seek(time)` | Перемотка |
| `play({speed?}?)`, `pause()` | Воспроизведение |
| `getRoutePlans(selection)` | Подготовить планы расчёта без сети |
| `prepareRoutes(client, options)` | Явно выполнить ограниченное число запросов маршрута |
| `clearRoutes()` | Удалить подготовленные маршруты |
| `on('change' / 'destroy', callback)`, `destroy()` | Подписки и удаление |

Без включённой интерполяции промежутки между наблюдениями остаются неизвестными. `observationInterpolation: {maxGapMs}` явно разрешает оценку между соседними измерениями; `maxGapMs` — 1–600 000 мс. В `TemporalPosition.source` различаются `location`, `observation`, `interpolated`, `unknown`.

Выбор для плана — `{entityId, basis, intermediate: 'observations' / 'none', from?, to?}`. Для `prepareRoutes` дополнительно обязательны `maxRequests`, `operationKeyPrefix`, необязателен `signal`. Сохраняйте префикс для повторов. Результат содержит `requested`, `reused`, `prepared`, `noRoute`. Подготовка вызывает платный расчёт маршрута; перемотка и воспроизведение — локальные действия.

`attachTimelineControl(mapOrElement, timeline, {order?, onPrepareRoutes?}?)` создаёт панель. Она предоставляет `element`, `setVisible(boolean)`, `destroy()`. `onPrepareRoutes({entityId, basis})` открывает Ваш интерфейс подготовки; само воспроизведение его не вызывает. [Все типы таймлайна](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/timeline.d.ts).

## Панели и меню точки

`map.layout.mount({id, element, slot: 'top' / 'bottom', order?, visible?})` размещает панель в общем стеке. Меньший `order` — ближе к центру карты. `map.layout.reserve({id, element, edge, gap?})` резервирует место под уже размещённую панель приложения; `edge` — `top`, `bottom`, `left`, `right`.

Оба метода возвращают `LayoutHandle`: `element`, `setVisible()`, `remove()`, `destroy()`. `layout.getInsets()` читает отступы, `layout.onChange(callback)` подписывается на их изменение и возвращает отписку.

`attachContextMenu(map, {element, search?, routes?, onError?})` подключает меню к контейнеру карты. Передайте существующий `SearchController` и `RouteControl`. Меню открывается правой кнопкой мыши или с клавиатуры; действия — «Что здесь?», «Маршрут отсюда», «Маршрут сюда». Недоступные сервисы не выполняются.

`MapContextMenu` содержит `element`, `open(point, screen?)`, `close()`, `isOpen()`, `destroy()`. Открытие меню бесплатно. Только выбор действия запускает обратный поиск или выбор точки маршрута с учётом режима пересчёта.

## Готовый виджет

`createMapWidget(options) → Promise<MapWidget>` объединяет инструменты карты. Используйте параметры карты, кроме `editable`, и добавьте нужные сервисы:

```javascript
import { createClient, createMapWidget } from 'https://cdn.mmapps.ru/sdk/1.0.0-rc.9/index.js';

const geo = createClient({ apiBaseUrl: 'https://mmapps.ru', apiKey: 'YOUR_BROWSER_KEY' });
const widget = await createMapWidget({
  element: document.getElementById('map'),
  mapId: 'ru',
  services: { geocoder: geo.geocoder, routes: geo.routes },
  search: { placeholder: 'Адрес или координаты' },
  routing: { recalculate: 'manual' },
});
await widget.ready;
// При удалении: widget.destroy(); geo.destroy();
```

Параметры виджета: `services`, `search` (настройки поиска или `false`), `routing` (boolean или настройки редактора), `controls` (без `search`), `timeline`, `timelineControl`, `contextMenu`, `onSelect`, `onRoute`, `onShare`, `onError`. Меню точки по умолчанию включено, `contextMenu: false` отключает его.

Виджет предоставляет `map`, `ready`, `search`, `routes`, `timeline`, `contextMenu`; необязательные инструменты могут быть `null`. `setServices(services)` меняет сервисы, сохраняя публичную карту и внешние объекты. `destroy()` освобождает виджет. [Типы виджета](https://cdn.mmapps.ru/sdk/1.0.0-rc.9/types/clients/maps/widget.d.ts).

## Ошибки и завершение работы

`MapSDKError` содержит `code`, `httpStatus`, `retryable`, `requestId`, `operationId`, `retryAfter`, `stage`. `GeocoderError` дополнительно предоставляет `status`. Используйте эти поля для сообщения об ошибке и решения о повторе.

Локальные коды включают `INVALID_ARGUMENT`, `DESTROYED`, `ABORTED`, `DEADLINE_EXCEEDED`, `RESOURCE_UNAVAILABLE`, `WORKER_CONFLICT`, `RENDERER_ERROR`, `INVALID_RESPONSE`, `STALE_CLUSTER`, `BUSY`, `RANDOM_SOURCE_UNAVAILABLE`. Коды сервисного API описаны в [разделе ошибок](https://mmapps.ru/docs/billing#osibki).

Храните функции отписки и возвращаемые handles. При размонтировании удаляйте самостоятельно созданные панели и контроллеры, затем карту и клиент. `destroy()` карты и виджета освобождает принадлежащие им ресурсы; внешний API-клиент освобождается отдельно. Не создавайте новые экземпляры при каждом обновлении состояния компонента.
