osmgl 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/osmgl.d.ts CHANGED
@@ -277,9 +277,45 @@ declare class Transform {
277
277
  height: number;
278
278
  minZoom: number;
279
279
  maxZoom: number;
280
+ /**
281
+ * Рамка, за которую не выпускается ЦЕНТР карты. `null` — ограничения нет.
282
+ *
283
+ * Прижимается в сеттере центра, а не при отрисовке: центр ставят и жесты, и анимация, и
284
+ * приложение, и проверить это в одном месте — единственный способ не забыть ни одного из них.
285
+ */
286
+ maxBounds: LngLatBounds | null;
287
+ /**
288
+ * Повторять ли мир по горизонтали при отъезде.
289
+ *
290
+ * На мелком зуме в кадр помещается больше одного оборота Земли, и копии закрывают пустоту по
291
+ * краям. Приложению это иногда мешает — например, когда по карте водят выделение и вторая копия
292
+ * ловит тот же клик, — поэтому повтор выключается (`map.setRenderWorldCopies`).
293
+ */
294
+ renderWorldCopies: boolean;
280
295
  minPitch: number;
281
296
  maxPitch: number;
297
+ /**
298
+ * КРЕН камеры, градусы: поворот вокруг оси взгляда.
299
+ *
300
+ * Наклон (`pitch`) кладёт камеру вперёд, поворот (`bearing`) вращает карту под ней, а крен
301
+ * заваливает сам кадр — как накренившийся самолёт. Нужен полётам и панорамам; на обычной карте
302
+ * остаётся нулём. Порядок сборки тот же, что у MapLibre: крен идёт МЕЖДУ отъездом камеры и
303
+ * наклоном, иначе он начал бы вращать не кадр, а землю под ним.
304
+ *
305
+ * Свойство, а не поле: присваивание обязано пометить камеру грязной, иначе матрицы останутся
306
+ * прежними и крен не доедет до кадра.
307
+ */
308
+ private _roll;
282
309
  private _center;
310
+ /**
311
+ * ВЫСОТА ЗЕМЛИ ПОД ЦЕНТРОМ КАРТЫ, метры. Ставится рельефом каждый кадр; без рельефа — ноль.
312
+ *
313
+ * Камера у нас стоит на фиксированном расстоянии от ТОЧКИ ЦЕНТРА, а точка эта лежала на нулевой
314
+ * высоте. Над горой в 2 км это значит, что камера оказывается внутри горы: земля поднялась, а
315
+ * она — нет. Поднимаем всю связку камера-центр на высоту земли — ровно так же поступает MapLibre
316
+ * (`transform.elevation`), и по той же причине: иначе рельеф нельзя даже осмотреть.
317
+ */
318
+ private _centerElevation;
283
319
  private _zoom;
284
320
  private _bearing;
285
321
  private _pitch;
@@ -325,6 +361,9 @@ declare class Transform {
325
361
  constructor();
326
362
  get center(): LngLat;
327
363
  set center(v: LngLat);
364
+ /** Высота земли под центром, метры (ставит рельеф). */
365
+ get centerElevation(): number;
366
+ set centerElevation(v: number);
328
367
  /**
329
368
  * Нижняя граница зума на глобусе: дальше шар становится точкой в углу экрана.
330
369
  *
@@ -341,6 +380,8 @@ declare class Transform {
341
380
  get pitch(): number;
342
381
  set pitch(p: number);
343
382
  /** Вертикальный угол обзора в градусах. */
383
+ get roll(): number;
384
+ set roll(deg: number);
344
385
  get fov(): number;
345
386
  set fov(deg: number);
346
387
  resize(width: number, height: number): void;
@@ -366,6 +407,15 @@ declare class Transform {
366
407
  worldPxToPoint(wx: number, wy: number, altitudeMeters?: number): ScreenPoint$1 | null;
367
408
  /** Пиксели экрана → география на уровне земли (луч сквозь плоскость z=0). */
368
409
  unproject(point: ScreenPoint$1): LngLat;
410
+ /**
411
+ * ВЫСОТА ЗЕМЛИ В ТОЧКЕ, метры — её ставит рельеф.
412
+ *
413
+ * Без неё обратный перевод «экран → место» считает землю плоскостью на нуле. На рельефе земля
414
+ * лежит на своей высоте, и на городском зуме при наклоне промах доходит до полутора километров:
415
+ * клик по дому открывал карточку соседнего квартала, а перетаскивание карты уезжало из-под
416
+ * пальца. Ровно это же (и ровно так же, итерациями) делает MapLibre в `pointCoordinate3D`.
417
+ */
418
+ elevationAt: ((mx: number, my: number) => number) | null;
369
419
  /** Пиксели экрана → мировые пиксели на плоскости земли. */
370
420
  pointToWorldPx(point: ScreenPoint$1): {
371
421
  x: number;
@@ -487,7 +537,7 @@ declare class Transform {
487
537
  * @param dir куда ЛЕТИТ свет (та же система, что у `u_key_dir`)
488
538
  * @param maxHeightMeters потолок сцены: выше него теней не бывает
489
539
  */
490
- updateShadow(dir: Vec3, maxHeightMeters: number): void;
540
+ updateShadow(dir: Vec3, maxHeightMeters: number, groundMin?: number, groundMax?: number): void;
491
541
  /** Матрицы теней посчитаны и пригодны. */
492
542
  get shadowReady(): boolean;
493
543
  /**
@@ -590,6 +640,169 @@ interface GestureOptions {
590
640
  * @param sinceFirstMove сколько прошло с первого замеченного движения, мс
591
641
  */
592
642
  declare function pitchIntent(a: ScreenPoint$1, b: ScreenPoint$1, sinceFirstMove: number): boolean | undefined;
643
+ /**
644
+ * Обработка ввода. Единственная точка, которая двигает Transform от пользователя;
645
+ * после каждого изменения дёргает onChange, а Map решает, когда рисовать кадр.
646
+ */
647
+ declare class GestureManager {
648
+ private canvas;
649
+ private tr;
650
+ private onChange;
651
+ private onInteractionEnd;
652
+ private opts;
653
+ private pointers;
654
+ private dragMode;
655
+ private lastPoint;
656
+ private panAnchor;
657
+ private samples;
658
+ private inertiaRaf;
659
+ private touch;
660
+ private lastTapTime;
661
+ private disposed;
662
+ /**
663
+ * ЧТО СЕЙЧАС ВЕДЁТ РУКА.
664
+ *
665
+ * Нужно `map.isMoving()` и родне: у MapLibre «движется» — это и анимация камеры, и жест, и
666
+ * инерция после него. Про анимацию знает карта, а про жест — только этот класс.
667
+ */
668
+ get moving(): boolean;
669
+ get rotating(): boolean;
670
+ get zooming(): boolean;
671
+ constructor(canvas: HTMLCanvasElement, tr: Transform, onChange: (reason: 'pan' | 'zoom' | 'rotate' | 'pitch') => void, onInteractionEnd: () => void, options?: Partial<GestureOptions>);
672
+ setOptions(options: Partial<GestureOptions>): void;
673
+ private bind;
674
+ private point;
675
+ private onContextMenu;
676
+ private onPointerDown;
677
+ private onPointerMove;
678
+ private onPointerUp;
679
+ private onWheel;
680
+ private onKeyDown;
681
+ private beginTouchGesture;
682
+ private updateTouchGesture;
683
+ /**
684
+ * Щипок: масштаб, поворот и перенос — одним движением.
685
+ *
686
+ * Точка, взятая между пальцами, остаётся под ними: сначала запоминаем её по ПРЕЖНЕМУ центру пары,
687
+ * потом меняем масштаб и азимут, и только затем возвращаем её под НОВЫЙ центр. Последний шаг заодно
688
+ * даёт панораму двумя пальцами — раньше её не было вовсе: карта под двумя пальцами не двигалась,
689
+ * пока расстояние между ними не менялось.
690
+ */
691
+ private applyZoomRotate;
692
+ private zoomAround;
693
+ private panBy;
694
+ private rotateBy;
695
+ private pushSample;
696
+ private startInertia;
697
+ private stopInertia;
698
+ destroy(): void;
699
+ }
700
+
701
+ /**
702
+ * ОБРАБОТЧИКИ ВВОДА КАК ОБЪЕКТЫ: `map.dragPan.disable()` и родня.
703
+ *
704
+ * У MapLibre каждый жест — отдельный объект с `enable`/`disable`/`isEnabled`, и приложения
705
+ * пользуются именно этим: «на время рисования отключить перетаскивание», «запретить зум колесом
706
+ * внутри формы». У нас жесты живут одним менеджером с набором флагов, и объекты здесь — тонкая
707
+ * обёртка над ними. Смысл тот же, а второго места, где решается, что делать с пальцем, не заводим.
708
+ */
709
+ declare class GestureHandler {
710
+ private manager;
711
+ private key;
712
+ private enabled;
713
+ constructor(manager: () => GestureManager | null, key: keyof GestureOptions, enabled: boolean);
714
+ isEnabled(): boolean;
715
+ isActive(): boolean;
716
+ enable(): void;
717
+ disable(): void;
718
+ private set;
719
+ }
720
+ /**
721
+ * ЗУМ РАМКОЙ: Shift + протянуть мышью.
722
+ *
723
+ * Живёт отдельно от менеджера жестов намеренно. Менеджер ведёт непрерывные жесты — тянет карту,
724
+ * крутит, щиплет; здесь же всё наоборот: рисуется рамка, карта стоит, а движение случается ОДИН
725
+ * раз, когда кнопку отпустили. Смешивать их значит вплетать в разбор указателя ещё одно состояние
726
+ * ради одной рамки.
727
+ */
728
+ declare class BoxZoomHandler {
729
+ private canvas;
730
+ private container;
731
+ private fit;
732
+ private enabled;
733
+ private start;
734
+ private box;
735
+ constructor(canvas: HTMLCanvasElement, container: HTMLElement, fit: (p0: {
736
+ x: number;
737
+ y: number;
738
+ }, p1: {
739
+ x: number;
740
+ y: number;
741
+ }) => void);
742
+ isEnabled(): boolean;
743
+ isActive(): boolean;
744
+ enable(): void;
745
+ disable(): void;
746
+ destroy(): void;
747
+ private onDown;
748
+ private onMove;
749
+ private onUp;
750
+ private finish;
751
+ private pointOf;
752
+ }
753
+ /**
754
+ * ОСТОРОЖНЫЕ ЖЕСТЫ: страница важнее карты.
755
+ *
756
+ * Карта во всю ширину статьи ловит прокрутку страницы колесом и не даёт её пролистать — знакомая
757
+ * беда встроенных карт. Включённый режим требует Ctrl (или двух пальцев на телефоне) и подсказывает
758
+ * это надписью поверх карты. Поведение и текст — как у MapLibre `cooperativeGestures`.
759
+ */
760
+ declare class CooperativeGesturesHandler {
761
+ private canvas;
762
+ private container;
763
+ private setScrollZoom;
764
+ private texts;
765
+ private enabled;
766
+ private hint;
767
+ private timer;
768
+ constructor(canvas: HTMLCanvasElement, container: HTMLElement, setScrollZoom: (on: boolean) => void, texts?: {
769
+ windows?: string;
770
+ mac?: string;
771
+ mobile?: string;
772
+ });
773
+ isEnabled(): boolean;
774
+ enable(): void;
775
+ disable(): void;
776
+ destroy(): void;
777
+ private onWheel;
778
+ private onTouch;
779
+ private show;
780
+ private hide;
781
+ private isMac;
782
+ }
783
+
784
+ /**
785
+ * КОНТРОЛЫ КАРТЫ: кнопки и плашки поверх канвы.
786
+ *
787
+ * Интерфейс дословно как у MapLibre — приложение, написанное под него, должно подключать свои
788
+ * контролы без правок. Договор простой: контрол отдаёт свой элемент, карта кладёт его в угол.
789
+ *
790
+ * ★ БЕЗ ВНЕШНЕГО CSS.
791
+ *
792
+ * У MapLibre вид контролов задаёт отдельный файл стилей, и забытый `<link>` превращает карту в
793
+ * россыпь голых кнопок — это первая беда, с которой сталкиваются при подключении. Наши контролы
794
+ * несут свои стили в себе (inline), поэтому работают всюду, где работает карта, и ничего не
795
+ * требуют от страницы.
796
+ */
797
+ /** Угол, в который встаёт контрол. Значения те же, что у MapLibre. */
798
+ type ControlPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
799
+ /** Что карта ждёт от контрола. `map` намеренно нетипизирован здесь — иначе вышел бы круг импортов. */
800
+ interface IControl {
801
+ onAdd(map: unknown): HTMLElement;
802
+ onRemove(map: unknown): void;
803
+ /** Куда встать, если место не указали при добавлении. */
804
+ getDefaultPosition?(): ControlPosition;
805
+ }
593
806
 
594
807
  /**
595
808
  * Обёртка над WebGL2 + кеш состояния.
@@ -630,6 +843,15 @@ declare class Context {
630
843
  private _boundTextures;
631
844
  private _stencilTest;
632
845
  constructor(gl: WebGL2RenderingContext);
846
+ /**
847
+ * ЗАБЫТЬ ВСЁ, ЧТО МЫ ЗНАЛИ О СОСТОЯНИИ GL.
848
+ *
849
+ * Нужно после чужого кода — своего слоя приложения (`CustomLayerInterface`). Он вправе поменять
850
+ * программу, буферы, смешивание и глубину; наш кеш после этого врёт, и следующий проход
851
+ * «пропускает» установку, которой на самом деле нет. Дешевле объявить кеш недействительным, чем
852
+ * потом искать, почему исчезли подписи.
853
+ */
854
+ resetState(): void;
633
855
  useProgram(program: WebGLProgram | null): boolean;
634
856
  bindVAO(vao: WebGLVertexArrayObject | null): void;
635
857
  /**
@@ -698,67 +920,26 @@ declare class Shader {
698
920
  }
699
921
 
700
922
  /**
701
- * Разбор Mapbox Vector Tile (spec 2.1) — наш тайлсервер отдаёт именно его
702
- * (схема OpenMapTiles v3: building / transportation / water / landuse / poi …).
923
+ * СВОИ ПРОТОКОЛЫ ЗАГРУЗКИ И ПРАВКА ЗАПРОСОВ.
703
924
  *
704
- * Читаем «лениво»: сначала только оглавление слоёв, геометрию каждой фичи
705
- * распаковываем по требованию. На тайл с 20 тыс. фич это разница между
706
- * 40 мс и 3 мс, когда генератору нужен один слой из пятнадцати.
707
- */
708
- declare const enum GeomType {
709
- Unknown = 0,
710
- Point = 1,
711
- LineString = 2,
712
- Polygon = 3
713
- }
714
- type PropValue = string | number | boolean | null;
715
- /** Кольцо/линия: плоский массив [x0,y0,x1,y1,...] в координатах тайла [0..extent]. */
716
- type Ring = number[];
717
- declare class VectorTileFeature {
718
- private pbf;
719
- readonly keys: string[];
720
- readonly values: PropValue[];
721
- readonly extent: number;
722
- id?: number;
723
- type: GeomType;
724
- properties: Record<string, PropValue>;
725
- /** Позиция блока геометрии в буфере; -1 — геометрии нет. Пишется читателем полей. */
726
- geomOffset: number;
727
- constructor(pbf: Pbf, keys: string[], values: PropValue[], extent: number);
728
- static read(pbf: Pbf, end: number, keys: string[], values: PropValue[], extent: number): VectorTileFeature;
729
- /** Кольца/линии фичи. Для полигонов внешние и внутренние идут вперемешку — см. splitPolygons. */
730
- loadGeometry(): Ring[];
731
- /** BBox фичи в координатах тайла — дешёвый отсев до триангуляции. */
732
- bbox(): [number, number, number, number];
733
- }
734
- declare class VectorTileLayer {
735
- private pbf;
736
- version: number;
737
- name: string;
738
- extent: number;
739
- readonly featureOffsets: number[];
740
- readonly keys: string[];
741
- readonly values: PropValue[];
742
- constructor(pbf: Pbf);
743
- static read(pbf: Pbf, end: number): VectorTileLayer;
744
- get length(): number;
745
- feature(i: number): VectorTileFeature;
746
- /** Итератор по всем фичам слоя. */
747
- features(): Generator<VectorTileFeature>;
748
- }
749
- declare class VectorTile {
750
- readonly layers: Record<string, VectorTileLayer>;
751
- constructor(buffer: ArrayBuffer | Uint8Array);
752
- }
753
- /**
754
- * Разбивает кольца полигона на группы «внешнее + его дырки».
755
- * Ориентация по знаку площади: в MVT внешние кольца по часовой (положительная
756
- * площадь в системе с Y вниз), дырки — против.
925
+ * Два разных вопроса, но оба про «откуда берутся байты тайла»:
926
+ *
927
+ * `addProtocol('pmtiles', …)` — адрес вида `pmtiles://…` обслуживает приложение само: читает из
928
+ * одного файла, из IndexedDB, из WebAssembly-читалки;
929
+ *
930
+ * `setTransformRequest(fn)` — обычный адрес, но перед отправкой его правят: подписывают, меняют
931
+ * домен, добавляют ключ.
932
+ *
933
+ * ★ И ТО И ДРУГОЕ ЖИВЁТ НА ГЛАВНОМ ПОТОКЕ.
934
+ *
935
+ * Так же у MapLibre, и по той же причине: обработчик приложенческий — ему нужны ключи, кеш и
936
+ * хранилища страницы. Требовать, чтобы он работал внутри воркера, значит требовать от приложения
937
+ * писать под воркер. Поэтому байты добываются здесь, а воркеру достаётся только разбор.
757
938
  */
758
- declare function splitPolygons(rings: Ring[]): {
759
- outer: Ring;
760
- holes: Ring[];
761
- }[];
939
+ /** Что обработчик протокола отдаёт: содержимое тайла или `null`, если тайла нет. */
940
+ type ProtocolHandler = (url: string, signal: AbortSignal) => Promise<ArrayBuffer | null>;
941
+ /** Правка адреса перед загрузкой. Возврат `null` означает «оставить как есть». */
942
+ type TransformRequest = (url: string, kind: 'tile' | 'raster' | 'glyph' | 'sprite') => string | null | undefined;
762
943
 
763
944
  /**
764
945
  * Формат стиля движка.
@@ -790,10 +971,21 @@ interface ColorFrom {
790
971
  /** Насколько затемнить, 0..1 (0.25 — четверть). */
791
972
  darken?: number;
792
973
  }
793
- /** Значение, зависящее от зума: число/цвет либо остановки для интерполяции. */
974
+ /**
975
+ * Значение, зависящее от зума.
976
+ *
977
+ * Три формы, и все три равноправны:
978
+ * само значение — `14`, `'#fff'`;
979
+ * наши остановки — `{ stops: [[10, 2], [16, 8]] }`, короткая запись линейной шкалы;
980
+ * выражение MapLibre — `['interpolate', ['linear'], ['zoom'], 10, 2, 16, 8]`.
981
+ *
982
+ * Выражения добавлены не вместо остановок, а рядом: на остановках написаны все наши стили, а на
983
+ * выражениях — всё, что приходит снаружи. Выбирать между ними не должно приходиться ни приложению,
984
+ * ни теме (см. `style/expression.ts`).
985
+ */
794
986
  type Interpolated<T> = T | {
795
987
  stops: [number, T][];
796
- };
988
+ } | unknown[];
797
989
  /**
798
990
  * Фильтр по свойствам фичи. Подмножество синтаксиса MapLibre — ровно то,
799
991
  * что встречается в схеме OMT.
@@ -803,7 +995,14 @@ type Interpolated<T> = T | {
803
995
  * ['has', 'name']
804
996
  * ['all', [...], [...]]
805
997
  */
806
- type Filter = ['==' | '!=' | '>' | '>=' | '<' | '<=', string, string | number | boolean] | ['in' | '!in', string, ...(string | number | boolean)[]] | ['has' | '!has', string] | ['all' | 'any', ...Filter[]] | ['!', Filter];
998
+ type Filter = ['==' | '!=' | '>' | '>=' | '<' | '<=', string, string | number | boolean] | ['in' | '!in', string, ...(string | number | boolean)[]] | ['has' | '!has', string] | ['all' | 'any', ...Filter[]] | ['!', Filter]
999
+ /**
1000
+ * Выражение MapLibre: `['==', ['get', 'class'], 'primary']` и любое другое.
1001
+ *
1002
+ * Отличается от старой формы тем, что на месте имени свойства стоит вложенное выражение — по
1003
+ * этому признаку они и различаются при разборе (см. `matchesFilter`).
1004
+ */
1005
+ | unknown[];
807
1006
  interface LayerBase {
808
1007
  id: string;
809
1008
  /**
@@ -1001,6 +1200,30 @@ interface LineLayerSpec extends LayerBase {
1001
1200
  */
1002
1201
  widthExtra?: Interpolated<number>;
1003
1202
  dashArray?: [number, number];
1203
+ /**
1204
+ * ШИРИНА В МЕТРАХ вместо пикселей — для РАЗМЕТКИ.
1205
+ *
1206
+ * Полоса разметки это объект местности: по ГОСТ Р 51256 сплошная осевая — 0.1–0.2 м, зебра —
1207
+ * полосы по 0.4 м. В пикселях такую линию задать нельзя: на z16 она была бы вдвое шире, чем
1208
+ * на z17, и на близком зуме вместо разметки получалась бы полоса во всю полосу движения.
1209
+ *
1210
+ * Задана — ширина считается от неё (`width` при этом не смотрится вовсе).
1211
+ */
1212
+ widthMeters?: Interpolated<number>;
1213
+ /**
1214
+ * СДВИГ ПОПЕРЁК ЛИНИИ В МЕТРАХ — тоже для разметки.
1215
+ *
1216
+ * Полоса лежит не по оси дороги, а на своём месте: осевая по середине, краевая в 3.3 м от неё,
1217
+ * разделительная между полосами. Это размер на земле, и задавать его пикселями нельзя.
1218
+ */
1219
+ offsetMeters?: Interpolated<number>;
1220
+ /**
1221
+ * Пунктир В МЕТРАХ: `[штрих, пропуск]`.
1222
+ *
1223
+ * У разметки 1.5 штрих и пропуск относятся как 1:3 (3 м и 9 м вне перекрёстка) — и это тоже
1224
+ * размер на земле, а не на экране.
1225
+ */
1226
+ dashMeters?: [number, number];
1004
1227
  /**
1005
1228
  * Стрелки направления (односторонние улицы). Рисуются аналитически
1006
1229
  * шевронами поверх полотна — без текстуры, поэтому чёткие на любом зуме.
@@ -1186,6 +1409,27 @@ interface SymbolLayerSpec extends LayerBase {
1186
1409
  maxLineChars?: number;
1187
1410
  /** Не участвовать в коллизиях (подпись всегда видна). */
1188
1411
  allowOverlap?: boolean;
1412
+ /**
1413
+ * НЕ ЗАНИМАТЬ МЕСТА У ОСТАЛЬНЫХ, но самому уступать занятому.
1414
+ *
1415
+ * Половина `allowOverlap`, как `icon-ignore-placement` у Mapbox. Нужна фоновым подписям —
1416
+ * названиям улиц и номерам домов: они рисуются там, где свободно, но не вытесняют собой
1417
+ * значки и названия мест. Без этого на плотной застройке номера домов съедают POI, а их
1418
+ * самих никто не читает.
1419
+ */
1420
+ ignorePlacement?: boolean;
1421
+ /**
1422
+ * ГРУППА РАСКЛАДКИ: с кем подпись всё-таки делит место.
1423
+ *
1424
+ * Работает в паре с `ignorePlacement`. Сам по себе он снимает подпись с учёта полностью — и
1425
+ * тогда названия улиц ложатся поверх номеров домов и друг друга: каждая уступает значкам, но
1426
+ * ни одна не знает о соседке. Общая группа возвращает их в коллизии между собой, оставляя
1427
+ * невидимыми для всех остальных слоёв.
1428
+ *
1429
+ * Значение — любая строка, важно лишь совпадение: слои с одной строкой расталкивают друг
1430
+ * друга, с разными — не видят.
1431
+ */
1432
+ placementGroup?: string;
1189
1433
  /**
1190
1434
  * Дополнительное поле вокруг подписи в пикселях — разрежает однотипные
1191
1435
  * метки. Номер трассы иначе повторяется на каждом отрезке: перекрытия нет,
@@ -1211,6 +1455,21 @@ interface SymbolLayerSpec extends LayerBase {
1211
1455
  iconFrame?: 'circle' | 'pin' | 'none';
1212
1456
  /** Диаметр подложки относительно значка. */
1213
1457
  iconFrameScale?: number;
1458
+ /**
1459
+ * СОСТАВНОЙ ЗНАЧОК: силуэты, нарисованные один поверх другого в одной точке.
1460
+ *
1461
+ * Атлас значков у нас общий с буквами и одноканальный — из него приходит форма, а цвет
1462
+ * ставится на весь поток вершин сразу. Разноцветной пиктограммы одной картинкой поэтому не
1463
+ * получится, и она собирается из частей: у светофора это корпус, три лампы и мачта. Части
1464
+ * перечисляются СНИЗУ ВВЕРХ (первая ложится под остальные), цвета им раздаёт
1465
+ * `paint.partColors`, обводку — `paint.partHaloWidths`.
1466
+ *
1467
+ * Совмещать части координатами не нужно: у них общий viewBox, поэтому и общий растр, и
1468
+ * рисуются они в один и тот же прямоугольник. Сам `iconImage` при этом обязателен — по нему
1469
+ * считается размер и место в коллизиях, и он же остаётся одноцветным запасным вариантом, пока
1470
+ * части не догрузились.
1471
+ */
1472
+ iconParts?: string[];
1214
1473
  /**
1215
1474
  * Свойство фичи с поворотом значка в градусах (0 — север, по часовой).
1216
1475
  * Значок доворачивается на поворот камеры, поэтому стрелка у входа смотрит
@@ -1312,6 +1571,15 @@ interface SymbolLayerSpec extends LayerBase {
1312
1571
  /** Рамка подложки. По умолчанию — цвет значка, приглушённый до трети. */
1313
1572
  frameHaloColor?: Interpolated<string>;
1314
1573
  frameHaloWidth?: Interpolated<number>;
1574
+ /**
1575
+ * Цвета частей составного значка (`layout.iconParts`), по порядку частей.
1576
+ *
1577
+ * Лежат в paint, а не рядом с именами частей: это ЦВЕТ, и тема вправе его перекрасить, как
1578
+ * перекрашивает всё остальное. Части без цвета красятся как обычный значок.
1579
+ */
1580
+ partColors?: (Interpolated<string> | ColorFrom)[];
1581
+ /** Обводка частей, по порядку частей. По умолчанию её нет — см. `partColors`. */
1582
+ partHaloWidths?: Interpolated<number>[];
1315
1583
  opacity?: Interpolated<number>;
1316
1584
  };
1317
1585
  }
@@ -1333,6 +1601,27 @@ interface SurfaceLayerSpec extends LayerBase {
1333
1601
  base?: number;
1334
1602
  /** Ширина фаски по горизонтали, метры. 0 — резкий край. */
1335
1603
  bevel?: number;
1604
+ /**
1605
+ * СТРОИТЬ ПОВЕРХНОСТЬ ИЗ ЛИНИЙ, а не из полигонов: полотно дороги, тротуар, дорожка.
1606
+ *
1607
+ * Дорога в данных — ось, а не площадь, и рисуется она лентой в экранных пикселях: у такой ленты
1608
+ * нет края в мире, поэтому нет и бордюра, и борта, и тени от него. С этим флагом ось
1609
+ * превращается в контур (`generators/ribbon`), и полотно становится обычной поверхностью — со
1610
+ * своим уровнем, фаской и юбкой. Ширина берётся из данных (атрибут `width`, метры), а если её
1611
+ * там нет — из `width` ниже.
1612
+ */
1613
+ fromLines?: boolean;
1614
+ /** Ширина полотна в метрах, когда в данных её нет (тропинка, тротуар). Только с `fromLines`. */
1615
+ width?: number;
1616
+ /** Прибавка к ширине из данных, метры: обочина, свес бордюра. Только с `fromLines`. */
1617
+ widthExtra?: number;
1618
+ /**
1619
+ * Толщина плиты у ПРИПОДНЯТОГО полотна (мост, эстакада), метры.
1620
+ *
1621
+ * Мост без низа читается плёнкой, висящей над улицей: сверху асфальт, а дальше сразу земля.
1622
+ * Толщина даёт ему боковую стенку — ту же, что у берега приподнятой поверхности.
1623
+ */
1624
+ deck?: number;
1336
1625
  /** На сколько фаска опускается, метры. */
1337
1626
  /**
1338
1627
  * Насколько широкую щель между кусками СЛИТЬ, метры. По умолчанию — общее значение движка.
@@ -1342,6 +1631,18 @@ interface SurfaceLayerSpec extends LayerBase {
1342
1631
  * белую плиту.
1343
1632
  */
1344
1633
  weld?: number;
1634
+ /**
1635
+ * УЧАСТВУЕТ ЛИ ПОВЕРХНОСТЬ В ПОЛЕ ВЫСОТ, по которому поднимаются линии. По умолчанию — да.
1636
+ *
1637
+ * Поле отвечает на вопрос «на чём лежит линия»: дорожка, проходящая по приподнятому двору,
1638
+ * поднимается вместе с ним (см. `generators/ground`). Полотну дорог это во вред: линия, которая
1639
+ * просто ПЕРЕСЕКАЕТ асфальт, взбирается на него и рисуется поверх. Самое заметное — русло реки:
1640
+ * мост через неё выходит перечёркнутым синей полосой ровно по проезжей части.
1641
+ *
1642
+ * Поэтому полотно из поля исключается. Своей разметке оно ничего не должно: краска и так лежит
1643
+ * выше асфальта собственной отметкой.
1644
+ */
1645
+ groundField?: boolean;
1345
1646
  bevelDrop?: number;
1346
1647
  paint: MaterialPaint & {
1347
1648
  color: Interpolated<string>;
@@ -1516,28 +1817,118 @@ interface SkySpec {
1516
1817
  }
1517
1818
  declare const DEFAULT_SKY: SkySpec;
1518
1819
  interface SourceSpec {
1519
- /** Шаблон вида `/tileserver/data/indoor/{z}/{x}/{y}.pbf`. */
1520
- url: string;
1820
+ /** Шаблон вида `/tileserver/data/indoor/{z}/{x}/{y}.pbf`. У `geojson` адреса нет. */
1821
+ url?: string;
1521
1822
  /**
1522
1823
  * Что лежит по адресу. Без поля — векторные тайлы, как было раньше.
1523
1824
  *
1524
1825
  * `raster` — подложка картинками: её не надо разбирать, поэтому она не идёт
1525
1826
  * через пул воркеров и рисуется одним квадом на тайл (см. `RasterSource`).
1526
1827
  */
1527
- type?: 'vector' | 'raster';
1828
+ type?: 'vector' | 'raster' | 'raster-dem' | 'geojson' | 'image' | 'video' | 'canvas';
1829
+ /**
1830
+ * Четыре угла картинки, видео или canvas: левый верхний, правый верхний, правый нижний, левый
1831
+ * нижний. Порядок и смысл как у MapLibre — углы независимы, прямоугольник необязателен.
1832
+ */
1833
+ coordinates?: unknown;
1834
+ /** Элемент canvas или его id (`type: 'canvas'`). */
1835
+ canvas?: unknown;
1836
+ /** Перезаливать ли текстуру каждый кадр (живой canvas). */
1837
+ animate?: boolean;
1838
+ /**
1839
+ * ДАННЫЕ ПРИЛОЖЕНИЯ вместо тайлов с сервера (`type: 'geojson'`).
1840
+ *
1841
+ * Сам набор или адрес, откуда его забрать. Нарезкой на тайлы занимается движок
1842
+ * (`source/geojson-source.ts`), поэтому слои ссылаются на такой источник как на обычный.
1843
+ */
1844
+ data?: unknown;
1845
+ /** Имя слоя внутри источника: на него смотрит `source-layer` слоя стиля. По умолчанию `geojson`. */
1846
+ sourceLayer?: string;
1847
+ /** Собирать ли точки в кластеры (`geojson`). */
1848
+ cluster?: boolean;
1849
+ /** Радиус кластера в пикселях экрана. */
1850
+ clusterRadius?: number;
1851
+ /** Зум, выше которого кластеров нет. */
1852
+ clusterMaxZoom?: number;
1853
+ /**
1854
+ * Кодировка высоты у `raster-dem`: `mapbox` (наш набор) или `terrarium`. См. `source/dem.ts`.
1855
+ */
1856
+ encoding?: 'mapbox' | 'terrarium';
1528
1857
  minzoom?: number;
1529
1858
  maxzoom?: number;
1530
1859
  }
1860
+ /**
1861
+ * РЕЛЬЕФ: земля перестаёт быть плоскостью.
1862
+ *
1863
+ * `source` — имя источника типа `raster-dem`, `exaggeration` — множитель высоты (1 — как в
1864
+ * данных, 0 — рельеф выключен). Высоту читает вершинный шейдер каждого слоя, который лежит по
1865
+ * земле: заливки, поверхности, дороги. Разбор подхода MapLibre и наш план — в `porting/terrain.md`.
1866
+ *
1867
+ * Тип ИМЕНОВАННЫЙ, а не инлайновый: его принимает `map.setTerrain` — тот самый метод, которым
1868
+ * рельеф включают на лету (имя и смысл как у MapLibre).
1869
+ */
1870
+ interface TerrainSpec {
1871
+ source: string;
1872
+ exaggeration?: number;
1873
+ /**
1874
+ * ЧЕМ РЕЛЬЕФ ПОКАЗАН, кроме собственной формы.
1875
+ *
1876
+ * Сверху склон почти не читается — на бумажных картах его для того и красят по высоте
1877
+ * (гипсометрия) и отмывают светом. Оба тона здесь, а не в движке, потому что это ЦВЕТ: у
1878
+ * ледяной темы он холодный, у ночной тёмный, и одним набором на все темы не обойтись.
1879
+ *
1880
+ * `low`/`high` — тон низин и вершин, `range` — между какими высотами растянут переход,
1881
+ * `tint`/`shade` — насколько сильно подмешивать окраску и отмывку (0..1).
1882
+ */
1883
+ tint?: {
1884
+ /**
1885
+ * Тона шкалы высот. НЕОБЯЗАТЕЛЬНЫ: без них рельеф остаётся в цветах самой карты, а объём ему
1886
+ * даёт одна отмывка. Так и нужно, когда карта уже раскрашена по смыслу — окраска по высоте
1887
+ * тогда спорит с ней за внимание.
1888
+ *
1889
+ * Три режима:
1890
+ * ни одного тона — только отмывка;
1891
+ * один `high` — тон нарастает к вершинам, низины остаются цветом карты;
1892
+ * `low` и `high` — полная гипсометрическая шкала по всему диапазону.
1893
+ */
1894
+ low?: string;
1895
+ /** Тон середины: между долиной и вершиной лежат склоны, и без своего тона они пропадают. */
1896
+ mid?: string;
1897
+ high?: string;
1898
+ range?: [number, number];
1899
+ tint?: number;
1900
+ shade?: number;
1901
+ };
1902
+ }
1531
1903
  interface StyleSpec {
1532
1904
  version: 1;
1533
1905
  name?: string;
1534
1906
  /** Цвет подложки. */
1535
1907
  background: string;
1908
+ /**
1909
+ * НА СКОЛЬКО МЕТРОВ ПОДНИМАЕТ КАЖДЫЙ УРОВЕНЬ ПЕРЕСЕЧЕНИЯ (`generators/level`): мост — 1, земля —
1910
+ * 0, тоннель — −1.
1911
+ *
1912
+ * Пока дороги были краской, уровень решал только ПОРЯДОК отрисовки (см. `network`). Полотно —
1913
+ * геометрия, и на пересечении без подъёма эстакада и улица под ней оказались бы на одной высоте:
1914
+ * два непрозрачных верха в одной плоскости.
1915
+ *
1916
+ * Число нарочно маленькое. Настоящая эстакада поднята на пять метров и более, но опор и съездов
1917
+ * у нас нет: на такой высоте мост обрывался бы в воздухе там, где кончается его пролёт. Полметра
1918
+ * хватает, чтобы пересечение читалось «над», и не хватает, чтобы стык с обычной дорогой стал
1919
+ * ступенькой.
1920
+ *
1921
+ * ВНИЗ НЕ ОПУСКАЕМ: землю мы не разрезаем, и тоннель под ней просто исчез бы вместе со своей
1922
+ * подписью. Он остаётся на уровне земли — ровно так же, как рисовался лентой до сих пор.
1923
+ */
1924
+ levelHeight?: number;
1536
1925
  light?: Partial<LightSpec>;
1537
1926
  /** Небо и дымка. */
1538
1927
  sky?: Partial<SkySpec>;
1539
1928
  /** Дополнительные источники тайлов помимо `base` (его задаёт карта опцией `tiles`). */
1540
1929
  sources?: Record<string, SourceSpec>;
1930
+ /** Рельеф под картой (см. `TerrainSpec`). */
1931
+ terrain?: TerrainSpec;
1541
1932
  /**
1542
1933
  * Ключ варианта 3D-моделей (см. `ThemeSpec.model3dKey`).
1543
1934
  *
@@ -1557,113 +1948,41 @@ interface StyleSpec {
1557
1948
  declare const DEFAULT_LIGHT: LightSpec;
1558
1949
 
1559
1950
  /**
1560
- * Генератор геометрии зданий: экструзия полигонов слоя `building` (схема OpenMapTiles v3).
1561
- * Работает в воркере, отдаёт готовые типизированные массивы — они уходят в главный
1562
- * поток transfer'ом (без копирования) и грузятся в GPU как есть.
1563
- *
1564
- * Формат вершины (stride 18 байт):
1565
- * a_pos 2 × Int16 off 0 координаты тайла [0..extent]
1566
- * a_height 1 × Uint16 off 4 высота вершины в метрах × 4 (до 16383 м, шаг 0.25)
1567
- * a_tint 1 × Uint8 off 6 оттенок здания (одинаков у всех его вершин)
1568
- * a_ao 1 × Uint8 off 7 0 — основание, 255 — верх
1569
- * a_normal 3 × Int8n off 8 нормаль
1570
- * a_kind 1 × Uint8 off 11 тип поверхности и класс фасада
1571
- * a_wall 3 × Uint16 off 12 фасад: смещение вдоль грани, её ширина и высота (метры × 4)
1572
- *
1573
- * Высота ушла из Float32 в Uint16 ради места под a_tint: без разброса оттенка
1574
- * квартал выглядит одной сплошной массой.
1951
+ * План для воркера и общие с ним имена.
1575
1952
  *
1576
- * `a_wall` появился ради РОВНОЙ раскладки окон. Сначала координата вдоль стены
1577
- * считалась в шейдере как проекция позиции на её направление — сетка тогда общая
1578
- * на всю карту, и грань режет её где придётся: у угла дома получается половина
1579
- * окна, а простенки у разных стен разной ширины. Ширина и высота грани позволяют
1580
- * ВПИСАТЬ сетку в неё: поля по краям, целое число ячеек, окно целиком.
1953
+ * Лежит ОТДЕЛЬНО от разбора стиля, хотя логически это его часть. Причина
1954
+ * механическая: воркер собирается в свой бандл и вшивается строкой в основной,
1955
+ * а `style.ts` тянет за собой встроенный стиль и два десятка тем — сотню
1956
+ * килобайт цветов, которые воркеру не нужны вовсе (он строит геометрию, красит
1957
+ * главный поток). Импорт одной функции оттуда удваивал вес этих JSON в сборке.
1581
1958
  */
1582
- declare const BUILDING_VERTEX_STRIDE = 18;
1583
- /** Множитель упаковки высоты в Uint16. */
1584
- declare const BUILDING_HEIGHT_SCALE = 32;
1959
+ /** План для воркера: что именно строить из тайла. Должен пережить structured clone. */
1960
+ /** Слои, которые вообще строятся из тайла: у подложки строить нечего. */
1961
+ type TiledLayerSpec = Extract<StyleLayerSpec, {
1962
+ 'source-layer': string;
1963
+ }>;
1585
1964
  /**
1586
- * Множитель упаковки ПОЛОЖЕНИЯ вершины: сколько единиц Int16 приходится на единицу тайла.
1587
- *
1588
- * Было единица — то есть 0.47 м на z14, потому что координаты в MVT целые и вершину писали как
1589
- * есть. Пока углы дома были прямыми, этого хватало: вершина и так стояла в узле сетки. Со
1590
- * СКРУГЛЕНИЕМ хватать перестало — дуга радиусом 1.6 м это семь точек в пределах трёх единиц тайла,
1591
- * и округление сажает их на ту же сетку: вместо дуги выходит лесенка со ступенью в полметра, она же
1592
- * «угловатость» на ближнем зуме.
1965
+ * ПОКРАСКА, ЗАВИСЯЩАЯ ОТ ФИЧИ: что посчитать в воркере и положить в вершину.
1593
1966
  *
1594
- * Восемь единиц на тайловую — это 5.9 см, и дуга снова читается дугой. Потолок: тайл 0..4096, при
1595
- * ×8 это 0..32768, и последнее значение на единицу больше, чем помещается в Int16, — оно
1596
- * прижимается (`clampI16`). Ошибка при этом 6 см и только у вершины, стоящей ровно на дальней
1597
- * границе тайла.
1967
+ * Лежит в плане, потому что считается там же, где есть фича, — при тесселяции (см.
1968
+ * `generators/data-paint.ts`). Голые выражения, то есть обычные массивы: structured clone их
1969
+ * переживает, а функции бы не пережил.
1598
1970
  */
1599
- declare const BUILDING_POS_SCALE = 8;
1600
- interface BuildingsMesh {
1601
- vertices: ArrayBuffer;
1602
- indices: ArrayBuffer;
1603
- /** Uint32Array: id фичи на каждый треугольник — для пикинга и подсветки. */
1604
- featureIds: ArrayBuffer;
1605
- vertexCount: number;
1606
- indexCount: number;
1607
- extent: number;
1608
- }
1609
- interface BuildingsOptions {
1610
- /**
1611
- * Радиус скругления углов в МЕТРАХ. Скругляется контур, а значит и стены, и
1612
- * крыша — она строится по тому же кольцу. Ноль (по умолчанию) — острые углы,
1613
- * как раньше.
1614
- */
1615
- cornerRadius?: number;
1616
- /** Максимум отрезков на дугу: меньше — дешевле и угловатее. */
1617
- cornerSegments?: number;
1971
+ interface DataPaintSpec {
1972
+ /** Выражение цвета. */
1973
+ color?: unknown;
1974
+ /** Выражение прозрачности: домножает альфу цвета. */
1975
+ opacity?: unknown;
1976
+ /** Выражение ширины линии, CSS-пиксели. */
1977
+ width?: unknown;
1618
1978
  /**
1619
- * Сколько метров в одной единице тайла. Нужен, чтобы решать по РАЗМЕРУ: дом
1620
- * в сотню квадратов получает скатную крышу, торговый центр — плоскую. Без
1621
- * него один и тот же порог на разных зумах означал бы разные здания.
1979
+ * Цвет слоя обычной формой — нужен, когда от фичи зависит только прозрачность.
1980
+ *
1981
+ * Вершинный путь в шейдере включается целиком: раз уж цвет берётся из вершины, в ней должен
1982
+ * лежать весь цвет, а не одна альфа.
1622
1983
  */
1623
- metersPerUnit?: number;
1984
+ base?: string;
1624
1985
  }
1625
- declare function generateBuildings(layer: VectorTileLayer, extent: number, filter?: Filter, options?: BuildingsOptions,
1626
- /**
1627
- * Куда складывать дома, задевающие границу тайла.
1628
- *
1629
- * Задан — такие дома здесь НЕ строятся: из куска правильной геометрии не выйдет, их соберут из
1630
- * всех кусков сразу. Не задан — прежнее поведение, каждый кусок сам по себе.
1631
- */
1632
- seams?: BuildingSeam[]): BuildingsMesh | null;
1633
- /**
1634
- * Кусок дома, задевающего границу тайла: контур с пометкой ЛОЖНЫХ рёбер.
1635
- *
1636
- * Уезжает на главный поток, чтобы там сложиться с кусками соседних тайлов в целый дом.
1637
- * Кольца — в координатах СВОЕГО тайла; общую систему строит уже сборщик, он знает номера тайлов.
1638
- */
1639
- interface BuildingSeam {
1640
- osmId: number;
1641
- height: number;
1642
- minHeight: number;
1643
- seed: number;
1644
- tint: number;
1645
- facade: number;
1646
- outer: number[];
1647
- holes: number[][];
1648
- /** `cut[i]` — ребро от вершины i к i+1 порождено обрезкой, а не стеной дома. */
1649
- outerCut: boolean[];
1650
- holeCuts: boolean[][];
1651
- }
1652
-
1653
- /**
1654
- * План для воркера и общие с ним имена.
1655
- *
1656
- * Лежит ОТДЕЛЬНО от разбора стиля, хотя логически это его часть. Причина
1657
- * механическая: воркер собирается в свой бандл и вшивается строкой в основной,
1658
- * а `style.ts` тянет за собой встроенный стиль и два десятка тем — сотню
1659
- * килобайт цветов, которые воркеру не нужны вовсе (он строит геометрию, красит
1660
- * главный поток). Импорт одной функции оттуда удваивал вес этих JSON в сборке.
1661
- */
1662
- /** План для воркера: что именно строить из тайла. Должен пережить structured clone. */
1663
- /** Слои, которые вообще строятся из тайла: у подложки строить нечего. */
1664
- type TiledLayerSpec = Extract<StyleLayerSpec, {
1665
- 'source-layer': string;
1666
- }>;
1667
1986
  interface LayerPlan {
1668
1987
  id: string;
1669
1988
  /**
@@ -1695,6 +2014,16 @@ interface LayerPlan {
1695
2014
  bevel?: number;
1696
2015
  weld?: number;
1697
2016
  bevelDrop?: number;
2017
+ /** Поверхность строится из осей линий (см. `SurfaceLayerSpec.fromLines`). */
2018
+ fromLines?: boolean;
2019
+ /** Ширина полотна в метрах, когда её нет в данных. */
2020
+ width?: number;
2021
+ /** Прибавка к ширине из данных, метры. */
2022
+ widthExtra?: number;
2023
+ /** Толщина плиты у приподнятого полотна (мост, эстакада), метры. */
2024
+ deck?: number;
2025
+ /** Метров на уровень пересечения (`StyleSpec.levelHeight`) — им поднимается полотно моста. */
2026
+ levelHeight?: number;
1698
2027
  /**
1699
2028
  * Линия ложится НА ПОВЕРХНОСТЬ, по которой идёт (см. `generators/ground`).
1700
2029
  *
@@ -1703,6 +2032,8 @@ interface LayerPlan {
1703
2032
  * отрисовка, — отсюда флаг в плане.
1704
2033
  */
1705
2034
  ground?: boolean;
2035
+ /** Кладёт ли ПОВЕРХНОСТЬ свои контуры в поле высот (`StyleLayerSpec.groundField`). */
2036
+ groundField?: boolean;
1706
2037
  /**
1707
2038
  * Класс поверхности → её уровень, метры. По этой таблице поднимается РАЗМЕТКА на асфальте.
1708
2039
  *
@@ -1712,6 +2043,8 @@ interface LayerPlan {
1712
2043
  * живут в стиле. Таблица и сводит одно с другим.
1713
2044
  */
1714
2045
  groundBy?: Record<string, number>;
2046
+ /** Покраска на фиче: выражения, которые воркер посчитает и положит в вершину. */
2047
+ dataPaint?: DataPaintSpec;
1715
2048
  /** Только для symbol: шаблоны подставляются уже в воркере, чтобы не гонять свойства. */
1716
2049
  textField?: string | string[];
1717
2050
  iconImage?: string | string[];
@@ -1731,6 +2064,239 @@ declare const BASE_SOURCE = "base";
1731
2064
  declare const SPLIT_SEP = "\u241F";
1732
2065
  declare const splitBucketKey: (layerId: string, value: string | number) => string;
1733
2066
 
2067
+ /**
2068
+ * ПОКРАСКА, ЗАВИСЯЩАЯ ОТ ФИЧИ (data-driven paint).
2069
+ *
2070
+ * Цвет и ширина слоя уходят в шейдер ЮНИФОРМАМИ — одно значение на весь слой. Это верно ровно до
2071
+ * тех пор, пока значение задано числом, остановками или зумовым выражением. Стоит написать
2072
+ * `['match', ['get', 'class'], 'primary', '#f00', '#999']`, и одного значения на слой уже не
2073
+ * хватает: у каждой дороги своё.
2074
+ *
2075
+ * Значит, считать надо ТАМ, ГДЕ ЕСТЬ ФИЧА, — в воркере, при тесселяции, — и класть результат
2076
+ * В ВЕРШИНУ. Ровно так же поступает MapLibre, раскладывая data-driven свойства по атрибутам
2077
+ * (`paint_vertex_arrays`): другого места, где фича ещё жива, в конвейере нет.
2078
+ *
2079
+ * Формат отдельного буфера, а не добавка к вершине слоя:
2080
+ *
2081
+ * a_dcolor 4 × Uint8 (normalized) off 0 цвет фичи, НЕ домноженный на альфу
2082
+ * a_dwidth 1 × Float32 off 4 ширина фичи в CSS-пикселях (0 — не задана)
2083
+ *
2084
+ * Буфер отдельный по двум причинам. Раскладка вершины у заливки, линии и дома разная — добавлять
2085
+ * поле пришлось бы в каждую, и каждая выросла бы навсегда, хотя data-driven покраска есть у
2086
+ * единиц слоёв. И буфера просто НЕТ у обычного слоя: атрибут остаётся отключённым, шейдер берёт
2087
+ * юниформ, и ни один старый слой не платит за это ни байта.
2088
+ *
2089
+ * ЗУМ. Выражение считается на зуме ТАЙЛА, а не экрана: тайл разбирается один раз и живёт в кеше на
2090
+ * нескольких зумах. Для `['match', ['get', …]]` это безразлично, а зумовая интерполяция, смешанная
2091
+ * с фичей, окажется ступенчатой по зуму — MapLibre в этом месте тоже запекает значения по зумам
2092
+ * тайла, только держит две ступени и смешивает их в шейдере.
2093
+ */
2094
+ declare const DATA_PAINT_STRIDE = 8;
2095
+
2096
+ /** Есть ли в слое хоть одно значение, которое придётся считать на фиче. */
2097
+ declare function needsDataPaint(spec: DataPaintSpec | undefined): boolean;
2098
+ /**
2099
+ * Накопитель значений на вершину.
2100
+ *
2101
+ * Генератор не знает заранее, сколько вершин даст фича: дробление под глобус добавляет
2102
+ * середины рёбер, скругление угла — дугу. Поэтому значения не пишутся по одному, а
2103
+ * ДОГОНЯЮТ геометрию: генератор говорит «вершин теперь столько», и накопитель заполняет
2104
+ * всё, что появилось с прошлого раза, значениями текущей фичи.
2105
+ */
2106
+ declare class FeaturePaint {
2107
+ private readonly spec;
2108
+ private readonly zoom;
2109
+ private buffer;
2110
+ private view;
2111
+ private capacity;
2112
+ /** Сколько вершин уже покрашено. */
2113
+ private filled;
2114
+ /** Значения текущей фичи. */
2115
+ private r;
2116
+ private g;
2117
+ private b;
2118
+ private a;
2119
+ private width;
2120
+ /** Цвет слоя, когда от фичи он не зависит: считается один раз на тайл. */
2121
+ private readonly base;
2122
+ constructor(spec: DataPaintSpec, zoom: number);
2123
+ /** Посчитать значения для фичи. Дальше они лягут во все её вершины. */
2124
+ setFeature(properties: Record<string, unknown>, id: number | string | null, geometryType: string | undefined): void;
2125
+ /** Догнать геометрию: все вершины до `vertexCount` красятся текущей фичей. */
2126
+ advanceTo(vertexCount: number): void;
2127
+ /**
2128
+ * Готовый буфер ровно на `vertexCount` вершин.
2129
+ *
2130
+ * Хвост добивается последней фичей: у генераторов бывают служебные вершины (крышка острова,
2131
+ * шов), и оставить их непокрашенными значит получить чёрные треугольники.
2132
+ */
2133
+ take(vertexCount: number): ArrayBuffer;
2134
+ private ensure;
2135
+ }
2136
+ /** Накопитель, если слою он нужен. */
2137
+ declare function makeFeaturePaint(spec: DataPaintSpec | undefined, zoom: number): FeaturePaint | undefined;
2138
+
2139
+ /**
2140
+ * Разбор Mapbox Vector Tile (spec 2.1) — наш тайлсервер отдаёт именно его
2141
+ * (схема OpenMapTiles v3: building / transportation / water / landuse / poi …).
2142
+ *
2143
+ * Читаем «лениво»: сначала только оглавление слоёв, геометрию каждой фичи
2144
+ * распаковываем по требованию. На тайл с 20 тыс. фич это разница между
2145
+ * 40 мс и 3 мс, когда генератору нужен один слой из пятнадцати.
2146
+ */
2147
+ declare const enum GeomType {
2148
+ Unknown = 0,
2149
+ Point = 1,
2150
+ LineString = 2,
2151
+ Polygon = 3
2152
+ }
2153
+ type PropValue = string | number | boolean | null;
2154
+ /** Кольцо/линия: плоский массив [x0,y0,x1,y1,...] в координатах тайла [0..extent]. */
2155
+ type Ring = number[];
2156
+ declare class VectorTileFeature {
2157
+ private pbf;
2158
+ readonly keys: string[];
2159
+ readonly values: PropValue[];
2160
+ readonly extent: number;
2161
+ id?: number;
2162
+ type: GeomType;
2163
+ properties: Record<string, PropValue>;
2164
+ /** Позиция блока геометрии в буфере; -1 — геометрии нет. Пишется читателем полей. */
2165
+ geomOffset: number;
2166
+ constructor(pbf: Pbf, keys: string[], values: PropValue[], extent: number);
2167
+ static read(pbf: Pbf, end: number, keys: string[], values: PropValue[], extent: number): VectorTileFeature;
2168
+ /** Кольца/линии фичи. Для полигонов внешние и внутренние идут вперемешку — см. splitPolygons. */
2169
+ loadGeometry(): Ring[];
2170
+ /** BBox фичи в координатах тайла — дешёвый отсев до триангуляции. */
2171
+ bbox(): [number, number, number, number];
2172
+ }
2173
+ declare class VectorTileLayer {
2174
+ private pbf;
2175
+ version: number;
2176
+ name: string;
2177
+ extent: number;
2178
+ readonly featureOffsets: number[];
2179
+ readonly keys: string[];
2180
+ readonly values: PropValue[];
2181
+ constructor(pbf: Pbf);
2182
+ static read(pbf: Pbf, end: number): VectorTileLayer;
2183
+ get length(): number;
2184
+ feature(i: number): VectorTileFeature;
2185
+ /** Итератор по всем фичам слоя. */
2186
+ features(): Generator<VectorTileFeature>;
2187
+ }
2188
+ declare class VectorTile {
2189
+ readonly layers: Record<string, VectorTileLayer>;
2190
+ constructor(buffer: ArrayBuffer | Uint8Array);
2191
+ }
2192
+ /**
2193
+ * Разбивает кольца полигона на группы «внешнее + его дырки».
2194
+ * Ориентация по знаку площади: в MVT внешние кольца по часовой (положительная
2195
+ * площадь в системе с Y вниз), дырки — против.
2196
+ */
2197
+ declare function splitPolygons(rings: Ring[]): {
2198
+ outer: Ring;
2199
+ holes: Ring[];
2200
+ }[];
2201
+
2202
+ /**
2203
+ * Генератор геометрии зданий: экструзия полигонов слоя `building` (схема OpenMapTiles v3).
2204
+ * Работает в воркере, отдаёт готовые типизированные массивы — они уходят в главный
2205
+ * поток transfer'ом (без копирования) и грузятся в GPU как есть.
2206
+ *
2207
+ * Формат вершины (stride 18 байт):
2208
+ * a_pos 2 × Int16 off 0 координаты тайла [0..extent]
2209
+ * a_height 1 × Uint16 off 4 высота вершины в метрах × 4 (до 16383 м, шаг 0.25)
2210
+ * a_tint 1 × Uint8 off 6 оттенок здания (одинаков у всех его вершин)
2211
+ * a_ao 1 × Uint8 off 7 0 — основание, 255 — верх
2212
+ * a_normal 3 × Int8n off 8 нормаль
2213
+ * a_kind 1 × Uint8 off 11 тип поверхности и класс фасада
2214
+ * a_wall 3 × Uint16 off 12 фасад: смещение вдоль грани, её ширина и высота (метры × 4)
2215
+ *
2216
+ * Высота ушла из Float32 в Uint16 ради места под a_tint: без разброса оттенка
2217
+ * квартал выглядит одной сплошной массой.
2218
+ *
2219
+ * `a_wall` появился ради РОВНОЙ раскладки окон. Сначала координата вдоль стены
2220
+ * считалась в шейдере как проекция позиции на её направление — сетка тогда общая
2221
+ * на всю карту, и грань режет её где придётся: у угла дома получается половина
2222
+ * окна, а простенки у разных стен разной ширины. Ширина и высота грани позволяют
2223
+ * ВПИСАТЬ сетку в неё: поля по краям, целое число ячеек, окно целиком.
2224
+ */
2225
+ declare const BUILDING_VERTEX_STRIDE = 18;
2226
+ /** Множитель упаковки высоты в Uint16. */
2227
+ declare const BUILDING_HEIGHT_SCALE = 32;
2228
+ /**
2229
+ * Множитель упаковки ПОЛОЖЕНИЯ вершины: сколько единиц Int16 приходится на единицу тайла.
2230
+ *
2231
+ * Было единица — то есть 0.47 м на z14, потому что координаты в MVT целые и вершину писали как
2232
+ * есть. Пока углы дома были прямыми, этого хватало: вершина и так стояла в узле сетки. Со
2233
+ * СКРУГЛЕНИЕМ хватать перестало — дуга радиусом 1.6 м это семь точек в пределах трёх единиц тайла,
2234
+ * и округление сажает их на ту же сетку: вместо дуги выходит лесенка со ступенью в полметра, она же
2235
+ * «угловатость» на ближнем зуме.
2236
+ *
2237
+ * Восемь единиц на тайловую — это 5.9 см, и дуга снова читается дугой. Потолок: тайл 0..4096, при
2238
+ * ×8 это 0..32768, и последнее значение на единицу больше, чем помещается в Int16, — оно
2239
+ * прижимается (`clampI16`). Ошибка при этом 6 см и только у вершины, стоящей ровно на дальней
2240
+ * границе тайла.
2241
+ */
2242
+ declare const BUILDING_POS_SCALE = 8;
2243
+ interface BuildingsMesh {
2244
+ vertices: ArrayBuffer;
2245
+ indices: ArrayBuffer;
2246
+ /** Uint32Array: id фичи на каждый треугольник — для пикинга и подсветки. */
2247
+ featureIds: ArrayBuffer;
2248
+ vertexCount: number;
2249
+ indexCount: number;
2250
+ extent: number;
2251
+ /** Покраска на фиче, если слой её просил (см. `generators/data-paint.ts`). */
2252
+ dataPaint?: ArrayBuffer;
2253
+ }
2254
+ interface BuildingsOptions {
2255
+ /**
2256
+ * Радиус скругления углов в МЕТРАХ. Скругляется контур, а значит и стены, и
2257
+ * крыша — она строится по тому же кольцу. Ноль (по умолчанию) — острые углы,
2258
+ * как раньше.
2259
+ */
2260
+ cornerRadius?: number;
2261
+ /** Максимум отрезков на дугу: меньше — дешевле и угловатее. */
2262
+ cornerSegments?: number;
2263
+ /**
2264
+ * Сколько метров в одной единице тайла. Нужен, чтобы решать по РАЗМЕРУ: дом
2265
+ * в сотню квадратов получает скатную крышу, торговый центр — плоскую. Без
2266
+ * него один и тот же порог на разных зумах означал бы разные здания.
2267
+ */
2268
+ metersPerUnit?: number;
2269
+ /** Покраска на фиче: цвет и прозрачность считаются здесь и ложатся в вершины. */
2270
+ paint?: FeaturePaint;
2271
+ }
2272
+ declare function generateBuildings(layer: VectorTileLayer, extent: number, filter?: Filter, options?: BuildingsOptions,
2273
+ /**
2274
+ * Куда складывать дома, задевающие границу тайла.
2275
+ *
2276
+ * Задан — такие дома здесь НЕ строятся: из куска правильной геометрии не выйдет, их соберут из
2277
+ * всех кусков сразу. Не задан — прежнее поведение, каждый кусок сам по себе.
2278
+ */
2279
+ seams?: BuildingSeam[]): BuildingsMesh | null;
2280
+ /**
2281
+ * Кусок дома, задевающего границу тайла: контур с пометкой ЛОЖНЫХ рёбер.
2282
+ *
2283
+ * Уезжает на главный поток, чтобы там сложиться с кусками соседних тайлов в целый дом.
2284
+ * Кольца — в координатах СВОЕГО тайла; общую систему строит уже сборщик, он знает номера тайлов.
2285
+ */
2286
+ interface BuildingSeam {
2287
+ osmId: number;
2288
+ height: number;
2289
+ minHeight: number;
2290
+ seed: number;
2291
+ tint: number;
2292
+ facade: number;
2293
+ outer: number[];
2294
+ holes: number[][];
2295
+ /** `cut[i]` — ребро от вершины i к i+1 порождено обрезкой, а не стеной дома. */
2296
+ outerCut: boolean[];
2297
+ holeCuts: boolean[][];
2298
+ }
2299
+
1734
2300
  /**
1735
2301
  * Сбор подписей из тайла.
1736
2302
  *
@@ -1830,6 +2396,13 @@ interface GeometryMesh {
1830
2396
  extent: number;
1831
2397
  /** id фичи на треугольник: пикинг, подсветка и вырезание заменённых моделью фич. */
1832
2398
  featureIds?: ArrayBuffer;
2399
+ /**
2400
+ * ПОКРАСКА НА ФИЧУ, по восемь байт на вершину (см. `generators/data-paint.ts`).
2401
+ *
2402
+ * Есть только у слоёв, чей цвет, прозрачность или ширина зависят от данных. У всех остальных
2403
+ * поля нет вовсе, атрибут в шейдере остаётся отключённым, и ничего не меняется.
2404
+ */
2405
+ dataPaint?: ArrayBuffer;
1833
2406
  /**
1834
2407
  * Только для поверхностей: сколько индексов в начале — обычная геометрия.
1835
2408
  * Хвост после них — крышки островов, их рисуют отдельно и только в глубину.
@@ -1875,12 +2448,32 @@ interface LoadRequest {
1875
2448
  * обычный виден на `z…z+1`, а тайл потолка растягивается на все зумы выше.
1876
2449
  */
1877
2450
  sourceMaxZoom: number;
2451
+ /**
2452
+ * Адрес тайла.
2453
+ *
2454
+ * Нужен там, где свойство фичи задано В ГЕОГРАФИИ, а не в единицах тайла: профиль подъёма рампы
2455
+ * приходит ломаной в градусах (см. `generators/lift`) и обязан лечь в ту же систему координат,
2456
+ * что и геометрия. Пересчитать градусы в единицы тайла без его адреса нельзя.
2457
+ */
2458
+ tile?: {
2459
+ z: number;
2460
+ x: number;
2461
+ y: number;
2462
+ };
1878
2463
  /** Что строить из этого тайла — план из стиля. */
1879
2464
  plan: LayerPlan[];
1880
2465
  }
1881
2466
  interface LoadedResponse {
1882
2467
  type: 'loaded';
1883
2468
  id: number;
2469
+ /**
2470
+ * СЫРОЙ ТАЙЛ, каким он приехал с сервера.
2471
+ *
2472
+ * Нужен запросам к данным (`queryRenderedFeatures`, `querySourceFeatures`): в геометрии остаются
2473
+ * одни номера фич, а свойства — здесь. Отдаётся ПЕРЕДАЧЕЙ владения, то есть бесплатно: воркеру
2474
+ * буфер после разбора не нужен. Так же держит тайлы MapLibre, и по той же причине.
2475
+ */
2476
+ raw?: ArrayBuffer;
1884
2477
  /**
1885
2478
  * Геометрия по id слоя стиля. Слои без геометрии в тайле просто отсутствуют.
1886
2479
  * У разбитых слоёв (`splitBy`) ключ составной — см. `splitBucketKey`.
@@ -1995,7 +2588,13 @@ declare function evaluateColor(v: Interpolated<string> | undefined, zoom: number
1995
2588
  * (кроме '!=' и '!in', где отсутствие считается несовпадением, — так же
1996
2589
  * ведёт себя MapLibre).
1997
2590
  */
1998
- declare function matchesFilter(filter: Filter | undefined, props: PropBag): boolean;
2591
+ declare function matchesFilter(filter: Filter | undefined, props: PropBag,
2592
+ /** Остальное окружение фичи: номер, тип геометрии, состояние. Нужно выражениям. */
2593
+ ctx?: {
2594
+ id?: number | string | null;
2595
+ geometryType?: string;
2596
+ featureState?: Record<string, unknown>;
2597
+ }): boolean;
1999
2598
 
2000
2599
  /**
2001
2600
  * Тема — набор правок поверх базового стиля, а не его копия.
@@ -2073,8 +2672,30 @@ declare class Style {
2073
2672
  /** Набор слоёв MVT, которые вообще нужны — по нему можно резать разбор. */
2074
2673
  sourceLayers(): string[];
2075
2674
  }
2675
+ /**
2676
+ * ПОКРАСКА, ЗАВИСЯЩАЯ ОТ ФИЧИ: что из неё придётся считать в воркере.
2677
+ *
2678
+ * Цвет, прозрачность и ширина уходят в шейдер юниформами — по одному значению на слой. Пока
2679
+ * значение задано числом, остановками или зумовым выражением, этого хватает; `['get', …]` внутри
2680
+ * значит, что у каждой фичи оно своё, и считать его надо там, где фича ещё жива, — при тесселяции
2681
+ * (см. `generators/data-paint.ts`).
2682
+ *
2683
+ * Слои с объёмом и рельефом (`surface`) сюда не входят: их цвет собирается из уровня, света и
2684
+ * материала, и одной подменой базового цвета не обойтись.
2685
+ */
2686
+ declare function featureDrivenPaint(layer: StyleLayerSpec): DataPaintSpec | null;
2076
2687
  /** Встроенный стиль: карта должна рисоваться без единого запроса за конфигом. */
2077
2688
  declare const DEFAULT_STYLE: StyleSpec;
2689
+ /**
2690
+ * РЕЛЬЕФНЫЙ ВАРИАНТ КАРТЫ: земля идёт по высотам, карта натянута на неё.
2691
+ *
2692
+ * Отдельный стиль, а не переключатель в основном, по двум причинам. Первая: рельеф меняет не
2693
+ * покраску, а САМ СПОСОБ отрисовки земли — плоский проход уезжает в текстуру тайла (см.
2694
+ * `drapeTerrain`), и слои, которые рисуются мимо него, на рельефе оказались бы на нуле. Поэтому
2695
+ * здесь нет объёмных поверхностей: вода, зелень и площадки — обычные заливки, и они драпируются
2696
+ * вместе со всем остальным. Вторая: основная карта остаётся ровно такой, какой была.
2697
+ */
2698
+ declare const TERRAIN_STYLE: StyleSpec;
2078
2699
  /**
2079
2700
  * Тот же стиль, но со скруглёнными углами (`scripts/make-soft-style.mjs`).
2080
2701
  *
@@ -2132,6 +2753,8 @@ declare class Tile {
2132
2753
  /** Ресурсы GPU по имени слоя — слой создаёт лениво, источник гарантированно чистит. */
2133
2754
  readonly buckets: Map<string, TileBucket>;
2134
2755
  constructor(id: TileID);
2756
+ /** Сырой тайл, каким он приехал: из него запросы берут свойства фич. */
2757
+ get raw(): ArrayBuffer | undefined;
2135
2758
  get hasGeometry(): boolean;
2136
2759
  destroy(): void;
2137
2760
  }
@@ -2178,6 +2801,42 @@ declare class TileSource extends Evented<TileSourceEvents> {
2178
2801
  private version;
2179
2802
  private opts;
2180
2803
  constructor(pool: WorkerPool, options: TileSourceOptions);
2804
+ /** План слоёв: что резать из тайла. Нужен наследнику, который собирает тайлы сам. */
2805
+ protected get plan(): LayerPlan[];
2806
+ /** Потолок зума источника. */
2807
+ protected get maxzoom(): number;
2808
+ /** Завести тайл в кеше: дальше его либо примут, либо отметят ошибкой. */
2809
+ protected newTile(id: TileID): Tile;
2810
+ /** Принять собранный тайл — как будто он приехал с сервера. */
2811
+ protected acceptTile(tile: Tile, data: LoadedResponse): void;
2812
+ /** Отметить тайл сбойным: собрать не удалось. */
2813
+ protected failTile(tile: Tile, message: string): void;
2814
+ /**
2815
+ * Выбросить собранное: данные источника сменились.
2816
+ *
2817
+ * Видимый набор при этом сохраняется — его пересчитает ближайший `update`, а до тех пор на карте
2818
+ * лучше пусто, чем чужая геометрия от прошлых данных.
2819
+ */
2820
+ protected invalidate(): void;
2821
+ /**
2822
+ * Правка адреса перед загрузкой (`map.setTransformRequest`).
2823
+ *
2824
+ * Ставится на источник, а не на пул: ключ бывает свой у каждого набора, и подписывать чужой
2825
+ * запрос чужим ключом — верный способ получить 403 там, где всё работало.
2826
+ */
2827
+ /**
2828
+ * Подробность набора тайлов: во сколько раз крупнее брать и сколько штук максимум.
2829
+ *
2830
+ * Видимый набор пересчитается на ближайшем `update` — дёргать что-то ещё не нужно.
2831
+ */
2832
+ setLodParams(params: {
2833
+ detailBias?: number;
2834
+ maxTiles?: number;
2835
+ }): void;
2836
+ setTransformRequest(transform: TransformRequest | null): void;
2837
+ private transform;
2838
+ /** Отмена загрузок по своему протоколу: у них своя сигнализация, пул о них не знает. */
2839
+ private protocolAborts;
2181
2840
  /** Тайлы, которые слой должен нарисовать в этом кадре. */
2182
2841
  get visibleTiles(): TileID[];
2183
2842
  /** Версия содержимого: меняется, когда приехал очередной тайл или сменился видимый набор. */
@@ -2259,7 +2918,14 @@ declare class TileSource extends Evented<TileSourceEvents> {
2259
2918
  * загрузки.
2260
2919
  */
2261
2920
  deactivate(): void;
2262
- private loadTile;
2921
+ /**
2922
+ * Заказать тайл.
2923
+ *
2924
+ * `protected`, потому что источник из данных приложения (`GeoJSONSource`) собирает тайл сам, без
2925
+ * сети и без воркера, — но всё остальное у него общее: тот же кеш, то же покрытие предками, та
2926
+ * же отмена при уходе из вида.
2927
+ */
2928
+ protected loadTile(id: TileID, priority?: number): void;
2263
2929
  /**
2264
2930
  * Готовый предок для тайла, который ещё грузится — чтобы на его месте не зияла
2265
2931
  * дыра. Возвращает null, если готового предка в кеше нет.
@@ -2280,14 +2946,131 @@ declare class TileSource extends Evented<TileSourceEvents> {
2280
2946
  }
2281
2947
 
2282
2948
  /**
2283
- * Загрузка SDF-глифов в формате Mapbox (`{fontstack}/{start}-{end}.pbf`).
2284
- * Тот же формат отдаёт наш tileserver-gl (`/tileserver/fonts/...`), поэтому
2285
- * подписи в движке выглядят ровно так же, как в остальных наших картах.
2949
+ * Атлас SDF: глифы и иконки лежат в ОДНОЙ текстуре.
2286
2950
  *
2287
- * Битмап глифа — знаковое поле расстояний в одном канале. Кодировка
2288
- * стандартная и её ВАЖНО не переизобретать: край буквы там не на 0, а на
2289
- * 192/255, а весь диапазон 0..255 растянут на −6..+2 пикселя. Иконки мы
2290
- * генерируем сами и обязаны попасть в ту же кодировку, иначе один шейдер не
2951
+ * Именно поэтому подпись и её иконка красятся одним цветом и одной обводкой —
2952
+ * шейдер не различает, откуда взят кусок текстуры. Ради этого иконки и
2953
+ * приводятся к глифовой кодировке SDF (см. sdf.ts).
2954
+ *
2955
+ * Упаковка полками: простая, без фрагментации на однородных по высоте данных
2956
+ * (а глифы одного кегля именно такие) и не требует перепаковки.
2957
+ */
2958
+ interface AtlasEntry {
2959
+ x: number;
2960
+ y: number;
2961
+ width: number;
2962
+ height: number;
2963
+ }
2964
+ declare class SdfAtlas {
2965
+ readonly size: number;
2966
+ private data;
2967
+ private entries;
2968
+ private shelfY;
2969
+ private shelfX;
2970
+ private shelfHeight;
2971
+ private texture;
2972
+ private dirty;
2973
+ private full;
2974
+ constructor(size?: number);
2975
+ get(key: string): AtlasEntry | undefined;
2976
+ /** Кладёт одноканальный SDF в атлас. Возвращает null, если места больше нет. */
2977
+ add(key: string, sdf: Uint8Array, width: number, height: number): AtlasEntry | null;
2978
+ /** Привязывает текстуру к юниту, при необходимости заливая изменения. */
2979
+ bind(ctx: Context, unit: number): void;
2980
+ destroy(ctx: Context): void;
2981
+ }
2982
+
2983
+ /**
2984
+ * КАРТИНКИ ПРИЛОЖЕНИЯ: цветные значки, спрайты, всё, что даёт `map.addImage`.
2985
+ *
2986
+ * Зачем отдельно от `IconSource`. Наши иконки — силуэты: SVG растрируется в маску, из маски
2987
+ * строится SDF, и он ложится в общий с буквами одноканальный атлас. Оттуда и главное свойство —
2988
+ * значок красится тем же цветом, что подпись. Цветную картинку так не показать: у неё свои цвета,
2989
+ * и канал в атласе всего один.
2990
+ *
2991
+ * Поэтому здесь второй атлас — RGBA, со своей текстурой, и своя ветка в шейдере подписей. Обе
2992
+ * дороги живут рядом, как и у MapLibre: `addImage(id, image, {sdf: true})` кладёт картинку в
2993
+ * SDF-путь (её будет красить стиль), без `sdf` — в цветной (рисуется как есть).
2994
+ */
2995
+ /** Картинка в том виде, в каком её отдаёт приложение. */
2996
+ interface StyleImageData {
2997
+ width: number;
2998
+ height: number;
2999
+ /** RGBA, по четыре байта на пиксель — как у `ImageData`. */
3000
+ data: Uint8Array | Uint8ClampedArray;
3001
+ }
3002
+ interface StyleImageOptions {
3003
+ /**
3004
+ * Во сколько раз картинка подробнее экранного пикселя. Двойка значит «нарисована для Retina»:
3005
+ * на экране она займёт вдвое меньше места, чем пикселей в файле.
3006
+ */
3007
+ pixelRatio?: number;
3008
+ /**
3009
+ * Картинка — это ПОЛЕ РАССТОЯНИЙ, а не рисунок.
3010
+ *
3011
+ * Тогда она уходит в общий с буквами атлас и красится стилем, как наши иконки. Требование то же,
3012
+ * что у MapLibre: в альфе должно лежать готовое SDF, а не силуэт — силуэт даст рваный край.
3013
+ */
3014
+ sdf?: boolean;
3015
+ }
3016
+ interface StyleImage extends StyleImageOptions {
3017
+ id: string;
3018
+ width: number;
3019
+ height: number;
3020
+ data: Uint8Array;
3021
+ }
3022
+ /**
3023
+ * Цветной атлас: полочная упаковка, как у SDF-атласа.
3024
+ *
3025
+ * Простая и без перепаковки: картинки приложения добавляются десятками, а не тысячами, и
3026
+ * фрагментация на таком количестве не успевает стать проблемой.
3027
+ */
3028
+ declare class ImageManager {
3029
+ readonly size: number;
3030
+ private images;
3031
+ private entries;
3032
+ private data;
3033
+ private texture;
3034
+ private dirty;
3035
+ private shelfX;
3036
+ private shelfY;
3037
+ private shelfHeight;
3038
+ private full;
3039
+ /** Растёт при любой правке набора: по нему раскладка понимает, что пора пересобрать квады. */
3040
+ version: number;
3041
+ constructor(size?: number);
3042
+ has(id: string): boolean;
3043
+ get(id: string): StyleImage | undefined;
3044
+ list(): string[];
3045
+ /**
3046
+ * Добавить картинку. Уже занятое имя НЕ перезаписывается — у MapLibre это ошибка, а молча
3047
+ * подменять чужой значок хуже, чем не добавить свой.
3048
+ */
3049
+ add(id: string, image: StyleImageData, options?: StyleImageOptions): boolean;
3050
+ /** Заменить картинку под тем же именем: размер может смениться, место в атласе берётся заново. */
3051
+ update(id: string, image: StyleImageData, options?: StyleImageOptions): boolean;
3052
+ remove(id: string): boolean;
3053
+ /**
3054
+ * Место картинки в атласе. Кладётся при первом обращении — ровно то, что нужно: добавленная, но
3055
+ * никем не нарисованная картинка не занимает ни пикселя.
3056
+ *
3057
+ * `null` — либо такой картинки нет, либо в атласе не осталось места.
3058
+ */
3059
+ entryFor(id: string): AtlasEntry | null;
3060
+ /** Привязать текстуру атласа к блоку. Данные заливаются только когда менялись. */
3061
+ bind(ctx: Context, unit: number): void;
3062
+ destroy(ctx: Context): void;
3063
+ }
3064
+
3065
+ /**
3066
+ * Загрузка SDF-глифов в формате Mapbox (`{fontstack}/{start}-{end}.pbf`).
3067
+ * Тот же формат отдаёт наш tileserver-gl (`/tileserver/fonts/...`), поэтому
3068
+ * подписи в движке выглядят ровно так же, как в остальных наших картах.
3069
+ *
3070
+ * Битмап глифа — знаковое поле расстояний в одном канале. Кодировка
3071
+ * стандартная и её ВАЖНО не переизобретать: край буквы там не на 0, а на
3072
+ * 192/255, а весь диапазон 0..255 растянут на −6..+2 пикселя. Иконки мы
3073
+ * генерируем сами и обязаны попасть в ту же кодировку, иначе один шейдер не
2291
3074
  * сможет рисовать текст и иконки одинаково.
2292
3075
  */
2293
3076
  declare const SDF_EDGE = 0.75;
@@ -2317,6 +3100,13 @@ declare class GlyphSource {
2317
3100
  constructor(
2318
3101
  /** Шаблон вида /tileserver/fonts/{fontstack}/{range}.pbf */
2319
3102
  urlTemplate: string, fontstack?: string);
3103
+ /**
3104
+ * Сменить адрес шрифтов (`map.setGlyphs`).
3105
+ *
3106
+ * Загруженные диапазоны выбрасываются: они пришли с прежнего сервера, и держать их рядом с
3107
+ * новыми значит показывать одну подпись двумя гарнитурами.
3108
+ */
3109
+ setUrl(template: string): void;
2320
3110
  get(code: number): Glyph | undefined;
2321
3111
  hasRange(code: number): boolean;
2322
3112
  /** Есть ли в строке символы, диапазоны которых ещё не загружены. */
@@ -2374,6 +3164,15 @@ interface Box {
2374
3164
  y1: number;
2375
3165
  x2: number;
2376
3166
  y2: number;
3167
+ /**
3168
+ * Чьё это место.
3169
+ *
3170
+ * `undefined` — место занято у ВСЕХ: так стоит обычная подпись или значок. Строка — место занято
3171
+ * только у своих, у подписей той же группы. Фоновому слою (название улицы, номер дома) нужно
3172
+ * именно это: значку кафе он уступает и не вытесняет его, а вот друг на друга такие подписи
3173
+ * налезать не должны — из двух названий, легших крест-накрест, не читается ни одно.
3174
+ */
3175
+ group?: string;
2377
3176
  }
2378
3177
  interface Candidate {
2379
3178
  layer: SymbolLayerSpec;
@@ -2442,12 +3241,35 @@ declare class SymbolRenderer {
2442
3241
  onAssetsReady: (() => void) | null;
2443
3242
  private pendingText;
2444
3243
  private pendingIcons;
3244
+ /** Сменить адрес шрифтов: `map.setGlyphs`. Разложенные подписи пересоберутся сами. */
3245
+ setGlyphsUrl(url: string): void;
3246
+ /**
3247
+ * Кого звать, когда стиль просит картинку, которой нет (`map.setMissingStyleImageResolver`).
3248
+ *
3249
+ * Зовётся ОДИН РАЗ НА ИМЯ: раскладка идёт каждый кадр, и без этой памяти обработчик дёргался бы
3250
+ * по шестьдесят раз в секунду на один и тот же отсутствующий значок.
3251
+ */
3252
+ setMissingImage(resolver: ((id: string) => void) | null): void;
3253
+ private missingImage;
3254
+ private askedImages;
3255
+ /** Картинки приложения (`map.addImage`): цветной атлас и своя ветка отрисовки. */
3256
+ readonly images: ImageManager;
2445
3257
  /**
2446
3258
  * Контур выбранного здания в мировых пикселях (треугольники по тайлам).
2447
3259
  * Кладёт рендерер перед сбором; по нему слои `showFor: 'highlight'` решают,
2448
3260
  * относится ли точка к выбранному дому.
2449
3261
  */
2450
3262
  highlightFootprint: Float64Array[] | null;
3263
+ /**
3264
+ * ВЫСОТА ЗЕМЛИ ПОД ТОЧКОЙ, метры — на рельефной карте.
3265
+ *
3266
+ * Подпись и значок — экранные, но привязаны к месту НА ЗЕМЛЕ, а земля на рельефе уже не
3267
+ * плоскость. Без этой высоты название посёлка в горах остаётся на уровне моря и уезжает от
3268
+ * самого посёлка на экране тем дальше, чем выше он лежит и сильнее наклонена камера.
3269
+ *
3270
+ * Ставит рендерер; на обычной карте возвращает ноль, и проекция идёт как раньше.
3271
+ */
3272
+ elevationAt: ((mercX: number, mercY: number) => number) | null;
2451
3273
  constructor(fontUrl: string, fontstack?: string);
2452
3274
  init(ctx: Context): void;
2453
3275
  /**
@@ -2674,6 +3496,105 @@ declare function arrowLabelOffset(layer: SymbolLayerSpec, zoom: number, pixelsPe
2674
3496
  */
2675
3497
  declare function iconRotation(layer: SymbolLayerSpec, rotations: Int16Array | null, i: number, bearing: number): number;
2676
3498
 
3499
+ /**
3500
+ * КАРТИНКА, ВИДЕО ИЛИ CANVAS, ПРИБИТЫЕ К МЕСТУ НА КАРТЕ.
3501
+ *
3502
+ * Тайлов тут нет вовсе: источник — одна картинка, растянутая по четырём углам. Так у MapLibre
3503
+ * устроены `image`, `video` и `canvas`, и так на карту кладут план здания, скан старой карты,
3504
+ * радарный снимок или поток с камеры.
3505
+ *
3506
+ * ★ ЧЕТЫРЕ УГЛА, А НЕ ПРЯМОУГОЛЬНИК.
3507
+ *
3508
+ * Углы задаются по отдельности и не обязаны образовывать прямоугольник: скан старой карты почти
3509
+ * всегда завален, и прямоугольная рамка легла бы мимо местности. Поэтому геометрия — квад из
3510
+ * четырёх настоящих точек, а не матрица поверх единичного квадрата.
3511
+ *
3512
+ * ★ ВИДЕО И CANVAS ОБНОВЛЯЮТСЯ КАЖДЫЙ КАДР.
3513
+ *
3514
+ * Текстура заливается заново, пока идёт воспроизведение (или пока приложение объявило canvas
3515
+ * живым). Картинка — один раз: перезаливать неподвижный снимок каждый кадр значит жечь шину без
3516
+ * повода.
3517
+ */
3518
+ type MediaKind = 'image' | 'video' | 'canvas';
3519
+ /** Углы: левый верхний, правый верхний, правый нижний, левый нижний — порядок MapLibre. */
3520
+ type MediaCoordinates = [LngLatLike, LngLatLike, LngLatLike, LngLatLike];
3521
+ interface MediaSourceOptions {
3522
+ kind: MediaKind;
3523
+ /** Адрес картинки или видео; для canvas — сам элемент. */
3524
+ url?: string | string[];
3525
+ canvas?: HTMLCanvasElement | string;
3526
+ coordinates: MediaCoordinates;
3527
+ /** Держать ли canvas живым: перезаливать текстуру каждый кадр. */
3528
+ animate?: boolean;
3529
+ }
3530
+ interface MediaSourceEvents extends Record<string, unknown> {
3531
+ data: {
3532
+ source: MediaSource;
3533
+ };
3534
+ error: {
3535
+ message: string;
3536
+ };
3537
+ }
3538
+ declare class MediaSource extends Evented<MediaSourceEvents> {
3539
+ private options;
3540
+ readonly kind: MediaKind;
3541
+ /** Готовый к отрисовке источник пикселей. */
3542
+ content: TexImageSource | null;
3543
+ /** Углы в нормализованном меркаторе: `[x, y]` по четырём точкам. */
3544
+ corners: [number, number][];
3545
+ /** Меняется при каждой замене содержимого: по нему слой понимает, что текстуру пора залить. */
3546
+ version: number;
3547
+ private video;
3548
+ private animate;
3549
+ constructor(options: MediaSourceOptions);
3550
+ /** Обновляются ли пиксели сами (видео, живой canvas). */
3551
+ get live(): boolean;
3552
+ /** Переставить углы: `setCoordinates` у MapLibre. */
3553
+ setCoordinates(coordinates: MediaCoordinates): this;
3554
+ getCoordinates(): MediaCoordinates;
3555
+ /** Заменить картинку, не трогая углы (`updateImage` у MapLibre). */
3556
+ updateImage(options: {
3557
+ url?: string;
3558
+ coordinates?: MediaCoordinates;
3559
+ }): Promise<this>;
3560
+ getVideo(): HTMLVideoElement | null;
3561
+ play(): void;
3562
+ pause(): void;
3563
+ /** Для canvas: включить или выключить перезаливку каждый кадр. */
3564
+ setAnimate(on: boolean): void;
3565
+ destroy(): void;
3566
+ private load;
3567
+ }
3568
+
3569
+ /**
3570
+ * СВОЙ СЛОЙ ПРИЛОЖЕНИЯ: чужой WebGL внутри нашего кадра.
3571
+ *
3572
+ * Договор дословно как у MapLibre (`CustomLayerInterface`): приложение получает контекст и матрицу
3573
+ * и рисует что хочет — свою модель, тепловую карту, эффект. Так на карту кладут то, чего в стиле
3574
+ * нет и не будет.
3575
+ *
3576
+ * ★ МАТРИЦА ТА ЖЕ, ЧТО У MapLibre.
3577
+ *
3578
+ * Локальные координаты — нормализованный меркатор (0..1 на весь мир), ось Z — метры. Слой,
3579
+ * написанный под MapLibre, рисуется у нас без единой правки.
3580
+ *
3581
+ * ★ ПОРЯДОК ЗАДАЁТСЯ ЯКОРЕМ, А НЕ МЕСТОМ В СТИЛЕ.
3582
+ *
3583
+ * Слой несёт функцию, а спецификация стиля у нас копируется — функция такого не переживает.
3584
+ * Поэтому свои слои живут отдельным списком, а куда встать, говорит `beforeId` при добавлении.
3585
+ */
3586
+ interface CustomLayer {
3587
+ id: string;
3588
+ type: 'custom';
3589
+ /** `'3d'` — рисуется с тестом глубины вместе с объёмом; `'2d'` — по земле. Подсказка приложения. */
3590
+ renderingMode?: '2d' | '3d';
3591
+ /** Позвали при добавлении: место завести буферы и программы. */
3592
+ onAdd?(map: unknown, gl: WebGL2RenderingContext): void;
3593
+ /** Позвали при снятии: место всё освободить. */
3594
+ onRemove?(map: unknown, gl: WebGL2RenderingContext): void;
3595
+ render(gl: WebGL2RenderingContext, matrix: Mat4 | Float32Array): void;
3596
+ }
3597
+
2677
3598
  interface AttribSpec {
2678
3599
  name: string;
2679
3600
  /** Число компонент: 1..4. */
@@ -2810,6 +3731,296 @@ declare class IndoorMask {
2810
3731
  } | null;
2811
3732
  }
2812
3733
 
3734
+ /**
3735
+ * ВЫСОТА ИЗ КАРТИНКИ: разбор тайла рельефа.
3736
+ *
3737
+ * Рельеф приезжает обычным PNG, в пикселе которого упакована высота. Так устроено у всех — у
3738
+ * Mapbox, у MapLibre, у Terrarium, — и по одной причине: высоту тогда раздаёт тот же тайлсервер,
3739
+ * что и остальное, а распаковать её умеет и GPU, и процессор.
3740
+ *
3741
+ * Наши тайлы собирает `deploy/terrain/build-terrain.sh` из Copernicus GLO-30 в кодировке `mapbox`
3742
+ * с потолком z10 — там же замер, почему потолок не 11 (рельеф стоил бы 84 % сетевого объёма карты).
3743
+ *
3744
+ * Здесь только ЧИСЛА: ни DOM, ни WebGL, поэтому разбор проверяется в node.
3745
+ */
3746
+ /** Кодировка высоты в пикселе. */
3747
+ type DemEncoding = 'mapbox' | 'terrarium';
3748
+ /**
3749
+ * Множители распаковки: `высота = R·r + G·g + B·b − shift`.
3750
+ *
3751
+ * Этот же вектор уезжает в шейдер юниформом (`u_terrain_unpack` у MapLibre): распаковка на GPU
3752
+ * обязана совпадать с распаковкой на CPU до последнего сантиметра, иначе подпись стоит на одной
3753
+ * высоте, а гора под ней нарисована на другой.
3754
+ */
3755
+ declare function unpackVector(encoding: DemEncoding): [number, number, number, number];
3756
+ /**
3757
+ * Разобранный тайл рельефа: квадрат высот со стороной `size`.
3758
+ *
3759
+ * Высоты держим `Float32Array`, а не распаковываем каждый раз из пикселей: выборка нужна камере на
3760
+ * каждый кадр и подписям на каждую точку привязки, а распаковка — три умножения и вычитание.
3761
+ */
3762
+ declare class DemTile {
3763
+ readonly heights: Float32Array;
3764
+ readonly size: number;
3765
+ readonly min: number;
3766
+ readonly max: number;
3767
+ constructor(pixels: Uint8Array | Uint8ClampedArray, size: number, encoding?: DemEncoding);
3768
+ /**
3769
+ * Высота в точке тайла, координаты 0..1 от его левого верхнего угла.
3770
+ *
3771
+ * СМЕСЬ ПО ЧЕТЫРЁМ СОСЕДЯМ, а не «ближайший пиксель». Пиксель рельефа — это ячейка шириной
3772
+ * 30–60 м, и на ближайшем соседе склон превращается в лестницу со ступенью в этаж: её видно и в
3773
+ * рисунке горы, и в том, как по ней едет дорога.
3774
+ *
3775
+ * Пиксель `i` описывает ЦЕНТР своей ячейки, то есть точку `(i + 0.5) / size` — отсюда сдвиг на
3776
+ * полпикселя. Та же договорённость у MapLibre (`DEM_CELL_CENTER_OFFSET`) и в расчёте теней
3777
+ * рельефа; разойдись мы с ней, высота уезжала бы на полклетки.
3778
+ */
3779
+ heightAt(x: number, y: number): number;
3780
+ }
3781
+
3782
+ /**
3783
+ * Источник рельефа: тайлы высот.
3784
+ *
3785
+ * Отдельный класс от `RasterSource` по той же причине, по которой тот отделён от векторного:
3786
+ * РАЗНАЯ РАБОТА С РЕЗУЛЬТАТОМ. Подложке картинка нужна как картинка — её отдают текстуре и
3787
+ * забывают. Рельефу она нужна дважды: как текстура для шейдера (там высоту распаковывает GPU) и
3788
+ * как числа для процессора — высоту под камерой, под подписью и под точкой клика спрашивают
3789
+ * каждый кадр, и гонять ради этого чтение с GPU нельзя.
3790
+ *
3791
+ * ПОТОЛОК ЗУМА НИЗКИЙ И ЭТО НОРМАЛЬНО. Наш набор собран до z10 (59 м на пиксель): рельеф — это
3792
+ * форма долины и хребта, а не бордюр, и выше он не несёт ничего, кроме трафика. Замер в
3793
+ * `deploy/terrain/build-terrain.sh`: z11 стоил бы 1 ГБ набора и 84 % сетевого объёма карты. Выше
3794
+ * потолка тайл не перезапрашивается — берётся тот же, что и на потолке, как это делает MapLibre.
3795
+ */
3796
+ type DemTileState = 'loading' | 'loaded' | 'error';
3797
+ /** Загруженный тайл рельефа: картинка для GPU и разобранные высоты для CPU. */
3798
+ declare class DemTileEntry {
3799
+ readonly id: TileID;
3800
+ state: DemTileState;
3801
+ /** Картинка как приехала: её отдают текстуре, распаковку делает шейдер. */
3802
+ image: ImageBitmap | null;
3803
+ /** Разобранные высоты. Появляются по первому запросу с CPU — см. `heights`. */
3804
+ dem: DemTile | null;
3805
+ error: string | null;
3806
+ /** Ресурс GPU, заводит слой рельефа; источник обязан освободить при вытеснении. */
3807
+ texture: {
3808
+ destroy(): void;
3809
+ } | null;
3810
+ constructor(id: TileID);
3811
+ destroy(): void;
3812
+ }
3813
+ interface DemSourceOptions extends CoverOptions {
3814
+ /** Шаблон вида `/tileserver/data/terrain/{z}/{x}/{y}.webp`. */
3815
+ url: string;
3816
+ /** Кодировка высоты в пикселе. По умолчанию `mapbox` — в ней собран наш набор. */
3817
+ encoding?: DemEncoding;
3818
+ /** Сколько тайлов держать сверх видимых. */
3819
+ cacheSize?: number;
3820
+ }
3821
+ interface DemSourceEvents extends Record<string, unknown> {
3822
+ data: {
3823
+ tile: DemTileEntry;
3824
+ };
3825
+ error: {
3826
+ tile: TileID;
3827
+ message: string;
3828
+ };
3829
+ }
3830
+ declare class DemSource extends Evented<DemSourceEvents> {
3831
+ private cache;
3832
+ private visible;
3833
+ private opts;
3834
+ private aborts;
3835
+ constructor(options: DemSourceOptions);
3836
+ get encoding(): DemEncoding;
3837
+ get visibleTiles(): TileID[];
3838
+ getTile(id: TileID): DemTileEntry | undefined;
3839
+ /** Пересчитывает видимый набор под камеру и подгружает недостающее. */
3840
+ update(tr: Transform): void;
3841
+ /**
3842
+ * Высоты тайла, разобранные в числа. Первый вызов декодирует картинку, дальше отдаётся готовое.
3843
+ *
3844
+ * Декодируем ЛЕНИВО: на кадр высота спрашивается в нескольких точках, а тайлов в кеше десятки —
3845
+ * разбирать все при загрузке значит платить за то, чего никто не спросит.
3846
+ */
3847
+ heights(tile: DemTileEntry): DemTile | null;
3848
+ destroy(): void;
3849
+ private loadTile;
3850
+ private fetchImage;
3851
+ }
3852
+
3853
+ /** Цель отрисовки одного тайла: цветная текстура с буфером глубины. */
3854
+ declare class TileTarget {
3855
+ private ctx;
3856
+ readonly size: number;
3857
+ readonly texture: WebGLTexture;
3858
+ readonly framebuffer: WebGLFramebuffer;
3859
+ /** Ключ содержимого: пока совпадает, перерисовывать нечего. */
3860
+ key: string;
3861
+ constructor(ctx: Context, size: number);
3862
+ /** Залить цель сплошным цветом: так выглядит квадрат, которому перерисовка ещё не досталась. */
3863
+ clear(color: readonly number[]): void;
3864
+ destroy(): void;
3865
+ }
3866
+ declare class Terrain {
3867
+ /** Преувеличение высоты. 1 — как в данных. */
3868
+ exaggeration: number;
3869
+ /**
3870
+ * Счётчик приехавших тайлов высот.
3871
+ *
3872
+ * По нему пересобирается то, что считает высоту НА ПРОЦЕССОРЕ и держит результат в буфере, —
3873
+ * маршрут, зоны. Пока тайл не приехал, высота нулевая, и без этого счётчика маршрут так бы и
3874
+ * остался лежать на нуле: геометрия у него не меняется, пересобирать её нечему.
3875
+ */
3876
+ revision: number;
3877
+ private source;
3878
+ private empty;
3879
+ private unsubscribe;
3880
+ /** Тайлы рельефа этого кадра — те самые, чьи решётки рисуются. */
3881
+ private current;
3882
+ /** Последний подошедший тайл: следующий вопрос почти наверняка про него же. */
3883
+ private lastCover;
3884
+ setSource(source: DemSource | null): void;
3885
+ get enabled(): boolean;
3886
+ /**
3887
+ * Ставит юниформы рельефа для конкретного тайла карты.
3888
+ *
3889
+ * Рельефа нет или тайл высот ещё не приехал — ставим нулевое преувеличение: шейдер пропускает
3890
+ * выборку и рисует по плоской земле. Это НЕ заглушка на чёрный день, а обычный кадр: тайлы
3891
+ * высот грузятся сетью, и первые кадры карта всегда плоская.
3892
+ */
3893
+ apply(ctx: Context, shader: Shader, tile: TileID): void;
3894
+ /**
3895
+ * DEM ДЛЯ ТАЙЛА КАРТЫ — ТОТ ЖЕ, ЧТО У ЗЕМЛИ ПОД НИМ.
3896
+ *
3897
+ * Дом обязан стоять на той земле, которая под ним нарисована. А нарисована она решёткой тайла
3898
+ * РЕЛЬЕФА, и тайл этот крупнее тайла карты (`TILE_DETAIL_BIAS`) — значит и DEM у него бывает
3899
+ * другой, погрубее: вдали тайл рельефа уже z8, когда у карты под ним ещё z10. Возьми дом высоту
3900
+ * из своего, подробного DEM — он встанет на настоящую вершину, а сглаженный холм под ним окажется
3901
+ * ниже, и дом повиснет в воздухе. Это и было видно на дальнем плане.
3902
+ *
3903
+ * Поэтому сначала ищем тайл рельефа, накрывающий тайл карты, берём ЕГО DEM и уже внутри него
3904
+ * вырезаем свой кусок. У MapLibre то же самое делает `getTerrainData`: объект получает текстуру и
3905
+ * матрицу того тайла рельефа, на котором стоит, а не ближайшую подробную.
3906
+ */
3907
+ private demFor;
3908
+ /**
3909
+ * Самая низкая и самая высокая земля в кадре, метры.
3910
+ *
3911
+ * Нужна теням: объём карты теней строится вокруг земли, а она на рельефе не на нуле. Берём по
3912
+ * центрам тайлов рельефа — их десятки, и точность здесь нужна до сотни метров, не до метра.
3913
+ */
3914
+ groundRange(): [number, number];
3915
+ /** Есть ли под тайлом карты земля этого кадра. Пусто — рисовать на нём нечего. */
3916
+ covers(tile: TileID): boolean;
3917
+ /** Тайл рельефа из нарисованного набора, накрывающий тайл карты. */
3918
+ private coverFor;
3919
+ /**
3920
+ * DEM-тайл под тайлом карты и перевод координат в него.
3921
+ *
3922
+ * Поднимаемся по пирамиде от самого тайла вверх: у рельефа свой потолок зума, и на городских
3923
+ * зумах подходящий предок находится с первой же попытки. Дальше корня не идём — там рельефа нет.
3924
+ */
3925
+ private tileFor;
3926
+ /**
3927
+ * Текстура DEM-тайла. `NEAREST` и без мипов: в пикселе лежит ЧИСЛО, разложенное по каналам, и
3928
+ * любое усреднение соседей смешивает разряды — из двух соседних высот получается третья, которой
3929
+ * в природе нет. Сглаживание делает сам шейдер, уже по распакованным высотам.
3930
+ */
3931
+ private textureFor;
3932
+ /** Заглушка 1×1 на кадры без рельефа: сэмплер обязан быть привязан, иначе программа не рисует. */
3933
+ private emptyTexture;
3934
+ /**
3935
+ * ВЫСОТА ЗЕМЛИ В ТОЧКЕ, метры. Нужна тому, что считается на процессоре: камере и подписям.
3936
+ *
3937
+ * Ищем самый подробный ЗАГРУЖЕННЫЙ тайл высот, накрывающий точку, и берём выборку из него.
3938
+ * Пока ни один не приехал — ноль: карта в этот момент и так плоская, а ждать загрузки, чтобы
3939
+ * поставить камеру, значит не показать ничего.
3940
+ */
3941
+ heightAt(lng: number, lat: number): number;
3942
+ /** То же по нормализованному меркатору 0..1 — так координату держат подписи и раскладка. */
3943
+ heightAtMerc(mx: number, my: number): number;
3944
+ /**
3945
+ * Тайл рельефа этого кадра, накрывающий точку меркатора.
3946
+ *
3947
+ * Начинаем с того, что подошёл в прошлый раз. Высоту спрашивают подряд для соседних точек —
3948
+ * вершин маршрута, мест подписей, — и почти всегда это тот же тайл. Без этой памяти каждый
3949
+ * вопрос перебирал бы весь набор, а их на кадр тысячи.
3950
+ */
3951
+ private coverForPoint;
3952
+ private inside;
3953
+ /**
3954
+ * ТАЙЛЫ РЕЛЬЕФА — СВОЙ НАБОР, а не тайлы карты.
3955
+ *
3956
+ * Так сделано у MapLibre (`TerrainTileManager.update`), и вот зачем. Каждому такому тайлу нужна
3957
+ * своя текстура 1024×1024 — четыре мегабайта видеопамяти. Тайлов карты на экране бывает под
3958
+ * сотню (на z14 их 73), и вместе это триста мегабайт, которые GPU молча не даёт: текстуры
3959
+ * перестают создаваться, а карта — рисоваться.
3960
+ *
3961
+ * Поэтому набор берётся КРУПНЕЕ: `detailBias` вдвое — это примерно на зум грубее, то есть вчетверо
3962
+ * меньше тайлов. Разрешение при этом не страдает: в текстуру тайла рельефа рисуются ВСЕ тайлы
3963
+ * карты, которые он накрывает, каждый своей матрицей.
3964
+ */
3965
+ tilesFor(tr: Transform): TileID[];
3966
+ /**
3967
+ * РАЗМЕР ТЕКСТУРЫ НА КАЖДЫЙ ТАЙЛ — ПО ЕГО МЕСТУ НА ЭКРАНЕ.
3968
+ *
3969
+ * Одинаковый размер на все тайлы расточителен с обеих сторон: дальний тайл занимает полсотни
3970
+ * пикселей и держит те же четыре мегабайта, а ближний растянут на весь экран и мылит. Поэтому
3971
+ * размер берётся от экранной стороны тайла, с оглядкой на плотность экрана, и укладывается в
3972
+ * бюджет видеопамяти: тайлы идут от ближнего к дальнему (`tilesFor` их так и сортирует), и
3973
+ * сначала своё получают те, на которые смотрят.
3974
+ */
3975
+ sizesFor(tr: Transform, tiles: TileID[], dpr: number): number[];
3976
+ private mesh;
3977
+ private targets;
3978
+ /**
3979
+ * Решётка тайла: квадрат 0..1, разбитый на клетки, плюс ЮБКА по краю.
3980
+ *
3981
+ * Одна на все тайлы — высоту в вершину подставляет шейдер, и геометрия от тайла не зависит. Так
3982
+ * же сделано у MapLibre: меш кешируется один раз на карту.
3983
+ */
3984
+ meshOf(ctx: Context, locations: Map<string, number>): {
3985
+ vao: VertexArray;
3986
+ count: number;
3987
+ };
3988
+ /**
3989
+ * Цель отрисовки для тайла: та самая 2D-карта, которая ляжет на рельеф.
3990
+ *
3991
+ * `key` решает, надо ли перерисовывать: пока не поменялись ни данные тайла, ни зум (ширина
3992
+ * дорог у нас экранная, и на другом зуме картинка другая), готовая текстура годится. Это и есть
3993
+ * отпечаток MapLibre (`RTTFingerprint`), только короче — у нас один источник на тайл.
3994
+ */
3995
+ /** Готовая цель тайла, если она есть. Не создаёт и не трогает ключ — только смотрит. */
3996
+ targetOf(dataKey: string): TileTarget | undefined;
3997
+ /**
3998
+ * Цель тайла. `fresh` — она только что заведена, и в ней ПУСТО.
3999
+ *
4000
+ * Ключ содержимого здесь НЕ проставляется: рисовать или отложить, решает вызывающий (на кадр
4001
+ * отводится бюджет перерисовок), а пометить готовым то, что не нарисовано, значит оставить в
4002
+ * кадре вчерашнюю картинку навсегда.
4003
+ */
4004
+ targetFor(ctx: Context, dataKey: string, size?: number): {
4005
+ target: TileTarget;
4006
+ fresh: boolean;
4007
+ };
4008
+ /** Убирает цели тайлов, которых больше нет в кадре: каждая — 4 МБ видеопамяти. */
4009
+ keepOnly(visible: Set<string>): void;
4010
+ /** Сторона текстуры драпировки — в неё же ставится вьюпорт на время отрисовки тайла. */
4011
+ get drapeSize(): number;
4012
+ /**
4013
+ * На сколько опускать юбку, метры. Считается ПО ТАЙЛУ, а не по камере.
4014
+ *
4015
+ * Юбка закрывает щель на шве: соседние тайлы берут высоту края из разных пикселей DEM и
4016
+ * расходятся тем сильнее, чем крупнее тайл. Значит и мерка у неё тайловая. По зуму КАМЕРЫ юбка
4017
+ * выходила одинаковой для всех тайлов в кадре: на ближних, мелких, она становилась в пятую часть
4018
+ * их размера и на пологом взгляде торчала из земли стеной с размазанной по ней картой.
4019
+ */
4020
+ skirtMeters(tileZoom: number): number;
4021
+ destroy(ctx: Context): void;
4022
+ }
4023
+
2813
4024
  /**
2814
4025
  * Источник растровых тайлов: подложка картинками.
2815
4026
  *
@@ -2866,6 +4077,18 @@ declare class RasterSource extends Evented<RasterSourceEvents> {
2866
4077
  /** Перечитать подложку (сменился набор тайлов на сервере). */
2867
4078
  reload(): void;
2868
4079
  destroy(): void;
4080
+ /** Правка адреса перед загрузкой (`map.setTransformRequest`). */
4081
+ /**
4082
+ * Подробность набора тайлов: во сколько раз крупнее брать и сколько штук максимум.
4083
+ *
4084
+ * Видимый набор пересчитается на ближайшем `update` — дёргать что-то ещё не нужно.
4085
+ */
4086
+ setLodParams(params: {
4087
+ detailBias?: number;
4088
+ maxTiles?: number;
4089
+ }): void;
4090
+ setTransformRequest(transform: TransformRequest | null): void;
4091
+ private transform;
2869
4092
  private loadTile;
2870
4093
  private fetchImage;
2871
4094
  }
@@ -3202,6 +4425,15 @@ declare function distToSegment(p: Projected, a: Projected, b: Projected): number
3202
4425
  declare function pointInRing(p: Projected, ring: Projected[]): boolean;
3203
4426
 
3204
4427
  declare class ObjectManager {
4428
+ /**
4429
+ * Высота земли в точке нормализованного меркатора — её даёт рельеф.
4430
+ *
4431
+ * Маршрут лежит НА ЗЕМЛЕ, а земля на рельефе не плоскость: без высоты трасса уходила под гору и
4432
+ * появлялась только там, где склон случайно ниже нуля.
4433
+ */
4434
+ elevationAt: ((mx: number, my: number) => number) | null;
4435
+ /** Ревизия рельефа: сменилась — высоты вершин пересчитываются. */
4436
+ elevationRevision: number;
3205
4437
  private objects;
3206
4438
  private markers;
3207
4439
  private gpu;
@@ -3250,6 +4482,13 @@ declare class ObjectManager {
3250
4482
  north: number;
3251
4483
  } | null;
3252
4484
  private gpuFor;
4485
+ /**
4486
+ * Высота земли под каждой вершиной части, метры.
4487
+ *
4488
+ * Читается прямо из готовой ленты: положение лежит первым полем вершины, а сама вершина задана в
4489
+ * меркаторе ОТНОСИТЕЛЬНО начала объекта — прибавляем начало и спрашиваем рельеф.
4490
+ */
4491
+ private groundOf;
3253
4492
  destroy(): void;
3254
4493
  }
3255
4494
 
@@ -3487,6 +4726,14 @@ declare class Model {
3487
4726
  userData: Record<string, unknown>;
3488
4727
  coordinates: LngLatLike;
3489
4728
  altitude: number;
4729
+ /**
4730
+ * ПОДЪЁМ РЕЛЬЕФОМ, метры: высота земли под моделью.
4731
+ *
4732
+ * Держится отдельно от `altitude`, а не складывается с ней при создании, по двум причинам: сама
4733
+ * высота модели приходит из данных и не должна теряться, а рельеф догружается сетью и меняется
4734
+ * уже после того, как модель поставлена.
4735
+ */
4736
+ groundZ: number;
3490
4737
  rotation: number;
3491
4738
  rotationTiltX: number;
3492
4739
  rotationTiltZ: number;
@@ -3774,6 +5021,13 @@ declare class ModelLayer {
3774
5021
  private readonly lightPacks;
3775
5022
  private uploadLights;
3776
5023
  private inView;
5024
+ /**
5025
+ * ВЫСОТА ЗЕМЛИ ПОД МОДЕЛЬЮ, метры. Ставит рендерер слоёв, когда включён рельеф.
5026
+ *
5027
+ * Отдельным крючком, а не полем модели: рельеф догружается сетью и меняется уже после того, как
5028
+ * модель поставлена, — значит спрашивать высоту надо каждый кадр, а не при создании.
5029
+ */
5030
+ elevationAt: ((mercX: number, mercY: number) => number) | null;
3777
5031
  advanceTransitions(): void;
3778
5032
  /** Проход глубины: модели отбрасывают тень вместе со зданиями. */
3779
5033
  renderDepth(ctx: Context, tr: Transform, shader: Shader, locations: Map<string, number>): void;
@@ -3834,12 +5088,32 @@ declare class LayerRenderer {
3834
5088
  private raster;
3835
5089
  /** Растровая подложка: текстуры тайлов и их отрисовка квадами. */
3836
5090
  private readonly rasterLayer;
3837
- private globeBody;
3838
- private globeClip;
3839
- private globeMesh;
3840
- /** Квадрат тайла: две треугольника под проход тени по земле. */
3841
- private quad;
3842
- private fillLocs;
5091
+ /**
5092
+ * Картинки, видео и canvas, прибитые к четырём углам.
5093
+ *
5094
+ * Отдельный проход, потому что отдельная геометрия: у тайла единичный квад и матрица, здесь —
5095
+ * четыре настоящих угла (см. `MediaLayer`). Набор источников карта передаёт вместе с растровыми.
5096
+ */
5097
+ private readonly mediaLayer;
5098
+ /** Медиа-источники этого кадра: их держит карта, рендеру они приходят на отрисовку. */
5099
+ mediaSources: Map<string, MediaSource>;
5100
+ /**
5101
+ * СВОИ СЛОИ ПРИЛОЖЕНИЯ (`CustomLayerInterface`).
5102
+ *
5103
+ * Живут не в стиле, а здесь, и вот почему: слой несёт ФУНКЦИЮ отрисовки, а спецификация стиля у
5104
+ * нас копируется (`structuredClone` при накладывании правок, отдача наружу из `getStyle`) —
5105
+ * функция такого не переживает. Поэтому в стиле их нет вовсе, а порядок задаётся якорем `before`.
5106
+ */
5107
+ customLayers: {
5108
+ layer: CustomLayer;
5109
+ before?: string;
5110
+ }[];
5111
+ private globeBody;
5112
+ private globeClip;
5113
+ private globeMesh;
5114
+ /** Квадрат тайла: две треугольника под проход тени по земле. */
5115
+ private quad;
5116
+ private fillLocs;
3843
5117
  private lineLocs;
3844
5118
  /** Линии рантайм-объектов рисуются своей программой — см. OBJECT_LINE_VERT. */
3845
5119
  private objectLine;
@@ -3962,6 +5236,23 @@ declare class LayerRenderer {
3962
5236
  * Поэтому маска геометрическая, ровно как привязка входов.
3963
5237
  */
3964
5238
  readonly indoorMask: IndoorMask;
5239
+ /**
5240
+ * Рельеф: высота под каждой точкой земли. Источник ставит карта по стилю (`syncTerrain`),
5241
+ * а здесь он раздаёт юниформы тем программам, которые рисуют по земле.
5242
+ */
5243
+ readonly terrain: Terrain;
5244
+ /**
5245
+ * Тайл, в текстуру которого сейчас рисуется плоская карта (драпировка рельефа).
5246
+ *
5247
+ * Пока он задан, весь плоский проход смотрит на мир иначе: матрица тайла становится
5248
+ * ортографической на его квадрат, а из видимых тайлов рисуется ровно этот. Так у нас устроен
5249
+ * `isRenderingToTexture` у MapLibre — там он живёт в контексте отрисовки.
5250
+ */
5251
+ private drapeTile;
5252
+ /** Остались квадраты рельефа, которым не хватило бюджета перерисовки: нужен ещё кадр. */
5253
+ private drapePending;
5254
+ private terrainShader;
5255
+ private terrainLocs;
3965
5256
  /**
3966
5257
  * Дома, которые не рисуем объёмом: их место заняла детальная 3D-модель.
3967
5258
  *
@@ -4133,6 +5424,38 @@ declare class LayerRenderer {
4133
5424
  * нужно знать, какому куску мира она принадлежит, — отсюда `u_tile_merc`.
4134
5425
  * На плоской карте всё наоборот: матрица своя у каждого тайла.
4135
5426
  */
5427
+ /**
5428
+ * ПЛОСКИЙ ПРОХОД: земля в порядке стиля, без теста глубины.
5429
+ *
5430
+ * Вынесен в метод, потому что зовётся из двух мест. Обычно — прямо на экран. С рельефом — в
5431
+ * текстуру каждого тайла, откуда её берёт решётка рельефа (`drapeTerrain`): тот же код, другая
5432
+ * матрица и другой кадровый буфер. Так же устроено у MapLibre, где слои перечисляет
5433
+ * `RenderToTexture.renderLayer`, а рисует их обычный `painter.renderLayer`.
5434
+ */
5435
+ private drawFlatLayers;
5436
+ /**
5437
+ * Отрисовать свои слои, привязанные к этому слою стиля.
5438
+ *
5439
+ * Матрица та же, что у рантайм-объектов: локальные координаты — нормализованный меркатор, ось Z —
5440
+ * метры. Ровно это MapLibre и отдаёт в `render(gl, matrix)`, поэтому слой, написанный под него,
5441
+ * рисуется у нас без правок.
5442
+ */
5443
+ private drawCustomBefore;
5444
+ private drawCustomRest;
5445
+ private drawCustom;
5446
+ /**
5447
+ * РЕЛЬЕФ: карта тайла — в текстуру, текстура — на решётку.
5448
+ *
5449
+ * Два шага, оба из MapLibre (RenderToTexture + drawTerrain):
5450
+ * 1. для каждого видимого тайла рисуем его плоскую карту в собственную текстуру —
5451
+ * ортографически и без камеры, получается «плитка карты» 1024×1024;
5452
+ * 2. рисуем решётку тайла, поднимая её вершины по DEM и натягивая эту плитку.
5453
+ *
5454
+ * Перерисовываем плитку не каждый кадр, а по ключу: пока не менялись ни данные тайла, ни зум
5455
+ * (ширина дорог у нас экранная), готовая картинка годится. Без этого кадр стоил бы столько
5456
+ * отрисовок карты, сколько тайлов на экране.
5457
+ */
5458
+ private drapeTerrain;
4136
5459
  private applyTileMatrix;
4137
5460
  /**
4138
5461
  * Юниформы дымки.
@@ -4216,7 +5539,7 @@ declare class LayerRenderer {
4216
5539
  * Юниформы теней, общие на слой. Матрица тени ставится отдельно, НА ТАЙЛ —
4217
5540
  * она своя у каждого, как и обычная.
4218
5541
  */
4219
- applyShadowUniforms(shader: Shader): void;
5542
+ applyShadowUniforms(shader: Shader, ground?: boolean): void;
4220
5543
  /** Активны ли тени в этом кадре — по этому флагу ставится матрица на тайл. */
4221
5544
  get shadowsActive(): boolean;
4222
5545
  /**
@@ -4257,6 +5580,13 @@ declare class LayerRenderer {
4257
5580
  * прямо по чётности координат — шесть цветов по кругу, и любые два смежных тайла всегда разные.
4258
5581
  */
4259
5582
  private tileTint;
5583
+ /**
5584
+ * Масштаб тайла, рисунок материала — и ВЫСОТА РЕЛЬЕФА.
5585
+ *
5586
+ * Рельеф ставится здесь же, а не отдельным вызовом у каждого прохода, ровно по той причине, по
5587
+ * которой здесь стоит всё остальное: это свойства ТАЙЛА, и нужны они каждому, кто по этому тайлу
5588
+ * рисует землю. Программе без соответствующих юниформов установка молча проходит мимо.
5589
+ */
4260
5590
  private applyTileScale;
4261
5591
  private applyMaterial;
4262
5592
  private drawFill;
@@ -4530,6 +5860,58 @@ declare class Objects3DManager {
4530
5860
  clear(drop: (m: Model) => void): void;
4531
5861
  }
4532
5862
 
5863
+ /**
5864
+ * ЧТО НАРИСОВАНО ПОД ТОЧКОЙ — с теми же именами полей, что у MapLibre.
5865
+ *
5866
+ * Отличие от нашего `queryBuilding` одно, зато принципиальное: тот отвечает «какое здание», а этот
5867
+ * — «какие фичи каких слоёв», включая их свойства из тайла. Свойства берутся из СЫРОГО тайла, а не
5868
+ * из геометрии: в геометрии остаются только номера фич (см. `GeometryMesh.featureIds`), и хранить
5869
+ * рядом с ней ещё и таблицу свойств значило бы держать их в памяти всегда, даже когда никто не
5870
+ * спрашивает. Сырой тайл лежит и так — его же кладёт обратно воркер.
5871
+ */
5872
+ interface QueriedFeature {
5873
+ /** Номер фичи из тайла (`osm_id` в нашей схеме). */
5874
+ id: number;
5875
+ /** Имя источника: `base`, `indoor`, … */
5876
+ source: string;
5877
+ /** Слой MVT, откуда фича. */
5878
+ sourceLayer: string;
5879
+ /** Идентификатор слоя СТИЛЯ, которым она нарисована. */
5880
+ layer: string;
5881
+ properties: Record<string, unknown>;
5882
+ /** Состояние фичи, назначенное `setFeatureState`. */
5883
+ state: Record<string, unknown>;
5884
+ /** Тайл, в котором нашлась. */
5885
+ tile: TileID;
5886
+ }
5887
+ /** Точка или прямоугольник экрана, по которому спрашивают. */
5888
+ type QueryGeometry = ScreenPoint$1 | [ScreenPoint$1, ScreenPoint$1];
5889
+ interface RenderedQueryOptions {
5890
+ /** Спрашивать только эти слои стиля. Пусто — все. */
5891
+ layers?: string[];
5892
+ /** Радиус вокруг точки, пиксели: без него тонкая линия ловится лишь идеальным попаданием. */
5893
+ tolerance?: number;
5894
+ }
5895
+ /**
5896
+ * ЧТО ПОПАЛО В ЗАПРОС — по нарисованной геометрии, а не по данным.
5897
+ *
5898
+ * Проверка идёт в координатах ТАЙЛА: прямоугольник экрана переводится в них обратной матрицей, и
5899
+ * дальше сравниваются числа одного порядка. Так же устроен и пикинг зданий (`picking.ts`), и по
5900
+ * той же причине: собирать мировые координаты каждой вершины — это мегабайты работы на клик.
5901
+ */
5902
+ declare function queryRendered(tr: Transform, sources: Map<string, TileSource>, layers: StyleLayerSpec[], geometry: QueryGeometry, opts: RenderedQueryOptions, stateOf: (source: string, sourceLayer: string, id: number) => Record<string, unknown>): QueriedFeature[];
5903
+ /**
5904
+ * ФИЧИ ИСТОЧНИКА — из сырых тайлов, без оглядки на то, что нарисовано.
5905
+ *
5906
+ * Отвечает на другой вопрос, чем `queryRendered`: не «что видно в этой точке», а «что вообще есть в
5907
+ * загруженных тайлах». У MapLibre ровно та же пара и ровно то же ограничение: за пределами уже
5908
+ * загруженных тайлов ответа нет, и полным он не бывает.
5909
+ */
5910
+ declare function querySource(source: TileSource, sourceName: string, params: {
5911
+ sourceLayer?: string;
5912
+ filter?: (props: Record<string, unknown>) => boolean;
5913
+ }, stateOf: (source: string, sourceLayer: string, id: number) => Record<string, unknown>): QueriedFeature[];
5914
+
4533
5915
  /**
4534
5916
  * Балун, привязанный к точке карты.
4535
5917
  *
@@ -4601,7 +5983,15 @@ interface BuildingHit {
4601
5983
  declare function pickBuilding(tr: Transform, source: TileSource, layerId: string, point: {
4602
5984
  x: number;
4603
5985
  y: number;
4604
- }): BuildingHit | null;
5986
+ },
5987
+ /**
5988
+ * Высота земли в точке нормализованного меркатора, метры. Есть только на рельефе.
5989
+ *
5990
+ * Меш здания лежит в тайле БЕЗ рельефа: высота в нём отсчитывается от нуля, а рисуется дом от
5991
+ * земли под собой. Значит и луч надо опустить на ту же землю, иначе он проходит под домом —
5992
+ * на городском зуме мимо на восемьсот метров, и клик не попадал ни во что.
5993
+ */
5994
+ groundAtMerc?: (mx: number, my: number) => number): BuildingHit | null;
4605
5995
  /**
4606
5996
  * Помещение плана этажа под курсором.
4607
5997
  *
@@ -4613,7 +6003,9 @@ declare function pickBuilding(tr: Transform, source: TileSource, layerId: string
4613
6003
  declare function pickFill(tr: Transform, source: TileSource, layerIds: string[], point: {
4614
6004
  x: number;
4615
6005
  y: number;
4616
- }): BuildingHit | null;
6006
+ },
6007
+ /** Высота земли, метры: план этажа на рельефе лежит на склоне, как и всё остальное. */
6008
+ groundAtMerc?: (mx: number, my: number) => number): BuildingHit | null;
4617
6009
  /**
4618
6010
  * Треугольники одной фичи — из них строится подсветка.
4619
6011
  *
@@ -5091,6 +6483,54 @@ interface RoundOptions {
5091
6483
  maxChord?: number;
5092
6484
  }
5093
6485
  declare function roundRing(ring: Ring, opts: RoundOptions): Ring;
6486
+ /**
6487
+ * СКРУГЛЕНИЕ УГЛОВ ОТКРЫТОЙ ЛОМАНОЙ — для ОСИ дороги, а не для её контура.
6488
+ *
6489
+ * `roundRing` выше скругляет уже готовый контур. Оси это не годится: у дороги скругляться должен
6490
+ * САМ ПУТЬ, а контур обязан идти за ним параллельно. Скругли мы контур — внешняя и внутренняя
6491
+ * кромки получили бы разный радиус, и полотно на повороте стало бы разной ширины.
6492
+ *
6493
+ * Зачем вообще. В данных ось дороги это ломаная, и в парке или на съезде её изломы доходят до
6494
+ * тридцати градусов между звеньями по три метра. Лента честно повторяет их углами, и дорожка
6495
+ * выглядит собранной из спичек — ровно то, чего в жизни не бывает: любая дорога поворачивает по
6496
+ * дуге, потому что по ней едут и ходят.
6497
+ *
6498
+ * Радиус ограничен половиной каждого соседнего звена, поэтому скругления двух соседних углов
6499
+ * никогда не налезают друг на друга, а на коротком звене радиус сам уменьшается до возможного.
6500
+ * Концы ломаной не трогаются вовсе: они лежат в узлах, и сдвинуть их — значит разорвать сеть.
6501
+ */
6502
+ declare function roundLine(line: Ring, radius: number, segments?: number): Ring;
6503
+ /**
6504
+ * ПОДРАЗБИЕНИЕ ЛОМАНОЙ ПО ЧАЙКИНУ: гладкая кривая там, где скруглять углы нечем.
6505
+ *
6506
+ * `roundLine` выше вписывает в угол дугу заданного радиуса и ограничивает её половиной соседнего
6507
+ * звена. На улице это ровно то, что нужно: прямые участки остаются точными прямыми, скругляется
6508
+ * только поворот. Но на парковой дорожке прямых нет вовсе — там ломаная из звеньев по три метра,
6509
+ * каждое со своим изломом, и радиус, ужатый под половину звена, срезает полтора метра из угла в
6510
+ * тридцать градусов. Дорожка остаётся угловатой.
6511
+ *
6512
+ * Чайкин работает иначе: он не вписывает дугу, а ЗАМЕНЯЕТ каждую вершину двумя точками на четверти
6513
+ * и трёх четвертях звена. Угол исчезает целиком, за две-три итерации ломаная становится гладкой
6514
+ * (в пределе это квадратичный B-сплайн), и длина звеньев ему безразлична.
6515
+ *
6516
+ * Плата — кривая срезает угол изнутри, до четверти звена. Для дорожки в парке это незаметно и
6517
+ * честнее исходных данных: она и в жизни поворачивает плавно. Для улицы с прямыми участками
6518
+ * лучше `roundLine` — там срезать прямую нельзя.
6519
+ *
6520
+ * Концы остаются на месте: они лежат в узлах сети.
6521
+ */
6522
+ declare function refineLine(line: Ring, iterations?: number): Ring;
6523
+ /**
6524
+ * УПЛОТНЕНИЕ ЛОМАНОЙ: ни одного звена длиннее `step`.
6525
+ *
6526
+ * Нужно перед подразбиением. Чайкин срезает угол на четверть соседнего звена, и на звене в тридцать
6527
+ * метров это семь метров — дорожка заметно уезжает внутрь поворота, а угол всё равно остаётся
6528
+ * читаемым, только собранным из четырёх граней вместо одной. Разбив звенья на пятиметровые, мы
6529
+ * получаем и срез в пределах метра, и по-настоящему гладкую кривую: сглаживанию есть за что взяться.
6530
+ *
6531
+ * Сама линия при этом не меняется ни на миллиметр — новые вершины ставятся на ней самой.
6532
+ */
6533
+ declare function densifyLine(line: Ring, step: number): Ring;
5094
6534
 
5095
6535
  /**
5096
6536
  * Плоские заливки: вода, зелень, землепользование.
@@ -5107,6 +6547,8 @@ interface FillMesh {
5107
6547
  indices: ArrayBuffer;
5108
6548
  vertexCount: number;
5109
6549
  indexCount: number;
6550
+ /** Покраска на фиче, если слой её просил (см. `generators/data-paint.ts`). */
6551
+ dataPaint?: ArrayBuffer;
5110
6552
  /**
5111
6553
  * Id фичи на ТРЕУГОЛЬНИК — тем же способом, что у зданий.
5112
6554
  *
@@ -5118,7 +6560,9 @@ interface FillMesh {
5118
6560
  }
5119
6561
  declare function generateFill(layer: VectorTileLayer, filter: Filter | undefined, maxSegment?: number, round?: RoundOptions,
5120
6562
  /** Обрезать по квадрату тайла: нужно полупрозрачным слоям. */
5121
- clip?: boolean, extent?: number): FillMesh | null;
6563
+ clip?: boolean, extent?: number,
6564
+ /** Покраска на фиче: цвет и прозрачность считаются здесь и ложатся в вершины. */
6565
+ paint?: FeaturePaint): FillMesh | null;
5122
6566
 
5123
6567
  /**
5124
6568
  * Альфа-маска (0..255, ширина × высота) → SDF того же размера, но с полем
@@ -5175,39 +6619,108 @@ declare function shapeText(text: string, source: GlyphSource, maxLineChars?: num
5175
6619
  declare function abbreviateStreet(name: string): string;
5176
6620
 
5177
6621
  /**
5178
- * Атлас SDF: глифы и иконки лежат в ОДНОЙ текстуре.
6622
+ * ДОРОЖНЫЕ ЗНАКИ — настоящие, а не пиктограммы.
5179
6623
  *
5180
- * Именно поэтому подпись и её иконка красятся одним цветом и одной обводкой —
5181
- * шейдер не различает, откуда взят кусок текстуры. Ради этого иконки и
5182
- * приводятся к глифовой кодировке SDF (см. sdf.ts).
6624
+ * Остальные наши значки (`text/icons.ts`) одноцветные: они уходят в SDF-атлас, и цвет им задаёт
6625
+ * стиль. Для POI это правильно — кафе на ночной карте должно быть светлым, на дневной тёмным.
5183
6626
  *
5184
- * Упаковка полками: простая, без фрагментации на однородных по высоте данных
5185
- * (а глифы одного кегля именно такие) и не требует перепаковки.
6627
+ * С дорожным знаком так нельзя. Знак узнаётся ФОРМОЙ И ЦВЕТОМ раньше, чем пиктограммой: красный
6628
+ * круг — запрет, синий круг — предписание, треугольник — предупреждение, восьмиугольник — «стоп».
6629
+ * Перекрашенный знак перестаёт быть знаком. Поэтому они лежат отдельно и идут в ЦВЕТНОЙ атлас
6630
+ * (`map.addImage`, см. `text/images.ts`), а не в SDF.
6631
+ *
6632
+ * Пропорции — по ГОСТ Р 52290 (он же в основе Венской конвенции): кайма запрещающего знака это
6633
+ * десятая часть диаметра, красная полоса «движение запрещено» идёт под 45°, у предупреждающего
6634
+ * треугольника углы скруглены. Всё нарисовано в квадрате 64×64, чтобы в атласе знак ложился
6635
+ * целым числом текселей на любом разумном размере.
5186
6636
  */
5187
- interface AtlasEntry {
5188
- x: number;
5189
- y: number;
5190
- width: number;
5191
- height: number;
5192
- }
5193
- declare class SdfAtlas {
5194
- readonly size: number;
5195
- private data;
5196
- private entries;
5197
- private shelfY;
5198
- private shelfX;
5199
- private shelfHeight;
5200
- private texture;
5201
- private dirty;
5202
- private full;
5203
- constructor(size?: number);
5204
- get(key: string): AtlasEntry | undefined;
5205
- /** Кладёт одноканальный SDF в атлас. Возвращает null, если места больше нет. */
5206
- add(key: string, sdf: Uint8Array, width: number, height: number): AtlasEntry | null;
5207
- /** Привязывает текстуру к юниту, при необходимости заливая изменения. */
5208
- bind(ctx: Context, unit: number): void;
5209
- destroy(ctx: Context): void;
6637
+ /** Сторона знака в SVG: все знаки рисуются в одном квадрате. */
6638
+ declare const SIGN_SIZE = 64;
6639
+ /**
6640
+ * НАБОР ЗНАКОВ.
6641
+ *
6642
+ * Здесь ровно то, что встречается на городской карте и что мы умеем расставить по данным OSM:
6643
+ * запреты и ограничения, приоритет, предписания, переход и переезд. Их немного намеренно — знак,
6644
+ * которого нет в данных, на карте взяться неоткуда, а в бандле он весит.
6645
+ */
6646
+ declare const ROAD_SIGNS: Record<string, string>;
6647
+ /**
6648
+ * Знак ограничения скорости с любым числом.
6649
+ *
6650
+ * Скорость в данных бывает какая угодно (30, 50, 80), и держать в наборе по знаку на каждую — это
6651
+ * лишний вес в бандле. Рисуем по запросу и отдаём тем же SVG, который уйдёт в цветной атлас.
6652
+ */
6653
+ declare function speedSign(limit: number): string;
6654
+
6655
+ /**
6656
+ * ДОРОЖНАЯ РАЗМЕТКА — ФИГУРАМИ НА ПОЛОТНЕ.
6657
+ *
6658
+ * Линиями задаётся только то, что и в жизни линия: осевая, краевая, полосы. Всё остальное —
6659
+ * стрелка направления по полосе, зебра, вафельница, стоп-линия — это ПЯТНО НА АСФАЛЬТЕ, и рисовать
6660
+ * его линией нельзя: у линии нет формы, она везде одинаковой ширины.
6661
+ *
6662
+ * Так же устроена разметка в картах, которые это уже сделали: в 2ГИС точечная разметка (стрелки
6663
+ * направления, пешеходные переходы) кладётся отдельным слоем «плоское изображение» с поворотом по
6664
+ * данным, линейная — слоем линий, а площадная (та самая «вафельница» 1.26) — полигоном.
6665
+ *
6666
+ * Здесь считаются КОНТУРЫ в метрах, в местных осях: X — вдоль дороги (вперёд по движению), Y —
6667
+ * поперёк (вправо). Дальше их поворачивают по азимуту полосы и переносят на место — так же, как
6668
+ * это делает любая расстановка значков. Размеры по ГОСТ Р 51256:
6669
+ *
6670
+ * 1.18 стрелка направления — длина 5 м (в городе 3 м), древко 0.3 м, голова до 1.4 м;
6671
+ * 1.14.1 зебра — полосы 0.4 м с промежутком 0.6 м, длина полосы 4 м;
6672
+ * 1.12 стоп-линия — 0.4 м;
6673
+ * 1.26 вафельница — сетка из полос 0.4 м с шагом 2 м, по диагонали.
6674
+ */
6675
+ /** Кольцо в местных осях: пары (вдоль, поперёк), в метрах. */
6676
+ type MarkingRing = number[];
6677
+ /** Что нарисовать стрелкой 1.18. */
6678
+ type LaneArrow = 'straight' | 'left' | 'right' | 'straight-left' | 'straight-right' | 'u-turn';
6679
+ /** Размеры стрелки 1.18, метры. Умолчания — городские (короткая стрелка). */
6680
+ interface ArrowSize {
6681
+ /** Полная длина стрелки вдоль дороги. */
6682
+ length?: number;
6683
+ /** Ширина древка. */
6684
+ shaft?: number;
6685
+ /** Размах головы поперёк. */
6686
+ head?: number;
6687
+ /** Длина головы вдоль. */
6688
+ headLength?: number;
6689
+ /** Вылет бокового пера (для «прямо и направо»). */
6690
+ branch?: number;
5210
6691
  }
6692
+ /**
6693
+ * СТРЕЛКА НАПРАВЛЕНИЯ ПО ПОЛОСЕ (1.18).
6694
+ *
6695
+ * Возвращает одно или несколько колец: у составных стрелок («прямо и направо») перо идёт отдельной
6696
+ * фигурой — так их и красят на асфальте, одной краской, но двумя мазками.
6697
+ */
6698
+ declare function laneArrow(kind: LaneArrow, size?: ArrowSize): MarkingRing[];
6699
+ /**
6700
+ * ЗЕБРА (1.14.1): полосы поперёк дороги.
6701
+ *
6702
+ * `width` — ширина проезжей части, `depth` — длина перехода вдоль движения (по ГОСТ 4 м, на
6703
+ * широкой дороге 6 м). Полосы идут ВДОЛЬ движения, поперёк проезжей части: пешеход идёт между
6704
+ * ними, и именно так зебра выглядит с высоты.
6705
+ */
6706
+ declare function zebra(width: number, depth?: number, stripe?: number, gap?: number): MarkingRing[];
6707
+ /** Стоп-линия (1.12): сплошная полоса поперёк проезжей части. */
6708
+ declare function stopLine(width: number, thickness?: number): MarkingRing[];
6709
+ /**
6710
+ * ВАФЕЛЬНИЦА (1.26): сетка из диагональных полос на перекрёстке.
6711
+ *
6712
+ * Квадрат со стороной `size`, полосы шириной `stripe` с шагом `step` в двух направлениях под 45°.
6713
+ * Полосы обрезаются по квадрату — иначе сетка вылезала бы за перекрёсток.
6714
+ */
6715
+ declare function waffle(size: number, stripe?: number, step?: number): MarkingRing[];
6716
+ /**
6717
+ * ФИГУРУ — НА МЕСТО: поворот по азимуту и перенос в точку.
6718
+ *
6719
+ * `angle` — направление дороги в радианах (0 — на восток, как у `Math.atan2`), `x`/`y` — точка в
6720
+ * тех же единицах, что и результат. Масштаб задаётся снаружи: на карте это метры в единицы
6721
+ * источника, на стенде — метры в градусы.
6722
+ */
6723
+ declare function placeMarking(ring: MarkingRing, angle: number, x: number, y: number, scaleX: number, scaleY?: number): number[];
5211
6724
 
5212
6725
  /**
5213
6726
  * Контуры в НОРМАЛИЗОВАННОМ МЕРКАТОРЕ и проверка принадлежности точки.
@@ -5365,6 +6878,30 @@ declare function splitByGround(field: GroundField, pts: number[]): {
5365
6878
  levels: number[];
5366
6879
  };
5367
6880
 
6881
+ /** Адрес тайла: профиль приходит в градусах, а геометрия — в единицах тайла. */
6882
+ interface TileAddress {
6883
+ z: number;
6884
+ x: number;
6885
+ y: number;
6886
+ }
6887
+ /** Высота над уровнем слоя в точке тайла, метры. */
6888
+ type LiftField = (x: number, y: number) => number;
6889
+ /**
6890
+ * Разобрать профиль в поле высот тайла.
6891
+ *
6892
+ * Возвращает `null`, если профиля нет или в нём меньше двух точек: одна точка не задаёт склона, и
6893
+ * обычная отметка `lift` справится с ней лучше и дешевле.
6894
+ */
6895
+ declare function parseLift(raw: unknown, tile: TileAddress, extent: number): LiftField | null;
6896
+ /**
6897
+ * Разбор профилей с кешем на тайл.
6898
+ *
6899
+ * Профиль один на всю рампу, а фич с ним — и полотно, и каждая линия разметки на нём. Разбирать
6900
+ * одну и ту же строку заново на каждую из них незачем, а вот пересчёт в единицы тайла у каждого
6901
+ * тайла свой, поэтому кеш живёт ровно столько, сколько тайл строится.
6902
+ */
6903
+ declare function liftFields(tile: TileAddress | undefined, extent: number): (props: Record<string, unknown>) => LiftField | null;
6904
+
5368
6905
  /**
5369
6906
  * Тесселяция линий (дороги, реки, границы).
5370
6907
  *
@@ -5392,6 +6929,7 @@ declare function splitByGround(field: GroundField, pts: number[]): {
5392
6929
  * a_ground 2 × Int8 off 10 уровень земли под p0 и под p1, сантиметры
5393
6930
  * a_dist 1 × Float32 off 12 расстояние от начала линии до p0, единицы тайла
5394
6931
  * a_width 1 × Float32 off 16 ширина объекта в МЕТРАХ, 0 — не задана
6932
+ * a_lift 2 × Int16 off 20 подъём полотна под p0 и под p1, сантиметры
5395
6933
 
5396
6934
  * a_width делает полотно объектом местности, а не линией в пикселях: у каждой дороги своя ширина,
5397
6935
  * а внутри слоя она была общей. Ноль означает «в тайле такого атрибута нет» — тогда шейдер берёт
@@ -5405,62 +6943,1028 @@ declare function splitByGround(field: GroundField, pts: number[]): {
5405
6943
  * a_ground — уровень поверхности, ПО КОТОРОЙ идёт дорога (см. `ground.ts`). Два числа, а не одно:
5406
6944
  * вершина знает оба конца отрезка и берёт свой, а между ними шейдер сам растянет подъём. Лёг в те
5407
6945
  * два байта, что и так уходили на выравнивание, — размер вершины не вырос.
6946
+ *
6947
+ * a_lift — ПОДЪЁМ ЭСТАКАДЫ И ЕЁ ПОДХОДА (см. `generators/lift`). Отдельно от `a_ground`, хотя
6948
+ * отвечают они на один вопрос: `a_ground` — байт в сантиметрах, и дальше метра с четвертью не
6949
+ * берёт, а подъём развязки — восемь метров. Расширить байт нельзя: он мерит разницу уровней
6950
+ * поверхностей, где важен как раз сантиметр. Поэтому у подъёма свои два Int16 — те же
6951
+ * сантиметры, но на триста метров вверх, чего хватит любой развязке.
6952
+ *
6953
+ * Без подъёма разметка оставалась лежать на земле под взлетевшей рампой: краска — это линии, а
6954
+ * высоту слой умел задавать только целым уровнем, одним на весь бакет.
5408
6955
  */
5409
- declare const LINE_VERTEX_STRIDE = 20;
6956
+ declare const LINE_VERTEX_STRIDE = 24;
5410
6957
  interface LineMesh {
5411
6958
  vertices: ArrayBuffer;
5412
6959
  indices: ArrayBuffer;
5413
6960
  vertexCount: number;
5414
6961
  indexCount: number;
6962
+ /** Покраска на фиче, если слой её просил (см. `generators/data-paint.ts`). */
6963
+ dataPaint?: ArrayBuffer;
6964
+ /**
6965
+ * id фичи НА ТРЕУГОЛЬНИК — тем же форматом, что у заливки и зданий.
6966
+ *
6967
+ * Нужен, чтобы вырезать линию из готового меша: под 3D-моделью прячется не только здание, но и
6968
+ * дорожки вокруг него, а их в наборе замен обычно больше всего.
6969
+ */
6970
+ featureIds: ArrayBuffer;
6971
+ }
6972
+ interface LineOptions {
6973
+ /** Покраска на фиче: цвет, прозрачность и ширина считаются здесь и ложатся в вершины. */
6974
+ paint?: FeaturePaint;
6975
+ /**
6976
+ * Уровень пересечения (см. `generators/level`): берём только фичи этого уровня.
6977
+ *
6978
+ * `undefined` — уровни не разделяем, слой строится целиком (так живут все слои, кроме дорожной
6979
+ * сети: у реки или границы пересечений без связи не бывает).
6980
+ */
6981
+ level?: number;
6982
+ /**
6983
+ * ПОДЪЁМ ПОЛОТНА ПОД ЛИНИЕЙ — профиль склона по свойствам фичи (см. `generators/lift`).
6984
+ *
6985
+ * Разметка на рампе обязана ехать вместе с асфальтом. Уровень (`level`) тут не помощник: он
6986
+ * целый и один на бакет, а подход к эстакаде набирает высоту непрерывно.
6987
+ */
6988
+ liftAt?: (properties: Record<string, unknown>) => LiftField | null;
6989
+ }
6990
+ declare function generateLine(layer: VectorTileLayer, filter: Filter | undefined, maxSegment?: number,
6991
+ /** Уровни поверхностей тайла: по ним дорога ложится на то, по чему идёт. Нет — лежит на нуле. */
6992
+ ground?: GroundField,
6993
+ /** Метров в единице тайла. Ноль — плавный переход ширины на стыках не строится. */
6994
+ metersPerUnit?: number, opts?: LineOptions): LineMesh | null;
6995
+
6996
+ /**
6997
+ * УРОВЕНЬ ПЕРЕСЕЧЕНИЯ — какой из двух дорог быть сверху там, где они пересекаются без связи.
6998
+ *
6999
+ * ЗАЧЕМ. Плоская карта рисуется краской: слои идут в порядке стиля, и внутри слоя порядок фич
7000
+ * произволен. Поэтому эстакада над улицей ничем не отличалась от перекрёстка — обводки сливались,
7001
+ * полотна пересекались, и место читалось как съезд, которого в жизни нет. Ровно этим у Яндекса
7002
+ * занят `line-z-level` (`["get","z_level"]` у КАЖДОГО дорожного слоя, все они в одной сети
7003
+ * `line-network: "roads"`): сеть рисуется не по слоям, а по уровням, и мост со своей тенью и
7004
+ * обводкой целиком ложится поверх того, что под ним.
7005
+ *
7006
+ * ОТКУДА ЧИСЛО. Готового `z_level` в наших тайлах нет, зато есть то, из чего его считают:
7007
+ * `layer` — тег OSM, честный уровень (−1 подземный, 1 над землёй, изредка 2…5);
7008
+ * `brunnel` — «мост / тоннель / брод», он приходит и там, где `layer` не проставлен.
7009
+ * Поэтому берём `layer`, а если его нет — выводим уровень из `brunnel`. Мост без `layer`
7010
+ * (в Душанбе таких большинство) получает 1, тоннель — −1, обычная улица — 0.
7011
+ *
7012
+ * Считается в ВОРКЕРЕ, при тесселяции: по уровню бьётся меш, и рендеру остаётся только порядок.
7013
+ */
7014
+ /**
7015
+ * Дальше этого уровни не разводим. В OSM попадается и `layer=5`, и опечатки в сотни: каждое
7016
+ * значение — это отдельный бакет и отдельный вызов отрисовки, поэтому хвост подрезаем. Пять этажей
7017
+ * развязки — больше, чем бывает на настоящих.
7018
+ */
7019
+ declare const LEVEL_LIMIT = 5;
7020
+ /** Уровень фичи: `layer` из тегов, иначе вывод из `brunnel`. */
7021
+ declare function featureLevel(props: Record<string, unknown>): number;
7022
+
7023
+ /**
7024
+ * СТЫКИ ДОРОГ: общая ширина в узле и плавный переход к ней.
7025
+ *
7026
+ * Лежит отдельным модулем, потому что этим пользуются ДВОЕ: лента дороги в пикселях
7027
+ * (`generators/line`) и её полотно как настоящая геометрия (`generators/ribbon`, через
7028
+ * `generators/surface`). Пока индекс жил внутри линий, полотно считало ширину по-своему —
7029
+ * постоянной на кусок, — и на каждом стыке двух кусков одной улицы выходил уступ: рисунок дороги
7030
+ * уже раскрылся к соседу, а асфальт под ним ещё нет. Общее место снимает расхождение
7031
+ * по построению.
7032
+ */
7033
+ /**
7034
+ * Узлы тайла: наибольшая ширина дороги в узле и число сошедшихся в нём КОНЦОВ.
7035
+ *
7036
+ * ЗАЧЕМ. Ширина полотна приходит из данных на КАЖДЫЙ кусок улицы отдельно, а куски у одного узла
7037
+ * бывают разной ширины по делу: съезд с развязки — одна полоса (3.8 м), улица, в которую он
7038
+ * вливается, — две (6.5 м). Без перехода полотно на стыке обрывается ступенькой в два с лишним
7039
+ * метра, и на ближнем зуме это читается как вырезанный уступ.
7040
+ *
7041
+ * Индекс даёт каждому концу линии ширину САМОГО ШИРОКОГО соседа. Дальше узкий кусок раскрывается
7042
+ * к ней на длине `taper`, а широкий остаётся как был: у обоих в самом узле получается одно и то
7043
+ * же число, поэтому стык сходится без ступеньки, кто бы из них ни рисовался первым.
7044
+ *
7045
+ * Строится по ВСЕМУ слою источника, а не по отфильтрованному слою стиля: улица и съезд у нас
7046
+ * попадают в разные слои стиля, и видеть друг друга иначе не могли бы.
7047
+ *
7048
+ * ★ ШИРИНА СЧИТАЕТСЯ БЕЗ ОГЛЯДКИ НА УРОВЕНЬ — и это важно именно для мостов. Мост и его подъезд
7049
+ * это ОДНА улица, разрезанная на два way по тегу `bridge`, и в узле у них общий конец: раскрыться
7050
+ * друг к другу они обязаны, иначе на въезде полотно скачком расширяется и мост читается «таблеткой»
7051
+ * поперёк улицы. А дороги, которые ПЕРЕСЕКАЮТСЯ БЕЗ СВЯЗИ, общего конца не имеют вовсе: эстакада
7052
+ * проходит над улицей серединой своей геометрии, узла там нет, и в индекс такая пара не попадает —
7053
+ * сливать их было нечего и раньше.
7054
+ *
7055
+ * ★ ЧИСЛО КОНЦОВ — С УРОВНЕМ, оно нужно тени моста: по нему видно, где мост КОНЧАЕТСЯ (единственный
7056
+ * конец этого уровня в узле — дальше идёт дорога на земле, и тень надо гасить), а где просто
7057
+ * состыкованы два его куска (два и больше — тень идёт насквозь, длинный мост нарезан на ways).
7058
+ *
7059
+ * Считается один раз на слой тайла: слоёв дорог в стиле полдюжины, и каждый спрашивает тот же
7060
+ * самый индекс.
7061
+ */
7062
+ interface Junctions {
7063
+ /** Узел → ширина самой широкой ПРОЕЗЖЕЙ ЧАСТИ, приходящей в него, метры (мосты уже поправлены). */
7064
+ width: Map<number, number>;
7065
+ /** Узел И УРОВЕНЬ → сколько концов линий этого уровня в нём сошлось. */
7066
+ ends: Map<number, number>;
7067
+ /** Узел И УРОВЕНЬ → ширина как записана в данных. По земле (уровень 0) её берёт поправка мостов. */
7068
+ raw: Map<number, number>;
7069
+ }
7070
+ declare function junctionIndex(layer: VectorTileLayer): Junctions;
7071
+ /**
7072
+ * ДЛИНА ПЛАВНОГО ПЕРЕХОДА НА СТЫКЕ, метры.
7073
+ *
7074
+ * Двенадцать метров — примерно длина, на которой съезд у настоящей развязки раскрывается к
7075
+ * проезжей части. Меньше — переход читается изломом, больше — расширение заезжает на весь съезд.
7076
+ */
7077
+ declare const TAPER_METERS = 12;
7078
+ /** Ширина вдоль оси: собственная ширина куска плюс раскрытие к соседям на концах. */
7079
+ interface TaperedWidth {
7080
+ /** Ширина куска, метры, уже с поправкой мостов (см. `carriageway`). */
7081
+ width: number;
7082
+ /** Ширина на расстоянии `d` от начала оси. `null` — раскрываться не к кому. */
7083
+ at: ((d: number) => number) | null;
7084
+ /** Полная длина оси в тех же единицах, что и координаты. */
7085
+ total: number;
7086
+ /** Где рампа ломается: эти расстояния полезно врезать в ось вершинами. */
7087
+ knots: number[];
7088
+ }
7089
+ /**
7090
+ * ПЛАВНЫЙ ПЕРЕХОД ШИРИНЫ НА СТЫКЕ.
7091
+ *
7092
+ * Узкий кусок раскрывается к ширине самого широкого соседа в узле, широкий остаётся как был. Оба
7093
+ * берут ОДНО И ТО ЖЕ число из индекса, поэтому в самом узле их полотна совпадают.
7094
+ *
7095
+ * Только расширяем, никогда не сужаем: сузить — значит соврать про ширину улицы там, где она
7096
+ * измерена, ради стыка со съездом.
7097
+ *
7098
+ * Раскрываемся к соседу, но НЕ БОЛЬШЕ ЧЕМ ВДВОЕ. Съезд с развязки и улица, в которую он вливается,
7099
+ * отличаются в полтора раза — этот случай переход и придуман сглаживать. А тропинка в 2,8 м,
7100
+ * упирающаяся в проспект в 20 м, при раскрытии «до самого широкого» превращается в клин с тёмным
7101
+ * пятном на конце: связь там есть, но полотна у них разного порядка, и общей ширины у стыка
7102
+ * не бывает.
7103
+ */
7104
+ declare function taperedWidth(pts: number[], opts: {
7105
+ junctions: Junctions;
7106
+ /** Ширина куска из данных, метры. */
7107
+ width: number;
7108
+ /** Уровень пересечения куска — по нему поправляются мосты. */
7109
+ level: number;
7110
+ /** Длина перехода в тех же единицах, что и координаты оси. */
7111
+ taper: number;
7112
+ }): TaperedWidth;
7113
+ /** Кусок улицы как ось: точки, свойства фичи-хозяина и номера всех фич, вошедших в цепочку. */
7114
+ interface AxisChain {
7115
+ pts: number[];
7116
+ props: Record<string, unknown>;
7117
+ ids: number[];
7118
+ }
7119
+ /**
7120
+ * ОСИ, СКЛЕЕННЫЕ В ЦЕПОЧКИ: улица перестаёт быть набором отдельных кусков.
7121
+ *
7122
+ * Зачем. В данных улица нарезана на ways по чему угодно — по смене имени, по мосту, по границе
7123
+ * района. Каждый кусок сам по себе давал свою ленту, а у ленты есть ТОРЦЫ: два полукруглых конца
7124
+ * с бордюром. Посреди сплошного асфальта это читается поперечным швом через всю проезжую часть —
7125
+ * ровно то, чего на настоящей улице нет.
7126
+ *
7127
+ * Склеиваем там, где склейка бесспорна: в узле ровно два конца, и у обоих кусков совпадают ширина
7128
+ * и уровень. Перекрёстки (три конца и больше) не трогаем: там ленты и должны пересекаться, а их
7129
+ * торцы прячутся под соседним полотном.
7130
+ *
7131
+ * Так же поступают процедурные генераторы дорог: сегменты между узлами строятся одной лентой, а
7132
+ * отдельная геометрия появляется только на перекрёстке.
7133
+ */
7134
+ declare function chainAxes(layer: VectorTileLayer, keep: (props: Record<string, unknown>) => boolean, widthOf: (props: Record<string, unknown>) => number | null): AxisChain[];
7135
+
7136
+ /**
7137
+ * ГРАФ ДОРОЖНОЙ СЕТИ: УЗЛЫ, ЛУЧИ И ЧЕСТНЫЕ СТЫКИ.
7138
+ *
7139
+ * В данных улица не существует — существуют куски. OSM режет линию где угодно: на смене имени, на
7140
+ * въезде на мост, на границе района, просто потому что так редактировали. Поэтому «две линии
7141
+ * сошлись в точке» НЕ ЗНАЧИТ «перекрёсток»: чаще всего это середина прямой улицы.
7142
+ *
7143
+ * Пока это не различается, всё, что делается «в узле», делается не там: разметка рвётся посреди
7144
+ * квартала, торцы лент проступают поперёк асфальта швом, стоп-линии встают там, где останавливаться
7145
+ * не перед чем. Ровно это и видно на любой карте, где дорога нарисована по кускам.
7146
+ *
7147
+ * Граф различает три случая по СТЕПЕНИ узла:
7148
+ *
7149
+ * 1 — тупик: конец улицы, дальше ничего;
7150
+ * 2 — СТЫК: два конца, улица продолжается. Ничего «узлового» здесь быть не должно — куски
7151
+ * склеиваются в одну ось (`chains`), и разметка идёт насквозь;
7152
+ * 3 и больше — ПЕРЕКРЁСТОК: вот здесь полотна пересекаются, разметка обрывается, а перед
7153
+ * обрывом встают стоп-линия и переход.
7154
+ *
7155
+ * ВЫЛЕТ ЧУЖОГО ПОЛОТНА (`clearance`) считается по углу, а не «на глазок». Дорога шириной w,
7156
+ * пересекающая нашу под углом α, накрывает наш путь на (w/2)/sin α: под прямым углом это половина
7157
+ * её ширины, под косым — заметно больше. Без синуса разметка на косом перекрёстке вылезает на
7158
+ * чужое полотно — та самая мелочь, по которой рисованная улица отличается от настоящей.
7159
+ *
7160
+ * Модуль СЧИТАЕТ В МЕТРАХ и ничего не знает ни о тайлах, ни о проекции: на входе оси в метрах, на
7161
+ * выходе — узлы и расстояния в метрах. Тайловый слой и geojson приводятся к этому снаружи.
7162
+ */
7163
+ /** Ось дороги: точки в метрах (x0,y0,x1,y1…) и то, что о ней известно из данных. */
7164
+ interface RoadAxis {
7165
+ pts: number[];
7166
+ /** Ширина проезжей части, метры. */
7167
+ width: number;
7168
+ /** Уровень пересечения: эстакада и улица под ней в одной точке — не узел. */
7169
+ level?: number;
7170
+ /** Одностороннее движение: встречного потока нет, значит нет и осевой. */
7171
+ oneway?: boolean;
7172
+ /** Число полос, если известно. */
7173
+ lanes?: number;
7174
+ /** Класс дороги из данных. */
7175
+ cls?: string;
7176
+ /** Номер исходной фичи — чтобы вернуть результат к ней. */
7177
+ id?: number;
7178
+ }
7179
+ /** Конец оси, приходящий в узел. */
7180
+ interface RoadRay {
7181
+ /** Номер оси в списке, поданном на вход. */
7182
+ axis: number;
7183
+ /** Этот конец — хвост оси (иначе голова). */
7184
+ atTail: boolean;
7185
+ /** Направление ОТ узла вдоль оси, радианы. */
7186
+ angle: number;
7187
+ /** Ширина этой оси, метры. */
7188
+ width: number;
7189
+ level: number;
7190
+ /** Класс дороги — по нему пятно перекрёстка берёт себе цвет самого крупного подхода. */
7191
+ cls: string;
7192
+ /**
7193
+ * Одностороннее движение по этой оси.
7194
+ *
7195
+ * Вместе с `atTail` даёт то, чего иначе не узнать: можно ли ПО НЕЙ УЕХАТЬ из узла. Ось
7196
+ * односторонняя и кончается здесь — значит движение по ней идёт к нам, и такого манёвра нет.
7197
+ */
7198
+ oneway: boolean;
7199
+ }
7200
+ interface RoadNode {
7201
+ x: number;
7202
+ y: number;
7203
+ /** Все концы, сошедшиеся здесь, по возрастанию угла. */
7204
+ rays: RoadRay[];
7205
+ /** Сколько концов сошлось. 1 — тупик, 2 — стык, 3 и больше — перекрёсток. */
7206
+ degree: number;
7207
+ /** Это настоящий перекрёсток, а не стык двух кусков одной улицы. */
7208
+ crossing: boolean;
7209
+ }
7210
+ /** Ось, склеенная из кусков: у улицы должен быть один асфальт, а не набор плиток. */
7211
+ interface RoadChain {
7212
+ pts: number[];
7213
+ /** Оси, вошедшие в цепочку, в порядке склейки. */
7214
+ axes: number[];
7215
+ /** Свойства взяты у первого куска: у склеенных они совпадают по построению. */
7216
+ width: number;
7217
+ level: number;
7218
+ oneway: boolean;
7219
+ lanes: number;
7220
+ cls: string;
7221
+ /** Узлы на концах цепочки. */
7222
+ head: number;
7223
+ tail: number;
7224
+ }
7225
+ interface RoadGraph {
7226
+ nodes: RoadNode[];
7227
+ /** Ось → номера узлов на её концах. */
7228
+ ends: [number, number][];
7229
+ /** Улицы, склеенные из кусков через стыки. */
7230
+ chains: RoadChain[];
7231
+ /**
7232
+ * Насколько далеко от центра узла тянется ЧУЖОЕ полотно вдоль этого луча, метры.
7233
+ *
7234
+ * Ноль у тупика и у стыка: там пересекаться не с чем. У перекрёстка — по самому «длинному»
7235
+ * из остальных лучей с поправкой на угол.
7236
+ */
7237
+ clearance(node: number, ray: RoadRay): number;
7238
+ /**
7239
+ * То же, но для направления, а не для луча из списка.
7240
+ *
7241
+ * Цепочка склеена из нескольких осей, и «своего» луча у неё нет: в узле лежит конец крайней
7242
+ * оси, а спрашивает вся улица. Направление и уровень она знает про себя сама.
7243
+ */
7244
+ clearanceAt(node: number, angle: number, level: number, exceptAxes: number[]): number;
7245
+ /**
7246
+ * Ширина САМОЙ ШИРОКОЙ проезжей части, приходящей в узел на этом уровне, метры.
7247
+ *
7248
+ * По ней полотна сходятся без уступа: узкий подход раскрывается к общему числу, широкий
7249
+ * остаётся как есть, и в самом узле у них совпадают кромки — кто бы из них ни рисовался первым.
7250
+ */
7251
+ nodeWidth(node: number, level: number): number;
7252
+ }
7253
+ interface RoadGraphOptions {
7254
+ /**
7255
+ * РАДИУС СКРУГЛЕНИЯ ПОВОРОТОВ ОСИ, в ширинах дороги. 0 — не скруглять.
7256
+ *
7257
+ * В данных ось это ломаная, и её изломы лента повторяет углами: дорожка в парке выглядит
7258
+ * собранной из спичек, хотя в жизни любая дорога поворачивает по дуге — по ней едут и ходят.
7259
+ * Радиус привязан к ширине, потому что широкая улица и поворачивает шире: у проспекта в 20 м
7260
+ * поворот заметно положе, чем у дворового проезда.
7261
+ *
7262
+ * Скругление живёт ЗДЕСЬ, а не в построении полотна, чтобы полотно и разметка шли по ОДНОЙ
7263
+ * кривой. Посчитай их по разным осям — и краска на повороте сойдёт с асфальта.
7264
+ */
7265
+ smooth?: number;
7266
+ /** Потолок радиуса скругления, метры: у самых широких дорог он иначе уезжает в десятки метров. */
7267
+ maxSmooth?: number;
7268
+ /**
7269
+ * ИТЕРАЦИИ ПОДРАЗБИЕНИЯ по Чайкину — для дорожек, у которых прямых участков нет вовсе.
7270
+ *
7271
+ * Скругление углов ограничено половиной звена, и на ломаной из трёхметровых звеньев его почти не
7272
+ * видно. Подразбиению длина звена безразлична: оно снимает угол целиком. Улицам это не нужно —
7273
+ * там прямая должна остаться прямой, — поэтому по умолчанию выключено.
7274
+ */
7275
+ refine?: number;
7276
+ /**
7277
+ * Максимальная длина звена перед подразбиением, метры. Работает только вместе с `refine`.
7278
+ *
7279
+ * Без него сглаживание на длинных звеньях и срезает много, и сглаживает мало — см. `densifyLine`.
7280
+ */
7281
+ densify?: number;
7282
+ }
7283
+ declare function buildRoadGraph(axes: RoadAxis[], options?: RoadGraphOptions): RoadGraph;
7284
+
7285
+ /**
7286
+ * РАЗМЕТКА ПО ГРАФУ ДОРОГ.
7287
+ *
7288
+ * Здесь считается вся краска на асфальте: осевая между встречными потоками, краевые у кромки
7289
+ * полотна, разделители между рядами, стоп-линии и переходы. Каждая линия получает СВОЁ место от
7290
+ * той дороги, которой принадлежит: её ширины, её числа полос, её направления.
7291
+ *
7292
+ * Работает по ЦЕПОЧКАМ (`RoadChain`), а не по кускам из данных. Это главное: улица в OSM нарезана
7293
+ * на ways по чему угодно, и разметка, построенная по кускам, рвётся посреди прямой на каждом шве.
7294
+ * Цепочка склеена через стыки, поэтому продольная краска идёт по улице насквозь и обрывается ровно
7295
+ * там, где ей и положено, — у перекрёстка.
7296
+ *
7297
+ * Отступ от перекрёстка берётся из графа (`clearanceAt`): он считает, насколько далеко чужое
7298
+ * полотно лезет на наш путь ПО УГЛУ пересечения. Под прямым углом это половина чужой ширины, под
7299
+ * косым — заметно больше, и именно поэтому на косых перекрёстках разметка обычно и вылезает
7300
+ * на соседнее полотно.
7301
+ *
7302
+ * Размеры по умолчанию — из ГОСТ Р 51256 и китайского GB 5768; они близки, а там, где расходятся,
7303
+ * задаются снаружи. Цвет здесь не решается вовсе: модуль отдаёт ГДЕ красить, а чем — дело стиля.
7304
+ * Это важно для Китая, где осевую красят жёлтым, а разделители полос белым.
7305
+ */
7306
+
7307
+ /** Что за линия разметки. Стиль красит их по-разному, поэтому вид едет вместе с геометрией. */
7308
+ type MarkingKind =
7309
+ /** Осевая между встречными потоками (в Китае жёлтая, у нас белая). */
7310
+ 'centre'
7311
+ /** Краевая у кромки проезжей части. */
7312
+ | 'edge'
7313
+ /** Разделитель полос одного направления — пунктир. */
7314
+ | 'lane'
7315
+ /** Стоп-линия поперёк своей половины проезжей части. */
7316
+ | 'stop'
7317
+ /** Полоса пешеходного перехода. */
7318
+ | 'zebra';
7319
+ interface Marking {
7320
+ kind: MarkingKind;
7321
+ /** Точки в метрах, в тех же осях, что и граф. */
7322
+ pts: number[];
7323
+ /** Класс дороги — стилю удобно фильтровать по нему. */
7324
+ cls: string;
7325
+ level: number;
7326
+ /**
7327
+ * НОМЕР ЦЕПОЧКИ в `graph.chains`, по которой нанесена эта краска.
7328
+ *
7329
+ * По нему линию можно свести с полотном той же цепочки — например, чтобы проверить, что она
7330
+ * легла на асфальт, а не рядом с ним.
7331
+ */
7332
+ chain: number;
7333
+ }
7334
+ interface MarkingOptions {
7335
+ /** Отступ краевой линии от кромки асфальта, метры. */
7336
+ edgeInset?: number;
7337
+ /** Половина зазора между линиями двойной осевой, метры. */
7338
+ centreGap?: number;
7339
+ /** Разметка не наносится на дороги уже этой ширины, метры. */
7340
+ minWidth?: number;
7341
+ /** Классы дорог, которые вообще не размечаются (проезды, дворы). */
7342
+ skipClasses?: string[];
7343
+ /** Классы, на подходах которых к перекрёстку ставятся стоп-линия и переход. */
7344
+ crossingClasses?: string[];
7345
+ /** Насколько краска не доходит до края перекрёстка, метры. */
7346
+ setback?: number;
7347
+ /** Куски короче этого не размечаются вовсе, метры. */
7348
+ minPiece?: number;
7349
+ /** Длина полосы перехода вдоль движения, метры. */
7350
+ zebraDepth?: number;
7351
+ /** Шаг полос перехода поперёк, метры. */
7352
+ zebraStep?: number;
7353
+ /**
7354
+ * СТАВИТЬ ЛИ ПЕРЕХОДЫ САМИМ.
7355
+ *
7356
+ * По умолчанию да: без данных о переходах это единственный способ их показать. Но догадка здесь
7357
+ * грубая — переход предполагается на каждом подходе к перекрёстку, а в жизни на раздельных
7358
+ * проезжих частях подходов вдвое больше, чем переходов, и на съездах их нет вовсе. Когда
7359
+ * переходы есть в данных (в OSM они размечены поштучно), надёжнее выключить это и взять их
7360
+ * оттуда; стоп-линии при этом остаются — они привязаны к подходу, а не к пешеходу.
7361
+ */
7362
+ zebra?: boolean;
7363
+ /**
7364
+ * ОСЬ, ПО КОТОРОЙ УЖЕ ПОСТРОЕНО ПОЛОТНО, — по номеру цепочки.
7365
+ *
7366
+ * Краска отмеряется от оси теми же полуширинами, что и кромка асфальта, и обязана лечь на него
7367
+ * один в один. Но ось, которую берёт полотно, — не та, что лежит в графе: сперва она
7368
+ * разглаживается до кривизны, при которой эквидистанта вообще существует, иначе кромка на
7369
+ * крутом повороте выворачивается наизнанку. Разметка же отмеряла от сырой — и на таком повороте
7370
+ * краевая линия уходила туда, где кромка давно разгладилась, то есть мимо асфальта.
7371
+ *
7372
+ * Считать разглаживание заново нельзя: полотно ведёт его по своей полуширине, с прибавкой на
7373
+ * обочину, и две «одинаковые» дуги расходятся на метры. Поэтому сюда кладут ГОТОВУЮ осевую из
7374
+ * тройки полотна (`RoadSurface.edges.centre` по `RoadSurface.chain`). Чего в списке нет, то
7375
+ * считается по-старому, от графа.
7376
+ */
7377
+ axes?: (number[] | undefined)[];
7378
+ /**
7379
+ * ВО СКОЛЬКО РАЗ КРАСКА МОЖЕТ РАЗДАТЬСЯ К УЗЛУ вслед за полотном.
7380
+ *
7381
+ * Улица шириной в 6.8 м, продолжающаяся куском в 10.2, — обычное дело в данных. Полотно на
7382
+ * таком шве раздаётся до ширины узла, чтобы кромки соседей сошлись; краска, отмеренная от
7383
+ * постоянной полуширины, — нет, и краевая линия обрывалась у шва уступом в метр семьдесят.
7384
+ *
7385
+ * Значения должны совпадать с теми, по которым строится полотно (`RoadSurfaceOptions`), иначе
7386
+ * линия снова разойдётся с кромкой.
7387
+ */
7388
+ maxGrow?: number;
7389
+ /** Наименьшая длина отгона ширины, метры. Совпадает с `taper` у полотна. */
7390
+ taper?: number;
7391
+ /**
7392
+ * ГДЕ КРАСКИ БЫТЬ НЕ ДОЛЖНО — например, на проезжей части кольцевой развязки.
7393
+ *
7394
+ * Подход к кольцу в данных не пересекает его ось и не делит с ним узла: он просто упирается в
7395
+ * круг и кончается где-то на его полотне. Граф такого пересечения не видит, отступать ему не от
7396
+ * чего, и разметка подхода идёт по кольцу насквозь — поперёк чужой проезжей части.
7397
+ *
7398
+ * Что именно занято, знает вызывающий: у кольца своя геометрия, и описать её общим правилом
7399
+ * нельзя. Краска обрезается по границе этой зоны — так же, как обрывается у перекрёстка.
7400
+ *
7401
+ * Третьим доводом идёт сама улица: без неё кольцо запретило бы краску самому себе — его
7402
+ * разметка целиком лежит внутри его же полотна, и вырезалась бы вся.
7403
+ */
7404
+ keepOut?: (x: number, y: number, chain: RoadChain) => boolean;
7405
+ /**
7406
+ * ГДЕ НА ПОДХОДЕ СТОИТ ПЕШЕХОДНЫЙ ПЕРЕХОД — расстояние ОТ УЗЛА вдоль оси, в метрах.
7407
+ *
7408
+ * Сам движок ставит его по отступу узла: перекрёсток кончился — через метр с небольшим переход.
7409
+ * Для одной проезжей части это верно, а для улицы из нескольких — нет: узлы у них на
7410
+ * перекрёстке стоят вразнобой, и переходы выходят лесенкой. Кто знает про соседей — тот и
7411
+ * скажет, где ряд; вернул `null` — считаем сами, как раньше.
7412
+ *
7413
+ * Стоп-линия отмеряется ОТ ПЕРЕХОДА, поэтому едет вместе с ним и остаётся к нему прижатой.
7414
+ */
7415
+ crossingAt?: (chain: RoadChain, index: number, node: number) => number | null;
7416
+ }
7417
+ declare function roadMarkings(graph: RoadGraph, options?: MarkingOptions): Marking[];
7418
+
7419
+ /**
7420
+ * СТРЕЛКИ НАПРАВЛЕНИЯ ПО ПОЛОСАМ — ИЗ ТОПОЛОГИИ ПЕРЕКРЁСТКА.
7421
+ *
7422
+ * На подходе к перекрёстку на асфальте нарисовано, куда с какой полосы можно ехать. В картах
7423
+ * Яндекса и Baidu это одна из самых заметных деталей: по стрелкам перекрёсток читается раньше, чем
7424
+ * по знакам.
7425
+ *
7426
+ * Откуда брать манёвры. В OSM для этого есть тег `turn:lanes` — но есть он далеко не везде: в
7427
+ * выкачке по Луцзяцзуй, например, его нет НИ У ОДНОЙ дороги. Поэтому манёвры выводятся из графа:
7428
+ * из узла видно, какие дороги из него выходят, под какими углами, и по каким из них вообще можно
7429
+ * уехать. Это та же логика, по которой навигатор строит манёвр, только результат рисуется краской.
7430
+ *
7431
+ * Куда нельзя: по односторонней дороге, которая в этот узел ВХОДИТ (её конец здесь — значит
7432
+ * движение по ней идёт к нам), и назад, откуда приехали. Всё остальное раскладывается по полосам:
7433
+ * левые манёвры к левому краю, правые к правому, «прямо» в середину.
7434
+ *
7435
+ * Стрелки ставятся только там, где известно число полос: рисовать три стрелки на дороге, у которой
7436
+ * полос может быть и две, и четыре, — значит выдумывать разметку.
7437
+ */
7438
+
7439
+ interface LaneGuide {
7440
+ /** Что нарисовано: стрелка того или иного манёвра. */
7441
+ kind: LaneArrow;
7442
+ /** Контур стрелки в метрах, замкнутый; составная стрелка отдаёт несколько таких. */
7443
+ pts: number[];
7444
+ cls: string;
7445
+ level: number;
7446
+ /**
7447
+ * ЦЕПОЧКА, НА КОТОРОЙ СТОИТ СТРЕЛКА, и расстояние вдоль неё до её середины.
7448
+ *
7449
+ * Нужны высоте. На развязке стрелка лежит на рампе, а прямо под рампой проходит улица — и по
7450
+ * плану одна ближе другой лишь на метры: взять отметку «у ближайшей оси» значит уронить стрелку
7451
+ * с эстакады на землю. Из цепочки же высота читается однозначно.
7452
+ */
7453
+ chain: number;
7454
+ at: number;
7455
+ }
7456
+ interface LaneGuideOptions {
7457
+ /** Классы дорог, на которых рисуются стрелки. */
7458
+ classes?: string[];
7459
+ /** На каком расстоянии от края перекрёстка ставить стрелку, метры. */
7460
+ setback?: number;
7461
+ /** Длина стрелки вдоль дороги, метры. */
7462
+ length?: number;
7463
+ /** Подход короче этого стрелок не получает: ставить их вплотную к предыдущему узлу незачем. */
7464
+ minApproach?: number;
7465
+ /** Угол, в пределах которого манёвр считается «прямо», радианы. */
7466
+ straightAngle?: number;
7467
+ /** Больше этого угол считается разворотом и в расчёт не идёт, радианы. */
7468
+ maxAngle?: number;
7469
+ /**
7470
+ * ГДЕ СТРЕЛКУ СТАВИТЬ НЕЛЬЗЯ — например, на пешеходном переходе.
7471
+ *
7472
+ * Стрелку рисуют на свободном асфальте перед стоп-линией, а не поверх зебры: две разметки на
7473
+ * одном месте не наносят никогда. Где именно лежат переходы, модуль знать не может — они
7474
+ * приходят из данных и стоят не там, где их поставила бы догадка, — поэтому проверку задаёт
7475
+ * вызывающий. При попадании стрелка отодвигается дальше от перекрёстка, а если места нет и
7476
+ * там — не рисуется вовсе.
7477
+ */
7478
+ blocked?: (x: number, y: number) => boolean;
7479
+ /** На сколько отодвигать стрелку, если место занято, метры. */
7480
+ retreat?: number;
7481
+ }
7482
+ declare function laneGuides(graph: RoadGraph, options?: LaneGuideOptions): LaneGuide[];
7483
+
7484
+ /**
7485
+ * ТРОЙКА «КРОМКА — ОСЬ — КРОМКА»: полотно, у которого ширина одинакова ВЕЗДЕ.
7486
+ *
7487
+ * До сих пор полотно строилось одной операцией: вокруг оси обводился контур, а всё, что при этом
7488
+ * заворачивалось внутрь (на повороте круче полуширины кромка неизбежно образует петлю), потом
7489
+ * распутывалось обрезкой. Так делают все библиотеки смещения, и на хорошей геометрии это работает.
7490
+ * Беда в том, что дорожная ось в данных хорошей не бывает: изломы по тридцать градусов между
7491
+ * трёхметровыми звеньями — норма. Петли идут одна за другой, обрезка сшивает не те края, и по
7492
+ * кромке ползут зубцы и вырезы.
7493
+ *
7494
+ * Здесь задача решается с другого конца. Полотно описывается ТРЕМЯ согласованными линиями — левой
7495
+ * кромкой, осью и правой кромкой, — и главное свойство задаётся ДО построения: ось разглаживается
7496
+ * так, чтобы её радиус кривизны нигде не был меньше полуширины. На такой оси эквидистанта
7497
+ * существует и не самопересекается — это геометрический факт, а не эвристика, — и обе кромки
7498
+ * получаются простым смещением по нормали, всегда параллельные, всегда на одном расстоянии.
7499
+ *
7500
+ * Распутывать после этого нечего, потому что путаться уже нечему.
7501
+ *
7502
+ * Кромки нужны не только полотну: по ним идёт краевая разметка, и взятая из той же тройки, она
7503
+ * ложится ровно вдоль края асфальта, а не «примерно там же».
7504
+ */
7505
+
7506
+ /** Полотно как три линии: обе кромки и ось между ними. Точки в метрах, по вершинам согласованы. */
7507
+ interface RoadEdges {
7508
+ /** Левая кромка по ходу оси. */
7509
+ left: number[];
7510
+ /** Разглаженная ось — та, от которой отмерены обе кромки. */
7511
+ centre: number[];
7512
+ /** Правая кромка по ходу оси. */
7513
+ right: number[];
7514
+ }
7515
+ /**
7516
+ * ОСЬ, РАЗГЛАЖЕННАЯ ДО ЗАДАННОГО РАДИУСА КРИВИЗНЫ.
7517
+ *
7518
+ * В каждой вершине радиус считается по описанной окружности трёх соседних точек. Где он меньше
7519
+ * нужного, вершина подтягивается к середине между соседями — тем сильнее, чем грубее нарушение.
7520
+ * Несколько проходов, и кривизна везде укладывается в норму.
7521
+ *
7522
+ * Это не косметика: при радиусе меньше полуширины эквидистанта внутренней стороны ФИЗИЧЕСКИ
7523
+ * выворачивается наизнанку. Никакой обводкой этого не исправить — можно только не подавать такую
7524
+ * ось. Настоящая дорога с таким поворотом тоже не построена: радиус поворота проезжей части всегда
7525
+ * больше её ширины, иначе по ней не проехать.
7526
+ *
7527
+ * Концы остаются на месте — они лежат в узлах сети. Смещение каждой вершины ограничено полушириной:
7528
+ * разгладить дорогу можно, а переложить её в другое место нельзя.
7529
+ */
7530
+ declare function relaxCurvature(line: number[], minRadius: number, iterations?: number): number[];
7531
+ /**
7532
+ * ОСЬ, У КОТОРОЙ НЕТ ИЗЛОМОВ КРУЧЕ ЗАДАННОГО УГЛА.
7533
+ *
7534
+ * Радиуса кривизны для ровной ширины мало, и это не сразу очевидно: радиус считается по трём
7535
+ * точкам и на ДЛИННЫХ звеньях выходит большим даже при изломе в сто двадцать градусов. А кромка
7536
+ * ставится по биссектрисе с выносом на косинус половины излома — и при таком угле вынос почти
7537
+ * вдвое больше полуширины. На настоящих данных именно это и давало разброс ширины в полтора раза
7538
+ * там, где радиус формально был в норме.
7539
+ *
7540
+ * Поэтому угол ограничивается отдельно: вершина подтягивается к середине между соседями, пока
7541
+ * излом не уложится в норму. При тридцати градусах вынос отличается от полуширины на три процента.
7542
+ */
7543
+ declare function relaxAngle(line: number[], maxTurn: number, iterations?: number, limit?: number): number[];
7544
+ /**
7545
+ * ТРОЙКА ЛИНИЙ ПО ОСИ.
7546
+ *
7547
+ * Ось сначала разглаживается до радиуса `half * safety`, потом обе кромки отмеряются от неё по
7548
+ * нормали. Нормаль в вершине — усреднённая по соседним звеньям, со срезом на косинус половины
7549
+ * излома: без него на повороте кромка подрезалась бы внутрь и полотно теряло ширину ровно там, где
7550
+ * его удобнее всего разглядывать.
7551
+ */
7552
+ declare function roadEdges(axis: number[], half: number, opts?: {
7553
+ safety?: number;
7554
+ iterations?: number;
7555
+ maxTurn?: number;
7556
+ /**
7557
+ * ПОЛУШИРИНА ВДОЛЬ ОСИ: `d` — расстояние от начала, метры.
7558
+ *
7559
+ * Нужна раскрытию к узлу: у одной улицы куски приходят разной ширины, и к общей ширине узла
7560
+ * полотно должно раскрываться плавно. Расстояние считается по УЖЕ разглаженной оси — на ней и
7561
+ * строятся кромки, так что раскрытие попадает ровно туда, куда задумано.
7562
+ */
7563
+ halfAt?: (d: number) => number;
7564
+ }): RoadEdges | null;
7565
+ /**
7566
+ * ТРОЙКА ДЛЯ ЗАМКНУТОЙ ОСИ — кольцевой развязки, круга во дворе.
7567
+ *
7568
+ * У кольца нет ни начала, ни конца, и в этом вся разница. Обычная тройка считает нормаль в
7569
+ * вершине по её соседям, а у первой и последней вершины соседа с одной стороны нет — направление
7570
+ * берётся по единственному звену. На разомкнутой улице это правильно (там торец), а на кольце
7571
+ * первая вершина — такая же рядовая, как все, просто с неё начали обход. Из-за этого в месте
7572
+ * замыкания кромка получала излом, и круг изнутри выглядел многоугольником.
7573
+ *
7574
+ * Здесь соседи берутся ПО КРУГУ: у первой вершины предыдущая — последняя. Разглаживание при этом
7575
+ * не нужно: замкнутую ось граф сглаживает сам, тоже по кругу (см. `unwrap` в `road-graph`).
7576
+ */
7577
+ declare function ringEdges(axis: number[], half: number): RoadEdges | null;
7578
+ /**
7579
+ * КОНТУР ПОЛОТНА ИЗ ТРОЙКИ: правая кромка вперёд, торец, левая назад, торец.
7580
+ *
7581
+ * Торцы — полукружия радиуса `half` вокруг концов оси: так кончается настоящая дорога, и так же
7582
+ * её кончают все, кто рисует дороги лентой.
7583
+ */
7584
+ declare function ribbonFromEdges(edges: RoadEdges, half: number, arcSegments?: number, flatHead?: boolean, flatTail?: boolean): Ring;
7585
+
7586
+ /**
7587
+ * ПОЛОТНО ДОРОГ ПО ГРАФУ: ПЕРЕГОНЫ И ПЯТНА ПЕРЕКРЁСТКОВ.
7588
+ *
7589
+ * Полотно, построенное «по куску за раз», не сходится никогда. Куски приходят из данных с разной
7590
+ * шириной (у одной улицы это сплошь и рядом 10 м и 10.2 м на соседних ways), и на каждом шве
7591
+ * асфальт даёт уступ в десяток сантиметров — ровно ту рваную кромку, по которой видно, что дорога
7592
+ * нарисована, а не построена. А на перекрёстке между лентами остаются вогнутые выемки: четыре
7593
+ * прямоугольника, наложенные крест-накрест, оставляют по углам пустые клинья.
7594
+ *
7595
+ * Настоящие карты (и процедурные генераторы дорог) строят дорожную сеть иначе, в два вида кусков:
7596
+ *
7597
+ * ПЕРЕГОН — лента между двумя узлами. Одна на всю улицу, а не по ленте на way: цепочки уже
7598
+ * склеены графом, поэтому торцов посреди квартала не возникает вовсе. Ширина плавно
7599
+ * раскрывается к ширине узла, и в самом узле у всех подходов кромки совпадают;
7600
+ *
7601
+ * ПЯТНО ПЕРЕКРЁСТКА — отдельная фигура, накрывающая всё пересечение. Её контур обходит концы
7602
+ * подходов по кругу и скругляется по радиусу поворота, как скругляется бордюр на настоящем
7603
+ * углу. Выемок в углах не остаётся по построению.
7604
+ *
7605
+ * Считает в метрах, как и весь граф. Дальше полигоны отдаются обычному слою поверхности — со своей
7606
+ * толщиной, фаской и бортом.
7607
+ */
7608
+
7609
+ interface RoadSurface {
7610
+ /** Перегон между узлами или пятно перекрёстка. */
7611
+ kind: 'carriageway' | 'junction' | 'fillet';
7612
+ outer: number[];
7613
+ /** Дырки: бывают у кольцевой развязки. */
7614
+ holes: number[][];
7615
+ cls: string;
7616
+ level: number;
7617
+ /** Ширина, по которой полотно построено, метры — стилю удобно ей сортировать. */
7618
+ width: number;
7619
+ /**
7620
+ * Узлы на концах перегона (у пятна и у слитой сети их нет).
7621
+ *
7622
+ * Нужны тому, кто считает высоту: эстакада выходит на неё рампой, а где эта рампа начинается,
7623
+ * видно только по узлу — если в него приходят дороги нижнего уровня, значит здесь съезд на
7624
+ * землю.
7625
+ */
7626
+ head?: number;
7627
+ tail?: number;
7628
+ /**
7629
+ * УЗЛЫ, КОТОРЫМ ПРИНАДЛЕЖИТ ПЯТНО ИЛИ ЗАКРУГЛЕНИЕ.
7630
+ *
7631
+ * Тому же, кто считает высоту. У перегона отметка берётся по его цепочке, а у пятна цепочки нет
7632
+ * вовсе — и без этого списка оно оставалось лежать на земле под взлетевшей развязкой бесформенным
7633
+ * тёмным пятном: асфальт есть, а дороги, которой он принадлежит, над ним.
7634
+ */
7635
+ nodes?: number[];
7636
+ /**
7637
+ * ТРОЙКА, ПО КОТОРОЙ ПОСТРОЕНО ЭТО ПОЛОТНО (только в режиме `edges`).
7638
+ *
7639
+ * Отдаётся наружу не для красоты: по кромкам идёт краевая разметка, а отладочный слой показывает
7640
+ * их поверх асфальта. Построй он свою тройку теми же параметрами — она всё равно разошлась бы с
7641
+ * полотном везде, где ширина переменная, и «край дороги не по графу» вернулся бы. Здесь лежит та
7642
+ * самая, из которой сделан контур.
7643
+ */
7644
+ edges?: RoadEdges;
7645
+ /**
7646
+ * ТРОЙКИ КУСКОВ, ИЗ КОТОРЫХ СЛЕПЛЕНА ЭТА ФИГУРА (только у слитой сети).
7647
+ *
7648
+ * Слияние сводит десятки полотен в один контур, и своей тройки у него уже нет — а показать, по
7649
+ * чему сеть построена, всё равно надо. Иначе отладка считает кромки заново, по другим
7650
+ * параметрам, и рисует их мимо асфальта: ровно то расхождение, из-за которого кажется, что край
7651
+ * дороги идёт не по графу.
7652
+ */
7653
+ sourceEdges?: RoadSourceEdges[];
7654
+ /**
7655
+ * НОМЕР ЦЕПОЧКИ в `graph.chains`, по которой построено это полотно.
7656
+ *
7657
+ * По нему разметка находит ту самую тройку, из которой сделан асфальт. Без него ей пришлось бы
7658
+ * считать свою — а своя расходится: полотно разглаживает ось по СВОЕЙ полуширине (с прибавкой
7659
+ * на обочину), и на крутом повороте две «одинаковые» дуги отходят друг от друга на метры.
7660
+ */
7661
+ chain?: number;
7662
+ }
7663
+ /** Тройка куска вместе с номером его цепочки: по нему её находит разметка. */
7664
+ interface RoadSourceEdges {
7665
+ chain: number;
7666
+ edges: RoadEdges;
7667
+ }
7668
+ interface RoadSurfaceOptions {
7669
+ /** Длина раскрытия ширины к узлу, метры. */
7670
+ taper?: number;
7671
+ /** Больше чем во столько раз к соседу не раскрываемся. */
7672
+ maxGrow?: number;
7673
+ /** Радиус скругления угла на перекрёстке, метры. */
7674
+ cornerRadius?: number;
7675
+ /** Прибавка к ширине из данных: обочина, свес бордюра, метры. */
7676
+ widthExtra?: number;
7677
+ /**
7678
+ * ДОПУСК УПРОЩЕНИЯ КОНТУРА ПОСЛЕ СЛИЯНИЯ, метры.
7679
+ *
7680
+ * Слитая сеть обходится по кромкам десятков отдельных лент, и там, где две кромки почти
7681
+ * совпадают, но не точно, на контуре остаётся ступенька в считанные сантиметры. Формы она не
7682
+ * несёт, зато край полотна становится ломаным, будто его рвали руками.
7683
+ *
7684
+ * Десятая доля метра. Полметра, стоявшие здесь раньше, ступеньки снимали, но вместе с ними
7685
+ * срезали и саму дорогу: на дворовом проезде в шесть метров это шестая часть ширины, и упрощение
7686
+ * уводило кромку внутрь настолько, что вдоль неё тянулась полоса, где асфальта уже нет, а по
7687
+ * графу дорога есть. По Шанхаю таких проб набиралось четыре с половиной тысячи против ста
7688
+ * восьмидесяти при десяти сантиметрах.
7689
+ *
7690
+ * Заодно выяснилось, что полметра ничего и не выпрямляли: с меньшим допуском контур выходит
7691
+ * РОВНЕЕ (средний излом 4.5° против 3.9°, острых углов 148 против 132) — упрощение само сдвигало
7692
+ * вершины и создавало изломы, которые должно было убирать. Платим за это двадцатью процентами
7693
+ * вершин контура.
7694
+ */
7695
+ tolerance?: number;
7696
+ /**
7697
+ * НАСКОЛЬКО БЛИЗКИЕ УЗЛЫ СЧИТАТЬ ОДНИМ ПЕРЕКРЁСТКОМ, метры.
7698
+ *
7699
+ * Перекрёсток улиц с раздельными проезжими частями состоит из нескольких узлов: каждая часть
7700
+ * пересекается с каждой отдельно. Пока пятна строились вокруг каждого по отдельности, между ними
7701
+ * оставались клинья голой земли.
7702
+ */
7703
+ cluster?: number;
7704
+ /**
7705
+ * ОБРЕЗАТЬ ПОЛОТНО У ПЕРЕКРЁСТКА, где в него входит дорога ШИРЕ.
7706
+ *
7707
+ * Лента строится на всю длину оси и кончается круглым торцом. В узле, где сходятся равные, это
7708
+ * правильно: торцы прячутся под пятном перекрёстка. А вот проезд, упирающийся в проспект, своим
7709
+ * торцом выезжает на его полотно и торчит поперёк полосы — мелкая деталь, по которой сразу
7710
+ * видно, что дороги нарисованы по отдельности.
7711
+ *
7712
+ * Насколько отступить, знает граф: `clearance` считает, как далеко чужое полотно лезет на наш
7713
+ * путь по УГЛУ пересечения. Обрезав на эту величину, мы ставим торец ровно на кромку чужой
7714
+ * дороги, а стык закрывает пятно перекрёстка.
7715
+ *
7716
+ * Обрезается только МЕНЬШАЯ: главная дорога через перекрёсток проходит насквозь, и укорачивать
7717
+ * её незачем.
7718
+ */
7719
+ trim?: boolean;
7720
+ /**
7721
+ * СТРОИТЬ ПОЛОТНО ТРОЙКОЙ «кромка — ось — кромка» вместо обводки контура.
7722
+ *
7723
+ * Обводка вокруг оси на плохой геометрии даёт петли, которые потом распутывает обрезка, — и на
7724
+ * дорожках с изломами по тридцать градусов она сшивает не те края, оставляя по кромке зубцы.
7725
+ * Тройка заходит с другого конца: ось сперва разглаживается до радиуса кривизны, при котором
7726
+ * эквидистанта существует, и кромки после этого просто отмеряются от неё. Распутывать нечего,
7727
+ * ширина держится с точностью до долей процента.
7728
+ *
7729
+ * Платит за это ось: её заломы круче полуширины разглаживаются, то есть геометрия слегка
7730
+ * отходит от данных. Там, где данные точны и важны (проезжая часть по измеренной оси), лучше
7731
+ * оставить обводку; там, где важнее вид (дорожки в парке), — тройку.
7732
+ */
7733
+ edges?: boolean;
7734
+ /**
7735
+ * СЛИТЬ ПОЛОТНА В СПЛОШНУЮ СЕТЬ и скруглить уже её контур, радиусом `cornerRadius`.
7736
+ *
7737
+ * Перегон и пятно перекрёстка — фигуры правильные, но ОТДЕЛЬНЫЕ: на их стыке лента упирается
7738
+ * в пятно под своим углом, и сеть собирается из граней. На проезжей части этого не видно —
7739
+ * там перегоны длинные, а перекрёстки редкие, — а вот в парке, где дорожки нарезаны кусками
7740
+ * по двадцать метров и сходятся под всеми углами сразу, вся сеть и выглядит гранёной.
7741
+ *
7742
+ * После слияния внутренних границ нет вовсе, и скругление работает по настоящему краю сети.
7743
+ * Стоит это булевой операции на весь набор, поэтому включается там, где кусков много и они
7744
+ * мелкие, — то есть на дорожках.
7745
+ */
7746
+ merge?: boolean;
7747
+ /**
7748
+ * СЛИВАТЬ И РАЗНЫЕ КЛАССЫ ТОЖЕ — всю сеть уровня в одну фигуру.
7749
+ *
7750
+ * Обычное слияние идёт по уровню И КЛАССУ: проспект и проезд остаются разными фигурами, потому
7751
+ * что красятся они по-разному, и слить их значило бы потерять цвет. Но там, где цвет у всех
7752
+ * один, класс разделять больше нечего, а платить за разделение приходится: проезд, влившийся в
7753
+ * проспект, лежит на нём своей фигурой, и слой строит по её контуру БОРТ — бордюрную стенку
7754
+ * поперёк чужого асфальта. На Шанхае таких швов набирается двадцать километров.
7755
+ *
7756
+ * Со слитыми классами внутренних границ не остаётся вовсе: борт идёт только по настоящему краю
7757
+ * сети. Класс у получившейся фигуры — самой широкой дороги в ней: если стиль всё же чем-то по
7758
+ * нему различает, пусть это будет главная.
7759
+ */
7760
+ mergeClasses?: boolean;
7761
+ /**
7762
+ * КУСКИ, КОТОРЫЕ НЕЛЬЗЯ СЛИВАТЬ С ОСТАЛЬНОЙ СЕТЬЮ.
7763
+ *
7764
+ * Слияние сводит десятки полотен в одну фигуру, и высота у неё может быть только одна на всех.
7765
+ * Для наземной сети это верно, а вот подход к эстакаде поднимается вдоль себя, и слитый с
7766
+ * землёй он либо утонет, либо утащит за собой квартал.
7767
+ *
7768
+ * Спрашивается про ВЕСЬ кусок, а не только про его цепочку: на развязке между рампами лежат ещё
7769
+ * и пятна перекрёстков с бордюрными закруглениями, у которых цепочки нет вовсе. Пока вопрос был
7770
+ * про цепочку, они уходили в общую кучу и оставались на земле — под поднявшейся развязкой от
7771
+ * них оставался бесформенный тёмный островок асфальта, к которому не подходит ни одна дорога.
7772
+ */
7773
+ apart?: (piece: RoadSurface) => boolean;
7774
+ /**
7775
+ * РАДИУС БОРДЮРНОГО ЗАКРУГЛЕНИЯ НА УГЛУ, метры. Ноль — не строить.
7776
+ *
7777
+ * На настоящем перекрёстке бордюр не ломается под прямым углом: он ПЛАВНО УХОДИТ с одной улицы
7778
+ * на другую дугой в несколько метров — по ней машина и поворачивает. Без неё на стыке остаётся
7779
+ * клин голой земли, вдвинутый в асфальт острым носом, и перекрёсток выглядит склеенным из
7780
+ * прямоугольников.
7781
+ *
7782
+ * Скруглением контура этого не получить, и вот почему: улицы разных классов красятся
7783
+ * по-разному, а значит и сливаются в разные фигуры. Угол между проспектом и проездом не
7784
+ * существует как вершина ни у одной из них — он возникает только там, где их полотна
7785
+ * НАКЛАДЫВАЮТСЯ. Округлять нечего.
7786
+ *
7787
+ * Поэтому закругление строится отдельной фигурой прямо по графу: берём два соседних по углу
7788
+ * луча узла, их обращённые друг к другу кромки, и заполняем асфальтом ровно то, что срезала бы
7789
+ * дуга, касающаяся обеих. Радиус подгоняется под узкий из подходов — на дворовом проезде
7790
+ * семиметровая дуга съела бы весь угол.
7791
+ */
7792
+ fillet?: number;
7793
+ }
7794
+ declare function roadSurfaces(graph: RoadGraph, options?: RoadSurfaceOptions): RoadSurface[];
7795
+
7796
+ /**
7797
+ * ВЫСОТА ДОРОЖНОЙ СЕТИ: ГРАФ В ПРОСТРАНСТВЕ.
7798
+ *
7799
+ * В данных высоты нет. Есть `layer` — целое число, которое говорит, кто над кем, и больше ничего:
7800
+ * ни на сколько метров, ни как туда подняться. Поэтому эстакада, построенная прямо по нему, встаёт
7801
+ * из земли стеной, а на её концах улица обрывается вертикальным уступом в восемь метров.
7802
+ *
7803
+ * Считать подъём на самой эстакаде тоже нельзя. Мост через канал — это сто метров пролёта, а
7804
+ * восемь метров набираются по нормальному уклону за сто тридцать: втиснутый в пролёт, подъём
7805
+ * выходит в пятнадцать процентов, вдвое круче любого съезда. В жизни эстакада поднята ВСЯ, а
7806
+ * поднимаются к ней ПОДХОДЫ — обычные улицы перед ней, которые в данных лежат тем же нулевым
7807
+ * слоем.
7808
+ *
7809
+ * Значит высота — свойство не куска, а СЕТИ, и решать её надо по графу целиком.
7810
+ *
7811
+ * 1. Всё, что лежит выше нуля, поднято на свою полную высоту по всей длине. Это и значит
7812
+ * `layer=1`: дорога идёт над чем-то, и над этим «чем-то» она не может быть ниже.
7813
+ *
7814
+ * 2. От концов поднятого высота РАСТЕКАЕТСЯ по сети, спадая ровно по заданному уклону: узел
7815
+ * получает наибольшую высоту, до которой можно дотянуться от какой-нибудь эстакады. Это
7816
+ * обычный поиск кратчайшего пути, только вместо расстояния — потеря высоты. Подход сам
7817
+ * находит себе длину: сколько нужно, столько улиц перед мостом и поднимется.
7818
+ *
7819
+ * Высота считается НА УЗЛЕ, а не на конце куска. Иначе в узле, где сходятся несколько
7820
+ * подходов, соседние куски получают разные отметки, и полотно рвётся: проверено, доходило
7821
+ * до шести метров разрыва.
7822
+ *
7823
+ * 3. Прямой конус, который из этого выходит, имеет два излома: у вершины (переход с плато на
7824
+ * уклон) и у подножия (переход с уклона на землю). На карте излом виден изломом полотна.
7825
+ *
7826
+ * Сглаживаем не расстояние, а САМУ ВЫСОТУ: `h = верх · S(h / верх)`, где `S(t) = t²(3−2t)` —
7827
+ * кубическая кривая с нулевой производной на обоих концах. У вершины и у подножия уклон
7828
+ * обращается в ноль, и переход выходит плавным; а поскольку это функция от самой высоты, то
7829
+ * на стыке двух кусков ничего не меняется — высота там и так общая, значит и уклон общий.
7830
+ *
7831
+ * Платит за это середина: у такой кривой она в полтора раза круче. Значит уклон в решателе
7832
+ * берётся в полтора раза меньше — чтобы в самом крутом месте выйти ровно на заданный.
7833
+ *
7834
+ * Считает в метрах, как и весь граф.
7835
+ */
7836
+
7837
+ interface RoadHeightOptions {
7838
+ /** Сколько метров приходится на один уровень `layer`. */
7839
+ levelHeight?: number;
5415
7840
  /**
5416
- * id фичи НА ТРЕУГОЛЬНИК — тем же форматом, что у заливки и зданий.
7841
+ * Наибольший уклон подъёма, доля (0.06 — шесть процентов).
5417
7842
  *
5418
- * Нужен, чтобы вырезать линию из готового меша: под 3D-моделью прячется не только здание, но и
5419
- * дорожки вокруг него, а их в наборе замен обычно больше всего.
7843
+ * СП 42.13330 для городских улиц даёт шестьдесят промилле; на съездах развязок берут до
7844
+ * восьмидесяти. Это уклон в САМОМ КРУТОМ месте кривой, а не средний.
5420
7845
  */
5421
- featureIds: ArrayBuffer;
7846
+ maxGrade?: number;
5422
7847
  }
5423
- interface LineOptions {
7848
+ interface RoadHeights {
7849
+ /** Высота узла, метры. */
7850
+ node(id: number): number;
7851
+ /** Высота на расстоянии `d` метров от головы цепочки. */
7852
+ at(chain: number, d: number): number;
7853
+ /** Длина цепочки по её оси, метры. */
7854
+ length(chain: number): number;
5424
7855
  /**
5425
- * Уровень пересечения (см. `generators/level`): берём только фичи этого уровня.
7856
+ * Цепочка лежит на земле целиком.
5426
7857
  *
5427
- * `undefined` — уровни не разделяем, слой строится целиком (так живут все слои, кроме дорожной
5428
- * сети: у реки или границы пересечений без связи не бывает).
7858
+ * Такую можно сливать с остальной наземной сетью в одну фигуру; поднятую или наклонную — нельзя,
7859
+ * у неё своя отметка.
5429
7860
  */
5430
- level?: number;
7861
+ flat(chain: number): boolean;
7862
+ /** Наибольший уклон, получившийся на этой цепочке, доля. */
7863
+ grade(chain: number): number;
5431
7864
  }
5432
- declare function generateLine(layer: VectorTileLayer, filter: Filter | undefined, maxSegment?: number,
5433
- /** Уровни поверхностей тайла: по ним дорога ложится на то, по чему идёт. Нет — лежит на нуле. */
5434
- ground?: GroundField,
5435
- /** Метров в единице тайла. Ноль — плавный переход ширины на стыках не строится. */
5436
- metersPerUnit?: number, opts?: LineOptions): LineMesh | null;
7865
+ declare function roadHeights(graph: RoadGraph, options?: RoadHeightOptions): RoadHeights;
5437
7866
 
5438
7867
  /**
5439
- * УРОВЕНЬ ПЕРЕСЕЧЕНИЯ — какой из двух дорог быть сверху там, где они пересекаются без связи.
7868
+ * МОСТ КАК СООРУЖЕНИЕ: продольный профиль и опоры.
5440
7869
  *
5441
- * ЗАЧЕМ. Плоская карта рисуется краской: слои идут в порядке стиля, и внутри слоя порядок фич
5442
- * произволен. Поэтому эстакада над улицей ничем не отличалась от перекрёстка — обводки сливались,
5443
- * полотна пересекались, и место читалось как съезд, которого в жизни нет. Ровно этим у Яндекса
5444
- * занят `line-z-level` (`["get","z_level"]` у КАЖДОГО дорожного слоя, все они в одной сети
5445
- * `line-network: "roads"`): сеть рисуется не по слоям, а по уровням, и мост со своей тенью и
5446
- * обводкой целиком ложится поверх того, что под ним.
7870
+ * Полотно моста у нас поднято на высоту уровня пересечения (см. `StyleSpec.levelHeight`), и до сих
7871
+ * пор подъём был СТУПЕНЬКОЙ: кусок дороги целиком либо на земле, либо наверху. На карте это видно
7872
+ * сразу — эстакада обрывается в воздухе на своих концах, а въезда на неё нет.
5447
7873
  *
5448
- * ОТКУДА ЧИСЛО. Готового `z_level` в наших тайлах нет, зато есть то, из чего его считают:
5449
- * `layer` — тег OSM, честный уровень (−1 подземный, 1 над землёй, изредка 2…5);
5450
- * `brunnel` — «мост / тоннель / брод», он приходит и там, где `layer` не проставлен.
5451
- * Поэтому берём `layer`, а если его нет — выводим уровень из `brunnel`. Мост без `layer`
5452
- * (в Душанбе таких большинство) получает 1, тоннель — −1, обычная улица — 0.
7874
+ * Здесь считаются две вещи, которых не хватало:
5453
7875
  *
5454
- * Считается в ВОРКЕРЕ, при тесселяции: по уровню бьётся меш, и рендеру остаётся только порядок.
7876
+ * РАМПА — продольный профиль высоты. Мост поднимается от земли плавно, с ограниченным уклоном,
7877
+ * и так же плавно спускается. Форма подъёма — S-образная (smoothstep), а не прямая: у настоящей
7878
+ * рампы переломов в начале и в конце нет, иначе машина «подпрыгивает» на въезде, и на карте это
7879
+ * читается изломом полотна.
7880
+ *
7881
+ * ОПОРЫ — столбы под плитой. Мост без них висит в воздухе; ставятся они с постоянным шагом и
7882
+ * только там, где под плитой есть место (на рампе у земли опора выродилась бы в пятно).
7883
+ *
7884
+ * Обе величины считаются по ОСИ дороги, в единицах тайла, и обе отдают готовые числа: генератору
7885
+ * поверхностей — высоту в каждой вершине, генератору опор — точки и высоты столбов. Так же
7886
+ * устроены процедурные генераторы дорог: сначала продольный профиль с ограничением уклона, потом
7887
+ * расстановка опор по нему.
5455
7888
  */
5456
7889
  /**
5457
- * Дальше этого уровни не разводим. В OSM попадается и `layer=5`, и опечатки в сотни: каждое
5458
- * значение — это отдельный бакет и отдельный вызов отрисовки, поэтому хвост подрезаем. Пять этажей
5459
- * развязки — больше, чем бывает на настоящих.
7890
+ * Наибольший продольный уклон эстакады, доля.
7891
+ *
7892
+ * Шесть процентов — верхняя граница для городской улицы (СП 42.13330 держит 60 ‰ для магистральных
7893
+ * улиц). Круче — это уже горная дорога, и на карте такой въезд читается обрывом.
5460
7894
  */
5461
- declare const LEVEL_LIMIT = 5;
5462
- /** Уровень фичи: `layer` из тегов, иначе вывод из `brunnel`. */
5463
- declare function featureLevel(props: Record<string, unknown>): number;
7895
+ declare const MAX_GRADE = 0.06;
7896
+ /** Шаг опор, метры: пролёт городской эстакады — два-три десятка метров. */
7897
+ declare const PIER_SPACING_METERS = 24;
7898
+ /**
7899
+ * Наименьшая высота опоры, метры: ниже она превращается в пятно под плитой.
7900
+ *
7901
+ * Считается от НИЗА плиты: у моста в метр высотой с полуметровой плитой под ней остаётся полметра,
7902
+ * и столб там не нужен — плита садится на насыпь.
7903
+ */
7904
+ declare const MIN_PIER_HEIGHT_METERS = 0.8;
7905
+ interface RampOptions {
7906
+ /** Высота полотна над землёй, метры. */
7907
+ height: number;
7908
+ /** Метров в одной единице оси. */
7909
+ metersPerUnit: number;
7910
+ /** Наибольший уклон, доля; по умолчанию `MAX_GRADE`. */
7911
+ maxGrade?: number;
7912
+ /** Начало оси стоит на земле — там нужен въезд. */
7913
+ rampStart?: boolean;
7914
+ /** Конец оси стоит на земле — там нужен съезд. */
7915
+ rampEnd?: boolean;
7916
+ }
7917
+ interface BridgeProfile {
7918
+ /** Высота полотна на расстоянии `d` от начала оси, метры. */
7919
+ heightAt: (d: number) => number;
7920
+ /** Длина въездной и съездной рампы в единицах оси (0 — рампы нет). */
7921
+ rampIn: number;
7922
+ rampOut: number;
7923
+ /** Где профиль ломается: эти расстояния полезно врезать в ось вершинами. */
7924
+ knots: number[];
7925
+ /** Полная длина оси в её единицах. */
7926
+ total: number;
7927
+ }
7928
+ /**
7929
+ * ПРОДОЛЬНЫЙ ПРОФИЛЬ МОСТА.
7930
+ *
7931
+ * Длина рампы берётся из уклона: у S-образной кривой уклон наибольший в середине и равен
7932
+ * `1.5 · высота / длина`, поэтому длина считается как `1.5 · высота / уклон`. Если моста не
7933
+ * хватает на две такие рампы, они укорачиваются поровну — тогда уклон выходит круче заданного, но
7934
+ * зато мост не превращается в горку без ровной части.
7935
+ */
7936
+ declare function bridgeProfile(axis: Ring | number[], opts: RampOptions): BridgeProfile;
7937
+ /** Опора моста: точка на оси, направление оси в ней и высота столба. */
7938
+ interface Pier {
7939
+ x: number;
7940
+ y: number;
7941
+ /** Направление оси в этой точке, радианы: опора ставится поперёк дороги. */
7942
+ angle: number;
7943
+ /** Высота столба от земли до низа плиты, метры. */
7944
+ height: number;
7945
+ /** Расстояние от начала оси, единицы оси. */
7946
+ at: number;
7947
+ }
7948
+ interface PierOptions {
7949
+ /** Метров в одной единице оси. */
7950
+ metersPerUnit: number;
7951
+ /** Толщина плиты, метры: столб держит её снизу. */
7952
+ deck: number;
7953
+ /** Шаг опор, метры; по умолчанию `PIER_SPACING_METERS`. */
7954
+ spacing?: number;
7955
+ /** Ниже этой высоты столб не ставится, метры. */
7956
+ minHeight?: number;
7957
+ }
7958
+ /**
7959
+ * ОПОРЫ ПОД ПЛИТОЙ.
7960
+ *
7961
+ * Ставятся с постоянным шагом от СЕРЕДИНЫ моста, а не от его начала: так они стоят симметрично, и
7962
+ * у короткой эстакады опора оказывается там, где она и должна быть — посередине пролёта.
7963
+ *
7964
+ * Пропускаются там, где плита идёт низко: на рампе у земли столб выродился бы в пятно под
7965
+ * полотном, а на настоящей эстакаде въезд и правда лежит на насыпи.
7966
+ */
7967
+ declare function bridgePiers(axis: Ring | number[], profile: BridgeProfile, opts: PierOptions): Pier[];
5464
7968
 
5465
7969
  /**
5466
7970
  * Положение камеры в адресе страницы.
@@ -5499,6 +8003,328 @@ declare function formatHash(camera: HashCamera, name?: string): string;
5499
8003
  */
5500
8004
  declare function parseHash(hash: string, name?: string): Partial<HashCamera> | null;
5501
8005
 
8006
+ /**
8007
+ * ИСТОЧНИК ИЗ ДАННЫХ ПРИЛОЖЕНИЯ, а не с сервера.
8008
+ *
8009
+ * У всех остальных источников тайлы режет сервер: приходит готовый MVT, воркер собирает из него
8010
+ * геометрию. Здесь резать приходится самим — данные приложение держит у себя целиком (маршрут,
8011
+ * выдача поиска, тысяча точек на карте) и меняет их когда захочет (`setData`).
8012
+ *
8013
+ * ★ ГЕОМЕТРИЮ СОБИРАЕТ ТОТ ЖЕ КОД, ЧТО И ДЛЯ ТАЙЛОВ.
8014
+ *
8015
+ * Нарезка заканчивается тем, что из кусков собирается «слой тайла» — объект той же формы, что
8016
+ * отдаёт разбор MVT. Дальше работает `buildTile`, общий с воркером. Иначе слой из GeoJSON рисовался
8017
+ * бы не так, как точно такой же слой из тайлов, и разницу пришлось бы объяснять в каждом стиле.
8018
+ *
8019
+ * ★ ГДЕ СЧИТАЕТСЯ: НА ГЛАВНОМ ПОТОКЕ.
8020
+ *
8021
+ * У MapLibre нарезка живёт в воркере. У нас воркер занят тайлами с сервера и умеет ровно одно —
8022
+ * забрать по адресу и разобрать; учить его хранить чужие наборы данных значит заводить им
8023
+ * состояние и протокол синхронизации. Наборы приложения при этом на порядки меньше карты (точки,
8024
+ * маршруты, зоны), и нарезка каждого тайла — это единицы миллисекунд. Когда набор станет большим,
8025
+ * его правильное место — тайлы с сервера, а не память страницы.
8026
+ */
8027
+ /** То, что принимает `setData`: сам набор или ссылка на него. */
8028
+ type GeoJSONData = GeoJSONFeatureCollection | GeoJSONFeature | string;
8029
+ interface GeoJSONFeature {
8030
+ type: 'Feature';
8031
+ id?: number | string;
8032
+ geometry: GeoJSONGeometry | null;
8033
+ properties?: Record<string, unknown> | null;
8034
+ }
8035
+ interface GeoJSONFeatureCollection {
8036
+ type: 'FeatureCollection';
8037
+ features: GeoJSONFeature[];
8038
+ }
8039
+ type GeoJSONGeometry = {
8040
+ type: 'Point';
8041
+ coordinates: [number, number];
8042
+ } | {
8043
+ type: 'MultiPoint';
8044
+ coordinates: [number, number][];
8045
+ } | {
8046
+ type: 'LineString';
8047
+ coordinates: [number, number][];
8048
+ } | {
8049
+ type: 'MultiLineString';
8050
+ coordinates: [number, number][][];
8051
+ } | {
8052
+ type: 'Polygon';
8053
+ coordinates: [number, number][][];
8054
+ } | {
8055
+ type: 'MultiPolygon';
8056
+ coordinates: [number, number][][][];
8057
+ };
8058
+ interface GeoJSONSourceOptions extends Partial<Omit<TileSourceOptions, 'url'>> {
8059
+ data?: GeoJSONData;
8060
+ /** Как называется слой внутри источника: на него ссылается `source-layer` слоя стиля. */
8061
+ sourceLayer?: string;
8062
+ /** Поле тайла в единицах тайла: геометрия у границы должна заезжать к соседу. */
8063
+ buffer?: number;
8064
+ /** Собирать ли точки в кластеры. */
8065
+ cluster?: boolean;
8066
+ /** Радиус кластера в пикселях экрана (как у MapLibre — 50). */
8067
+ clusterRadius?: number;
8068
+ /** Зум, выше которого кластеров нет: точки показываются по одной. */
8069
+ clusterMaxZoom?: number;
8070
+ }
8071
+ declare class GeoJSONSource extends TileSource {
8072
+ private features;
8073
+ private readonly layerName;
8074
+ private readonly bufferUnits;
8075
+ private readonly clusterOn;
8076
+ private readonly clusterRadiusPx;
8077
+ private readonly clusterTopZoom;
8078
+ /** Кластеры по зумам: считаются лениво и переживают панораму — набор от камеры не зависит. */
8079
+ private clusters;
8080
+ constructor(pool: WorkerPool, options?: GeoJSONSourceOptions);
8081
+ /**
8082
+ * Сменить данные. Строка — адрес: набор скачается сам.
8083
+ *
8084
+ * Тайлы после этого собираются заново: готовые выбрасываются, потому что они собраны из прежних
8085
+ * данных. Это и есть `setData` у MapLibre, вплоть до поведения — карта перерисуется, когда новые
8086
+ * тайлы соберутся.
8087
+ */
8088
+ setData(data: GeoJSONData): Promise<void>;
8089
+ /** Сколько фич в наборе — по нему видно, доехали ли данные. */
8090
+ get featureCount(): number;
8091
+ /**
8092
+ * Тайл собирается ЗДЕСЬ И СРАЗУ, без воркера и без сети.
8093
+ *
8094
+ * Дальше он неотличим от тайла с сервера: та же структура ответа, та же геометрия, тот же кеш.
8095
+ */
8096
+ protected loadTile(id: TileID): void;
8097
+ /**
8098
+ * КЛАСТЕРЫ НА ЗУМ — жадной сеткой, а не иерархией.
8099
+ *
8100
+ * Точки складываются в ячейки размером с радиус кластера и сливаются в одну, с числом собранных
8101
+ * в свойстве `point_count` — так же, как это видит приложение у MapLibre. Иерархического
8102
+ * дерева (supercluster) здесь нет намеренно: оно нужно наборам в сотни тысяч точек, а на тех
8103
+ * размерах данные всё равно пора отдавать тайлами с сервера.
8104
+ */
8105
+ private clustersFor;
8106
+ }
8107
+
8108
+ /**
8109
+ * Зум и компас.
8110
+ *
8111
+ * Компас показывает поворот и наклон СРАЗУ: стрелка поворачивается вместе с картой, и по ней видно,
8112
+ * куда «север», даже когда карта наклонена. Клик возвращает и поворот, и наклон — раздельные кнопки
8113
+ * для этого у MapLibre были, и от них отказались именно потому, что вернуть хотят обычно всё.
8114
+ */
8115
+ declare class NavigationControl implements IControl {
8116
+ private options;
8117
+ private group;
8118
+ private needle;
8119
+ private map;
8120
+ private onMove;
8121
+ constructor(options?: {
8122
+ showCompass?: boolean;
8123
+ showZoom?: boolean;
8124
+ visualizePitch?: boolean;
8125
+ });
8126
+ getDefaultPosition(): ControlPosition;
8127
+ onAdd(map: unknown): HTMLElement;
8128
+ onRemove(map: unknown): void;
8129
+ private syncCompass;
8130
+ }
8131
+ /** Линейка масштаба. Метры и футы — как у MapLibre, включая морские мили. */
8132
+ declare class ScaleControl implements IControl {
8133
+ private options;
8134
+ private element;
8135
+ private map;
8136
+ private onMove;
8137
+ constructor(options?: {
8138
+ maxWidth?: number;
8139
+ unit?: 'metric' | 'imperial' | 'nautical';
8140
+ });
8141
+ getDefaultPosition(): ControlPosition;
8142
+ onAdd(map: unknown): HTMLElement;
8143
+ onRemove(map: unknown): void;
8144
+ setUnit(unit: 'metric' | 'imperial' | 'nautical'): void;
8145
+ private sync;
8146
+ }
8147
+ /** Атрибуция: кто дал данные. Сворачивается в кружок «i», как у MapLibre. */
8148
+ declare class AttributionControl implements IControl {
8149
+ private options;
8150
+ constructor(options?: {
8151
+ compact?: boolean;
8152
+ customAttribution?: string | string[];
8153
+ });
8154
+ getDefaultPosition(): ControlPosition;
8155
+ onAdd(): HTMLElement;
8156
+ onRemove(): void;
8157
+ }
8158
+ /** Полный экран для контейнера карты. */
8159
+ declare class FullscreenControl implements IControl {
8160
+ private options;
8161
+ private target;
8162
+ constructor(options?: {
8163
+ container?: HTMLElement;
8164
+ });
8165
+ getDefaultPosition(): ControlPosition;
8166
+ onAdd(map: unknown): HTMLElement;
8167
+ onRemove(): void;
8168
+ }
8169
+ /**
8170
+ * Геолокация: показать, где пользователь.
8171
+ *
8172
+ * Разрешение спрашивает БРАУЗЕР, и только по клику — это его правило, а не наше. Слежение
8173
+ * (`trackUserLocation`) держит подписку, поэтому снимается при удалении контрола: забытый
8174
+ * `watchPosition` продолжает будить GPS и на вкладке, которую давно не смотрят.
8175
+ */
8176
+ declare class GeolocateControl implements IControl {
8177
+ private options;
8178
+ private watchId;
8179
+ private map;
8180
+ constructor(options?: {
8181
+ trackUserLocation?: boolean;
8182
+ fitBoundsOptions?: {
8183
+ zoom?: number;
8184
+ };
8185
+ positionOptions?: PositionOptions;
8186
+ });
8187
+ getDefaultPosition(): ControlPosition;
8188
+ onAdd(map: unknown): HTMLElement;
8189
+ onRemove(): void;
8190
+ private locate;
8191
+ }
8192
+ /** Тумблер рельефа: включает и выключает горы. */
8193
+ declare class TerrainControl implements IControl {
8194
+ private options;
8195
+ private button;
8196
+ private saved;
8197
+ constructor(options: {
8198
+ source: string;
8199
+ exaggeration?: number;
8200
+ });
8201
+ getDefaultPosition(): ControlPosition;
8202
+ onAdd(map: unknown): HTMLElement;
8203
+ onRemove(): void;
8204
+ }
8205
+ /** Тумблер глобуса: плоская карта ↔ шар. */
8206
+ declare class GlobeControl implements IControl {
8207
+ getDefaultPosition(): ControlPosition;
8208
+ onAdd(map: unknown): HTMLElement;
8209
+ onRemove(): void;
8210
+ }
8211
+ /** Логотип или любая другая ссылка в углу. */
8212
+ declare class LogoControl implements IControl {
8213
+ private options;
8214
+ constructor(options?: {
8215
+ compact?: boolean;
8216
+ href?: string;
8217
+ src?: string;
8218
+ label?: string;
8219
+ });
8220
+ getDefaultPosition(): ControlPosition;
8221
+ onAdd(): HTMLElement;
8222
+ onRemove(): void;
8223
+ }
8224
+
8225
+ /**
8226
+ * ВЫРАЖЕНИЯ СТИЛЯ MapLibre.
8227
+ *
8228
+ * Стиль, написанный под MapLibre, почти целиком состоит из них: ширина дороги — `interpolate` по
8229
+ * зуму, цвет — `match` по классу, видимость — `case`. Без интерпретатора такой стиль у нас не
8230
+ * работает вовсе, а переписывать его на наши упрощённые формы (`stops`) для каждого приложения —
8231
+ * это ровно та работа, которой перенос и должен избавить.
8232
+ *
8233
+ * ★ ИНТЕРПРЕТАТОР, А НЕ КОМПИЛЯТОР.
8234
+ *
8235
+ * MapLibre компилирует выражение в замыкания один раз и потом зовёт их. Мы считаем прямо по
8236
+ * массиву, но КЕШИРУЕМ разбор (`compile`) — на зумовых свойствах это вызов на слой за кадр, и
8237
+ * разница между подходами там неизмерима. На фильтрах, где вызовов десятки тысяч на тайл, кеш
8238
+ * решает: разобранное выражение переиспользуется для каждой фичи.
8239
+ *
8240
+ * ★ ЧТО УМЕЕТ, А ЧТО НЕТ.
8241
+ *
8242
+ * Умеет всё, что встречается в стилях: данные (`get`, `has`, `id`, `geometry-type`,
8243
+ * `feature-state`, `global-state`), решения (`case`, `match`, `coalesce`, логика, сравнения),
8244
+ * шкалы (`interpolate` линейный, экспоненциальный и по кривой Безье, `step`), арифметику,
8245
+ * строки, цвета, переменные (`let`/`var`), массивы (`at`, `length`, `slice`, `in`).
8246
+ *
8247
+ * Не умеет: `format` (богатый текст с разными шрифтами в одной подписи — у нас подпись
8248
+ * однородная), `image` в значении «встроить картинку в текст», `distance`, `within` для линий и
8249
+ * полигонов (точка проверяется, остальное — нет), `resolved-locale`. Всё перечисленное отмечено и
8250
+ * в `porting/maplibre-api.md`.
8251
+ */
8252
+ /** Значение выражения: то, чем оперирует язык. */
8253
+ type ExprValue = number | string | boolean | null | ExprValue[] | RGBAColor;
8254
+ /** Цвет как значение языка: четыре канала 0..1, как их отдаёт `parseColor`. */
8255
+ interface RGBAColor {
8256
+ r: number;
8257
+ g: number;
8258
+ b: number;
8259
+ a: number;
8260
+ }
8261
+ /** Всё, что выражение может спросить об окружении. */
8262
+ interface ExprContext {
8263
+ zoom?: number;
8264
+ /** Свойства фичи (для фильтров и data-driven значений). */
8265
+ properties?: Record<string, unknown>;
8266
+ /** Номер фичи: `['id']`. */
8267
+ id?: number | string | null;
8268
+ /** Тип геометрии: `Point`, `LineString`, `Polygon`. */
8269
+ geometryType?: string;
8270
+ /** Состояние фичи (`map.setFeatureState`). */
8271
+ featureState?: Record<string, unknown>;
8272
+ /** Общее состояние стиля (`map.setGlobalStateProperty`). */
8273
+ globalState?: Record<string, unknown>;
8274
+ /** Точка фичи в координатах карты — нужна `within` для точечных фич. */
8275
+ point?: [number, number];
8276
+ }
8277
+ /** Похоже ли значение на выражение: массив, первый элемент которого — имя операции. */
8278
+ declare function isExpression(value: unknown): value is ExprValue[];
8279
+ /**
8280
+ * Посчитать выражение.
8281
+ *
8282
+ * Разбор кешируется на самом массиве: стиль живёт долго, а значения из него берут каждый кадр.
8283
+ */
8284
+ declare function evaluateExpression(expr: unknown, ctx: ExprContext): ExprValue;
8285
+ /** Число из выражения; `fallback`, если получилось не число. */
8286
+ declare function expressionNumber(expr: unknown, ctx: ExprContext, fallback: number): number;
8287
+ /** Цвет из выражения в наш формат `[r, g, b, a]`. */
8288
+ declare function expressionColor(expr: unknown, ctx: ExprContext): [number, number, number, number] | null;
8289
+
8290
+ interface RibbonOptions {
8291
+ /** Полуширина полотна в единицах тайла. */
8292
+ halfWidth: number;
8293
+ /**
8294
+ * ПОЛУШИРИНА ВДОЛЬ ОСИ: `d` — расстояние от начала оси в единицах тайла.
8295
+ *
8296
+ * Нужна плавному переходу на стыке (см. `generators/junctions`): у одной улицы куски приходят
8297
+ * разной ширины, и без раскрытия к соседу асфальт обрывается уступом ровно там, где рисунок
8298
+ * дороги уже расширился. Не задана — лента постоянной ширины, как раньше.
8299
+ */
8300
+ halfWidthAt?: (d: number) => number;
8301
+ /** Метров в единице тайла — по нему считается дробность дуг. */
8302
+ metersPerUnit: number;
8303
+ /**
8304
+ * Замкнутую ось (кольцевая развязка, двор) обходим как кольцо: у неё нет концов, и торцевые
8305
+ * полукружия на стыке начала с концом оставили бы наплыв.
8306
+ */
8307
+ closed?: boolean;
8308
+ }
8309
+ /**
8310
+ * Контур ленты вокруг ломаной. `null` — из такой оси ленты не выходит
8311
+ * (одна точка, нулевая ширина, вырожденная геометрия).
8312
+ */
8313
+ declare function ribbonRing(line: Ring, opts: RibbonOptions): Ring | null;
8314
+ /**
8315
+ * ЛЕНТА КАК ФИГУРА: внешний контур и дырки.
8316
+ *
8317
+ * Дырка бывает ровно у замкнутой оси — у кольцевой развязки, у круга во дворе. Её полотно это
8318
+ * КОЛЬЦО: снаружи один контур, внутри другой, и между ними асфальт. Одним контуром такую фигуру
8319
+ * не задать: обход внешней стороны и обход внутренней склеились бы перемычкой, а заливка (и у
8320
+ * тесселяции, и у canvas правило одно — по числу оборотов) закрасила бы середину целиком.
8321
+ * Кольцевая развязка на карте превращалась при этом в сплошной асфальтовый блин.
8322
+ */
8323
+ declare function ribbonShape(line: Ring, opts: RibbonOptions): {
8324
+ outer: Ring;
8325
+ holes: Ring[];
8326
+ } | null;
8327
+
5502
8328
  /**
5503
8329
  * Рельефные поверхности: парк приподнят над землёй, вода утоплена, площадка
5504
8330
  * лежит своим уровнем.
@@ -5592,6 +8418,43 @@ interface SurfaceOptions {
5592
8418
  * переход на голой земле.
5593
8419
  */
5594
8420
  liftBy?: (properties: Record<string, unknown>) => number;
8421
+ /**
8422
+ * ПОДЪЁМ НА ВЕРШИНУ, а не на фигуру — профиль склона (см. `generators/lift`).
8423
+ *
8424
+ * Нужен рампе: восемь метров эстакады набираются склоном в сотню метров, и одной отметкой такой
8425
+ * склон не описать. Возвращает поле высот по свойствам фичи или `null` — тогда работает обычный
8426
+ * `liftBy`, одно число на всю фигуру.
8427
+ */
8428
+ liftAt?: (properties: Record<string, unknown>) => LiftField | null;
8429
+ /**
8430
+ * СТРОИТЬ ПОВЕРХНОСТЬ ИЗ ЛИНИЙ: полотно дороги, тротуар, дорожка.
8431
+ *
8432
+ * Функция возвращает ширину полотна в МЕТРАХ по свойствам фичи (в тайле она лежит в `width`,
8433
+ * см. `Transportation.roadWidthMeters`), ноль или null — такую линию пропускаем. Ось
8434
+ * превращается в контур (`generators/ribbon`), дальше всё как у обычного полигона: сварка,
8435
+ * фаска-бордюр, юбка, свет.
8436
+ */
8437
+ widthOf?: (properties: Record<string, unknown>) => number | null;
8438
+ /**
8439
+ * Прибавка к ширине полотна из стиля, МЕТРЫ (обочина, свес бордюра).
8440
+ *
8441
+ * Нужна отдельно от `widthOf`, хотя та её уже включает: плавный переход на стыке
8442
+ * (`generators/junctions`) считается по ширине ПРОЕЗЖЕЙ ЧАСТИ — по тому же числу, что и у
8443
+ * рисунка дороги, — а прибавка кладётся поверх результата. Смешай их, и полотно раскрывалось бы
8444
+ * к соседу на другое число, чем лента поверх него.
8445
+ */
8446
+ widthExtra?: number;
8447
+ /**
8448
+ * ТОЛЩИНА ПЛИТЫ У ПРИПОДНЯТОГО ПОЛОТНА (мост, эстакада), МЕТРЫ.
8449
+ *
8450
+ * Обычное полотно лежит на земле, и низа у него нет вовсе: юбка вырождается в ноль. У моста
8451
+ * низ есть — это и делает его сооружением, а не плёнкой на воздухе. Поэтому пол у приподнятого
8452
+ * куска опускается на толщину плиты, и по её краю идёт та же юбка, что у берега поверхности.
8453
+ *
8454
+ * Только у приподнятых: мостом кусок делает уровень пересечения (см. `StyleSpec.levelHeight`),
8455
+ * и у дороги на земле никакой плиты нет.
8456
+ */
8457
+ deck?: number;
5595
8458
  }
5596
8459
  interface SurfaceMesh {
5597
8460
  vertices: ArrayBuffer;
@@ -5670,6 +8533,14 @@ interface Shape {
5670
8533
  outer: Ring;
5671
8534
  holes: Ring[];
5672
8535
  lift?: number;
8536
+ /**
8537
+ * Своё поле высот (склон рампы, см. `generators/lift`).
8538
+ *
8539
+ * Объединение сливает только то, что лежит на ОДНОЙ высоте: слитые фигуры дальше живут как одна,
8540
+ * с одной отметкой на всех. У склона отметки нет вовсе — она у него в каждой вершине своя, — и
8541
+ * слить его с соседом значило бы уронить рампу на землю. Поэтому склон в объединение не идёт.
8542
+ */
8543
+ at?: unknown;
5673
8544
  }
5674
8545
 
5675
8546
  /**
@@ -5850,6 +8721,26 @@ declare function fogPlanes(tr: Transform, style: Style): FogPlanes;
5850
8721
  * })
5851
8722
  */
5852
8723
 
8724
+ /** Правка камеры перед кадром (см. `Map.setTransformCameraUpdate`). */
8725
+ type CameraUpdate = (camera: {
8726
+ center: LngLat;
8727
+ zoom: number;
8728
+ bearing: number;
8729
+ pitch: number;
8730
+ }) => Partial<{
8731
+ center: LngLatLike;
8732
+ zoom: number;
8733
+ bearing: number;
8734
+ pitch: number;
8735
+ }> | void;
8736
+ /** Своё ограничение камеры (см. `Map.setTransformConstrain`). */
8737
+ type TransformConstrain = (camera: {
8738
+ center: LngLat;
8739
+ zoom: number;
8740
+ }) => {
8741
+ center?: LngLatLike;
8742
+ zoom?: number;
8743
+ } | void;
5853
8744
  interface MapOptions {
5854
8745
  container: string | HTMLElement;
5855
8746
  center?: LngLatLike;
@@ -5891,6 +8782,12 @@ interface MapOptions {
5891
8782
  background?: string;
5892
8783
  /** Ограничение devicePixelRatio: на 4K-экранах 2× часто избыточно. */
5893
8784
  maxPixelRatio?: number;
8785
+ /**
8786
+ * Осторожные жесты: карта не перехватывает прокрутку страницы.
8787
+ *
8788
+ * Нужно встроенной карте внутри статьи — иначе колесо над ней листает не страницу, а зум.
8789
+ */
8790
+ cooperativeGestures?: boolean;
5894
8791
  /**
5895
8792
  * Детальные 3D-объекты (сервис Objects3D).
5896
8793
  *
@@ -6054,6 +8951,8 @@ declare class Map$1 extends Evented<MapEvents> {
6054
8951
  * которые сегодня не знают о подложке вовсе.
6055
8952
  */
6056
8953
  private rasterSources;
8954
+ /** Источники высот (`raster-dem`). Рельеф читает их вершинный шейдер каждого слоя земли. */
8955
+ private demSources;
6057
8956
  private renderer;
6058
8957
  private objects;
6059
8958
  /** Слой над канвой под DOM-маркеры: сам событий не ловит, маркеры — ловят. */
@@ -6066,10 +8965,59 @@ declare class Map$1 extends Evented<MapEvents> {
6066
8965
  /** Поля видимой области: место, занятое интерфейсом (см. setPadding). */
6067
8966
  private padding;
6068
8967
  private animation;
6069
- private loaded;
8968
+ /** Стиль разобран и первый кадр показан. Имя `loaded` занято методом MapLibre. */
8969
+ private ready;
6070
8970
  private destroyed;
6071
8971
  private background;
6072
8972
  private maxPixelRatio;
8973
+ /** Шаг прилипания зума у кнопок (`zoomIn`/`zoomOut`). Ноль — не липнет. */
8974
+ /** Плотность буфера кадра, как её посчитал `resize`. */
8975
+ private pixelRatio;
8976
+ /**
8977
+ * Правки структуры слоёв, сделанные на лету: добавления, удаления, перестановки, зумы, фильтры.
8978
+ *
8979
+ * Хранятся списком РАСПОРЯЖЕНИЙ, а не результатом: стиль пересобирается из исходной
8980
+ * спецификации при каждой смене темы, и правки накладываются на неё заново (см. `setStyle`).
8981
+ */
8982
+ private layerEdits;
8983
+ /**
8984
+ * Состояние фич: ключ `источник␟слой␟номер` → произвольные поля.
8985
+ *
8986
+ * Живёт у карты, а не у тайлов: тайл пересобирается при каждой смене темы и при каждом новом
8987
+ * зуме, а «выбрано» и «под курсором» обязаны это пережить.
8988
+ */
8989
+ private featureStates;
8990
+ /** Держится ли центр за землю (см. `setCenterClampedToGround`). */
8991
+ private centerClamped;
8992
+ /** Основной спрайт и дополнительные: их значки живут как обычные картинки. */
8993
+ private spriteUrl;
8994
+ private extraSprites;
8995
+ /** Какая картинка из какого спрайта пришла: по этому и снимаются при `removeSprite`. */
8996
+ private spriteImages;
8997
+ private glyphsUrl;
8998
+ /** Картинки, видео и canvas, прибитые к углам: тайлов у них нет, и живут они отдельно. */
8999
+ private mediaSources;
9000
+ /**
9001
+ * Правка адреса от приложения.
9002
+ *
9003
+ * Хранится, а не только раздаётся: источники заводятся и после `setTransformRequest` (тема
9004
+ * добавила слой, приложение позвало `addSource`), и новому источнику правку надо отдать тоже.
9005
+ */
9006
+ private transformRequest;
9007
+ private cameraUpdate;
9008
+ private transformConstrain;
9009
+ /** Углы для контролов над канвой. */
9010
+ private controls;
9011
+ /**
9012
+ * Обработчики ввода объектами (`map.dragPan.disable()`).
9013
+ *
9014
+ * Заводятся сразу, до жестов: приложение вправе выключить перетаскивание ещё до первого кадра, а
9015
+ * менеджер жестов появляется только вместе с контекстом GL.
9016
+ */
9017
+ private handlers;
9018
+ private zoomSnap;
9019
+ /** Порог доворота на север, градусы (`snapToNorth`). Значение MapLibre. */
9020
+ private readonly bearingSnap;
6073
9021
  /** Текущий профиль качества и то, разрешено ли двигать его самому. */
6074
9022
  private quality;
6075
9023
  /**
@@ -6169,6 +9117,14 @@ declare class Map$1 extends Evented<MapEvents> {
6169
9117
  * источник иначе.
6170
9118
  */
6171
9119
  private venuesForMask;
9120
+ /**
9121
+ * Свести рельеф стиля с загруженными источниками высот.
9122
+ *
9123
+ * Стиль и источник приезжают в разном порядке: `setStyle` объявляет и то и другое, а источник
9124
+ * заводится асинхронно. Поэтому связь пересобирается и после смены стиля, и после каждого
9125
+ * `addSource`, а не один раз при старте.
9126
+ */
9127
+ private syncTerrain;
6172
9128
  /** Подключить источник тайлов. Слои стиля ссылаются на него полем `source`. */
6173
9129
  addSource(name: string, spec: SourceSpec): this;
6174
9130
  private initInput;
@@ -6229,8 +9185,128 @@ declare class Map$1 extends Evented<MapEvents> {
6229
9185
  cameraForBounds(bounds: LngLatBounds, opts?: FitBoundsOptions): CameraOptions | null;
6230
9186
  /** Показать рамку целиком. `padding` — место, занятое интерфейсом. */
6231
9187
  fitBounds(bounds: LngLatBounds, opts?: FitBoundsOptions): this;
9188
+ /**
9189
+ * Сдвинуть карту на столько-то ПИКСЕЛЕЙ ЭКРАНА.
9190
+ *
9191
+ * Сдвигается карта, а не камера: `panBy([100, 0])` уводит содержимое влево, как палец,
9192
+ * потянувший карту вправо. Центр ищется обратным переводом точки экрана — и на рельефе он
9193
+ * честно ложится на землю (см. `Transform.elevationAt`).
9194
+ */
9195
+ panBy(offset: [number, number], opts?: AnimationOptions): this;
9196
+ /** Перелёт к точке без смены зума, наклона и поворота. */
9197
+ panTo(center: LngLatLike, opts?: AnimationOptions): this;
9198
+ /** Зум до значения. Без `duration` — мгновенно. */
9199
+ zoomTo(zoom: number, opts?: AnimationOptions): this;
9200
+ zoomIn(opts?: AnimationOptions): this;
9201
+ zoomOut(opts?: AnimationOptions): this;
9202
+ /** Поворот до азимута. */
9203
+ rotateTo(bearing: number, opts?: AnimationOptions): this;
9204
+ /** Повернуть на север. */
9205
+ resetNorth(opts?: AnimationOptions): this;
9206
+ /** Повернуть на север И положить карту плашмя. */
9207
+ resetNorthPitch(opts?: AnimationOptions): this;
9208
+ /**
9209
+ * Довернуть на север, если до него осталось немного.
9210
+ *
9211
+ * Порог тот же, что у MapLibre (`bearingSnap`, 7°): карту редко поворачивают «почти на север»
9212
+ * нарочно, обычно это недоворот рукой.
9213
+ */
9214
+ snapToNorth(opts?: AnimationOptions): this;
9215
+ /** Остановить анимацию камеры. Жест, начатый рукой, она не трогает — его останавливает палец. */
9216
+ stop(): this;
9217
+ /** Идёт анимация камеры (`easeTo`, `flyTo`, `fitBounds`). */
9218
+ isEasing(): boolean;
9219
+ /** Карта движется — хоть анимацией, хоть рукой. */
9220
+ isMoving(): boolean;
9221
+ isZooming(): boolean;
9222
+ isRotating(): boolean;
9223
+ getMinZoom(): number;
9224
+ setMinZoom(zoom: number): this;
9225
+ getMaxZoom(): number;
9226
+ setMaxZoom(zoom: number): this;
9227
+ getMinPitch(): number;
9228
+ setMinPitch(pitch: number): this;
9229
+ getMaxPitch(): number;
9230
+ setMaxPitch(pitch: number): this;
9231
+ /** Рамка, за которую камеру не выпускают. `null` — снять ограничение. */
9232
+ getMaxBounds(): LngLatBounds | null;
9233
+ setMaxBounds(bounds: LngLatBounds | null): this;
9234
+ /**
9235
+ * Шаг, к которому липнет зум у кнопок.
9236
+ *
9237
+ * Ноль — не липнет вовсе (так по умолчанию и у нас, и у MapLibre). Единица даёт целые зумы:
9238
+ * с 14.3 кнопка «плюс» ведёт на 15, а не на 15.3.
9239
+ */
9240
+ getZoomSnap(): number;
9241
+ setZoomSnap(snap: number): this;
9242
+ /** Вертикальный угол обзора, градусы. */
9243
+ getVerticalFieldOfView(): number;
9244
+ setVerticalFieldOfView(degrees: number): this;
9245
+ /** Высота земли под центром, метры: её ставит рельеф, но задать можно и руками. */
9246
+ getCenterElevation(): number;
9247
+ setCenterElevation(elevation: number): this;
9248
+ /** Высота точки, на которую смотрит камера. У MapLibre это то же, что высота центра. */
9249
+ getCameraTargetElevation(): number;
9250
+ getContainer(): HTMLElement;
9251
+ getCanvas(): HTMLCanvasElement;
9252
+ getCanvasContainer(): HTMLElement;
9253
+ /** Плотность буфера кадра. Ноль или меньше — вернуться к плотности экрана. */
9254
+ getPixelRatio(): number;
9255
+ setPixelRatio(ratio: number): this;
9256
+ /** Перерисовать кадр. Синоним `triggerRepaint` — под именем MapLibre. */
9257
+ redraw(): this;
9258
+ /** Снести карту. Синоним `destroy` — под именем MapLibre. */
9259
+ remove(): void;
9260
+ /** Карта готова: стиль разобран и видимые тайлы на месте. */
9261
+ loaded(): boolean;
9262
+ /**
9263
+ * Все ли тайлы видимой области уже пришли.
9264
+ *
9265
+ * Считаются ТОЛЬКО те источники, которые сейчас рисуются. Выключенный слой (пробки, планы
9266
+ * этажей, размещения моделей) держит свой прошлый набор тайлов и ничего не грузит — по нему
9267
+ * карта никогда не стала бы «готовой», хотя показывать он ничего и не собирается.
9268
+ */
9269
+ areTilesLoaded(): boolean;
9270
+ /**
9271
+ * Пришли ли все видимые тайлы этого источника.
9272
+ *
9273
+ * Пустой тайл и тайл с ошибкой считаются пришедшими: ждать их больше нечего, и без этого
9274
+ * `loaded()` навсегда оставался бы ложью на любой дырке в данных.
9275
+ */
9276
+ isSourceLoaded(id: string): boolean;
9277
+ /**
9278
+ * Вписать в кадр прямоугольник, заданный ДВУМЯ ТОЧКАМИ ЭКРАНА.
9279
+ *
9280
+ * Нужно рамке выделения: пользователь тянет прямоугольник мышью, и карта должна показать ровно
9281
+ * то, что он обвёл. Точки переводятся в координаты обратным переводом, дальше обычный `fitBounds`.
9282
+ */
9283
+ fitScreenCoordinates(p0: ScreenPoint$1, p1: ScreenPoint$1, bearing?: number, opts?: FitBoundsOptions): this;
9284
+ /**
9285
+ * Камера «откуда — куда»: поставить её в точку `from` на высоте `altitudeFrom` и направить на
9286
+ * точку `to`. Возвращает обычные параметры камеры — их можно отдать в `jumpTo` или `easeTo`.
9287
+ *
9288
+ * Формулы из MapLibre (`MercatorTransform.calculateCameraOptionsFromTo`, BSD-3-Clause).
9289
+ */
9290
+ calculateCameraOptionsFromTo(from: LngLatLike, altitudeFrom: number, to: LngLatLike, altitudeTo?: number): CameraOptions & {
9291
+ elevation: number;
9292
+ };
9293
+ /** Зум, прилипший к шагу `zoomSnap`. */
9294
+ private snapZoom;
6232
9295
  private stepAnimation;
6233
9296
  private afterCameraChange;
9297
+ /**
9298
+ * ПРАВКИ КАМЕРЫ ОТ ПРИЛОЖЕНИЯ.
9299
+ *
9300
+ * Зовутся в одном месте — здесь, после любого изменения камеры, — и в одном порядке: сначала
9301
+ * поправка (`setTransformCameraUpdate`), потом ограничение (`setTransformConstrain`). Порядок не
9302
+ * произвольный: ограничение обязано быть последним, иначе поправка смогла бы вынести камеру за
9303
+ * рамку уже после проверки.
9304
+ *
9305
+ * Защита от рекурсии обязательна: обработчик ставит центр, сеттер снова зовёт этот метод, и без
9306
+ * флага получается бесконечный спуск.
9307
+ */
9308
+ private applyCameraHooks;
9309
+ private inCameraHooks;
6234
9310
  /** move шлём не чаще кадра — на нём часто висит перерисовка UI. */
6235
9311
  private emitMove;
6236
9312
  project(lngLat: LngLatLike, altitudeMeters?: number): ScreenPoint$1;
@@ -6245,8 +9321,283 @@ declare class Map$1 extends Evented<MapEvents> {
6245
9321
  * перемалываются заново, картинка меняется в том же кадре.
6246
9322
  */
6247
9323
  setStyle(spec: StyleSpec): this;
9324
+ /**
9325
+ * Добавить слой. `beforeId` — перед каким слоем встать; без него слой ложится поверх всех.
9326
+ *
9327
+ * Тайлы пересобираются, только если слою нужна геометрия, которой ещё никто не просил: решает
9328
+ * это сравнение планов в `setStyle`, а не мы здесь.
9329
+ */
9330
+ addLayer(layer: StyleLayerSpec | CustomLayer, beforeId?: string): this;
9331
+ /** Убрать слой. Несуществующий — молча ничего: у MapLibre это ошибка, но нам она ни к чему. */
9332
+ removeLayer(id: string): this;
9333
+ /** Переставить слой: перед `beforeId`, а без него — наверх. */
9334
+ moveLayer(id: string, beforeId?: string): this;
9335
+ /** Слой стиля как он есть сейчас, со всеми правками. `undefined` — такого слоя нет. */
9336
+ getLayer(id: string): StyleLayerSpec | CustomLayer | undefined;
9337
+ /** Порядок слоёв снизу вверх — их идентификаторы. */
9338
+ getLayersOrder(): string[];
9339
+ /** Диапазон зумов, на которых слой виден. */
9340
+ setLayerZoomRange(id: string, minzoom?: number, maxzoom?: number): this;
9341
+ /** Фильтр слоя: какие фичи тайла ему достаются. У растрового слоя фильтра нет вовсе. */
9342
+ getFilter(id: string): Filter | undefined;
9343
+ /**
9344
+ * Сменить фильтр слоя.
9345
+ *
9346
+ * Фильтр решает, что попадёт в геометрию, поэтому тайлы пересобираются — в отличие от
9347
+ * `setPaintProperty`, который меняет только юниформы.
9348
+ */
9349
+ setFilter(id: string, filter: Filter | null): this;
9350
+ /** Спецификация стиля целиком — копией, чтобы правки снаружи не меняли живой стиль. */
9351
+ getStyle(): StyleSpec;
9352
+ /**
9353
+ * Стиль разобран.
9354
+ *
9355
+ * У нас он приходит объектом и разбирается сразу в конструкторе, поэтому ответ всегда
9356
+ * положительный — в отличие от MapLibre, где стиль может ещё качаться по ссылке.
9357
+ */
9358
+ isStyleLoaded(): boolean;
9359
+ /** Источники света стиля. */
9360
+ getLight(): LightSpec;
9361
+ /** Рельеф: источник высот и преувеличение. `null` — выключить. */
9362
+ setTerrain(terrain: TerrainSpec | null): this;
9363
+ getTerrain(): TerrainSpec | null;
9364
+ /**
9365
+ * Источник по имени: векторный, растровый или из данных приложения.
9366
+ *
9367
+ * Им приложение добирается до того, что умеет сам источник: у `geojson` это `setData`. Имя и
9368
+ * смысл как у MapLibre, где смена данных устроена так же — `map.getSource(id).setData(…)`.
9369
+ */
9370
+ getSource(id: string): TileSource | RasterSource | DemSource | MediaSource | undefined;
9371
+ /**
9372
+ * Отключить источник и выбросить его тайлы.
9373
+ *
9374
+ * Слои, которые на него ссылались, остаются в стиле и просто перестают рисоваться — это
9375
+ * нормальное состояние (см. `sourceFor` в рендерере), и оно же позволяет вернуть источник
9376
+ * обратно, не трогая стиль.
9377
+ */
9378
+ removeSource(id: string): this;
9379
+ /** Перекачать тайлы источника заново — данные на сервере поменялись. */
9380
+ refreshTiles(id: string): this;
9381
+ /** Крен камеры, градусы: поворот вокруг оси взгляда. */
9382
+ getRoll(): number;
9383
+ setRoll(roll: number): this;
9384
+ /**
9385
+ * Наложить на спецификацию правки структуры слоёв, сделанные на лету.
9386
+ *
9387
+ * Список, а не результат: стиль пересобирается из исходной спецификации при каждой смене темы,
9388
+ * и правки надо уметь применить заново — в том же порядке, в каком их сделали.
9389
+ */
9390
+ private applyLayerEdits;
9391
+ /**
9392
+ * ЧТО НАРИСОВАНО В ЭТОЙ ТОЧКЕ (или в этом прямоугольнике) ЭКРАНА.
9393
+ *
9394
+ * Отвечает по НАРИСОВАННОЙ геометрии: фича, которую слой отфильтровал или которая не попала в
9395
+ * видимый зум, в ответ не придёт, даже если в тайле она есть. Для второго вопроса — «что вообще
9396
+ * есть в данных» — существует `querySourceFeatures`.
9397
+ *
9398
+ * Без аргументов отвечает по всему экрану: так же ведёт себя MapLibre.
9399
+ */
9400
+ queryRenderedFeatures(geometry?: QueryGeometry, opts?: RenderedQueryOptions): QueriedFeature[];
9401
+ /**
9402
+ * ЧТО ЕСТЬ В ЗАГРУЖЕННЫХ ТАЙЛАХ ИСТОЧНИКА.
9403
+ *
9404
+ * Ответ неполон по устройству: за пределами загруженного его взять неоткуда. Ровно то же
9405
+ * ограничение и та же оговорка у MapLibre.
9406
+ */
9407
+ querySourceFeatures(sourceId: string, params?: {
9408
+ sourceLayer?: string;
9409
+ filter?: (props: Record<string, unknown>) => boolean;
9410
+ }): QueriedFeature[];
9411
+ /**
9412
+ * СОСТОЯНИЕ ФИЧИ: наведение, выбор, всё, что не в данных, но влияет на вид.
9413
+ *
9414
+ * Хранится отдельно от тайлов, потому что переживает их пересборку: тайл приедет заново, а
9415
+ * «эта улица выбрана» останется. Сейчас состояние отдаётся в ответах запросов; красить по нему
9416
+ * будут выражения стиля (`['feature-state', …]`) — это фаза 7.
9417
+ */
9418
+ setFeatureState(target: {
9419
+ source: string;
9420
+ sourceLayer?: string;
9421
+ id: number;
9422
+ }, state: Record<string, unknown>): this;
9423
+ getFeatureState(target: {
9424
+ source: string;
9425
+ sourceLayer?: string;
9426
+ id: number;
9427
+ }): Record<string, unknown>;
9428
+ /**
9429
+ * Снять состояние: всей фичи, одного ключа или целого источника.
9430
+ *
9431
+ * Без `id` чистится весь источник — так у MapLibre сбрасывают подсветку разом.
9432
+ */
9433
+ removeFeatureState(target: {
9434
+ source: string;
9435
+ sourceLayer?: string;
9436
+ id?: number;
9437
+ }, key?: string): this;
9438
+ /**
9439
+ * Высота земли в точке, метры. `null` — рельеф выключен или тайл высот ещё не приехал.
9440
+ *
9441
+ * Та же высота, по которой рисуется сама земля: одна функция на всех (`Terrain.heightAt`), иначе
9442
+ * подпись стояла бы на одной отметке, а склон под ней был бы нарисован на другой.
9443
+ */
9444
+ queryTerrainElevation(lngLat: LngLatLike): number | null;
9445
+ /**
9446
+ * Держится ли центр карты ЗА ЗЕМЛЮ.
9447
+ *
9448
+ * По умолчанию да: центр лежит на склоне, и камера поднимается вместе с ним. Выключение
9449
+ * оставляет центр на той высоте, где он оказался, — это нужно облётам, где камера идёт по своей
9450
+ * траектории, а не ползёт по рельефу.
9451
+ */
9452
+ setCenterClampedToGround(clamped: boolean): this;
9453
+ getCenterClampedToGround(): boolean;
9454
+ /**
9455
+ * Состояние фичи — КОПИЕЙ.
9456
+ *
9457
+ * Отдай мы живой объект, снятие одного ключа меняло бы и то, что приложение уже держит у себя:
9458
+ * ответ запроса, сделанного минуту назад, вдруг менялся бы задним числом.
9459
+ */
9460
+ private featureStateOf;
9461
+ /**
9462
+ * Добавить картинку под именем, на которое ссылается `iconImage` слоя.
9463
+ *
9464
+ * Занятое имя не перезаписывается — для замены есть `updateImage`. Так же ведёт себя MapLibre:
9465
+ * молча подменить чужой значок хуже, чем не добавить свой.
9466
+ */
9467
+ addImage(id: string, image: StyleImageData, options?: StyleImageOptions): this;
9468
+ /** Заменить картинку под тем же именем. Размер может смениться. */
9469
+ updateImage(id: string, image: StyleImageData, options?: StyleImageOptions): this;
9470
+ removeImage(id: string): this;
9471
+ hasImage(id: string): boolean;
9472
+ getImage(id: string): StyleImage | undefined;
9473
+ /** Имена всех картинок приложения. Встроенные значки сюда не входят — они не картинки. */
9474
+ listImages(): string[];
9475
+ /**
9476
+ * ЧТО ДЕЛАТЬ, ЕСЛИ СТИЛЬ ПРОСИТ КАРТИНКУ, КОТОРОЙ НЕТ.
9477
+ *
9478
+ * Обработчик получает имя и может дорисовать её на лету (`addImage`) — так у MapLibre грузят
9479
+ * значки по требованию, вместо того чтобы тянуть весь набор заранее.
9480
+ */
9481
+ setMissingStyleImageResolver(resolver: ((id: string) => void) | null): this;
9482
+ /**
9483
+ * СПРАЙТ: одна картинка с сеткой значков и описание, где какой лежит.
9484
+ *
9485
+ * Грузится парой файлов, как у MapLibre: `<url>.json` с рамками и `<url>.png` с пикселями.
9486
+ * Разрезается на обычные картинки, дальше они ничем не отличаются от добавленных вручную.
9487
+ */
9488
+ setSprite(url: string | null): Promise<this>;
9489
+ getSprite(): {
9490
+ id: string;
9491
+ url: string;
9492
+ }[];
9493
+ /** Добавить ещё один спрайт: его значки получают имена вида `id:name`, как у MapLibre. */
9494
+ addSprite(id: string, url: string): Promise<this>;
9495
+ removeSprite(id: string): this;
9496
+ /** Откуда брать глифы: шаблон вида `/fonts/{fontstack}/{range}.pbf`. */
9497
+ getGlyphs(): string;
9498
+ setGlyphs(url: string): this;
9499
+ /**
9500
+ * Разрезать спрайт на картинки.
9501
+ *
9502
+ * Имя значка из спрайта по умолчанию — как в описании; у дополнительного спрайта оно
9503
+ * получает приставку `id:`, чтобы не спорить с основным (правило MapLibre).
9504
+ */
9505
+ private loadSprite;
9506
+ /**
9507
+ * Поставить контрол в угол карты.
9508
+ *
9509
+ * Без `position` контрол встаёт туда, куда просится сам (`getDefaultPosition`), а без этого —
9510
+ * в правый верхний угол. Те же правила и те же названия углов, что у MapLibre.
9511
+ */
9512
+ addControl(control: IControl, position?: ControlPosition): this;
9513
+ removeControl(control: IControl): this;
9514
+ hasControl(control: IControl): boolean;
9515
+ /** Перетаскивание карты пальцем или мышью. */
9516
+ get dragPan(): GestureHandler;
9517
+ /** Поворот и наклон правой кнопкой. */
9518
+ get dragRotate(): GestureHandler;
9519
+ /** Зум колесом. */
9520
+ get scrollZoom(): GestureHandler;
9521
+ /** Зум двойным щелчком. */
9522
+ get doubleClickZoom(): GestureHandler;
9523
+ /** Щипок двумя пальцами: зум и поворот. */
9524
+ get touchZoomRotate(): GestureHandler;
9525
+ /** Наклон двумя пальцами. */
9526
+ get touchPitch(): GestureHandler;
9527
+ /** Управление с клавиатуры. */
9528
+ get keyboard(): GestureHandler;
9529
+ /** Зум рамкой: Shift + протянуть мышью. */
9530
+ get boxZoom(): BoxZoomHandler;
9531
+ /** Осторожные жесты: карта не перехватывает прокрутку страницы. */
9532
+ get cooperativeGestures(): CooperativeGesturesHandler;
9533
+ /**
9534
+ * СВОЙ ПРОТОКОЛ ЗАГРУЗКИ: `pmtiles://`, `custom://`, что угодно.
9535
+ *
9536
+ * Приложение отдаёт функцию, которая по адресу возвращает содержимое тайла. Дальше тайл идёт
9537
+ * обычным путём — тем же разбором и теми же генераторами, что тайл с сервера.
9538
+ *
9539
+ * Обработчик живёт на ГЛАВНОМ потоке, как и у MapLibre: он приложенческий, может ходить в
9540
+ * IndexedDB, в свой кеш, в WebAssembly-читалку, и переносить всё это в воркер значит требовать
9541
+ * от приложения писать под воркер.
9542
+ */
9543
+ static addProtocol(scheme: string, handler: ProtocolHandler): void;
9544
+ static removeProtocol(scheme: string): void;
9545
+ /**
9546
+ * ПРАВКА ЗАПРОСА ПЕРЕД ОТПРАВКОЙ: подпись, ключ, свой домен.
9547
+ *
9548
+ * Возвращённый адрес уходит в загрузку вместо исходного. Зовётся на главном потоке — там, где
9549
+ * приложение и держит свои ключи.
9550
+ */
9551
+ setTransformRequest(transform: TransformRequest | null): this;
9552
+ /**
9553
+ * ПРАВКА КАМЕРЫ ПЕРЕД КАДРОМ.
9554
+ *
9555
+ * Зовётся на каждое изменение камеры и может вернуть свои значения — так делают привязку к
9556
+ * маршруту, ограничение по коридору, плавное сопровождение. У MapLibre это
9557
+ * `setTransformCameraUpdate`.
9558
+ */
9559
+ setTransformCameraUpdate(update: CameraUpdate | null): this;
9560
+ /**
9561
+ * ОГРАНИЧЕНИЕ КАМЕРЫ: своя замена встроенной рамке.
9562
+ *
9563
+ * Возвращает поправленные центр и зум. Нужно там, где рамка не прямоугольная — например, карта
9564
+ * одного города с вырезом.
9565
+ */
9566
+ setTransformConstrain(constrain: TransformConstrain | null): this;
9567
+ /**
9568
+ * ПОДРОБНОСТЬ НАБОРА ТАЙЛОВ У ИСТОЧНИКА.
9569
+ *
9570
+ * `detailBias` — во сколько раз крупнее брать тайлы (двойка это примерно на зум грубее и вчетверо
9571
+ * меньше тайлов), `maxTiles` — потолок числа тайлов в наборе. Тем же у MapLibre заведует
9572
+ * `setSourceTileLodParams`; у нас это настройки покрытия, и они же решают, сколько сети и памяти
9573
+ * уйдёт на источник.
9574
+ */
9575
+ setSourceTileLodParams(id: string, params: {
9576
+ detailBias?: number;
9577
+ maxTiles?: number;
9578
+ }): this;
9579
+ /**
9580
+ * ОБЩЕЕ СОСТОЯНИЕ СТИЛЯ: значение, которое читают выражения (`['global-state', 'key']`).
9581
+ *
9582
+ * Им переключают вид карты, не пересобирая стиль: «показать этажи выше третьего», «подсветить
9583
+ * выбранный маршрут». Значение живёт у стиля, а не у слоя, — поэтому и имя общее.
9584
+ */
9585
+ setGlobalStateProperty(key: string, value: unknown): this;
9586
+ getGlobalState(): Record<string, unknown>;
9587
+ /** Повторять ли мир по горизонтали при отъезде. */
9588
+ getRenderWorldCopies(): boolean;
9589
+ setRenderWorldCopies(render: boolean): this;
6248
9590
  /** Переключить тему оформления поверх текущей структуры слоёв. */
6249
9591
  setTheme(theme: string | ThemeSpec): this;
9592
+ /**
9593
+ * СМЕНИТЬ ОСНОВУ, на которую ложатся темы.
9594
+ *
9595
+ * `setStyle` ставит стиль на один раз: следующий же `setTheme` соберёт тему поверх той основы, с
9596
+ * которой карту создали, и всё вернётся. Для переключения варианта карты (например, обычная ↔
9597
+ * рельефная) нужно заменить саму основу — этим и занимается этот метод. Тема применяется сразу,
9598
+ * чтобы палитра не слетала на время переключения.
9599
+ */
9600
+ setBaseStyle(style: StyleSpec, theme?: string | ThemeSpec): this;
6250
9601
  /** Имена доступных тем. */
6251
9602
  static get themes(): string[];
6252
9603
  /**
@@ -6274,6 +9625,10 @@ declare class Map$1 extends Evented<MapEvents> {
6274
9625
  * Правка paint-свойств слоя на лету. Геометрия НЕ пересобирается — меняются
6275
9626
  * только юниформы, поэтому цвет и ширину можно крутить хоть каждый кадр.
6276
9627
  * Для правки filter/source-layer нужен setStyle: там меняется геометрия.
9628
+ *
9629
+ * ИСКЛЮЧЕНИЕ — значение, зависящее от ФИЧИ (`['get', …]` в цвете, прозрачности или ширине).
9630
+ * Такое считается в воркере и лежит в вершинах (см. `generators/data-paint.ts`), поэтому правка
9631
+ * тянет за собой новый план и перекачку разбора — как `textField` у раскладки.
6277
9632
  */
6278
9633
  setPaintProperty(layerId: string, prop: string, value: unknown): this;
6279
9634
  /**
@@ -6322,6 +9677,13 @@ declare class Map$1 extends Evented<MapEvents> {
6322
9677
  * метку с названием: иначе тот же текст оказывается на карте дважды.
6323
9678
  */
6324
9679
  setSymbolHidden(hit: SymbolHit | null): this;
9680
+ /**
9681
+ * Высота земли в точке, метры — для того, что считается на процессоре.
9682
+ *
9683
+ * Поле, а не метод: его передают колбэком в пикинг, и связанная функция создаётся один раз, а не
9684
+ * на каждый клик. Рельефа нет — возвращается `undefined`, и пикинг работает по плоской земле.
9685
+ */
9686
+ private readonly groundAt;
6325
9687
  queryBuilding(point: ScreenPoint$1): BuildingHit | null;
6326
9688
  /**
6327
9689
  * Подсветить здание (или снять подсветку, передав null).
@@ -6553,4 +9915,4 @@ declare class Map$1 extends Evented<MapEvents> {
6553
9915
  destroy(): void;
6554
9916
  }
6555
9917
 
6556
- export { type AnimationOptions, AssetLoader, type AssetLoaderOptions, BASE_SOURCE, BUILDING_HEIGHT_SCALE, BUILDING_POS_SCALE, BUILDING_VERTEX_STRIDE, type BuildingHit, type BuildingsMesh, CITY_STYLE, type CameraOptions, Circle, type CircleOptions, type CoverOptions, DEFAULT_ICONS, DEFAULT_LIGHT, DEFAULT_SHADOWS, DEFAULT_SKY, DEFAULT_STYLE, DepthTarget, type DeviceHints, EARTH_CIRCUMFERENCE, FILL_VERTEX_STRIDE, type Filter, type FitBoundsOptions, type FitViewport, type FogPlanes, GLOBE_MAX_PITCH, GLOBE_ZOOM_MAX, GLOBE_ZOOM_MIN, GLYPH_BORDER, GLYPH_SIZE, GROUND_SCALE, GeomType, type GestureOptions, type Glyph, type GlyphMetric, GlyphSource, type GpuClass, GroundArrows, IconSource, IndoorMask, type Interpolated, LEVEL_LIMIT, LINE_VERTEX_STRIDE, type LayerPlan, LayerRenderer, type LightSpec, type LinePlacement, type LngLat, LngLatBounds, type LngLatLike, MAX_LATITUDE, MODEL_VERTEX_STRIDE, Map$1 as Map, type MapEvents, MapObject, type MapObjectOptions, type MapOptions, Marker, type MarkerOptions, Model, ModelLayer, type ModelMesh, type ModelOptions, type ModelPrimitive, ObjectManager, type ObjectPaint, type ObjectPart, type Padding, type PlacedGlyph, type PlacedSymbol, Polygon, type PolygonOptions, type PolygonPiece, Polyline, type PolylineOptions, type PolylineSegment, Popup, type PopupOptions, type Projection, type QualityChange, QualityGovernor, type QualityName, type QualityProfile, RELIEF_STYLE, RUNTIME_FILL_STRIDE, RUNTIME_LINE_STRIDE, RasterSource, type RasterSourceOptions, RasterTile, type RoundOptions, type RuntimeMesh, SDF_EDGE, SDF_PX, SOFT_STYLE, SPLIT_SEP, SURFACE_CUT_BLEED, SURFACE_LEVEL_SCALE, SURFACE_VERTEX_STRIDE, type ScreenPoint$1 as ScreenPoint, SdfAtlas, type ShadowSettings, type SkySpec, Style, type StyleLayerSpec, type StyleSpec, type SymbolHit, SymbolRenderer, THEMES, TILE_SIZE, type ThemeSpec, TileID, Transform, VectorTile, VectorTileFeature, VectorTileLayer, abbreviateStreet, addGround, altitudeFromMercatorZ, applyTheme, arrowLabelOffset, arrowVertices, buildRuntimeFill, buildRuntimeLine, cameraForBounds, circumferenceAtLatitude, clampLat, clampPixelRatio, classifyRenderer, clipLineToRect, coordinateDigits, coveringTiles, createGround, Map$1 as default, deviceHints, distToSegment, evaluateColor, evaluateNumber, extractFeature, featureLevel, fillFootprint, fogPlanes, footprintOf, formatHash, generateBuildings, generateFill, generateLine, generateSurface, generateSymbols, globeBasis, globeFlatMix, globeRadius, globeToLocal, groundAt, iconRotation, insideFootprint, latFromMercatorY, lngFromMercatorX, lngLatToUnit, matchesFilter, mercatorMetersPerTile, mercatorX, mercatorY, mercatorZFromAltitude, metersPerPixel, metersPerTile, nextQualityDown, parseGlb, parseGlbAsync, parseGlyphs, parseHash, patchUV, pickBuilding, pickFill, pitchIntent, pixelsPerMeter, placeAlongLine, pointInRing, qualityFor, qualityProfile, raySphere, resolveTemplate, roundRing, sdfFromAlpha, shapeText, splitBucketKey, splitByGround, splitPolygons, stitchPieces, styleWithTheme, tileOriginMeters, tileUrl, toLngLat, unitToLngLat, worldSize };
9918
+ export { type AnimationOptions, type ArrowSize, AssetLoader, type AssetLoaderOptions, AttributionControl, type AxisChain, BASE_SOURCE, BUILDING_HEIGHT_SCALE, BUILDING_POS_SCALE, BUILDING_VERTEX_STRIDE, BoxZoomHandler, type BridgeProfile, type BuildingHit, type BuildingsMesh, CITY_STYLE, type CameraOptions, type CameraUpdate, Circle, type CircleOptions, type ControlPosition, CooperativeGesturesHandler, type CoverOptions, type CustomLayer, DATA_PAINT_STRIDE, DEFAULT_ICONS, DEFAULT_LIGHT, DEFAULT_SHADOWS, DEFAULT_SKY, DEFAULT_STYLE, type DataPaintSpec, type DemEncoding, DemSource, type DemSourceOptions, DemTile, DemTileEntry, DepthTarget, type DeviceHints, EARTH_CIRCUMFERENCE, type ExprContext, type ExprValue, FILL_VERTEX_STRIDE, FeaturePaint, type Filter, type FitBoundsOptions, type FitViewport, type FogPlanes, FullscreenControl, GLOBE_MAX_PITCH, GLOBE_ZOOM_MAX, GLOBE_ZOOM_MIN, GLYPH_BORDER, GLYPH_SIZE, GROUND_SCALE, type GeoJSONData, type GeoJSONFeature, type GeoJSONFeatureCollection, type GeoJSONGeometry, GeoJSONSource, GeolocateControl, GeomType, GestureHandler, type GestureOptions, GlobeControl, type Glyph, type GlyphMetric, GlyphSource, type GpuClass, GroundArrows, type IControl, IconSource, ImageManager, IndoorMask, type Interpolated, type Junctions, LEVEL_LIMIT, LINE_VERTEX_STRIDE, type LaneArrow, type LaneGuide, type LaneGuideOptions, type LayerPlan, LayerRenderer, type LiftField, type LightSpec, type LinePlacement, type LngLat, LngLatBounds, type LngLatLike, LogoControl, MAX_GRADE, MAX_LATITUDE, MIN_PIER_HEIGHT_METERS, MODEL_VERTEX_STRIDE, Map$1 as Map, type MapEvents, MapObject, type MapObjectOptions, type MapOptions, Marker, type MarkerOptions, type Marking, type MarkingKind, type MarkingOptions, type MarkingRing, type MediaCoordinates, type MediaKind, MediaSource, Model, ModelLayer, type ModelMesh, type ModelOptions, type ModelPrimitive, NavigationControl, ObjectManager, type ObjectPaint, type ObjectPart, PIER_SPACING_METERS, type Padding, type Pier, type PierOptions, type PlacedGlyph, type PlacedSymbol, Polygon, type PolygonOptions, type PolygonPiece, Polyline, type PolylineOptions, type PolylineSegment, Popup, type PopupOptions, type Projection, type ProtocolHandler, type QualityChange, QualityGovernor, type QualityName, type QualityProfile, type QueriedFeature, type QueryGeometry, RELIEF_STYLE, ROAD_SIGNS, RUNTIME_FILL_STRIDE, RUNTIME_LINE_STRIDE, type RampOptions, RasterSource, type RasterSourceOptions, RasterTile, type RenderedQueryOptions, type RoadAxis, type RoadChain, type RoadEdges, type RoadGraph, type RoadGraphOptions, type RoadHeightOptions, type RoadHeights, type RoadNode, type RoadRay, type RoadSourceEdges, type RoadSurface, type RoadSurfaceOptions, type RoundOptions, type RuntimeMesh, SDF_EDGE, SDF_PX, SIGN_SIZE, SOFT_STYLE, SPLIT_SEP, SURFACE_CUT_BLEED, SURFACE_LEVEL_SCALE, SURFACE_VERTEX_STRIDE, ScaleControl, type ScreenPoint$1 as ScreenPoint, SdfAtlas, type ShadowSettings, type SkySpec, Style, type StyleImage, type StyleImageData, type StyleImageOptions, type StyleLayerSpec, type StyleSpec, type SymbolHit, SymbolRenderer, TAPER_METERS, TERRAIN_STYLE, THEMES, TILE_SIZE, type TaperedWidth, TerrainControl, type ThemeSpec, type TileAddress, TileID, Transform, type TransformConstrain, type TransformRequest, VectorTile, VectorTileFeature, VectorTileLayer, abbreviateStreet, addGround, altitudeFromMercatorZ, applyTheme, arrowLabelOffset, arrowVertices, bridgePiers, bridgeProfile, buildRoadGraph, buildRuntimeFill, buildRuntimeLine, cameraForBounds, chainAxes, circumferenceAtLatitude, clampLat, clampPixelRatio, classifyRenderer, clipLineToRect, coordinateDigits, coveringTiles, createGround, Map$1 as default, densifyLine, deviceHints, distToSegment, evaluateColor, evaluateExpression, evaluateNumber, expressionColor, expressionNumber, extractFeature, featureDrivenPaint, featureLevel, fillFootprint, fogPlanes, footprintOf, formatHash, generateBuildings, generateFill, generateLine, generateSurface, generateSymbols, globeBasis, globeFlatMix, globeRadius, globeToLocal, groundAt, iconRotation, insideFootprint, isExpression, junctionIndex, laneArrow, laneGuides, latFromMercatorY, liftFields, lngFromMercatorX, lngLatToUnit, makeFeaturePaint, matchesFilter, mercatorMetersPerTile, mercatorX, mercatorY, mercatorZFromAltitude, metersPerPixel, metersPerTile, needsDataPaint, nextQualityDown, parseGlb, parseGlbAsync, parseGlyphs, parseHash, parseLift, patchUV, pickBuilding, pickFill, pitchIntent, pixelsPerMeter, placeAlongLine, placeMarking, pointInRing, qualityFor, qualityProfile, queryRendered, querySource, raySphere, refineLine, relaxAngle, relaxCurvature, resolveTemplate, ribbonFromEdges, ribbonRing, ribbonShape, ringEdges, roadEdges, roadHeights, roadMarkings, roadSurfaces, roundLine, roundRing, sdfFromAlpha, shapeText, speedSign, splitBucketKey, splitByGround, splitPolygons, stitchPieces, stopLine, styleWithTheme, taperedWidth, tileOriginMeters, tileUrl, toLngLat, unitToLngLat, unpackVector, waffle, worldSize, zebra };