osmgl 0.31.31 → 0.31.32
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/core.d.ts +3784 -171
- package/dist/icons-full.d.cts +3 -0
- package/dist/icons-full.d.ts +3 -0
- package/dist/nav.d.ts +3591 -3
- package/dist/navigram.d.ts +6465 -6
- package/dist/osmgl.d.ts +20809 -7
- package/package.json +1 -1
- package/dist/index-BGX0JCbO.d.ts +0 -639
- package/dist/model-mshqbG69.d.ts +0 -4239
- package/dist/nav-C8QvUodP.d.ts +0 -2660
- package/dist/speed-index-kbbQG2xX.d.ts +0 -197
package/dist/nav.d.ts
CHANGED
|
@@ -1,3 +1,3591 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { Geom } from 'polygon-clipping';
|
|
2
|
+
|
|
3
|
+
/** Константы расписания объявлений (метры, секунды, м/с). */
|
|
4
|
+
declare const NAV_VOICE: {
|
|
5
|
+
/** Запас на произнесение реплики: `m_startBeforeSecondsVehicle` Organic Maps и его зажим. */
|
|
6
|
+
readonly ADVANCE_SECONDS: 8;
|
|
7
|
+
readonly ADVANCE_MIN_M: 35;
|
|
8
|
+
readonly ADVANCE_MAX_M: 150;
|
|
9
|
+
/** Предупреждение с дистанцией: `m_timeSeconds` и зажим метрической системы. */
|
|
10
|
+
readonly FAR_SECONDS: 30;
|
|
11
|
+
readonly FAR_MIN_M: 200;
|
|
12
|
+
readonly FAR_MAX_M: 2000;
|
|
13
|
+
/** Шаг короче — предупреждения нет (`m_minDistToSayNotificationMeters`). */
|
|
14
|
+
readonly TOO_CLOSE_M: 170;
|
|
15
|
+
/** Наименьший разрыв между предупреждением и командой. */
|
|
16
|
+
readonly MIN_GAP_M: 40;
|
|
17
|
+
/** Следующий манёвр не дальше этого И не дольше этого — одной фразой («…, затем налево»). */
|
|
18
|
+
readonly MERGE_MAX_M: 120;
|
|
19
|
+
readonly MERGE_MAX_S: 8;
|
|
20
|
+
/** Скорость шага, когда сервер не дал длительности (30 км/ч). */
|
|
21
|
+
readonly FALLBACK_SPEED_MPS: 8.33;
|
|
22
|
+
/** Порог прошёл не дальше этого — говорим фразу плана как есть; дальше — дистанцию пересчитываем. */
|
|
23
|
+
readonly FRESH_M: 30;
|
|
24
|
+
};
|
|
25
|
+
/** Произносимые дистанции — `GetAllSoundedDistMeters()` Organic Maps. */
|
|
26
|
+
declare const NAV_SPOKEN_GRID: number[];
|
|
27
|
+
/** Пороги, к которым округляется вверх дистанция объявления, — `GetSoundedDistMeters()` Organic Maps. */
|
|
28
|
+
declare const NAV_THRESHOLD_GRID: number[];
|
|
29
|
+
/** Дистанция словами есть только для ru и en: tg/uz говорят шаблоном с цифрой (`navPhrase`). */
|
|
30
|
+
declare function navHasSpokenDistance(lang: string): boolean;
|
|
31
|
+
/** Порог объявления → что сказать: округление ВВЕРХ до `NAV_THRESHOLD_GRID` (выше сетки — её край). */
|
|
32
|
+
declare function navRoundedForSpeech(meters: number): number;
|
|
33
|
+
/**
|
|
34
|
+
* Слово сетки произносимых дистанций — `DistToTextId` Organic Maps: первое не меньшее, но предыдущее,
|
|
35
|
+
* если до него вдвое ближе («130 → 100, 135 → 200»).
|
|
36
|
+
*/
|
|
37
|
+
declare function navSnapSpoken(meters: number): number;
|
|
38
|
+
/** «Через триста метров» / «In three hundred meters»; для других языков — `null`. */
|
|
39
|
+
declare function navSpokenDistance(lang: string, meters: number): string | null;
|
|
40
|
+
/** Первая буква строчная (фраза приклеивается к дистанции одним выдохом); аббревиатуру не трогаем. */
|
|
41
|
+
declare function navStartingLowercase(text: string): string;
|
|
42
|
+
/** «Через триста метров поверните направо» — без запятой; нет слов для языка — фраза как есть. */
|
|
43
|
+
declare function navWithDistance(lang: string, meters: number, phrase: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Текст сервера к синтезу (только ru): `27мкр` → «микрорайон 27» — синтез читает «двадцать семь
|
|
46
|
+
* эм-ка-эр». Словаря сокращений улиц нет сознательно: «на ул. Фирдавси» → «на улица Фирдавси» хуже.
|
|
47
|
+
*/
|
|
48
|
+
declare function navSpeechText(text: string, lang: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Короткая фраза манёвра (ru/en — строки Organic Maps, tg/uz — свои) или `null` — своими словами сказать нечего
|
|
51
|
+
* (тогда звучит фраза сервера). `approaching` — фраза пойдёт с дистанцией (у прибытия она другая).
|
|
52
|
+
*/
|
|
53
|
+
declare function navManeuverPhrase(lang: string, type?: string | null, modifier?: string | null, exit?: number | null, approaching?: boolean): string | null;
|
|
54
|
+
/** Связка «, затем налево» для близкого следующего манёвра; `null` — сливать нечего. */
|
|
55
|
+
declare function navThen(lang: string, type?: string | null, modifier?: string | null): string | null;
|
|
56
|
+
/** Скорость на шаге (м/с): длина / время сервера (с пробками); нет — `FALLBACK_SPEED_MPS`. */
|
|
57
|
+
declare function navStepSpeed(step: NavStep | null | undefined): number;
|
|
58
|
+
/** Запас на реплику, м. */
|
|
59
|
+
declare function navAdvanceM(speed: number): number;
|
|
60
|
+
/** Порог команды (без дистанции), м до манёвра. */
|
|
61
|
+
declare function navNearThreshold(legM: number, speed: number): number;
|
|
62
|
+
/** Порог предупреждения с дистанцией, м до манёвра. */
|
|
63
|
+
declare function navFarThreshold(legM: number, speed: number): number;
|
|
64
|
+
/** Есть ли у шага длиной `legM` предупреждение с дистанцией (`TooCloseForFisrtNotification` и разрыв). */
|
|
65
|
+
declare function navHasFar(legM: number, speed: number): boolean;
|
|
66
|
+
/** Одно объявление плана: о манёвре шага `idx`, на остатке `atM` метров до него. */
|
|
67
|
+
interface NavVoiceCue {
|
|
68
|
+
idx: number;
|
|
69
|
+
kind: 'far' | 'near';
|
|
70
|
+
atM: number;
|
|
71
|
+
/** Что сказать. Пусто — сказать нечего (манёвр всё равно считается объявленным). */
|
|
72
|
+
text: string;
|
|
73
|
+
/** Произнесённая дистанция (только `far`), м. */
|
|
74
|
+
spokenM?: number;
|
|
75
|
+
/** Запас на реплику на этом шаге, м — для пересчёта дистанции, если порог проехали. */
|
|
76
|
+
advanceM: number;
|
|
77
|
+
/** Приклеенная связка «, затем …» (только `far`). */
|
|
78
|
+
then?: string;
|
|
79
|
+
}
|
|
80
|
+
/** Текст предупреждения: дистанция словами (ru/en) или шаблоном с цифрой (tg/uz — `navPhrase`), плюс связка. */
|
|
81
|
+
declare function navFarText(lang: string, step: NavStep, spokenM: number, then: string | undefined, textOf: (s: NavStep) => string, phraseWithDistance: (lang: string, text: string, dist: number) => string): string;
|
|
82
|
+
/**
|
|
83
|
+
* План объявлений трассы: для каждого манёвра (кроме старта) — команда и, если хватает места,
|
|
84
|
+
* предупреждение. Индексы — шаги `route.steps`: манёвр шага `idx` стоит в его начале, едем к нему по
|
|
85
|
+
* шагу `idx − 1` (его длина — «плечо», его скорость — скорость объявления). `textOf` — текст
|
|
86
|
+
* сервера шага на языке (`navStepText`); `phraseWithDistance` — шаблон с цифрой для tg/uz (`navPhrase`).
|
|
87
|
+
*/
|
|
88
|
+
declare function navVoicePlan(route: NavRoute, lang: string, textOf: (s: NavStep) => string, phraseWithDistance: (lang: string, text: string, dist: number) => string): NavVoiceCue[];
|
|
89
|
+
/**
|
|
90
|
+
* На каком расстоянии ДО промежуточной точки сказать «Вы прибыли в промежуточную точку», м.
|
|
91
|
+
*
|
|
92
|
+
* Обычная «ближняя» команда звучит за 35–150 м — для прибытия это рано: человек ещё едет, а ему уже сказали, что он на
|
|
93
|
+
* месте. У финиша эту роль играет `NAV_ARRIVE_M` приложения; у промежуточной точки приложения своей проверки нет,
|
|
94
|
+
* поэтому команду ставит план — на том же расстоянии.
|
|
95
|
+
*/
|
|
96
|
+
declare const NAV_VIA_SAY_M = 25;
|
|
97
|
+
/** Все фразы плана без повторов — для заранее синтезированных файлов (сетевые голоса). */
|
|
98
|
+
declare function navVoicePhrases(plan: NavVoiceCue[]): string[];
|
|
99
|
+
|
|
100
|
+
type LonLat$3 = [number, number];
|
|
101
|
+
declare function distM(a: LonLat$3, b: LonLat$3): number;
|
|
102
|
+
declare function bearing(a: LonLat$3, b: LonLat$3): number;
|
|
103
|
+
/** Поворот курса к нужному кратчайшей дугой: без этого камера на переходе через 360° крутится назад. */
|
|
104
|
+
declare function turnTo(cur: number | null, want: number, k: number): number;
|
|
105
|
+
/**
|
|
106
|
+
* Шаг времени кадра, с: нечисловой или отрицательный (часы назад, первый кадр без прошлого) — ноль, то есть «стоим».
|
|
107
|
+
* Сглаживание `1 − exp(−dt/τ)` от NaN дало бы NaN, а он, попав в зум или метр, остаётся там навсегда — камера с NaN
|
|
108
|
+
* рисует пустую карту.
|
|
109
|
+
*/
|
|
110
|
+
declare function navDt(dt: number): number;
|
|
111
|
+
/** Расстояние от точки до отрезка и доля вдоль него. */
|
|
112
|
+
declare function projectOnSegment(p: LonLat$3, a: LonLat$3, b: LonLat$3, lat0: number): {
|
|
113
|
+
t: number;
|
|
114
|
+
off: number;
|
|
115
|
+
};
|
|
116
|
+
declare const fmtDist: (m: number) => string;
|
|
117
|
+
declare const fmtDur: (s: number) => string;
|
|
118
|
+
/** Расстояние крупной цифрой и мелкой единицей — так число читается быстрее. */
|
|
119
|
+
declare function distParts(m: number): [string, string];
|
|
120
|
+
interface NavStep {
|
|
121
|
+
type?: string;
|
|
122
|
+
modifier?: string;
|
|
123
|
+
name?: string;
|
|
124
|
+
text?: Record<string, string>;
|
|
125
|
+
distanceMeters?: number;
|
|
126
|
+
durationSeconds?: number;
|
|
127
|
+
/** Номер съезда с кольца (`maneuver.exit` OSRM), если сервер его дал. */
|
|
128
|
+
exit?: number;
|
|
129
|
+
/** Номер дороги шага («M41», несколько — через « / ») — щит в карточке манёвра; нет — пусто. */
|
|
130
|
+
ref?: string | null;
|
|
131
|
+
/** Накопленная длина до начала шага — точка, где манёвр выполняется. */
|
|
132
|
+
at: number;
|
|
133
|
+
end: number;
|
|
134
|
+
}
|
|
135
|
+
interface NavUpcoming {
|
|
136
|
+
idx: number;
|
|
137
|
+
step: NavStep;
|
|
138
|
+
toM: number;
|
|
139
|
+
next: NavStep | null;
|
|
140
|
+
}
|
|
141
|
+
interface NavTrafficBand {
|
|
142
|
+
from: number;
|
|
143
|
+
to: number;
|
|
144
|
+
length: number;
|
|
145
|
+
level: number;
|
|
146
|
+
color: string;
|
|
147
|
+
}
|
|
148
|
+
/** Ответ `/route/taxi/best` (уже разобранный JSON) — то, из чего строится трасса. */
|
|
149
|
+
interface NavBestRoute {
|
|
150
|
+
engine?: string;
|
|
151
|
+
geometryGeoJson: string;
|
|
152
|
+
distanceMeters?: number;
|
|
153
|
+
durationSeconds?: number;
|
|
154
|
+
durationFreeflowSeconds?: number;
|
|
155
|
+
steps?: Omit<NavStep, 'at' | 'end'>[];
|
|
156
|
+
/**
|
|
157
|
+
* Пробки по трассе: с точки `at` геометрии (номер в `coordinates`) — фактор задержки `factor`
|
|
158
|
+
* (время с пробками / свободное). Нет — пробок по участкам нет, полоса считается по шагам.
|
|
159
|
+
*/
|
|
160
|
+
trafficSpans?: {
|
|
161
|
+
at: number;
|
|
162
|
+
factor: number;
|
|
163
|
+
}[];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Загруженность — как у навигатора `navigator-main` (`congestionLevel` в `routing/RouterTraffic.kt`,
|
|
167
|
+
* цвета — `map/RouteMap.kt`): уровень 0…4 — сколько порогов `NAV_TRAFFIC_LEVELS` перешагнул фактор
|
|
168
|
+
* задержки; 0 — зелёный, 1 — жёлтый, 2 — оранжевый, 3 и 4 — красный. Те же цвета у линии маршрута
|
|
169
|
+
* (`route-line.ts`, `ROUTE_TRAFFIC`) и у полосы загруженности на панели — одна дорога, один цвет.
|
|
170
|
+
*/
|
|
171
|
+
declare const NAV_TRAFFIC_LEVELS: number[];
|
|
172
|
+
declare const NAV_TRAFFIC_COLORS: string[];
|
|
173
|
+
/** Уровень загруженности 0…4 по фактору задержки (не число — 0). */
|
|
174
|
+
declare function navTrafficLevel(factor: number): number;
|
|
175
|
+
/**
|
|
176
|
+
* Трасса с манёврами.
|
|
177
|
+
*
|
|
178
|
+
* Важное про шаги: у OSRM манёвр шага стоит В НАЧАЛЕ шага, а `distance` — длина дороги ПОСЛЕ него,
|
|
179
|
+
* до следующего манёвра. Поэтому «at» (накопленная длина до начала шага) — это точка, где манёвр
|
|
180
|
+
* выполняется, и пока едем по шагу i, впереди нас ждёт манёвр шага i+1. Если перепутать, карточка
|
|
181
|
+
* и голос будут называть уже пройденный поворот.
|
|
182
|
+
*/
|
|
183
|
+
declare class NavRoute {
|
|
184
|
+
readonly engine: string;
|
|
185
|
+
readonly coords: LonLat$3[];
|
|
186
|
+
readonly distanceM: number;
|
|
187
|
+
readonly etaSec: number;
|
|
188
|
+
readonly freeflowSec: number;
|
|
189
|
+
readonly lat0: number;
|
|
190
|
+
readonly cum: number[];
|
|
191
|
+
readonly total: number;
|
|
192
|
+
readonly steps: NavStep[];
|
|
193
|
+
/** Пробки по участкам геометрии (`at` — номер точки `coords`), по возрастанию `at`; пусто — нет. */
|
|
194
|
+
readonly trafficSpans: {
|
|
195
|
+
at: number;
|
|
196
|
+
factor: number;
|
|
197
|
+
}[];
|
|
198
|
+
constructor(best: NavBestRoute);
|
|
199
|
+
/** Индекс вершины перед точкой d (двоичным поиском — трасса может быть в тысячи вершин). */
|
|
200
|
+
private seg;
|
|
201
|
+
positionAt(d: number): LonLat$3;
|
|
202
|
+
/** Курс с упреждением: камера должна поворачивать вместе с машиной, а не после неё. */
|
|
203
|
+
headingAt(d: number, aheadM?: number): number;
|
|
204
|
+
/**
|
|
205
|
+
* Разбивка трассы по загруженности — для полосы маршрута. Есть пробки по участкам
|
|
206
|
+
* (`trafficSpans`) — по ним, метрами геометрии: ровно то, чем раскрашена линия. Нет — по шагам:
|
|
207
|
+
* темп шага (с/м) против среднего темпа этого же маршрута БЕЗ пробок. Пороги и цвета —
|
|
208
|
+
* `NAV_TRAFFIC_LEVELS`/`NAV_TRAFFIC_COLORS`.
|
|
209
|
+
*/
|
|
210
|
+
trafficBands(): NavTrafficBand[];
|
|
211
|
+
/** Ближайший ещё не выполненный манёвр. */
|
|
212
|
+
upcoming(d: number): NavUpcoming | null;
|
|
213
|
+
/** Пройденная часть трассы — для «съеденной» линии под машиной. */
|
|
214
|
+
passed(d: number): LonLat$3[];
|
|
215
|
+
/**
|
|
216
|
+
* Проекция точки на трассу: сколько проехали и на сколько метров ушли в сторону. Рядом с известной
|
|
217
|
+
* позицией смотрим только окрестность — иначе на маршруте-петле точку притянет к другому концу.
|
|
218
|
+
*/
|
|
219
|
+
snap(p: LonLat$3, nearD: number | null): {
|
|
220
|
+
dist: number;
|
|
221
|
+
off: number;
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
type NavLang = 'ru' | 'tg' | 'uz' | 'en';
|
|
225
|
+
declare const LANG_TAG: Record<NavLang, string>;
|
|
226
|
+
/** Текст инструкции шага на языке озвучки; нет перевода — русский. */
|
|
227
|
+
declare function navStepText(s: NavStep | null, lang: string): string;
|
|
228
|
+
/** Инструкция с расстоянием, по правилам каждого языка. */
|
|
229
|
+
declare function navPhrase(lang: string, text: string, dist: number): string;
|
|
230
|
+
declare function navSpeeding(lang: string, limit: number): string;
|
|
231
|
+
type NavSayKey = 'rerouting' | 'rerouted' | 'arrived' | 'started';
|
|
232
|
+
declare function navSay(lang: string, key: string): string;
|
|
233
|
+
/**
|
|
234
|
+
* Прежняя схема порогов (по длине перегона) и округления. `ManeuverAnnouncer` ими больше не
|
|
235
|
+
* пользуется — расписание теперь по скорости, как у Organic Maps (`nav-voice.ts`); функции оставлены
|
|
236
|
+
* для внешних вызовов и их фикстур.
|
|
237
|
+
*/
|
|
238
|
+
declare function farThreshold(legM: number): number;
|
|
239
|
+
/** Прежнее округление дистанции (см. `farThreshold`); озвучка теперь округляет `navRoundedForSpeech`. */
|
|
240
|
+
declare function roundDist(m: number): number;
|
|
241
|
+
/** Что сказать: срочное (`urgent`) говорится сразу, обрывая предыдущее; обычное — в очередь за текущим. */
|
|
242
|
+
interface NavUtterance {
|
|
243
|
+
text: string;
|
|
244
|
+
urgent: boolean;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Голосовое сопровождение манёвров: само решает, что и когда сказать; повторов нет. Говорит не само —
|
|
248
|
+
* возвращает фразы (`say`), а синтез (браузерный, Android TTS, AVSpeech) — у приложения.
|
|
249
|
+
*
|
|
250
|
+
* Расписание — план трассы `navVoicePlan` (`nav-voice.ts`, как у навигатора `navigator-main` и Organic
|
|
251
|
+
* Maps): предупреждение с дистанцией на пороге по скорости шага и команда без дистанции на «запасе».
|
|
252
|
+
* Предупреждение идёт в очередь (не обрывает «Маршрут построен» на старте), команда — срочная.
|
|
253
|
+
* Проехали порог предупреждения дальше `NAV_VOICE.FRESH_M` (пересчёт, скачок) — дистанция
|
|
254
|
+
* пересчитывается от того, что осталось; осталось меньше разрыва до команды — предупреждения нет.
|
|
255
|
+
*/
|
|
256
|
+
declare class ManeuverAnnouncer {
|
|
257
|
+
lang: string;
|
|
258
|
+
private spoken;
|
|
259
|
+
private plan;
|
|
260
|
+
private planFor;
|
|
261
|
+
private planLang;
|
|
262
|
+
constructor(lang?: string);
|
|
263
|
+
reset(): void;
|
|
264
|
+
/** План объявлений трассы на текущем языке (кэш по трассе и языку). */
|
|
265
|
+
planOf(route: NavRoute, textOf: (s: NavStep | null) => string): NavVoiceCue[];
|
|
266
|
+
update(route: NavRoute, dist: number, textOf: (s: NavStep | null) => string): {
|
|
267
|
+
upcoming: NavUpcoming | null;
|
|
268
|
+
say: NavUtterance[];
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/** Значения — по ПДД Таджикистана: в городе 60, в жилой зоне 20, вне города 90, на магистрали 110. */
|
|
272
|
+
declare const CLASS_LIMIT: Record<string, number>;
|
|
273
|
+
declare function parseMaxspeed(v: unknown): number;
|
|
274
|
+
interface SpeedLimit {
|
|
275
|
+
kmh: number;
|
|
276
|
+
source: 'osm' | 'class';
|
|
277
|
+
cls?: string;
|
|
278
|
+
urban?: boolean;
|
|
279
|
+
}
|
|
280
|
+
/** Найденная запросом карты фича — ровно то, что ядру нужно знать о ней. */
|
|
281
|
+
interface NavFeature {
|
|
282
|
+
sourceLayer?: string;
|
|
283
|
+
properties?: Record<string, unknown> | null;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Ограничение по фичам под машиной — чистая часть `speedLimitAt` веба (запрос карты — у приложения).
|
|
287
|
+
*
|
|
288
|
+
* `road` — фичи запроса ТОЧКОЙ с растущим допуском (3, 7, 12 px), подряд: первая проезжая дорога и
|
|
289
|
+
* есть та, по которой едем. `around` — фичи вокруг (допуск 12 px): по ним решается «в городе ли».
|
|
290
|
+
* Ничего не нашли — `null`, вызывающий оставит прежнее значение, чтобы знак не мигал.
|
|
291
|
+
*/
|
|
292
|
+
declare function speedLimitFromFeatures(road: NavFeature[], around: NavFeature[], exact?: boolean): SpeedLimit | null;
|
|
293
|
+
/**
|
|
294
|
+
* ЗНАК НАВИГАТОРА БЕЗ МИГАНИЯ: новое значение ограничения принимается, только когда оно держится
|
|
295
|
+
* `holdMs` подряд (по всем запросам за это время). Короткий кусок другого класса на стыке, перекрёсток,
|
|
296
|
+
* съезд не перекрашивают знак туда-обратно. Первое значение — сразу; «не нашли» (`null`) знак не трогает.
|
|
297
|
+
* Время — явным `now` (мс).
|
|
298
|
+
*/
|
|
299
|
+
declare const SPEED_LIMIT_HOLD_MS = 4000;
|
|
300
|
+
declare class SpeedLimitHold {
|
|
301
|
+
readonly holdMs: number;
|
|
302
|
+
value: SpeedLimit | null;
|
|
303
|
+
private candidate;
|
|
304
|
+
private since;
|
|
305
|
+
constructor(holdMs?: number);
|
|
306
|
+
update(limit: SpeedLimit | null, now: number): SpeedLimit | null;
|
|
307
|
+
reset(): void;
|
|
308
|
+
}
|
|
309
|
+
/** Допуски запроса точкой, по которым приложение собирает `road` для `speedLimitFromFeatures`. */
|
|
310
|
+
declare const SPEED_QUERY_TOLERANCES: number[];
|
|
311
|
+
declare const SPEED_AROUND_TOLERANCE = 12;
|
|
312
|
+
interface SpeedVerdict {
|
|
313
|
+
over: boolean;
|
|
314
|
+
started: boolean;
|
|
315
|
+
shouldSpeak: boolean;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Контроль скорости: короткий выброс GPS не включает сирену, непрерывное превышение — включает и
|
|
319
|
+
* повторяется, пока не сбросят. Время — явным `now` (мс), ноль зарезервирован под «не начато».
|
|
320
|
+
*/
|
|
321
|
+
declare class SpeedWatch {
|
|
322
|
+
over: boolean;
|
|
323
|
+
private since;
|
|
324
|
+
private lastSaid;
|
|
325
|
+
private o;
|
|
326
|
+
constructor(o?: Partial<{
|
|
327
|
+
toleranceKmh: number;
|
|
328
|
+
holdMs: number;
|
|
329
|
+
repeatMs: number;
|
|
330
|
+
}>);
|
|
331
|
+
update(speedKmh: number, limitKmh: number, now: number): SpeedVerdict;
|
|
332
|
+
reset(): void;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Ушли от трассы дальше 45 м дольше 5 с — пора перестраивать от текущего места. `update` получает
|
|
336
|
+
* отступ от `snap` на каждом фиксе GPS; `true` — перестраивать (счётчик при этом сброшен). Звать
|
|
337
|
+
* только пока есть точка назначения: без неё навигатор страницы водителя таймер не трогал.
|
|
338
|
+
*/
|
|
339
|
+
declare class OffRouteWatch {
|
|
340
|
+
readonly thresholdM: number;
|
|
341
|
+
readonly holdMs: number;
|
|
342
|
+
private since;
|
|
343
|
+
constructor(thresholdM?: number, holdMs?: number);
|
|
344
|
+
update(offM: number, now: number): boolean;
|
|
345
|
+
reset(): void;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Прежний постоянный наклон ведения. Камера навигатора теперь берёт наклон по зуму
|
|
349
|
+
* (`navTiltForZoom`); константа осталась для тех, кто ставит камеру сам.
|
|
350
|
+
*/
|
|
351
|
+
declare const NAV_PITCH = 62;
|
|
352
|
+
/** Отступ сверху при слежении: машина — в нижней части экрана, впереди видно больше дороги. */
|
|
353
|
+
declare const NAV_TOP_PAD = 0.42;
|
|
354
|
+
/** Масштаб по скорости — как в автомобильных навигаторах (км/ч → зум). */
|
|
355
|
+
declare const ZOOM_BY_SPEED: [number, number][];
|
|
356
|
+
/** Тронул карту — слежение выключается и возвращается само через столько миллисекунд. */
|
|
357
|
+
declare const FOLLOW_BACK_MS = 12000;
|
|
358
|
+
/** Шаг кадра больше этого не бывает: после паузы вкладки машина не должна прыгать. */
|
|
359
|
+
declare const NAV_MAX_DT = 0.1;
|
|
360
|
+
/** Сколько метров до финиша считается «прибыли». */
|
|
361
|
+
declare const NAV_ARRIVE_M = 25;
|
|
362
|
+
/** Целевой масштаб по скорости; манёвр ближе 150 м — приближаем ещё немного (но не ближе z18.6). */
|
|
363
|
+
declare function navTargetZoom(speedKmh: number, toManeuverM: number | null): number;
|
|
364
|
+
/**
|
|
365
|
+
* Наклон камеры по зуму — как у навигатора `navigator-main` (`map/NavPitch.kt`), а тот — кривая
|
|
366
|
+
* Organic Maps `ScreenBase::CalculateAutoPerspectiveAngle` (Apache-2.0). Масштаб у Organic Maps —
|
|
367
|
+
* градусы долготы на пиксель; зум osmgl, как у MapLibre, — мир в 512 px. Дальше z15.3 карта плоская,
|
|
368
|
+
* к z17.8 наклон растёт до 45°, к z19.1 — до 55°, ближе — 54.45°. На зумах ведения (16…18.6) это
|
|
369
|
+
* 28…52°: карта заваливается, когда приближается к манёвру, и выпрямляется на скорости.
|
|
370
|
+
*/
|
|
371
|
+
declare const NAV_TILT: {
|
|
372
|
+
readonly START_SCALE: 0.000017;
|
|
373
|
+
readonly MIDDLE_SCALE: 0.000003;
|
|
374
|
+
readonly END_SCALE: 0.0000013;
|
|
375
|
+
readonly FIRST: 45;
|
|
376
|
+
readonly MAX: 55;
|
|
377
|
+
};
|
|
378
|
+
declare function navTiltForZoom(zoom: number): number;
|
|
379
|
+
/**
|
|
380
|
+
* Зазор между машиной и верхом нижней панели приложения, доля ВСЕЙ высоты карты. Доля, а не пиксели:
|
|
381
|
+
* сдвиг линеен по единицам (`navCameraShiftPx(px) / density` = сдвиг в dp), и постоянный зазор в
|
|
382
|
+
* пикселях это бы сломал. 6 % — центр машины на ≈48 dp выше панели у телефона в портрете (800 dp)
|
|
383
|
+
* и на ≈25 dp в альбоме (411 dp): значок машины (≈40 dp) панель не задевает.
|
|
384
|
+
*/
|
|
385
|
+
declare const NAV_CAR_CLEAR = 0.06;
|
|
386
|
+
/** Меньше этой доли высоты видимая часть не бывает (`navInsets`). */
|
|
387
|
+
declare const NAV_MIN_VISIBLE = 0.2;
|
|
388
|
+
/** Строка экрана (от верха карты), на которой стоит машина ведения; см. `navCameraShiftPx`. */
|
|
389
|
+
declare function navCarScreenY(heightPx: number, topPx?: number, bottomPx?: number): number;
|
|
390
|
+
/**
|
|
391
|
+
* На сколько пикселей машина ниже центра карты высотой `heightPx`. Без отступов — доля всей высоты
|
|
392
|
+
* (отступ сверху 42 %, машина на 0.71 высоты). С отступами панелей (`topPx`, `bottomPx` — в тех же
|
|
393
|
+
* единицах) машина остаётся в видимой части: не ниже `NAV_CAR_CLEAR` над нижней панелью и не выше
|
|
394
|
+
* середины видимого (`navCarScreenY`). Портрет с обычной панелью поездки от этого не меняется
|
|
395
|
+
* (машина и так выше панели), альбом — меняется: панель там в нижней четверти и закрывала машину.
|
|
396
|
+
* Камера ставится так: центр — на машину, `q = unproject(w/2, h/2 + dy)`, затем центр = 2·машина − q
|
|
397
|
+
* (`navCameraCenter`) — при тех же зуме, наклоне и повороте это ровно переносит машину в q.
|
|
398
|
+
*/
|
|
399
|
+
declare function navCameraShiftPx(heightPx: number, topPx?: number, bottomPx?: number): number;
|
|
400
|
+
/** Синоним `navCameraShiftPx` — имя из первого плана переноса. */
|
|
401
|
+
declare const cameraCenterShift: typeof navCameraShiftPx;
|
|
402
|
+
/** Центр камеры, при котором машина `pos` окажется там, где сейчас точка `q` (см. `navCameraShiftPx`). */
|
|
403
|
+
declare function navCameraCenter(pos: LonLat$3, q: LonLat$3): LonLat$3;
|
|
404
|
+
/**
|
|
405
|
+
* РЕЖИМ КАМЕРЫ ВЕДЕНИЯ (26.10.2026, просьба владельца «машина стоит на месте, двигается карта — как в Яндексе»).
|
|
406
|
+
* • `smooth` — как было и по умолчанию: камера идёт за ЛИНИЕЙ маршрута своим сглаженным курсом с упреждением
|
|
407
|
+
* (`NavViewCamera`, доворот к выходу из поворота до `PRE_ROTATE_MAX`). Машина на экране поворачивается на
|
|
408
|
+
* поворотах, на перестроении переезжает в соседнюю полосу (`laneCarPose`), а её экранная точка немного гуляет
|
|
409
|
+
* со сменой наклона (сдвиг вперёд у приложений приближённый — `navCameraAhead`/`navCameraPosition`).
|
|
410
|
+
* • `locked` — «машина на месте»: опора камеры — ПОЗА МАШИНЫ ЭТОГО КАДРА (та же точка и тот же курс, что
|
|
411
|
+
* нарисованы, вместе со сдвигом в полосу ленты). Машина стоит в точке якоря (`navCarScreenY`, с учётом панелей)
|
|
412
|
+
* носом строго вверх; повороты, перестроения и съезд в полосу отрабатывает карта. Зум и наклон — те же, что в
|
|
413
|
+
* `smooth` (`navViewTarget` + `NavViewCamera`): кадр дороги по-прежнему растёт и сжимается у манёвра.
|
|
414
|
+
* ★ ВТОРОГО СГЛАЖИВАНИЯ НЕТ. Поза машины уже сглажена `NavTracker`ом (счисление, подтяжка, курс τ 0,3 с), и камера
|
|
415
|
+
* берёт РОВНО её, в том же кадре: своё сглаживание поверх отставало бы от машины, и машина гуляла бы по экрану, а
|
|
416
|
+
* привязка к сырому фиксу дала бы дрожь.
|
|
417
|
+
* • `north` — «север вверх» (28.10.2026, `FollowNorthUp` камеры TomTom, компас в ведении Google Maps): карта не
|
|
418
|
+
* вращается и лежит плашмя, машина — в ЦЕНТРЕ экрана (курс у неё любой, и якорь внизу увёл бы её под край, когда
|
|
419
|
+
* едем на юг), стрелка машины поворачивается сама. Опора — поза машины, как в `locked`; зум — тот же.
|
|
420
|
+
*/
|
|
421
|
+
type NavFollowMode = 'smooth' | 'locked' | 'north';
|
|
422
|
+
/**
|
|
423
|
+
* Режим из настройки или ключа запуска: `locked` (`1`, `true`, `on`) — «машина на месте», `north` (`north-up`,
|
|
424
|
+
* `northup`) — «север вверх», остальное — `smooth`.
|
|
425
|
+
*/
|
|
426
|
+
declare function navFollowModeOf(v: unknown): NavFollowMode;
|
|
427
|
+
/** Опора камеры ведения: точка, которую камера ставит в якорь, и курс камеры. */
|
|
428
|
+
interface NavAim {
|
|
429
|
+
pos: LonLat$3;
|
|
430
|
+
bearing: number;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Опора камеры в кадре: `smooth` — точка линии маршрута и свой (сглаженный) курс камеры, как было; `locked` — поза
|
|
434
|
+
* машины, нарисованной в этом кадре. Нечисловая поза машины в `locked` — опора `smooth` (камера не уходит в NaN).
|
|
435
|
+
*/
|
|
436
|
+
declare function navFollowAim(mode: NavFollowMode, line: NavAim, car: NavAim): NavAim;
|
|
437
|
+
/** Наклон камеры ведения в режиме `mode`: `north` — плашмя (0), иначе — `pitch` как есть. */
|
|
438
|
+
declare function navFollowPitch(mode: NavFollowMode, pitch: number): number;
|
|
439
|
+
/** Сдвиг машины ниже середины карты в режиме `mode`: `north` — 0 (машина в центре), иначе — `shiftPx` как есть. */
|
|
440
|
+
declare function navFollowShiftPx(mode: NavFollowMode, shiftPx: number): number;
|
|
441
|
+
/**
|
|
442
|
+
* Центр камеры «машина на месте»: при зуме `zoom`, наклоне `pitchDeg` и курсе `bearing` машина `car` встаёт ровно на
|
|
443
|
+
* `shiftPx` ниже середины карты высотой `heightPx` (те же единицы, что у `navCameraShiftPx`: CSS px / dp), то есть в
|
|
444
|
+
* точку якоря. ТОЧНО по проекции движка (`Transform`: камера на `0,5·h/tan(FOV/2)` от центра, угол `NAV_VIEW.FOV`,
|
|
445
|
+
* плоская земля), а не приближённо (`navCameraAhead` приложений: `сдвиг·м/px / cos(наклон)` расходится с проекцией на
|
|
446
|
+
* 15–20 px при наклоне 30–50°, и машина гуляла бы по экрану со сменой наклона у манёвра). Счёт — в мировых пикселях
|
|
447
|
+
* меркатора, в которых движок и проецирует. Веб ставит якорь обратной проекцией карты (`navCameraCenter`) — это то же.
|
|
448
|
+
* Нечисловые или неположительные `shiftPx`/`heightPx` — без сдвига (центр — машина).
|
|
449
|
+
*/
|
|
450
|
+
declare function navLockedCenter(car: LonLat$3, bearing: number, zoom: number, pitchDeg: number, shiftPx: number, heightPx: number): LonLat$3;
|
|
451
|
+
/**
|
|
452
|
+
* КАМЕРА ВЕДЕНИЯ КАДРИРУЕТ ДОРОГУ, А НЕ ДЕРЖИТ ЗУМ ПО СКОРОСТИ.
|
|
453
|
+
*
|
|
454
|
+
* Образец — камера ведения Mapbox Navigation SDK (`FollowingFrameOptions`: кадр по геометрии до
|
|
455
|
+
* манёвра и чуть после, `pitchNearManeuvers`, сглаживание курса по трассе впереди); так же ведут
|
|
456
|
+
* себя Google Maps и Яндекс.Навигатор:
|
|
457
|
+
* • ЗУМ — такой, чтобы от машины до точки `FRAME_AFTER_M` за поворотом всё влезло в экран выше
|
|
458
|
+
* машины; поворот издалека виден целиком, вблизи камера приближается, а за поворотом кадр снова
|
|
459
|
+
* растёт с тех же метров (без рывка зума на выходе). На прямой кадр смотрит
|
|
460
|
+
* вперёд на скорость × `LOOK_S` (не ближе `LOOK_MIN_M`, не дальше `LOOK_MAX_M`) — быстрее едешь,
|
|
461
|
+
* дальше видишь. Это заменяет таблицу «зум по скорости»: она не знала, где поворот;
|
|
462
|
+
* • НАКЛОН — на прямой `CRUISE_PITCH` (дорога уходит далеко), в зоне поворота плавно к
|
|
463
|
+
* `TURN_PITCH`: сверху круче, и рисунок перекрёстка читается, а не прячется за домами;
|
|
464
|
+
* • КУРС — по трассе с упреждением, а в зоне поворота заранее доворачивается к выходу из него, но
|
|
465
|
+
* не больше `PRE_ROTATE_MAX` от курса машины: дорога, на которую сворачиваем, видна до поворота,
|
|
466
|
+
* а машина не встаёт поперёк экрана.
|
|
467
|
+
* Зона поворота — `TURN_ZONE_S` секунд хода до манёвра (`TURN_ZONE_MIN_M`…`TURN_ZONE_MAX_M`), вход и
|
|
468
|
+
* выход плавные (smoothstep), после манёвра режим держится `AFTER_M` и отпускается за `RELEASE_M`.
|
|
469
|
+
* Манёвры «прямо» (выезд, продолжение, смена имени) поворотом не считаются.
|
|
470
|
+
*/
|
|
471
|
+
declare const NAV_VIEW: {
|
|
472
|
+
readonly CRUISE_PITCH: 50;
|
|
473
|
+
readonly TURN_PITCH: 30;
|
|
474
|
+
readonly TURN_ZONE_S: 7;
|
|
475
|
+
readonly TURN_ZONE_MIN_M: 90;
|
|
476
|
+
readonly TURN_ZONE_MAX_M: 260;
|
|
477
|
+
/** Длина плавного входа в зону поворота, м. */
|
|
478
|
+
readonly TURN_RAMP_M: 60;
|
|
479
|
+
readonly AFTER_M: 35;
|
|
480
|
+
readonly RELEASE_M: 40;
|
|
481
|
+
readonly FRAME_AFTER_M: 40;
|
|
482
|
+
readonly LOOK_S: 20;
|
|
483
|
+
readonly LOOK_MIN_M: 200;
|
|
484
|
+
readonly LOOK_MAX_M: 900;
|
|
485
|
+
readonly ZOOM_MIN: 15.6;
|
|
486
|
+
readonly ZOOM_MAX: 18.8;
|
|
487
|
+
readonly PRE_ROTATE_MAX: 30;
|
|
488
|
+
/** Над точкой кадра остаётся столько экрана (доля высоты) — край не упирается в панель. */
|
|
489
|
+
readonly TOP_MARGIN: 0.14;
|
|
490
|
+
/** Угол обзора камеры движка по вертикали, градусы (`Transform._fov`). */
|
|
491
|
+
readonly FOV: 36.87;
|
|
492
|
+
/** Постоянные сглаживания, с. */
|
|
493
|
+
readonly ZOOM_TAU: 1.2;
|
|
494
|
+
readonly PITCH_TAU: 0.9;
|
|
495
|
+
readonly BEARING_TAU: 0.45;
|
|
496
|
+
};
|
|
497
|
+
/** Цель камеры ведения и доля «режима поворота» (0 — прямая, 1 — у самого манёвра). */
|
|
498
|
+
interface NavViewTarget {
|
|
499
|
+
zoom: number;
|
|
500
|
+
pitch: number;
|
|
501
|
+
bearing: number;
|
|
502
|
+
turn: number;
|
|
503
|
+
}
|
|
504
|
+
/** Манёвр, при котором камера готовит поворот: всё, кроме движения прямо. */
|
|
505
|
+
declare function navIsTurn(step: NavStep | null | undefined): boolean;
|
|
506
|
+
/** Доля режима поворота на метре `d` трассы при скорости `speedMps`. */
|
|
507
|
+
declare function navTurnWeight(route: NavRoute, d: number, speedMps: number): number;
|
|
508
|
+
/**
|
|
509
|
+
* Сколько метров земли видно от машины до точки на доле `TOP_MARGIN` ниже верха видимой части, на
|
|
510
|
+
* пиксель масштаба (то есть в пикселях мира). Плоская земля, камера движка: машина ниже центра на
|
|
511
|
+
* `navCameraShiftPx(h, topPx, bottomPx)`, угол обзора `FOV`, наклон `pitchDeg`. Верхняя точка кадра —
|
|
512
|
+
* на `TOP_MARGIN` высоты ниже верхнего отступа, но не ниже середины между ним и машиной: иначе при
|
|
513
|
+
* больших панелях она оказалась бы под машиной.
|
|
514
|
+
*/
|
|
515
|
+
declare function navAheadPx(heightPx: number, pitchDeg: number, topPx?: number, bottomPx?: number): number;
|
|
516
|
+
/**
|
|
517
|
+
* Зум, при котором `meters` впереди машины влезают в экран высотой `heightPx` на широте `lat`
|
|
518
|
+
* (`topPx`/`bottomPx` — отступы панелей, см. `navCameraShiftPx`).
|
|
519
|
+
*/
|
|
520
|
+
declare function navZoomForAhead(meters: number, heightPx: number, pitchDeg: number, lat: number, topPx?: number, bottomPx?: number): number;
|
|
521
|
+
/**
|
|
522
|
+
* Цель камеры на метре `d`: зум, наклон и курс (см. `NAV_VIEW`). `heightPx` — высота карты в
|
|
523
|
+
* пикселях масштаба (CSS px / dp), `carBearing` — курс машины по трассе, `topPx`/`bottomPx` —
|
|
524
|
+
* отступы панелей в тех же единицах (те же, что у `navCameraShiftPx` этого кадра).
|
|
525
|
+
*/
|
|
526
|
+
declare function navViewTarget(route: NavRoute, d: number, speedMps: number, heightPx: number, carBearing: number, topPx?: number, bottomPx?: number): NavViewTarget;
|
|
527
|
+
/**
|
|
528
|
+
* ЗУМ ВЕДЕНИЯ С ЗАЛИПАНИЕМ (26.10.2026; натив — `NAV_ZOOM_STICK`, `native/src/navigram/nav_follow.h`, Kotlin —
|
|
529
|
+
* `NavZoomStick.kt`). Зум ведения идёт к цели экспонентой (τ = 1,2 с), и хвост экспоненты — сдвиги в тысячные
|
|
530
|
+
* уровня — будил петлю ведения и перекладывал подписи ещё секунды после того, как глаз перестал что-либо видеть
|
|
531
|
+
* (у NaviGram — после закрытия поездки, у веба — после прибытия и в ровной езде). Изменение меньше порога кадр не
|
|
532
|
+
* показывает (масштаб 0,07 %, у края экрана — доли пикселя); переход зума у манёвра (сотые за кадр) идёт как шёл.
|
|
533
|
+
*/
|
|
534
|
+
declare const NAV_ZOOM_STICK: number;
|
|
535
|
+
/** Показанный зум ведения с залипанием (`NavFollow::stickZoom` натива). */
|
|
536
|
+
declare class NavZoomStick {
|
|
537
|
+
/** Показанный зум; NaN — ещё не было. */
|
|
538
|
+
shown: number;
|
|
539
|
+
/** Зум, который показать кадру: прежний, пока просимый отличается от него меньше чем на `NAV_ZOOM_STICK`. */
|
|
540
|
+
stick(zoom: number): number;
|
|
541
|
+
/** Ведение кончилось (или водитель тронул карту): следующий показанный зум — как есть. */
|
|
542
|
+
reset(): void;
|
|
543
|
+
}
|
|
544
|
+
/** Сглаженная камера ведения: зум, наклон и курс плавно идут к цели `navViewTarget`. */
|
|
545
|
+
declare class NavViewCamera {
|
|
546
|
+
zoom: number | null;
|
|
547
|
+
pitch: number | null;
|
|
548
|
+
bearing: number | null;
|
|
549
|
+
step(dt: number, t: NavViewTarget): {
|
|
550
|
+
zoom: number;
|
|
551
|
+
pitch: number;
|
|
552
|
+
bearing: number;
|
|
553
|
+
};
|
|
554
|
+
reset(): void;
|
|
555
|
+
}
|
|
556
|
+
/** Цель кадра: что знает поездка (GPS или демо) в этот момент. */
|
|
557
|
+
interface NavTrip {
|
|
558
|
+
route: NavRoute | null;
|
|
559
|
+
/** Метр трассы последнего фикса (после `snap`). */
|
|
560
|
+
tripDist: number;
|
|
561
|
+
speedKmh: number;
|
|
562
|
+
/** Сырая точка GPS — нужна, только пока маршрута нет. */
|
|
563
|
+
tripPos: LonLat$3 | null;
|
|
564
|
+
tripBrg: number | null;
|
|
565
|
+
/**
|
|
566
|
+
* Сколько секунд назад снят фикс `tripDist` (с учётом задержки доставки). Метр фикса упреждаем на
|
|
567
|
+
* скорость × возраст (полностью — до `NAV_PREDICT_MAX`, дальше вера гаснет), иначе машина вечно
|
|
568
|
+
* «догоняет» прошлое и её дёргает назад к каждому новому фиксу. Нет — 0 (демо, старые вызовы).
|
|
569
|
+
*/
|
|
570
|
+
fixAgeSec?: number;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Горизонт упреждения: столько секунд после фикса машина едет с его скоростью в полную веру. Дальше
|
|
574
|
+
* вера гаснет с постоянной `NAV_COAST_TAU` — GPS пропал, и машина плавно докатывается, а не уезжает.
|
|
575
|
+
*/
|
|
576
|
+
declare const NAV_PREDICT_MAX = 2;
|
|
577
|
+
/** Постоянная гашения веры в скорость за горизонтом: без фиксов машина проезжает не больше скорость × 2.5 с. */
|
|
578
|
+
declare const NAV_COAST_TAU = 0.5;
|
|
579
|
+
/**
|
|
580
|
+
* Фильтр скорости фикса — несимметричный: растущей верим за `NAV_SPEED_RISE_TAU`, падающей — почти
|
|
581
|
+
* сразу. Обгон машины не исправить (назад не едем), отставание подтяжка исправит сама.
|
|
582
|
+
*/
|
|
583
|
+
declare const NAV_SPEED_RISE_TAU = 1;
|
|
584
|
+
declare const NAV_SPEED_FALL_TAU = 0.2;
|
|
585
|
+
/** Постоянные подтяжки к цели (метр) и поворота курса, с. */
|
|
586
|
+
declare const NAV_PULL_TAU = 0.45;
|
|
587
|
+
/**
|
|
588
|
+
* Подтяжка метра машины к упреждённому фиксу на маршруте, с (17.10.2026: было `NAV_PULL_TAU` = 0.45). Фикс приходит раз
|
|
589
|
+
* в секунду с ошибкой вдоль трассы 1–3 м; при 0.45 с почти вся поправка выбиралась за первые полсекунды после фикса, и
|
|
590
|
+
* скорость машины, а с ней и камеры, пульсировала раз в секунду (модель: шаг кадра против среднего за 0.5 с — p99 30 %,
|
|
591
|
+
* разброс скорости 18.7 % против 10.9 % без шума; с 1.0 с — 13 % и 14.0 %). Секунда — длина анимации курсора у Mapbox
|
|
592
|
+
* Navigation (`NavigationLocationProvider`, 1000 мс по умолчанию — интервал фиксов). Скорость при этом не отстаёт:
|
|
593
|
+
* её ведёт счисление, подтяжка правит только ошибку положения. Без маршрута — прежняя `NAV_PULL_TAU`: там счисления нет,
|
|
594
|
+
* и длинная подтяжка значила бы отставание на скорость × постоянную.
|
|
595
|
+
*/
|
|
596
|
+
declare const NAV_ROUTE_PULL_TAU = 1;
|
|
597
|
+
declare const NAV_BEARING_TAU = 0.3;
|
|
598
|
+
/** Курс берётся с упреждением: камера поворачивает вместе с машиной, а не после неё. */
|
|
599
|
+
declare const NAV_LOOK_AHEAD_M = 25;
|
|
600
|
+
/** Отставание от упреждённого фикса, после которого машина может и поехать назад (разворот, перестроение). */
|
|
601
|
+
declare const NAV_BACK_M = 40;
|
|
602
|
+
/** Медленнее этого (м/с) — стоим: счисление выключено, дрожание точки машину не двигает. */
|
|
603
|
+
declare const NAV_STILL_MPS = 0.5;
|
|
604
|
+
/**
|
|
605
|
+
* Что нарисовано прямо сейчас — счисление пути навигатора `navigator-main` (`map/DeadReckoning.kt`).
|
|
606
|
+
* GPS и демо задают только цель; как до неё доехать, решает кадр:
|
|
607
|
+
* • скорость фикса сглаживается несимметрично (`NAV_SPEED_RISE_TAU`/`NAV_SPEED_FALL_TAU`): доплер
|
|
608
|
+
* шумит, и шум попадал в позицию дважды — интегрированием и упреждением; медленнее
|
|
609
|
+
* `NAV_STILL_MPS` — сразу ноль (наше дополнение: на стоянке машина не ползёт);
|
|
610
|
+
* • цель — метр фикса, упреждённый на его возраст скоростью × возраст до горизонта
|
|
611
|
+
* `NAV_PREDICT_MAX`, дальше вера гаснет (`NAV_COAST_TAU`). ТЕМ ЖЕ числом двигается и машина:
|
|
612
|
+
* раньше за горизонтом цель замирала, а машина ехала дальше, и стояла только потому, что подтяжка
|
|
613
|
+
* тянула назад, — рывок раз в секунду и отставание;
|
|
614
|
+
* • между фиксами машина едет со скоростью (× вера) и плавно подтягивается к цели (`NAV_PULL_TAU`),
|
|
615
|
+
* но назад не едет — отстали от цели, значит, притормаживаем, пока фиксы не догонят (так у Mapbox
|
|
616
|
+
* и OsmAnd). Назад — только при отставании больше `NAV_BACK_M` (развернулись, перестроили);
|
|
617
|
+
* • курс — по трассе с упреждением `NAV_LOOK_AHEAD_M` и сглаживанием `NAV_BEARING_TAU`: на повороте
|
|
618
|
+
* машина и камера поворачивают вместе по дуге, а не рывком на узле.
|
|
619
|
+
* Без маршрута тянемся прямо к точке GPS — вести по трассе нечего.
|
|
620
|
+
*/
|
|
621
|
+
declare class NavTracker {
|
|
622
|
+
dist: number | null;
|
|
623
|
+
pos: LonLat$3 | null;
|
|
624
|
+
brg: number | null;
|
|
625
|
+
zoom: number | null;
|
|
626
|
+
/** Сглаженная скорость, м/с; `null` — ещё не с чего сглаживать. */
|
|
627
|
+
speed: number | null;
|
|
628
|
+
tick(dtIn: number, trip: NavTrip): void;
|
|
629
|
+
/** Масштаб плавно идёт к цели (`navTargetZoom`); первый кадр — сразу в цель. */
|
|
630
|
+
zoomStep(dt: number, target: number): number;
|
|
631
|
+
reset(): void;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Скорость демо-поездки: обычно чуть медленнее знака, а в средней трети маршрута сознательно
|
|
635
|
+
* нарушаем — проверяется и тревога, и её снятие. Знака нет — считаем 60.
|
|
636
|
+
*/
|
|
637
|
+
declare function demoSpeedKmh(share: number, limitKmh: number): number;
|
|
638
|
+
/**
|
|
639
|
+
* ЖИВАЯ СКОРОСТЬ ДЕМО (29.10.2026, просьба владельца: «в демо скорость не меняется»). Прежняя модель (`demoSpeedKmh`)
|
|
640
|
+
* держала ровно 85 % знака и меняла скорость только со сменой знака — на улице с одним знаком демо шло минутами на одной
|
|
641
|
+
* цифре, а нарочное превышение было лишь в средней трети пути. Теперь — как у водителя:
|
|
642
|
+
* • обычный ход — `CRUISE` от знака с плавным колебанием ±`WOBBLE` по пути (волна `WOBBLE_M` метров);
|
|
643
|
+
* • каждые `SPEED_EVERY_M` метров — отрезок `SPEED_LEN_M` с нарочным превышением `SPEED_K` (проверяются плашка, голос
|
|
644
|
+
* и вибрация; `SpeedWatch` берёт превышение больше 5 км/ч дольше 2.5 с — отрезка хватает и на знаке 20);
|
|
645
|
+
* • за `TURN_M` до поворота — сброс до `TURN_KMH` (линейно по расстоянию);
|
|
646
|
+
* • разгон не круче `ACCEL`, торможение не круче `BRAKE` (м/с²) — когда известна прежняя скорость.
|
|
647
|
+
* Всё — от метра пути, без случайности: эталоны и платформы совпадают кадр в кадр.
|
|
648
|
+
*/
|
|
649
|
+
declare const NAV_DEMO: {
|
|
650
|
+
readonly CRUISE: 0.85;
|
|
651
|
+
readonly WOBBLE: 0.08;
|
|
652
|
+
readonly WOBBLE_M: 260;
|
|
653
|
+
readonly SPEED_EVERY_M: 1500;
|
|
654
|
+
readonly SPEED_FROM_M: 1100;
|
|
655
|
+
readonly SPEED_LEN_M: 350;
|
|
656
|
+
readonly SPEED_K: 1.3;
|
|
657
|
+
readonly TURN_M: 150;
|
|
658
|
+
readonly TURN_KMH: 22;
|
|
659
|
+
readonly ACCEL: 2.2;
|
|
660
|
+
readonly BRAKE: 3.2;
|
|
661
|
+
};
|
|
662
|
+
/** Желанная скорость демо в метре `dist` (без разгона и торможения); знака нет — 60. */
|
|
663
|
+
declare function demoTargetKmh(route: NavRoute, dist: number, limitKmh: number): number;
|
|
664
|
+
/**
|
|
665
|
+
* Шаг демо: новая скорость, метр и точка поездки (`dt` в секундах). `prevKmh` — скорость прошлого шага: с ней скорость
|
|
666
|
+
* идёт к желанной не круче `NAV_DEMO.ACCEL`/`BRAKE`; без неё (NaN) — сразу желанная.
|
|
667
|
+
*/
|
|
668
|
+
declare function navDemoStep(route: NavRoute, dist: number, limitKmh: number, dt: number, prevKmh?: number): {
|
|
669
|
+
speedKmh: number;
|
|
670
|
+
dist: number;
|
|
671
|
+
pos: LonLat$3;
|
|
672
|
+
};
|
|
673
|
+
/**
|
|
674
|
+
* ЗНАЧКИ МАНЁВРОВ «КАК У ORGANIC MAPS, СО СКРУГЛЕНИЕМ» (29.10.2026, просьба владельца для NaviGram): толстый ствол с
|
|
675
|
+
* изгибом по дуге и крупный треугольный наконечник, как у растровых значков OM, — но концы и углы скруглены. Внутренность
|
|
676
|
+
* SVG 24×24 (без `<svg>`): ствол — линия, наконечник — залитый многоугольник с выемкой у основания и СВОЕЙ тонкой обводкой (`stroke-width` у элемента); рисовать с
|
|
677
|
+
* `stroke-width = ARROW_ICON_STROKE`, `stroke-linecap/linejoin = round` (обводка и даёт скругление углов наконечника).
|
|
678
|
+
* Скругление — по правилам системных значков Material: радиус углов 0…4 на сетке 24 (концы ствола 1.9, углы наконечника 0.8 — острый читается лучше), внутренние
|
|
679
|
+
* углы прямые, всё нарисованное — с учётом обводки — внутри живой области 2…22 (поля по 2).
|
|
680
|
+
* Ключи и отражение — как у `ARROW_PATHS`/`maneuverArrow`: левые повороты — правые, отражённые по горизонтали.
|
|
681
|
+
*/
|
|
682
|
+
declare const ARROW_ICON_STROKE = 3.8;
|
|
683
|
+
declare const ARROW_ICONS: Record<string, string>;
|
|
684
|
+
declare const ARROW_PATHS: Record<string, string>;
|
|
685
|
+
/** Какая стрелка у манёвра: ключ `ARROW_PATHS` и отражение по горизонтали (левые повороты). */
|
|
686
|
+
declare function maneuverArrow(type?: string | null, modifier?: string | null): {
|
|
687
|
+
key: string;
|
|
688
|
+
flip: boolean;
|
|
689
|
+
};
|
|
690
|
+
/** Внутренность SVG стрелки (24×24) по type/modifier — без внешнего тега. */
|
|
691
|
+
declare function arrowPath(type?: string | null, modifier?: string | null): string;
|
|
692
|
+
declare const NAV_COLORS: {
|
|
693
|
+
ROUTE_BLUE: string;
|
|
694
|
+
/** Тёмный кант под белым кольцом: на светлом асфальте одно белое кольцо теряется. */
|
|
695
|
+
CURSOR_RIM: string;
|
|
696
|
+
CURSOR_RIM_OPACITY: number;
|
|
697
|
+
CURSOR_CONE_OPACITY: number;
|
|
698
|
+
/** Насколько виден курсор за домом — как у призрачных слоёв страницы водителя. */
|
|
699
|
+
CURSOR_GHOST: number;
|
|
700
|
+
};
|
|
701
|
+
/** Курсор в пикселях экрана — те же размеры, что у картинки курсора страницы водителя (128 px, ×0.95/2). */
|
|
702
|
+
declare const CURSOR_SCALE: number;
|
|
703
|
+
declare const CURSOR: {
|
|
704
|
+
cone: number;
|
|
705
|
+
coneHalf: number;
|
|
706
|
+
body: number;
|
|
707
|
+
ring: number;
|
|
708
|
+
rim: number;
|
|
709
|
+
arrow: number[][];
|
|
710
|
+
};
|
|
711
|
+
/**
|
|
712
|
+
* Фигуры курсора на земле: конус направления (замкнут), круг корпуса (41 точка, замкнут; он же
|
|
713
|
+
* кольцо и кант — линиями шириной `CURSOR.ring`/`CURSOR.rim` px) и стрелка (4 точки, НЕ замкнута —
|
|
714
|
+
* полигону добавить первую в конец). Размеры заданы в пикселях экрана и переводятся в метры через
|
|
715
|
+
* `metersPerPixel` (широта машины, текущий зум), поэтому курсор одного размера на любом зуме.
|
|
716
|
+
*/
|
|
717
|
+
declare function navCursorShapes(pos: LonLat$3, brgDeg: number, metersPerPixel: number): {
|
|
718
|
+
cone: LonLat$3[];
|
|
719
|
+
ring: LonLat$3[];
|
|
720
|
+
arrow: LonLat$3[];
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
type LonLat$2 = [number, number];
|
|
724
|
+
/** Фикс геолокации. `timeMs` — время самого фикса (монотонное у источника), не время приёма. */
|
|
725
|
+
interface NavFix {
|
|
726
|
+
pos: LonLat$2;
|
|
727
|
+
timeMs: number;
|
|
728
|
+
/** Радиус ошибки, м; нет — неизвестно. */
|
|
729
|
+
accuracyM?: number | null;
|
|
730
|
+
speedMps?: number | null;
|
|
731
|
+
bearingDeg?: number | null;
|
|
732
|
+
/** Источник: Android знает его точно; веб — нет (тогда судим по точности). */
|
|
733
|
+
provider?: 'gps' | 'network' | 'fused' | null;
|
|
734
|
+
/**
|
|
735
|
+
* Сколько фиксу было в момент приёма, мс (Android: `elapsedRealtimeNanos` приёма минус фикса). Нет —
|
|
736
|
+
* свежий. Время самого фикса (`timeMs`) этого не говорит: часы источника с часами приёма не сравнить.
|
|
737
|
+
*/
|
|
738
|
+
ageMs?: number | null;
|
|
739
|
+
}
|
|
740
|
+
/** Хуже этой точности фикс не берём вовсе, если GPS жив. */
|
|
741
|
+
declare const FIX_ACC_MAX = 100;
|
|
742
|
+
/** Хуже этой точности фикс не двигает машину по трассе (OsmAnd: 50 м). */
|
|
743
|
+
declare const FIX_ACC_ROUTE = 50;
|
|
744
|
+
/** Без явного источника: точность хуже — считаем фикс сетевым. */
|
|
745
|
+
declare const FIX_NET_ACC = 30;
|
|
746
|
+
/** Сколько после последнего GPS-фикса сетевые не принимаем (OsmAnd: 12 с). */
|
|
747
|
+
declare const FIX_NET_HOLD_MS = 12000;
|
|
748
|
+
/** Предел скорости между фиксами, м/с (≈216 км/ч), и запас к прошлой скорости. */
|
|
749
|
+
declare const FIX_VMAX = 60;
|
|
750
|
+
/**
|
|
751
|
+
* Сколько ОТВЕРГНУТЫХ, но согласных между собой фиксов подряд нужно, чтобы поверить: машина и правда там
|
|
752
|
+
* (туннель, долгий провал). Несогласные — разные выбросы — счёт начинают заново.
|
|
753
|
+
*/
|
|
754
|
+
declare const FIX_REJECT_RESET = 3;
|
|
755
|
+
/**
|
|
756
|
+
* Фикс старше этого при приёме (мс) — не текущее место: последняя известная точка при подписке, пачка
|
|
757
|
+
* после фона. OsmAnd считает кешированную точку устаревшей через 2 мин, мы для ведения строже: 10 с —
|
|
758
|
+
* больше любой задержки доставки живого фикса и меньше, чем машина проедет незаметно.
|
|
759
|
+
*/
|
|
760
|
+
declare const FIX_MAX_AGE_MS = 10000;
|
|
761
|
+
/**
|
|
762
|
+
* Живой поток одного источника: возраст соседних фиксов отличается не больше чем на это (мс) и не больше
|
|
763
|
+
* половины шага между ними. У пачки старых фиксов, доставленных разом, возраст убывает ровно на шаг.
|
|
764
|
+
*/
|
|
765
|
+
declare const FIX_STREAM_JITTER_MS = 1000;
|
|
766
|
+
/** Меньший возраст живого потока больше этого (мс) — это сдвиг часов источника, а не задержка доставки. */
|
|
767
|
+
declare const FIX_SKEW_MIN_MS = 2000;
|
|
768
|
+
type NavFixVerdict = 'ok' | 'stale' | 'network' | 'inaccurate' | 'worse' | 'jump';
|
|
769
|
+
/** Сетевой ли фикс: по источнику, а без него — по точности. */
|
|
770
|
+
declare function isNetworkFix(f: NavFix): boolean;
|
|
771
|
+
/** Устарел ли фикс к моменту приёма. */
|
|
772
|
+
declare const isOldFix: (f: NavFix) => boolean;
|
|
773
|
+
/**
|
|
774
|
+
* Отбор фиксов. Первый свежий фикс берём всегда (хоть какой-то лучше никакого); дальше — по правилам выше.
|
|
775
|
+
* Пока GPS не было (или он пропал дольше чем на 12 с), сетевые фиксы — единственный источник, их берём.
|
|
776
|
+
* Не по порядку и устаревшие ('stale') не берём никогда и состояние ими не трогаем.
|
|
777
|
+
*/
|
|
778
|
+
declare class NavFixFilter {
|
|
779
|
+
/** Последний принятый — приведённый к часам приёма (как `norm`). */
|
|
780
|
+
last: NavFix | null;
|
|
781
|
+
lastGpsMs: number;
|
|
782
|
+
rejects: number;
|
|
783
|
+
/** Последний отвергнутый (не 'stale', не 'network'): с ним сверяется следующий, подтверждая новое место. */
|
|
784
|
+
pending: NavFix | null;
|
|
785
|
+
/** Последний поданный фикс, приведённый к часам приёма: время плюс сдвиг часов источника, возраст без сдвига. */
|
|
786
|
+
norm: NavFix | null;
|
|
787
|
+
/** Свежие, но «не новее принятого» подряд (и по порядку между собой): `FIX_REJECT_RESET` — принятый из будущего. */
|
|
788
|
+
staleRun: number;
|
|
789
|
+
private staleLastMs;
|
|
790
|
+
/** Поток каждого источника: прошлый фикс (время и возраст), меньший возраст живого потока, сдвиг часов. */
|
|
791
|
+
private streams;
|
|
792
|
+
/** Сдвиг часов источника (мс), снятый по его живому потоку; 0 — часы согласны с часами приёма. */
|
|
793
|
+
skewMs(provider: string | null | undefined): number;
|
|
794
|
+
/**
|
|
795
|
+
* Привести фикс к часам приёма. Живой поток (возраст соседних почти не меняется) учит сдвиг часов источника —
|
|
796
|
+
* меньший возраст потока, если он больше `FIX_SKEW_MIN_MS`; сдвиг помнится и через разрывы потока. Фикс без
|
|
797
|
+
* возраста (веб) не трогаем.
|
|
798
|
+
*/
|
|
799
|
+
private normalize;
|
|
800
|
+
accept(f0: NavFix): NavFixVerdict;
|
|
801
|
+
reset(): void;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Окно поиска проекции: назад — max(80 м, точность), чтобы увидеть разворот (сам откат принимаем
|
|
805
|
+
* только подтверждённым), вперёд — 150 м плюс путь за время. Дальше назад не смотрим: на петле
|
|
806
|
+
* точку притянуло бы к другому её концу.
|
|
807
|
+
*/
|
|
808
|
+
declare const PROG_BACK_MIN = 80;
|
|
809
|
+
declare const PROG_AHEAD = 150;
|
|
810
|
+
/** Откат назад, после которого (3 фикса подряд) верим, что развернулись (Mapbox iOS). */
|
|
811
|
+
declare const PROG_BACK_CONFIRM = 3;
|
|
812
|
+
declare const PROG_BACK_M = 50;
|
|
813
|
+
/** Прыжок вперёд дальше правдоподобного — верим со второго фикса подряд. */
|
|
814
|
+
declare const PROG_JUMP_CONFIRM = 2;
|
|
815
|
+
/** Дальше этого (м) от проекции в окне — ищем по всей трассе (долгий провал, туннель, сход). */
|
|
816
|
+
declare const PROG_WIDE_M = 60;
|
|
817
|
+
/**
|
|
818
|
+
* Дальнюю проекцию (по всей трассе) берём, только если фикс лежит на самой трассе: не дальше
|
|
819
|
+
* max(этого, точности фикса), м. Иначе мы просто съехали, а у далёкого куска трассы, который проходит
|
|
820
|
+
* рядом с объездом, машине делать нечего: перестроение всё равно заберёт её оттуда.
|
|
821
|
+
*/
|
|
822
|
+
declare const PROG_WIDE_ON_M = 25;
|
|
823
|
+
/**
|
|
824
|
+
* Гистерезис выбора отрезка: проекция дальше правдоподобного пути вперёд берётся, только если она ближе
|
|
825
|
+
* к фиксу хотя бы на столько, м. На параллельных проездах, развязках и эстакадах, где трасса идёт рядом
|
|
826
|
+
* сама с собой, шум GPS в несколько метров иначе перекидывал метр на другой её кусок.
|
|
827
|
+
*/
|
|
828
|
+
declare const PROG_SWITCH_M = 10;
|
|
829
|
+
/**
|
|
830
|
+
* Ближнее окно вперёд: путь за время по скорости фикса (не меньше `PROG_NEAR_MPS`) с запасом в полтора раза,
|
|
831
|
+
* плюс точность и `PROG_NEAR_M`. Всё, что дальше, — только через гистерезис `PROG_SWITCH_M`.
|
|
832
|
+
*/
|
|
833
|
+
declare const PROG_NEAR_MPS = 3;
|
|
834
|
+
declare const PROG_NEAR_M = 15;
|
|
835
|
+
/**
|
|
836
|
+
* Грубый фикс (хуже `FIX_ACC_ROUTE`) держит метр, только пока точный был не раньше этого (мс, по времени фиксов).
|
|
837
|
+
* Дальше грубые двигают метр вперёд в ближнем окне: спутник в руке в машине бывает грубее 50 м подолгу, и метка
|
|
838
|
+
* стояла бы всю дорогу.
|
|
839
|
+
*/
|
|
840
|
+
declare const PROG_COARSE_HOLD_MS = 5000;
|
|
841
|
+
interface NavProgressOut {
|
|
842
|
+
/** Метр трассы, который принимаем. */
|
|
843
|
+
dist: number;
|
|
844
|
+
/** Сколько метров фикс от трассы (для `OffRouteWatch`); `Infinity` — фикс трассу не трогал. */
|
|
845
|
+
off: number;
|
|
846
|
+
/** Скорость вдоль трассы, м/с: из фикса, а без неё — по приросту метра. */
|
|
847
|
+
speedMps: number;
|
|
848
|
+
/** Подтверждён откат назад: развернулись — приложение перестраивает маршрут. */
|
|
849
|
+
reversed: boolean;
|
|
850
|
+
}
|
|
851
|
+
/** Ближайшая проекция точки на участок трассы [from, to] (в метрах); отступ — до точки внутри участка. */
|
|
852
|
+
declare function snapWindow(r: NavRoute, p: LonLat$2, from: number, to: number): {
|
|
853
|
+
dist: number;
|
|
854
|
+
off: number;
|
|
855
|
+
};
|
|
856
|
+
/**
|
|
857
|
+
* Прогресс по трассе. Ищем проекцию в окне вокруг принятого метра: назад `PROG_BACK_MIN`, вперёд — сперва
|
|
858
|
+
* правдоподобный путь за время, дальше до `PROG_AHEAD` — только если там ближе на `PROG_SWITCH_M`; не нашли
|
|
859
|
+
* близко (> `PROG_WIDE_M`) — по всей трассе (долгий провал), но берём её, только если фикс на ней (`PROG_WIDE_ON_M`). Дальше — монотонность: мелкий откат — дрожание, метр стоит; откат
|
|
860
|
+
* от 50 м три фикса подряд — развернулись; прыжок вперёд дальше правдоподобного — верим со второго.
|
|
861
|
+
* Фикс хуже 50 м по точности метр не двигает (только сообщает `off`).
|
|
862
|
+
*/
|
|
863
|
+
declare class NavProgress {
|
|
864
|
+
dist: number | null;
|
|
865
|
+
last: NavFix | null;
|
|
866
|
+
back: number;
|
|
867
|
+
jumps: number;
|
|
868
|
+
/** Время последнего точного фикса (не грубее `FIX_ACC_ROUTE`). */
|
|
869
|
+
accurateMs: number;
|
|
870
|
+
update(r: NavRoute, f: NavFix): NavProgressOut;
|
|
871
|
+
reset(): void;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Переезд точки к новому фиксу, мс: по интервалу между фиксами (точка едет непрерывно, а не прыгает раз в
|
|
875
|
+
* секунду), но не короче `PUCK_GLIDE_MIN_MS` и не дольше `PUCK_GLIDE_MAX_MS` (Organic Maps: стрелка не
|
|
876
|
+
* анимируется дольше 1.5 с; MapLibre `LocationAnimatorCoordinator`: длительность — интервал фиксов).
|
|
877
|
+
*/
|
|
878
|
+
declare const PUCK_GLIDE_MIN_MS = 200;
|
|
879
|
+
declare const PUCK_GLIDE_MAX_MS = 1500;
|
|
880
|
+
/** Дальше этого (м) — переносим сразу: ехать полкилометра по экрану — не сглаживание, а ложное движение. */
|
|
881
|
+
declare const PUCK_TELEPORT_M = 500;
|
|
882
|
+
/**
|
|
883
|
+
* СТОИМ — ТОЧКУ НЕ ДВИГАЕМ (22.10.2026). Спутник у неподвижного телефона дрожит на метры: Redmi Note 11 у окна —
|
|
884
|
+
* точность 8.8 м, скорость 0.0, каждую секунду новая точка в паре метров от прежней. Каждый такой фикс вёл точку, а у OM
|
|
885
|
+
* и камеру слежения (`easeTo` на время переезда ≈1 с), и неподвижная карта рисовала 25–57 кадров/с при 60–90 % ядра.
|
|
886
|
+
* Фикс со скоростью ниже `PUCK_STILL_MPS` в пределах своей точности от ПОКАЗАННОЙ точки (не ближе `PUCK_STILL_MIN_M`, не
|
|
887
|
+
* дальше `PUCK_STILL_MAX_M`) — то же место: `push` отвечает `false` и ставит `held`. Сравнение — с показанной точкой, а не
|
|
888
|
+
* с прошлым фиксом: медленный дрейф не копится незаметно, а вывозит точку, как только уйдёт за порог. Без скорости
|
|
889
|
+
* (сеть, веб) — не держим: идущего пешком не отличить от дрожания.
|
|
890
|
+
*/
|
|
891
|
+
declare const PUCK_STILL_MPS = 0.5;
|
|
892
|
+
declare const PUCK_STILL_MIN_M = 3;
|
|
893
|
+
declare const PUCK_STILL_MAX_M = 15;
|
|
894
|
+
/**
|
|
895
|
+
* Точка «я здесь» без маршрута (свободная карта у обоих приложений). Раньше приложение двигало метку на
|
|
896
|
+
* КАЖДЫЙ фикс подряд — спутниковый и сетевой вперемешку, и сетевой (Wi-Fi/сота, 20–500 м) уводил точку
|
|
897
|
+
* в сторону, а следующий спутниковый возвращал: «прыгает и сразу назад». Теперь:
|
|
898
|
+
* - фикс проходит тот же `NavFixFilter`, что и в ведении (сеть при живом GPS, выбросы, порядок, возраст);
|
|
899
|
+
* - устаревший фикс показывается, только пока не показано ничего (последняя известная точка при
|
|
900
|
+
* запуске — «Моё место» не ждёт спутника), и следующий свежий берётся без сверки с ним;
|
|
901
|
+
* - точка не переносится, а ЕДЕТ от нарисованного места к новому фиксу (`at`), прерванный переезд
|
|
902
|
+
* продолжается с того места, где точка сейчас.
|
|
903
|
+
* Время — часы приложения (мс), те же для `push` и `at`.
|
|
904
|
+
*/
|
|
905
|
+
declare class NavPuck {
|
|
906
|
+
readonly filter: NavFixFilter;
|
|
907
|
+
/** Последний показанный фикс (цель переезда); `null` — показывать нечего. */
|
|
908
|
+
fix: NavFix | null;
|
|
909
|
+
/** Показан устаревший фикс («пока ничего лучше нет»). */
|
|
910
|
+
provisional: boolean;
|
|
911
|
+
private from;
|
|
912
|
+
private t0;
|
|
913
|
+
private dur;
|
|
914
|
+
private lastPushMs;
|
|
915
|
+
/** Последний `push` ответил `false`, потому что стоим (`PUCK_STILL_MPS`), а не потому что фикс отвергнут. */
|
|
916
|
+
held: boolean;
|
|
917
|
+
/** Фикс пришёл в `nowMs`: `true` — точку надо вести к нему (новая цель), `false` — отвергнут или стоим (`held`). */
|
|
918
|
+
push(f: NavFix, nowMs: number): boolean;
|
|
919
|
+
/** Где нарисовать точку в `nowMs`; `null` — ещё нечего. */
|
|
920
|
+
at(nowMs: number): LonLat$2 | null;
|
|
921
|
+
/** Длительность текущего переезда, мс (0 — перенос): камере слежения — та же, чтобы точка не уплывала из центра. */
|
|
922
|
+
get glideMs(): number;
|
|
923
|
+
/** Идёт ли переезд: приложению — крутить кадры, пока `true`. */
|
|
924
|
+
moving(nowMs: number): boolean;
|
|
925
|
+
reset(): void;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
type LonLat$1 = [number, number];
|
|
929
|
+
type NavCarKind = 'arrow' | 'arrow3d' | 'sedan' | 'pickup' | 'suv';
|
|
930
|
+
/** Варианты, у которых есть 3D-модель (всё, кроме плоской стрелки — её рисуют фигуры курсора). */
|
|
931
|
+
type NavCarModelKind = Exclude<NavCarKind, 'arrow'>;
|
|
932
|
+
/** Порядок выбора в настройках: прежняя стрелка первой, объёмная — рядом с ней. */
|
|
933
|
+
declare const NAV_CAR_KINDS: NavCarKind[];
|
|
934
|
+
declare const NAV_CAR_LABELS: Record<NavCarKind, string>;
|
|
935
|
+
/** Настройка → вариант; пусто и неизвестное — стрелка (так было до машин). */
|
|
936
|
+
declare function navCarKind(id?: string | null): NavCarKind;
|
|
937
|
+
declare const NAV_CAR: {
|
|
938
|
+
/**
|
|
939
|
+
* Наименьшая длина машины на экране, пиксели. Как у Google: на обзорных зумах машина не тает в
|
|
940
|
+
* точку, а держит размер значка (заметно крупнее круга курсора — 28.5 px); настоящие 4.5 м
|
|
941
|
+
* начинаются только вблизи (z20+).
|
|
942
|
+
*/
|
|
943
|
+
minPx: number;
|
|
944
|
+
/** Зазор над линией маршрута, метры: колёса стоят на ленте, а не в ней (у курсора тоже +0.02). */
|
|
945
|
+
lift: number;
|
|
946
|
+
/** Высота фаски низа кузова, метры. */
|
|
947
|
+
chamfer: number;
|
|
948
|
+
};
|
|
949
|
+
/**
|
|
950
|
+
* Кузов: сечения поперёк машины от носа к корме — `[s, w, belt, wt, top]`: расстояние от носа,
|
|
951
|
+
* полуширина по борту, высота пояса (низ окон), полуширина и высота верха (крыша/капот). Между
|
|
952
|
+
* сечениями — сегменты (`segs`, на один меньше), буква решает, чем покрыт верх и бока над поясом:
|
|
953
|
+
* B — кузов, W — стекло сверху и с боков (лобовое, заднее), R — крыша с боковыми окнами,
|
|
954
|
+
* P — стойка (весь сегмент кузовом), D — кузов пикапа (стенки и пол — тёмным пластиком).
|
|
955
|
+
*/
|
|
956
|
+
interface NavCarBody {
|
|
957
|
+
length: number;
|
|
958
|
+
/** Полуширина по борту (наибольшая), метры. */
|
|
959
|
+
width: number;
|
|
960
|
+
/** Клиренс: низ кузова над землёй. */
|
|
961
|
+
clearance: number;
|
|
962
|
+
color: string;
|
|
963
|
+
sections: number[][];
|
|
964
|
+
segs: string;
|
|
965
|
+
/** Колёса: радиус, ширина, расстояния осей от носа. */
|
|
966
|
+
wheel: {
|
|
967
|
+
r: number;
|
|
968
|
+
w: number;
|
|
969
|
+
axles: [number, number];
|
|
970
|
+
};
|
|
971
|
+
/** Рейлинги на крыше: от и до (расстояние от носа), полуразнос, высота. */
|
|
972
|
+
rails?: [number, number, number, number];
|
|
973
|
+
}
|
|
974
|
+
declare const NAV_CAR_BODIES: Record<Exclude<NavCarKind, 'arrow' | 'arrow3d'>, NavCarBody>;
|
|
975
|
+
/**
|
|
976
|
+
* ОБЪЁМНАЯ СТРЕЛКА (`arrow3d`) — значок «как у Яндекс Навигатора»: скруглённый шеврон-«подушка» с
|
|
977
|
+
* белой каймой, глянцем и мягкой тенью. Своя геометрия, не снятая с чужой модели.
|
|
978
|
+
*
|
|
979
|
+
* • СИЛУЭТ — шеврон в плане (x вправо, z назад, остриё в −z): остриё, правое крыло, выемка, левое
|
|
980
|
+
* крыло. Каждый угол скруглён ДУГОЙ ОКРУЖНОСТИ — рациональной квадратичной кривой Безье (`corners`:
|
|
981
|
+
* доли входящего и исходящего ребра и вес = синус половины угла), `steps` отрезков на дугу. Только
|
|
982
|
+
* сложение, умножение и деление: тригонометрии нет, порты совпадают с вебом до байта. Формула
|
|
983
|
+
* записана симметрично (`t·u`, `(wa+wb)+wp`), поэтому левая половина — точное отражение правой.
|
|
984
|
+
* • ОБЪЁМ — кольца, отступающие от силуэта внутрь по нормали вершины: вертикальный борт (`base` →
|
|
985
|
+
* `wall`), белая скруглённая кайма (`rim`: ширина, высота — четверть эллипса) и синяя «подушка»
|
|
986
|
+
* (`dome`), над ней — крышка с подъёмом середины (`crown`). Профиль четверти — таблица точек
|
|
987
|
+
* единичной окружности с рациональными координатами (`arc`: 3-4-5, 7-24-25), нормали гладкие:
|
|
988
|
+
* по профилю и по силуэту, поэтому свет и блик текут по всей стрелке без граней.
|
|
989
|
+
* • ЦВЕТ И ГЛЯНЕЦ — без новых шейдеров, в материалах GLB: синий маршрута с градиентом по высоте
|
|
990
|
+
* (`low` у основания подушки → `top` → светлая крышка `high`), борт глубже, кайма белая; малая
|
|
991
|
+
* шероховатость даёт блик солнца и френелевское отражение неба у краёв (ветка `reflection`
|
|
992
|
+
* программы моделей). Ночью — свой набор (`NAV_ARROW_PALETTES.night`, модель `arrow3d-night`):
|
|
993
|
+
* ЯРКОСТЬ ДАЁТ СВЕЧЕНИЕ, А НЕ ЦВЕТ. Ночная тема гасит свет сцены (у NaviGram: рассеянный 0.34
|
|
994
|
+
* голубоватый, ключевой 0.3 против 0.48/0.6 днём) — освещённая часть синего падает на треть. Первый
|
|
995
|
+
* набор (цвет светлее дневного, свечение 0.05–0.3) под таким светом выходил ТЕМНЕЕ дневной стрелки
|
|
996
|
+
* (замер Redmi 23.10.2026: L 116 против 129); светлее она была только на стилях MapLibre с дневным
|
|
997
|
+
* светом. Поэтому ночью цвет темнее, а свечение крупное: под ночным светом стрелка заметно ярче
|
|
998
|
+
* дневной, и от света темы зависит слабо (оценка по граням: 135 ночью против 99 днём, на дневном
|
|
999
|
+
* свете 154). Проверка — тест `ночная стрелка ярче` в `tests/nav-car.test.mjs`.
|
|
1000
|
+
* • ПРЕДЕЛ — отступ колец (`rim[0] + dome[0]`) меньше радиуса самой тугой дуги (остриё, крылья —
|
|
1001
|
+
* ≈0.26 м): иначе внутренние кольца на углах завернулись бы.
|
|
1002
|
+
*/
|
|
1003
|
+
declare const NAV_ARROW: {
|
|
1004
|
+
/** Длина скруглённого силуэта от острия до концов крыльев, метры (по ней поза держит `minPx`, как у машин). */
|
|
1005
|
+
length: number;
|
|
1006
|
+
/** Опорный контур до скругления: остриё, правое крыло, выемка, левое крыло. */
|
|
1007
|
+
outer: number[][];
|
|
1008
|
+
/** Скругление углов: доля ребра к предыдущей вершине, доля ребра к следующей, вес дуги. */
|
|
1009
|
+
corners: number[][];
|
|
1010
|
+
/** Отрезков на дугу угла (чётное: середина дуги острия и выемки ложится точно на ось). */
|
|
1011
|
+
steps: number;
|
|
1012
|
+
/** Низ борта над подложкой-тенью, метры. */
|
|
1013
|
+
base: number;
|
|
1014
|
+
/** Верх борта. */
|
|
1015
|
+
wall: number;
|
|
1016
|
+
/** Белая кайма: ширина внутрь и высота скругления. */
|
|
1017
|
+
rim: number[];
|
|
1018
|
+
/** Синяя «подушка»: ширина внутрь и высота. */
|
|
1019
|
+
dome: number[];
|
|
1020
|
+
/** Подъём середины крышки над краем подушки. */
|
|
1021
|
+
crown: number;
|
|
1022
|
+
/** Центр крышки и подложки в плане (из него силуэт виден целиком). */
|
|
1023
|
+
center: number[];
|
|
1024
|
+
/** Четверть профиля: (cos, sin) от «наружу» до «вверх» — рациональные точки окружности. */
|
|
1025
|
+
arc: number[][];
|
|
1026
|
+
};
|
|
1027
|
+
/** Цвета стрелки (`#rrggbb`): крышка, подушка, низ подушки, кайма, борт; свечение — RGB 0..1 (ночью). */
|
|
1028
|
+
interface NavArrowPalette {
|
|
1029
|
+
high: string;
|
|
1030
|
+
top: string;
|
|
1031
|
+
low: string;
|
|
1032
|
+
bevel: string;
|
|
1033
|
+
side: string;
|
|
1034
|
+
highGlow?: [number, number, number];
|
|
1035
|
+
topGlow?: [number, number, number];
|
|
1036
|
+
lowGlow?: [number, number, number];
|
|
1037
|
+
bevelGlow?: [number, number, number];
|
|
1038
|
+
sideGlow?: [number, number, number];
|
|
1039
|
+
}
|
|
1040
|
+
declare const NAV_ARROW_PALETTES: {
|
|
1041
|
+
day: NavArrowPalette;
|
|
1042
|
+
night: NavArrowPalette;
|
|
1043
|
+
};
|
|
1044
|
+
/** Материалы — в этом порядке и уходят в GLB (пустые пропускаются). */
|
|
1045
|
+
interface NavCarMaterial {
|
|
1046
|
+
name: string;
|
|
1047
|
+
color: [number, number, number, number];
|
|
1048
|
+
metallic: number;
|
|
1049
|
+
roughness: number;
|
|
1050
|
+
emissive?: [number, number, number];
|
|
1051
|
+
blend?: boolean;
|
|
1052
|
+
}
|
|
1053
|
+
/** `#rrggbb` → RGBA 0..1. */
|
|
1054
|
+
declare function navCarColor(hex: string): [number, number, number, number];
|
|
1055
|
+
/** Кусок меша одного материала: треугольники подряд, у каждого свои три вершины (плоские грани). */
|
|
1056
|
+
interface NavCarPart {
|
|
1057
|
+
material: NavCarMaterial;
|
|
1058
|
+
positions: number[];
|
|
1059
|
+
normals: number[];
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Силуэт стрелки: точки по кругу (остриё → правое крыло → выемка → левое крыло, по `steps + 1` на угол;
|
|
1063
|
+
* между углами — прямые рёбра) и единичные нормали вершин наружу (средняя двух соседних рёбер).
|
|
1064
|
+
*/
|
|
1065
|
+
declare function navArrowOutline(): {
|
|
1066
|
+
points: number[][];
|
|
1067
|
+
normals: number[][];
|
|
1068
|
+
};
|
|
1069
|
+
/** Меш значка кусками по материалам (пустые куски выброшены); `night` — ночные цвета (только у стрелки). */
|
|
1070
|
+
declare function navCarMesh(kind: NavCarModelKind, night?: boolean): NavCarPart[];
|
|
1071
|
+
/** Число с четырьмя знаками: округление к ближайшему (половина — вверх по модулю). */
|
|
1072
|
+
declare function fmt4(v: number): string;
|
|
1073
|
+
/** Число с одним знаком (пути превью). */
|
|
1074
|
+
declare function fmt1(v: number): string;
|
|
1075
|
+
/**
|
|
1076
|
+
* Имя модели в GLB и в адресе: вариант, у ночной стрелки — с `-night`. У машин ночного набора нет —
|
|
1077
|
+
* их GLB от `night` не зависит.
|
|
1078
|
+
*/
|
|
1079
|
+
declare function navCarModelId(kind: NavCarModelKind, night?: boolean): string;
|
|
1080
|
+
/** GLB значка: одна сетка, примитив на материал, позиции и нормали Float32, индексы Uint32. */
|
|
1081
|
+
declare function navCarGlb(kind: NavCarModelKind, night?: boolean): Uint8Array;
|
|
1082
|
+
/** FNV-1a 32 бита — отпечаток GLB в фикстурах (строкой из восьми шестнадцатеричных цифр). */
|
|
1083
|
+
declare function navCarHash(bytes: Uint8Array): string;
|
|
1084
|
+
interface NavCarPose {
|
|
1085
|
+
lng: number;
|
|
1086
|
+
lat: number;
|
|
1087
|
+
/** Высота колёс над землёй, метры: над линией маршрута и лентой, на эстакаде — с её отметкой. */
|
|
1088
|
+
altitude: number;
|
|
1089
|
+
/** Курс по часовой, градусы 0..360 (`rotation` модели). */
|
|
1090
|
+
rotation: number;
|
|
1091
|
+
/** Множитель к настоящему размеру: 1 вблизи, больше — чтобы машина не была мельче `minPx`. */
|
|
1092
|
+
scale: number;
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Поза машины: точка и курс курсора (их сглаживает `NavTracker`), масштаб по метрам в пикселе
|
|
1096
|
+
* экрана на широте машины и высота полотна под ней (`roadLiftM` — эстакада по ленте, 0 на земле).
|
|
1097
|
+
* Для стрелки — `null`: её рисуют фигуры `navCursorShapes`.
|
|
1098
|
+
*/
|
|
1099
|
+
declare function navCarPose(kind: NavCarKind, pos: LonLat$1, brgDeg: number, metersPerPixel: number, roadLiftM?: number): NavCarPose | null;
|
|
1100
|
+
/**
|
|
1101
|
+
* Отметка ленты на метре трассы `m`: профили — станции и высоты точек площадных кусков ленты
|
|
1102
|
+
* (`RibbonFeature.stations` + `heightAt` по уровню). Внутри куска — линейно между соседними
|
|
1103
|
+
* станциями; кусков на метре несколько (полосы рядом) — наибольшая; вне ленты — 0.
|
|
1104
|
+
*/
|
|
1105
|
+
declare function navCarLiftAt(profiles: {
|
|
1106
|
+
stations: number[];
|
|
1107
|
+
heights: number[];
|
|
1108
|
+
}[], m: number): number;
|
|
1109
|
+
interface NavCarIcon {
|
|
1110
|
+
width: number;
|
|
1111
|
+
height: number;
|
|
1112
|
+
/** Пути SVG с заливкой, снизу вверх. */
|
|
1113
|
+
paths: {
|
|
1114
|
+
d: string;
|
|
1115
|
+
fill: string;
|
|
1116
|
+
}[];
|
|
1117
|
+
}
|
|
1118
|
+
/** SVG-разметка превью целиком (для веба). */
|
|
1119
|
+
declare function navCarIconSvg(kind: NavCarKind): string;
|
|
1120
|
+
/**
|
|
1121
|
+
* Превью 48×24: машина сбоку (нос влево) по тем же сечениям, что модель, — силуэт цвета кузова,
|
|
1122
|
+
* окна по сегментам W/R, колёса с колпаками. Стрелка — синий круг с белой стрелкой курсора.
|
|
1123
|
+
*/
|
|
1124
|
+
declare function navCarIcon(kind: NavCarKind): NavCarIcon;
|
|
1125
|
+
|
|
1126
|
+
interface LngLat {
|
|
1127
|
+
lng: number;
|
|
1128
|
+
lat: number;
|
|
1129
|
+
}
|
|
1130
|
+
type LngLatLike = LngLat | [number, number];
|
|
1131
|
+
|
|
1132
|
+
type Mat4 = Float64Array;
|
|
1133
|
+
type Vec3 = [number, number, number];
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Прямоугольник по координатам.
|
|
1137
|
+
*
|
|
1138
|
+
* Нужен ровно для одного — «покажи это целиком»: маршрут, выдачу поиска,
|
|
1139
|
+
* границу района. Имена методов те же, что у Mapbox и MapLibre (`extend`,
|
|
1140
|
+
* `getCenter`, `contains`), чтобы чужой код переносился без переписывания.
|
|
1141
|
+
*
|
|
1142
|
+
* Через 180-й меридиан прямоугольник НЕ переносится: у нас нет данных по обе
|
|
1143
|
+
* стороны океана, а поддержка переноса тянет за собой особый случай в каждом
|
|
1144
|
+
* методе. Если понадобится — добавлять его надо вместе с тестами на склейку.
|
|
1145
|
+
*/
|
|
1146
|
+
/**
|
|
1147
|
+
* Рамка любым из видов MapLibre: `LngLatBounds`, пара углов `[юго-запад, северо-восток]` или `[запад, юг, восток, север]`
|
|
1148
|
+
* (02.11.2026).
|
|
1149
|
+
*/
|
|
1150
|
+
type LngLatBoundsLike = LngLatBounds | readonly [LngLatLike, LngLatLike] | readonly [number, number, number, number];
|
|
1151
|
+
declare class LngLatBounds {
|
|
1152
|
+
west: number;
|
|
1153
|
+
south: number;
|
|
1154
|
+
east: number;
|
|
1155
|
+
north: number;
|
|
1156
|
+
constructor(sw?: LngLatLike, ne?: LngLatLike);
|
|
1157
|
+
/** Рамка по набору точек. Пустой набор даёт пустую рамку, а не ошибку. */
|
|
1158
|
+
/** Рамка из любого вида `LngLatBoundsLike`; `LngLatBounds` возвращается как есть. */
|
|
1159
|
+
static convert(input: LngLatBoundsLike): LngLatBounds;
|
|
1160
|
+
static from(points: Iterable<LngLatLike>): LngLatBounds;
|
|
1161
|
+
extend(point: LngLatLike | LngLatBounds): this;
|
|
1162
|
+
isEmpty(): boolean;
|
|
1163
|
+
getCenter(): LngLat;
|
|
1164
|
+
getWest(): number;
|
|
1165
|
+
getSouth(): number;
|
|
1166
|
+
getEast(): number;
|
|
1167
|
+
getNorth(): number;
|
|
1168
|
+
getNorthWest(): LngLat;
|
|
1169
|
+
getSouthEast(): LngLat;
|
|
1170
|
+
/** `[[запад, юг], [восток, север]]`, как `toArray` MapLibre. */
|
|
1171
|
+
toArray(): [[number, number], [number, number]];
|
|
1172
|
+
getSouthWest(): LngLat;
|
|
1173
|
+
getNorthEast(): LngLat;
|
|
1174
|
+
contains(point: LngLatLike): boolean;
|
|
1175
|
+
/** Расширить на долю своего размера — «немного воздуха» вокруг объекта. */
|
|
1176
|
+
pad(fraction: number): this;
|
|
1177
|
+
/** Углы в нормализованном меркаторе — в этих единицах и считается вписывание. */
|
|
1178
|
+
toMercator(): {
|
|
1179
|
+
minX: number;
|
|
1180
|
+
minY: number;
|
|
1181
|
+
maxX: number;
|
|
1182
|
+
maxY: number;
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Идентификатор тайла. wrap — номер копии мира по X (карта повторяется при панораме
|
|
1188
|
+
* через антимеридиан): данные у копий одни и те же, а матрица разная, поэтому
|
|
1189
|
+
* ключ кеша данных (dataKey) и ключ отрисовки (key) — разные вещи.
|
|
1190
|
+
*/
|
|
1191
|
+
declare class TileID {
|
|
1192
|
+
readonly z: number;
|
|
1193
|
+
readonly x: number;
|
|
1194
|
+
readonly y: number;
|
|
1195
|
+
readonly wrap: number;
|
|
1196
|
+
private dataKeyOrNull;
|
|
1197
|
+
private keyOrNull;
|
|
1198
|
+
constructor(z: number, x: number, y: number, wrap?: number);
|
|
1199
|
+
get dataKey(): string;
|
|
1200
|
+
get key(): string;
|
|
1201
|
+
parent(): TileID | null;
|
|
1202
|
+
children(): TileID[];
|
|
1203
|
+
/** Предок на заданном зуме (или сам тайл). */
|
|
1204
|
+
scaledTo(z: number): TileID;
|
|
1205
|
+
isAncestorOf(other: TileID): boolean;
|
|
1206
|
+
/** Границы тайла в нормализованном меркаторе, с учётом копии мира. */
|
|
1207
|
+
bounds(): {
|
|
1208
|
+
minX: number;
|
|
1209
|
+
minY: number;
|
|
1210
|
+
maxX: number;
|
|
1211
|
+
maxY: number;
|
|
1212
|
+
};
|
|
1213
|
+
toString(): string;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
interface GlobeBasis {
|
|
1217
|
+
/** Восток, юг, вверх в точке центра карты — колонки перехода в локальные координаты. */
|
|
1218
|
+
east: Vec3;
|
|
1219
|
+
south: Vec3;
|
|
1220
|
+
up: Vec3;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
type Projection = 'mercator' | 'globe';
|
|
1224
|
+
interface ScreenPoint {
|
|
1225
|
+
x: number;
|
|
1226
|
+
y: number;
|
|
1227
|
+
}
|
|
1228
|
+
declare class Transform {
|
|
1229
|
+
width: number;
|
|
1230
|
+
height: number;
|
|
1231
|
+
minZoom: number;
|
|
1232
|
+
maxZoom: number;
|
|
1233
|
+
/**
|
|
1234
|
+
* Рамка, за которую не выпускается ЦЕНТР карты. `null` — ограничения нет.
|
|
1235
|
+
*
|
|
1236
|
+
* Прижимается в сеттере центра, а не при отрисовке: центр ставят и жесты, и анимация, и
|
|
1237
|
+
* приложение, и проверить это в одном месте — единственный способ не забыть ни одного из них.
|
|
1238
|
+
*/
|
|
1239
|
+
maxBounds: LngLatBounds | null;
|
|
1240
|
+
/**
|
|
1241
|
+
* Повторять ли мир по горизонтали при отъезде.
|
|
1242
|
+
*
|
|
1243
|
+
* На мелком зуме в кадр помещается больше одного оборота Земли, и копии закрывают пустоту по
|
|
1244
|
+
* краям. Приложению это иногда мешает — например, когда по карте водят выделение и вторая копия
|
|
1245
|
+
* ловит тот же клик, — поэтому повтор выключается (`map.setRenderWorldCopies`).
|
|
1246
|
+
*/
|
|
1247
|
+
renderWorldCopies: boolean;
|
|
1248
|
+
minPitch: number;
|
|
1249
|
+
maxPitch: number;
|
|
1250
|
+
private _fogCutoff;
|
|
1251
|
+
/**
|
|
1252
|
+
* ДАЛЬНОСТЬ ОТСЕЧЕНИЯ ТУМАНА (`fog` стиля): глубина по оси взгляда в расстояниях до центра кадра.
|
|
1253
|
+
*
|
|
1254
|
+
* Становится ДАЛЬНЕЙ ПЛОСКОСТЬЮ камеры: за ней GPU не рисует ничего, а отбор тайлов (та же пирамида
|
|
1255
|
+
* видимости в `isTileVisible`) их не выбирает и не грузит — как и 3D-модели (`isSphereVisible`).
|
|
1256
|
+
* Туман в шейдере меряет ту же глубину, поэтому на кромке он уже сплошной. `Infinity` — не режем.
|
|
1257
|
+
*/
|
|
1258
|
+
get fogCutoff(): number;
|
|
1259
|
+
/**
|
|
1260
|
+
* Дальняя плоскость тумана в мировых пикселях по оси взгляда: `fogCutoff` в долях расстояния до
|
|
1261
|
+
* центра, но НЕ БЛИЖЕ `FOG_CUTOFF_MIN_M` метров. Доли на крупном зуме — десятки метров (z22: ~32 м), и
|
|
1262
|
+
* камера обрезала дорогу прямо перед собой. `Infinity` — не режем.
|
|
1263
|
+
*/
|
|
1264
|
+
fogCutoffDepth(): number;
|
|
1265
|
+
set fogCutoff(k: number);
|
|
1266
|
+
/**
|
|
1267
|
+
* КРЕН камеры, градусы: поворот вокруг оси взгляда.
|
|
1268
|
+
*
|
|
1269
|
+
* Наклон (`pitch`) кладёт камеру вперёд, поворот (`bearing`) вращает карту под ней, а крен
|
|
1270
|
+
* заваливает сам кадр — как накренившийся самолёт. Нужен полётам и панорамам; на обычной карте
|
|
1271
|
+
* остаётся нулём. Порядок сборки тот же, что у MapLibre: крен идёт МЕЖДУ отъездом камеры и
|
|
1272
|
+
* наклоном, иначе он начал бы вращать не кадр, а землю под ним.
|
|
1273
|
+
*
|
|
1274
|
+
* Свойство, а не поле: присваивание обязано пометить камеру грязной, иначе матрицы останутся
|
|
1275
|
+
* прежними и крен не доедет до кадра.
|
|
1276
|
+
*/
|
|
1277
|
+
private _roll;
|
|
1278
|
+
private _center;
|
|
1279
|
+
/**
|
|
1280
|
+
* ВЫСОТА ЗЕМЛИ ПОД ЦЕНТРОМ КАРТЫ, метры. Ставится рельефом каждый кадр; без рельефа — ноль.
|
|
1281
|
+
*
|
|
1282
|
+
* Камера у нас стоит на фиксированном расстоянии от ТОЧКИ ЦЕНТРА, а точка эта лежала на нулевой
|
|
1283
|
+
* высоте. Над горой в 2 км это значит, что камера оказывается внутри горы: земля поднялась, а
|
|
1284
|
+
* она — нет. Поднимаем всю связку камера-центр на высоту земли — ровно так же поступает MapLibre
|
|
1285
|
+
* (`transform.elevation`), и по той же причине: иначе рельеф нельзя даже осмотреть.
|
|
1286
|
+
*/
|
|
1287
|
+
private _centerElevation;
|
|
1288
|
+
private _zoom;
|
|
1289
|
+
private _bearing;
|
|
1290
|
+
private _pitch;
|
|
1291
|
+
/** Наклон, который просили (`pitch =`); на экране — он же, урезанный пределом по зуму. */
|
|
1292
|
+
private _wantedPitch;
|
|
1293
|
+
/** Предел наклона по зуму. У голой математики камеры выключен; карта включает его опцией `pitchByZoom`. */
|
|
1294
|
+
private _pitchByZoom;
|
|
1295
|
+
private _fov;
|
|
1296
|
+
private _worldSize;
|
|
1297
|
+
private _pixelsPerMeter;
|
|
1298
|
+
private _cameraToCenterDistance;
|
|
1299
|
+
private _centerX;
|
|
1300
|
+
private _centerY;
|
|
1301
|
+
private _projMatrix;
|
|
1302
|
+
/** Элемент [14] перспективы: z клипа = A·z + B (см. `projZOffset`). */
|
|
1303
|
+
private _projZ;
|
|
1304
|
+
private _viewProjPx;
|
|
1305
|
+
private _viewMatrix;
|
|
1306
|
+
private _pixelMatrix;
|
|
1307
|
+
private _pixelMatrixInverse;
|
|
1308
|
+
private _cameraPosPx;
|
|
1309
|
+
private _frustumPlanes;
|
|
1310
|
+
private _dirty;
|
|
1311
|
+
/**
|
|
1312
|
+
* Проекция. `globe` действует только до GLOBE_ZOOM_MAX — выше карта всё равно
|
|
1313
|
+
* плоская, и держать сферу там значило бы платить за неё зря.
|
|
1314
|
+
*/
|
|
1315
|
+
projection: Projection;
|
|
1316
|
+
/**
|
|
1317
|
+
* ЗУМЫ ПЕРЕХОДА ШАР → ПЛОСКОСТЬ ИЗ СТИЛЯ (этап y, `porting/unified-style-format.md` §15): `projection.type`
|
|
1318
|
+
* MapLibre по зуму (`["interpolate", …, ["zoom"], z0, "vertical-perspective", z1, "mercator"]`, сахар
|
|
1319
|
+
* `globe` — z11 → z12). Доля плоскости между ними — кривой `interpolate` (у MapLibre — линейная смесь
|
|
1320
|
+
* положений, `GlobeProjection.transitionState`); `max: Infinity` — шар на любом зуме (`vertical-perspective`).
|
|
1321
|
+
* `null` — свои зумы глобуса (`GLOBE_ZOOM_MIN`–`GLOBE_ZOOM_MAX`, гладкая ступенька).
|
|
1322
|
+
*/
|
|
1323
|
+
globeZooms: {
|
|
1324
|
+
min: number;
|
|
1325
|
+
max: number;
|
|
1326
|
+
base?: number;
|
|
1327
|
+
} | null;
|
|
1328
|
+
/** Зум, с которого шар уже не нужен. */
|
|
1329
|
+
private get globeTop();
|
|
1330
|
+
/** Доля плоской карты на зуме: из стиля — кривой `interpolate`, иначе своя гладкая ступенька. */
|
|
1331
|
+
private flatMixAt;
|
|
1332
|
+
private _f32;
|
|
1333
|
+
private _tileMatrix;
|
|
1334
|
+
private _localView;
|
|
1335
|
+
private _localProj;
|
|
1336
|
+
private _localPixel;
|
|
1337
|
+
private _localPixelInverse;
|
|
1338
|
+
private _localF32;
|
|
1339
|
+
private _globeBasis;
|
|
1340
|
+
private _objectF32;
|
|
1341
|
+
private _objectMatrix;
|
|
1342
|
+
private _shadowView;
|
|
1343
|
+
private _shadowProj;
|
|
1344
|
+
private _shadowVP;
|
|
1345
|
+
/** То же, но принимает высоту в метрах — как projMatrix. */
|
|
1346
|
+
private _shadowVPm;
|
|
1347
|
+
private _shadowTile;
|
|
1348
|
+
private _shadowObject;
|
|
1349
|
+
private _shadowTile32;
|
|
1350
|
+
private _shadowObject32;
|
|
1351
|
+
private _shadowValid;
|
|
1352
|
+
constructor();
|
|
1353
|
+
get center(): LngLat;
|
|
1354
|
+
set center(v: LngLat);
|
|
1355
|
+
/** Высота земли под центром, метры (ставит рельеф). */
|
|
1356
|
+
get centerElevation(): number;
|
|
1357
|
+
set centerElevation(v: number);
|
|
1358
|
+
/**
|
|
1359
|
+
* Нижняя граница зума на глобусе: дальше шар становится точкой в углу экрана.
|
|
1360
|
+
*
|
|
1361
|
+
* Считается от размера кадра, а не числом: радиус сферы в мировых пикселях
|
|
1362
|
+
* растёт с зумом, и «достаточно крупно» — это доля меньшей стороны экрана.
|
|
1363
|
+
*/
|
|
1364
|
+
globeMinZoom(): number;
|
|
1365
|
+
/** Нижняя граница зума с учётом проекции. */
|
|
1366
|
+
effectiveMinZoom(): number;
|
|
1367
|
+
get zoom(): number;
|
|
1368
|
+
set zoom(z: number);
|
|
1369
|
+
get bearing(): number;
|
|
1370
|
+
set bearing(b: number);
|
|
1371
|
+
get pitch(): number;
|
|
1372
|
+
set pitch(p: number);
|
|
1373
|
+
/** Наклон, который просили; на экране может быть меньше — по пределу зума (`pitchByZoom`). */
|
|
1374
|
+
get wantedPitch(): number;
|
|
1375
|
+
/**
|
|
1376
|
+
* Наклон по высоте камеры (`PITCH_BY_ZOOM`). У карты по умолчанию включён (`MapOptions.pitchByZoom`).
|
|
1377
|
+
* Выключают его те, кто ставит камеру сам и знает, зачем: ведение навигатора держит свой наклон.
|
|
1378
|
+
*/
|
|
1379
|
+
get pitchByZoom(): boolean;
|
|
1380
|
+
set pitchByZoom(on: boolean);
|
|
1381
|
+
/** Предел наклона на текущем зуме: `maxPitch`, глобус и высота камеры. */
|
|
1382
|
+
pitchLimit(): number;
|
|
1383
|
+
private applyPitch;
|
|
1384
|
+
/** Вертикальный угол обзора в градусах. */
|
|
1385
|
+
get roll(): number;
|
|
1386
|
+
set roll(deg: number);
|
|
1387
|
+
get fov(): number;
|
|
1388
|
+
set fov(deg: number);
|
|
1389
|
+
resize(width: number, height: number): void;
|
|
1390
|
+
get worldSize(): number;
|
|
1391
|
+
get pixelsPerMeter(): number;
|
|
1392
|
+
get cameraToCenterDistance(): number;
|
|
1393
|
+
get cameraPositionPx(): [number, number, number];
|
|
1394
|
+
/**
|
|
1395
|
+
* Свободный член глубины перспективы (B в z клипа = A·z + B).
|
|
1396
|
+
*
|
|
1397
|
+
* Нужен, чтобы сдвинуть глубину точки к камере, не сдвигая её на экране: приблизить точку на долю
|
|
1398
|
+
* `k` её расстояния — то же самое, что прибавить к z клипа `B·k/(1−k)`, одну постоянную на кадр
|
|
1399
|
+
* (см. `LineLayerSpec.depthTest`).
|
|
1400
|
+
*/
|
|
1401
|
+
get projZOffset(): number;
|
|
1402
|
+
get projMatrix(): Mat4;
|
|
1403
|
+
/** Мировые пиксели → экран в однородных координатах (x, y, z, w; делить на w). */
|
|
1404
|
+
get pixelMatrix(): Mat4;
|
|
1405
|
+
get viewMatrix(): Mat4;
|
|
1406
|
+
/** Центр карты в мировых пикселях. */
|
|
1407
|
+
get centerPointPx(): {
|
|
1408
|
+
x: number;
|
|
1409
|
+
y: number;
|
|
1410
|
+
};
|
|
1411
|
+
clone(): Transform;
|
|
1412
|
+
/** Географическая точка (+ высота в метрах) → пиксели экрана. */
|
|
1413
|
+
project(lngLat: LngLat, altitudeMeters?: number): ScreenPoint;
|
|
1414
|
+
/**
|
|
1415
|
+
* Мировые пиксели → экран. null, если точка за камерой: при наклоне 70°
|
|
1416
|
+
* половина мира оказывается позади, и без этой проверки подписи «отражаются»
|
|
1417
|
+
* в верхнюю часть экрана.
|
|
1418
|
+
*/
|
|
1419
|
+
worldPxToPoint(wx: number, wy: number, altitudeMeters?: number): ScreenPoint | null;
|
|
1420
|
+
/** Пиксели экрана → география на уровне земли (луч сквозь плоскость z=0). */
|
|
1421
|
+
unproject(point: ScreenPoint): LngLat;
|
|
1422
|
+
/**
|
|
1423
|
+
* ВЫСОТА ЗЕМЛИ В ТОЧКЕ, метры — её ставит рельеф.
|
|
1424
|
+
*
|
|
1425
|
+
* Без неё обратный перевод «экран → место» считает землю плоскостью на нуле. На рельефе земля
|
|
1426
|
+
* лежит на своей высоте, и на городском зуме при наклоне промах доходит до полутора километров:
|
|
1427
|
+
* клик по дому открывал карточку соседнего квартала, а перетаскивание карты уезжало из-под
|
|
1428
|
+
* пальца. Ровно это же (и ровно так же, итерациями) делает MapLibre в `pointCoordinate3D`.
|
|
1429
|
+
*/
|
|
1430
|
+
elevationAt: ((mx: number, my: number) => number) | null;
|
|
1431
|
+
/** Пиксели экрана → мировые пиксели на плоскости земли. */
|
|
1432
|
+
pointToWorldPx(point: ScreenPoint): {
|
|
1433
|
+
x: number;
|
|
1434
|
+
y: number;
|
|
1435
|
+
};
|
|
1436
|
+
/** Сдвигает центр так, чтобы координата lngLat оказалась под точкой экрана. */
|
|
1437
|
+
setLocationAtPoint(lngLat: LngLat, point: ScreenPoint): void;
|
|
1438
|
+
/**
|
|
1439
|
+
* Центр, посчитанный жестом или инерцией (минуя сеттер, без рамки `maxBounds` — её держит карта). Точка экрана
|
|
1440
|
+
* за горизонтом или нечисловое касание дают NaN — такой шаг пропускается, как и в сеттере.
|
|
1441
|
+
*/
|
|
1442
|
+
private placeCenter;
|
|
1443
|
+
/**
|
|
1444
|
+
* Сдвигает карту на (dx, dy) пикселей экрана В МАСШТАБЕ ЦЕНТРА: содержимое у центра уезжает ровно на
|
|
1445
|
+
* столько же.
|
|
1446
|
+
*
|
|
1447
|
+
* Нужен инерции броска. Сдвиг здесь ЛИНЕЕН по дельте (пиксель центра переводится в мировые пиксели
|
|
1448
|
+
* один раз, разностью по полпикселя в обе стороны), поэтому сумма шагов равна одному большому шагу
|
|
1449
|
+
* — путь броска не зависит от частоты кадров, в том числе при наклоне. `setLocationAtPoint` вокруг
|
|
1450
|
+
* центра этого не даёт: при перспективе он нелинеен, и десять мелких шагов уводят не туда, куда один
|
|
1451
|
+
* крупный. На шаре линейного сдвига нет — там тот же поворот, что у жеста.
|
|
1452
|
+
*/
|
|
1453
|
+
panByScreenDelta(dx: number, dy: number): void;
|
|
1454
|
+
/**
|
|
1455
|
+
* Матрица тайла: локальные координаты MVT [0..extent] + высота в метрах → клип.
|
|
1456
|
+
* Возвращает переиспользуемый Float32Array — грузить в юниформ сразу, не хранить.
|
|
1457
|
+
*/
|
|
1458
|
+
tileMatrix(tile: TileID, extent: number): Float32Array;
|
|
1459
|
+
/** Та же матрица, но в Float64 — для расчётов на CPU (пикинг, отладка). */
|
|
1460
|
+
tileMatrix64(tile: TileID, extent: number, out?: Mat4): Mat4;
|
|
1461
|
+
/**
|
|
1462
|
+
* Матрица рантайм-объекта: локальные координаты — нормализованный меркатор
|
|
1463
|
+
* ОТНОСИТЕЛЬНО origin, Z — метры над землёй.
|
|
1464
|
+
*
|
|
1465
|
+
* Отличается от тайловой только тем, откуда берётся начало и масштаб: у тайла
|
|
1466
|
+
* они продиктованы сеткой, у объекта их выбирает он сам по своей рамке. Смысл
|
|
1467
|
+
* переноса тот же — увести из Float32 огромные мировые координаты.
|
|
1468
|
+
*/
|
|
1469
|
+
objectMatrix(originX: number, originY: number, altitudeMeters?: number): Float32Array;
|
|
1470
|
+
/**
|
|
1471
|
+
* КВАДРАТ ДРАПИРОВКИ, в который сейчас рисуется плоская карта (15.10.2026). `null` — рисуем на экран.
|
|
1472
|
+
*
|
|
1473
|
+
* На рельефе плоский проход уходит в текстуры тайлов рельефа (`Renderer.drapeTerrain`), и всё, что в
|
|
1474
|
+
* нём рисуется, обязано класть себя в КВАДРАТ ТАЙЛА, а не в кадр. Слои стиля так и делают (матрица
|
|
1475
|
+
* тайла), а свои слои приложения и стрелки входов берут матрицу у `objectMatrix` — и получали
|
|
1476
|
+
* экранную камеру. Полотно детальных дорог (`ld-pascal-mesh` на вебе, `LdMesh` на Android/iOS)
|
|
1477
|
+
* рисовалось в каждую текстуру тайла по проекции кадра и легло на рельеф обрывками тёмных полос
|
|
1478
|
+
* поперёк реки (жалоба с navi.gram.tj, Худжанд, z17, наклон 65°). Пока квадрат задан, `objectMatrix`
|
|
1479
|
+
* переводит меркатор в квадрат рельефа, а высоту в метрах обнуляет: в текстуре земля плоская, рельеф
|
|
1480
|
+
* ей даст решётка.
|
|
1481
|
+
*/
|
|
1482
|
+
drapeSquare: {
|
|
1483
|
+
z: number;
|
|
1484
|
+
x: number;
|
|
1485
|
+
y: number;
|
|
1486
|
+
} | null;
|
|
1487
|
+
/**
|
|
1488
|
+
* `objectMatrix` в драпировке: локальный меркатор (от `origin`) → клип квадрата `drapeSquare`.
|
|
1489
|
+
*
|
|
1490
|
+
* x = 2ⁿ⁺¹·lx + (2ⁿ⁺¹·ox − 2·T.x − 1), y = −2ⁿ⁺¹·ly + (−2ⁿ⁺¹·oy + 2·T.y + 1), z = 0
|
|
1491
|
+
*
|
|
1492
|
+
* Та же раскладка, что у тайла карты в драпировке (`Renderer.applyTileMatrix`): ось Y перевёрнута, её
|
|
1493
|
+
* возвращает шейдер рельефа. Перенос считается в двойной точности — как у обычной `objectMatrix`.
|
|
1494
|
+
*/
|
|
1495
|
+
private drapeObjectMatrix;
|
|
1496
|
+
/**
|
|
1497
|
+
* Экранная координата Y линии горизонта (сверху вниз, CSS-пиксели).
|
|
1498
|
+
*
|
|
1499
|
+
* Крена у нашей камеры нет, поэтому горизонт — всегда ГОРИЗОНТАЛЬНАЯ линия, и
|
|
1500
|
+
* небо можно рисовать вертикальным градиентом в экранных координатах. Это и
|
|
1501
|
+
* точно, и на порядок дешевле, чем считать направление луча в каждом фрагменте.
|
|
1502
|
+
*
|
|
1503
|
+
* Значение может быть отрицательным: при малом наклоне горизонт уходит выше
|
|
1504
|
+
* кадра, и неба на экране просто нет.
|
|
1505
|
+
*/
|
|
1506
|
+
horizonScreenY(): number;
|
|
1507
|
+
/** Сфера сейчас активна: режим включён и зум ещё не перевалил за переход. */
|
|
1508
|
+
get globeActive(): boolean;
|
|
1509
|
+
/** Доля плоской карты в смеси: 0 — чистая сфера, 1 — чистый меркатор. */
|
|
1510
|
+
get globeFlatMix(): number;
|
|
1511
|
+
get globeRadiusPx(): number;
|
|
1512
|
+
/**
|
|
1513
|
+
* Радиус шара в мировых пикселях. Свой — по экватору (`globeRadius`); у стиля MapLibre (зумы перехода
|
|
1514
|
+
* из стиля, этап y) — как у `GlobeTransform` MapLibre: делённый на косинус широты центра, чтобы масштаб
|
|
1515
|
+
* в центре кадра совпадал с меркатором и переход шар → плоскость не менял размера карты.
|
|
1516
|
+
*/
|
|
1517
|
+
private globeRadiusOf;
|
|
1518
|
+
get globeBasis(): GlobeBasis;
|
|
1519
|
+
/** Та же матрица в Float64 — для расчётов на CPU. */
|
|
1520
|
+
get localProjMatrix(): Mat4;
|
|
1521
|
+
/**
|
|
1522
|
+
* Камера относительно ЦЕНТРА ШАРА, в локальных координатах: направление и
|
|
1523
|
+
* косинус угла до горизонта.
|
|
1524
|
+
*
|
|
1525
|
+
* Считать горизонт от вертикали в центре карты нельзя — это верно только при
|
|
1526
|
+
* наклоне 0. При наклоне камера уходит вбок, настоящий горизонт наклоняется
|
|
1527
|
+
* вместе с ней, а плоскость, перпендикулярная вертикали, срезает шар по
|
|
1528
|
+
* прямой: ровно это и было видно как «часть глобуса скрывается».
|
|
1529
|
+
*/
|
|
1530
|
+
globeCamera(): {
|
|
1531
|
+
dir: Vec3;
|
|
1532
|
+
horizonCos: number;
|
|
1533
|
+
};
|
|
1534
|
+
/**
|
|
1535
|
+
* Направления лучей через углы кадра, в локальных координатах.
|
|
1536
|
+
*
|
|
1537
|
+
* Нужны небу: чтобы посчитать, на каком угловом расстоянии от края планеты
|
|
1538
|
+
* находится пиксель, нужен его луч. Считать обратную проекцию в каждом
|
|
1539
|
+
* фрагменте дорого и незачем — направления линейны по экрану, поэтому хватает
|
|
1540
|
+
* четырёх углов и интерполяции. Так же делает Mapbox (u_frustum_tl/tr/br/bl).
|
|
1541
|
+
*
|
|
1542
|
+
* Порядок: левый верх, правый верх, левый низ, правый низ.
|
|
1543
|
+
*/
|
|
1544
|
+
localRayCorners(): Vec3[];
|
|
1545
|
+
/**
|
|
1546
|
+
* Базис камеры в МИРОВЫХ осях движка (X восток, Y юг, Z вверх) и фокус в пикселях вида.
|
|
1547
|
+
*
|
|
1548
|
+
* По ним из пикселя восстанавливается луч: dir = normalize(fwd*focal + right*dx + up*dy). Небу
|
|
1549
|
+
* этого хватает, чтобы считаться от УГЛА ЛУЧА, а не от экранной высоты, — только так у горизонта
|
|
1550
|
+
* получается настоящая воздушная масса, а не нарисованная полоса.
|
|
1551
|
+
*/
|
|
1552
|
+
cameraRay(): {
|
|
1553
|
+
right: Vec3;
|
|
1554
|
+
up: Vec3;
|
|
1555
|
+
fwd: Vec3;
|
|
1556
|
+
focal: number;
|
|
1557
|
+
axisElev: number;
|
|
1558
|
+
};
|
|
1559
|
+
/** Направление на центр планеты и её угловой радиус — для каймы атмосферы. */
|
|
1560
|
+
globeDisc(): {
|
|
1561
|
+
dir: Vec3;
|
|
1562
|
+
angle: number;
|
|
1563
|
+
};
|
|
1564
|
+
/** Матрица камеры для ЛОКАЛЬНЫХ координат (глобус их и отдаёт). */
|
|
1565
|
+
localMatrix(): Float32Array;
|
|
1566
|
+
/**
|
|
1567
|
+
* Нормализованный меркатор → локальные координаты. Повторяет формулу
|
|
1568
|
+
* вершинного шейдера один в один: разойдись они — и подписи, размещаемые на
|
|
1569
|
+
* CPU, поедут относительно геометрии.
|
|
1570
|
+
*/
|
|
1571
|
+
globeLocal(mx: number, my: number): Vec3;
|
|
1572
|
+
/** Локальные координаты → экран. null, если точка за камерой. */
|
|
1573
|
+
localToPoint(local: Vec3): ScreenPoint | null;
|
|
1574
|
+
/**
|
|
1575
|
+
* Обратная проекция на сфере: луч из камеры до пересечения с шаром.
|
|
1576
|
+
*
|
|
1577
|
+
* Центр шара в локальных координатах — (0, 0, −R): начало там, где сфера
|
|
1578
|
+
* касается касательной плоскости, а центр на радиус ниже.
|
|
1579
|
+
*/
|
|
1580
|
+
globeUnproject(point: ScreenPoint): LngLat | null;
|
|
1581
|
+
/**
|
|
1582
|
+
* Видна ли точка сферы: и по горизонту, и по кадру.
|
|
1583
|
+
*
|
|
1584
|
+
* Горизонт считается от ВЫСОТЫ камеры, а не «передней полусферы»: с конечной
|
|
1585
|
+
* высоты видно заметно меньше половины шара, и без этого дальняя сторона
|
|
1586
|
+
* рисовалась бы поверх ближней (тест глубины у плоских слоёв выключен).
|
|
1587
|
+
*/
|
|
1588
|
+
isOnGlobeFront(lng: number, lat: number, slack?: number): boolean;
|
|
1589
|
+
/**
|
|
1590
|
+
* Пересчитывает матрицы карты теней под текущую камеру.
|
|
1591
|
+
*
|
|
1592
|
+
* Объём источника подгоняется под ВИДИМУЮ землю, а не задаётся раз и навсегда:
|
|
1593
|
+
* при наклоне 70° видимая площадь больше, чем при взгляде сверху, в десятки
|
|
1594
|
+
* раз, и фиксированный объём либо обрежет тени спереди, либо размажет их в
|
|
1595
|
+
* кашу сзади (весь бюджет текселей уйдёт на пустоту).
|
|
1596
|
+
*
|
|
1597
|
+
* @param dir куда ЛЕТИТ свет (та же система, что у `u_key_dir`)
|
|
1598
|
+
* @param maxHeightMeters потолок сцены: выше него теней не бывает
|
|
1599
|
+
*/
|
|
1600
|
+
updateShadow(dir: Vec3, maxHeightMeters: number, groundMin?: number, groundMax?: number): void;
|
|
1601
|
+
/** Матрицы теней посчитаны и пригодны. */
|
|
1602
|
+
get shadowReady(): boolean;
|
|
1603
|
+
/**
|
|
1604
|
+
* Матрица тени для рантайм-объекта: локальные координаты (нормализованный
|
|
1605
|
+
* меркатор относительно origin) → клип источника света.
|
|
1606
|
+
*/
|
|
1607
|
+
shadowObjectMatrix(originX: number, originY: number, altitudeMeters?: number): Float32Array;
|
|
1608
|
+
/** Матрица тайла для прохода глубины: локальные координаты MVT → клип источника. */
|
|
1609
|
+
shadowTileMatrix(tile: TileID, extent: number): Float32Array;
|
|
1610
|
+
/**
|
|
1611
|
+
* Сколько единиц глубины карты теней приходится на МЕТР высоты.
|
|
1612
|
+
*
|
|
1613
|
+
* Нужно, чтобы задавать смещение против самозатенения в метрах, а не в долях
|
|
1614
|
+
* диапазона глубины. Диапазон зависит от объёма источника, а тот — от зума и
|
|
1615
|
+
* наклона: одно и то же «0.0015» означало 49 метров на z16 и 15 на z19, и
|
|
1616
|
+
* тени обычных домов просто съедались смещением.
|
|
1617
|
+
*
|
|
1618
|
+
* Матрица источника ортографическая, то есть аффинная, поэтому производная
|
|
1619
|
+
* глубины по высоте — это просто её коэффициент при z.
|
|
1620
|
+
*/
|
|
1621
|
+
shadowDepthPerMeter(): number;
|
|
1622
|
+
/** Размер текселя карты теней в мировых пикселях — для наклонного bias. */
|
|
1623
|
+
shadowTexelWorldSize(mapSize: number): number;
|
|
1624
|
+
/**
|
|
1625
|
+
* AABB тайла (мировые пиксели, Z тоже пиксели) против пирамиды видимости.
|
|
1626
|
+
* Классический тест «полностью снаружи одной из плоскостей»: даёт ложные
|
|
1627
|
+
* попадания на углах, но не теряет видимое — для отбора тайлов этого достаточно.
|
|
1628
|
+
*/
|
|
1629
|
+
isTileVisible(tile: TileID): boolean;
|
|
1630
|
+
/**
|
|
1631
|
+
* Видна ли сфера: центр задан в нормализованном меркаторе, высота и радиус — в метрах.
|
|
1632
|
+
*
|
|
1633
|
+
* Тем же набором плоскостей, что и у тайлов ([[isTileVisible]]). Нужна отсечению 3D-моделей: их
|
|
1634
|
+
* отбирают по расстоянию от ЦЕНТРА КАРТЫ, а не по кадру, и при наклонной камере заметная часть
|
|
1635
|
+
* радиуса приходится за спину — рисовать её незачем.
|
|
1636
|
+
*/
|
|
1637
|
+
isSphereVisible(mercX: number, mercY: number, heightM: number, radiusM: number): boolean;
|
|
1638
|
+
/**
|
|
1639
|
+
* Лежит ли точка ЗА КРОМКОЙ ТУМАНА (`fogCutoff`): её глубина по оси взгляда дальше дальней плоскости.
|
|
1640
|
+
* Мировые пиксели по X/Y, высота в метрах. Нужна тому, что рисуется не GPU, — DOM-маркерам.
|
|
1641
|
+
*/
|
|
1642
|
+
beyondFog(x: number, y: number, altitudeM?: number): boolean;
|
|
1643
|
+
/** Расстояние от камеры до ближайшей точки AABB тайла (мировые пиксели). */
|
|
1644
|
+
distanceToTile(tile: TileID): number;
|
|
1645
|
+
/**
|
|
1646
|
+
* Самая дальняя точка земли у НЕНАКЛОНЁННОЙ карты — точка отсчёта для слова «далеко».
|
|
1647
|
+
*
|
|
1648
|
+
* Смотрим сверху вниз: дальше всех от камеры угол экрана. Всё, что дальше этого, на плоской карте
|
|
1649
|
+
* не встречается вовсе и появляется только от наклона.
|
|
1650
|
+
*/
|
|
1651
|
+
get flatFarDistance(): number;
|
|
1652
|
+
/**
|
|
1653
|
+
* Зум, на который тайл ТЯНЕТ с такого расстояния.
|
|
1654
|
+
*
|
|
1655
|
+
* При наклоне карта одним кадром показывает и двор под ногами, и город за несколько километров, а
|
|
1656
|
+
* зум у неё один на всех. Дальние тайлы от этого рисуются со всей мелочью ближнего плана: номера
|
|
1657
|
+
* домов, тропинки, дворовые проезды — всё то, что на таком расстоянии сливается в грязь, но честно
|
|
1658
|
+
* стоит вершин и вызовов отрисовки. Здесь считается, во сколько раз тайл дальше самого дальнего
|
|
1659
|
+
* угла плоской карты, и на столько же зум-уровней он «отодвигается»: слой со своим `minzoom` сам
|
|
1660
|
+
* пропадёт там, где стал бы неразличим. Ровно это происходит у Mapbox, где дальние тайлы физически
|
|
1661
|
+
* берутся с меньшего зума.
|
|
1662
|
+
*
|
|
1663
|
+
* На ненаклонённой карте не меняет ничего: там дальше отсчётной точки ничего и нет.
|
|
1664
|
+
*
|
|
1665
|
+
* @param distancePx расстояние от камеры до тайла в мировых пикселях (`distanceToTile`)
|
|
1666
|
+
*/
|
|
1667
|
+
detailZoom(distancePx: number): number;
|
|
1668
|
+
private _ensure;
|
|
1669
|
+
private _calc;
|
|
1670
|
+
private _calcCameraPosition;
|
|
1671
|
+
/**
|
|
1672
|
+
* Плоскости пирамиды видимости из обратной view-projection: берём 8 углов клипа,
|
|
1673
|
+
* переводим в мир и строим 6 плоскостей с нормалями внутрь.
|
|
1674
|
+
*/
|
|
1675
|
+
private _calcFrustum;
|
|
1676
|
+
/** Размер тайла зума z в пикселях экрана на расстоянии dist — критерий дробления. */
|
|
1677
|
+
tileScreenSize(z: number, dist: number): number;
|
|
1678
|
+
/** Сколько копий мира по X видно (панорама через антимеридиан). */
|
|
1679
|
+
visibleWrapRange(): {
|
|
1680
|
+
min: number;
|
|
1681
|
+
max: number;
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
interface ModelPrimitive {
|
|
1686
|
+
/** Смещение в общем индексном буфере и число индексов. */
|
|
1687
|
+
indexOffset: number;
|
|
1688
|
+
indexCount: number;
|
|
1689
|
+
/** Базовый цвет материала, RGBA 0..1. */
|
|
1690
|
+
baseColor: [number, number, number, number];
|
|
1691
|
+
/**
|
|
1692
|
+
* СОБСТВЕННОЕ свечение материала: `emissiveFactor` × `emissiveStrength`.
|
|
1693
|
+
*
|
|
1694
|
+
* Прибавляется к освещённому цвету и НЕ умножается на свет сцены — в этом весь
|
|
1695
|
+
* смысл: поверхность светится сама. Художник задаёт им «нижний порог» яркости,
|
|
1696
|
+
* чтобы объект не проваливался в черноту, а окна и вывески читались ночью.
|
|
1697
|
+
*
|
|
1698
|
+
* В наших моделях это заполнено осмысленно: у стены 0.22, у двери 0.5, у окна
|
|
1699
|
+
* 0.12, у вывески 0.3. Раньше мы читали из материала только базовый цвет, и всё
|
|
1700
|
+
* это терялось — модель выходила темнее задуманного, а ночью гасла совсем.
|
|
1701
|
+
* Тот же приём у Mapbox называется `emissive-strength`.
|
|
1702
|
+
*/
|
|
1703
|
+
emissive: [number, number, number];
|
|
1704
|
+
/**
|
|
1705
|
+
* Металличность и шероховатость — по ним считается отражение.
|
|
1706
|
+
*
|
|
1707
|
+
* ОТСТУПЛЕНИЕ ОТ СПЕЦИФИКАЦИИ, и оно намеренное. По стандарту оба значения по умолчанию равны
|
|
1708
|
+
* ЕДИНИЦЕ, то есть материал без разметки — это «идеально шероховатый металл». На практике так
|
|
1709
|
+
* не бывает: экспортёры просто не пишут поля, которых художник не трогал. В библиотеке карты у
|
|
1710
|
+
* дворца два материала из трёх именно такие, и буквальное чтение стандарта делало из белого
|
|
1711
|
+
* камня чёрный металл.
|
|
1712
|
+
*
|
|
1713
|
+
* Поэтому правило такое: если НЕ ЗАДАНО НИ ОДНО из полей — материал считается матовым
|
|
1714
|
+
* диэлектриком (0 и 1), то есть ровно тем, чем он выглядит. Стоит указать хотя бы одно —
|
|
1715
|
+
* работают умолчания стандарта, и автор получает ровно то, что записал.
|
|
1716
|
+
*/
|
|
1717
|
+
metallic: number;
|
|
1718
|
+
roughness: number;
|
|
1719
|
+
/** Индекс изображения в `images` или -1. */
|
|
1720
|
+
texture: number;
|
|
1721
|
+
/** Двусторонний материал — отсечение граней выключается. */
|
|
1722
|
+
doubleSided: boolean;
|
|
1723
|
+
/**
|
|
1724
|
+
* Полупрозрачный материал (`alphaMode: BLEND`).
|
|
1725
|
+
*
|
|
1726
|
+
* Различать обязательно: в моделях сплошь и рядом лежит «подложка-тень» —
|
|
1727
|
+
* плоскость под объектом с полупрозрачной текстурой. Нарисованная непрозрачно,
|
|
1728
|
+
* она выглядит чёрной кляксой (ровно это и было видно под машиной), а в карту
|
|
1729
|
+
* теней такую плоскость пускать нельзя вовсе.
|
|
1730
|
+
*/
|
|
1731
|
+
blend: boolean;
|
|
1732
|
+
/** Порог отсечения для `alphaMode: MASK`. */
|
|
1733
|
+
alphaCutoff: number;
|
|
1734
|
+
/**
|
|
1735
|
+
* Узел, который двигает этот примитив, или −1.
|
|
1736
|
+
*
|
|
1737
|
+
* У неподвижных примитивов преобразование узла ЗАПЕЧЕНО в позиции (так дешевле
|
|
1738
|
+
* всего: один буфер, один вызов). У подвижных запечь нечего — их матрица
|
|
1739
|
+
* меняется каждый кадр, — поэтому позиции хранятся в системе самого узла, а
|
|
1740
|
+
* матрицу даёт `Model.nodeMatrix`.
|
|
1741
|
+
*/
|
|
1742
|
+
node: number;
|
|
1743
|
+
}
|
|
1744
|
+
/** Дорожка одного свойства узла: когда и во что оно превращается. */
|
|
1745
|
+
interface AnimChannel {
|
|
1746
|
+
path: 'translation' | 'rotation' | 'scale';
|
|
1747
|
+
/** Моменты времени в секундах. */
|
|
1748
|
+
times: Float32Array;
|
|
1749
|
+
/** Значения подряд: 3 числа на кадр у сдвига и масштаба, 4 у кватерниона. */
|
|
1750
|
+
values: Float32Array;
|
|
1751
|
+
/** Ступенькой, а не линейно. */
|
|
1752
|
+
step: boolean;
|
|
1753
|
+
}
|
|
1754
|
+
/** Подвижный узел: неизменная часть цепочки предков + его собственные дорожки. */
|
|
1755
|
+
interface AnimNode {
|
|
1756
|
+
/**
|
|
1757
|
+
* Ближайший подвижный ПРЕДОК или −1.
|
|
1758
|
+
*
|
|
1759
|
+
* Без него анимация предка теряется: у клипа с подъёмом и разворотом это два
|
|
1760
|
+
* разных узла (верхний поднимает, нижний вертит), и запомни мы только
|
|
1761
|
+
* статическую матрицу предков — объект остался бы вертеться на месте, не
|
|
1762
|
+
* поднимаясь. Матрица подвижного узла поэтому собирается по цепочке.
|
|
1763
|
+
*/
|
|
1764
|
+
parentNode: number;
|
|
1765
|
+
/** Статическая часть цепочки — от подвижного предка (не включая) до этого узла. */
|
|
1766
|
+
parent: Mat4;
|
|
1767
|
+
/** Значения узла без анимации: тем, чего нет в клипе, остаётся своё. */
|
|
1768
|
+
base: {
|
|
1769
|
+
t: [number, number, number];
|
|
1770
|
+
r: [number, number, number, number];
|
|
1771
|
+
s: [number, number, number];
|
|
1772
|
+
};
|
|
1773
|
+
channels: AnimChannel[];
|
|
1774
|
+
}
|
|
1775
|
+
interface ModelAnimation {
|
|
1776
|
+
/** Длительность клипа в секундах — по ней время заворачивается в цикл. */
|
|
1777
|
+
duration: number;
|
|
1778
|
+
nodes: Map<number, AnimNode>;
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Источник света из `KHR_lights_punctual` — то, чем художник рисует ночную подсветку объекта.
|
|
1782
|
+
*
|
|
1783
|
+
* Координаты и радиус — В ПРОСТРАНСТВЕ МОДЕЛИ, там же, где лежат вершины. Это не мелочь: модель
|
|
1784
|
+
* ставится на карту своей матрицей, и переводить светильники в мировые координаты пришлось бы на
|
|
1785
|
+
* каждый экземпляр. В пространстве модели они общие для всех экземпляров одного файла, и шейдер
|
|
1786
|
+
* считает освещение прямо там.
|
|
1787
|
+
*/
|
|
1788
|
+
interface ModelLight {
|
|
1789
|
+
/** `directional` держим ради полноты: свет сцены у нас свой, и такой источник шейдер пропускает. */
|
|
1790
|
+
type: 'point' | 'spot' | 'directional';
|
|
1791
|
+
color: [number, number, number];
|
|
1792
|
+
/** Кандела у точечного и прожектора, люкс у направленного — как в спецификации. */
|
|
1793
|
+
intensity: number;
|
|
1794
|
+
/** Радиус влияния в единицах модели; 0 — не ограничен. */
|
|
1795
|
+
range: number;
|
|
1796
|
+
position: [number, number, number];
|
|
1797
|
+
/** Куда светит прожектор: −Z узла, приведённая к единичной длине. */
|
|
1798
|
+
direction: [number, number, number];
|
|
1799
|
+
/** Косинусы углов конуса: внутри `cosInner` светит в полную силу, за `cosOuter` не светит вовсе. */
|
|
1800
|
+
cosInner: number;
|
|
1801
|
+
cosOuter: number;
|
|
1802
|
+
}
|
|
1803
|
+
interface ModelMesh {
|
|
1804
|
+
vertices: ArrayBuffer;
|
|
1805
|
+
indices: Uint32Array;
|
|
1806
|
+
primitives: ModelPrimitive[];
|
|
1807
|
+
/** Картинки базового цвета в виде байтов файла (PNG/JPEG) — декодирует уже GPU-слой. */
|
|
1808
|
+
images: Uint8Array[];
|
|
1809
|
+
/** Габариты в единицах модели (обычно метры) — по ним считается масштаб на карте. */
|
|
1810
|
+
min: [number, number, number];
|
|
1811
|
+
max: [number, number, number];
|
|
1812
|
+
/** Анимация узлов; null — модель неподвижна. */
|
|
1813
|
+
animation: ModelAnimation | null;
|
|
1814
|
+
/** Собственная подсветка объекта из `KHR_lights_punctual`. Пусто у моделей без неё. */
|
|
1815
|
+
lights: ModelLight[];
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
/**
|
|
1819
|
+
* 3D-модель на карте: glTF/GLB по координате.
|
|
1820
|
+
*
|
|
1821
|
+
* Главное требование к этой подсистеме было не «уметь glTF», а **жить в одной
|
|
1822
|
+
* сцене со зданиями**: модель обязана отбрасывать тень на дом и получать тень от
|
|
1823
|
+
* него. Поэтому отдельного прохода теней у моделей нет — они идут в тот же
|
|
1824
|
+
* `renderShadowMap`, что и экструзия. Второй shadow-map мы уже пробовали в
|
|
1825
|
+
* `maps3d-web` и уронили этим кадр (память `maps3d-object-shadows`).
|
|
1826
|
+
*/
|
|
1827
|
+
interface ModelOptions {
|
|
1828
|
+
/** Адрес .glb. Обычный glTF с внешними файлами не поддерживается. */
|
|
1829
|
+
url: string;
|
|
1830
|
+
/**
|
|
1831
|
+
* Своя доставка байтов вместо простого `fetch(url)`.
|
|
1832
|
+
*
|
|
1833
|
+
* Нужна ассетам Objects3D: перед разбором у них кеш в IndexedDB, запрос
|
|
1834
|
+
* ключа и расшифровка AES-GCM. Знание об этом сервисе модели ни к чему —
|
|
1835
|
+
* ей нужен GLB, а откуда он взялся, решает вызывающий. `url` при этом
|
|
1836
|
+
* остаётся: он идёт в сообщения об ошибках.
|
|
1837
|
+
*/
|
|
1838
|
+
fetchBytes?: () => Promise<ArrayBuffer>;
|
|
1839
|
+
coordinates: LngLatLike;
|
|
1840
|
+
/** Высота над землёй, метры. */
|
|
1841
|
+
altitude?: number;
|
|
1842
|
+
/**
|
|
1843
|
+
* Множитель к единицам модели.
|
|
1844
|
+
*
|
|
1845
|
+
* Единицы у моделей какие угодно: в наборе Khronos ToyCar имеет габарит 7 см,
|
|
1846
|
+
* а Fox — 155 «метров». Приводить их к общему знаменателю на глаз бессмысленно,
|
|
1847
|
+
* поэтому масштаб — обязанность приложения. Помогает `fitHeight`.
|
|
1848
|
+
*/
|
|
1849
|
+
scale?: number;
|
|
1850
|
+
/** Подогнать масштаб так, чтобы высота модели стала такой, метры. */
|
|
1851
|
+
fitHeight?: number;
|
|
1852
|
+
/** Разворот вокруг вертикали, градусы (0 — модель смотрит на север). */
|
|
1853
|
+
rotation?: number;
|
|
1854
|
+
/**
|
|
1855
|
+
* Доборные наклоны в градусах — вокруг осей X и Z ПРОСТРАНСТВА МОДЕЛИ.
|
|
1856
|
+
*
|
|
1857
|
+
* Нужны размещениям из редактора: там у объекта три угла, и хотя в наших
|
|
1858
|
+
* данных сейчас ненулевой только разворот, поворот лежащего набок объекта
|
|
1859
|
+
* задаётся именно ими.
|
|
1860
|
+
*/
|
|
1861
|
+
rotationTiltX?: number;
|
|
1862
|
+
rotationTiltZ?: number;
|
|
1863
|
+
/**
|
|
1864
|
+
* Появляться ростом из земли, как здания.
|
|
1865
|
+
*
|
|
1866
|
+
* По умолчанию ВЫКЛЮЧЕНО: модель, добавленную приложением через `addModel`,
|
|
1867
|
+
* оно ожидает увидеть на своём месте сразу. Включают его детальные
|
|
1868
|
+
* 3D-объекты карты — там объект подъезжает сам, и рост объясняет, откуда он.
|
|
1869
|
+
*/
|
|
1870
|
+
growIn?: boolean;
|
|
1871
|
+
visible?: boolean;
|
|
1872
|
+
userData?: Record<string, unknown>;
|
|
1873
|
+
id?: string;
|
|
1874
|
+
}
|
|
1875
|
+
declare class Model {
|
|
1876
|
+
readonly id: string;
|
|
1877
|
+
visible: boolean;
|
|
1878
|
+
userData: Record<string, unknown>;
|
|
1879
|
+
coordinates: LngLatLike;
|
|
1880
|
+
altitude: number;
|
|
1881
|
+
/**
|
|
1882
|
+
* ПОДЪЁМ РЕЛЬЕФОМ, метры: высота земли под моделью.
|
|
1883
|
+
*
|
|
1884
|
+
* Держится отдельно от `altitude`, а не складывается с ней при создании, по двум причинам: сама
|
|
1885
|
+
* высота модели приходит из данных и не должна теряться, а рельеф догружается сетью и меняется
|
|
1886
|
+
* уже после того, как модель поставлена.
|
|
1887
|
+
*/
|
|
1888
|
+
groundZ: number;
|
|
1889
|
+
rotation: number;
|
|
1890
|
+
rotationTiltX: number;
|
|
1891
|
+
rotationTiltZ: number;
|
|
1892
|
+
scale: number;
|
|
1893
|
+
fitHeight?: number;
|
|
1894
|
+
/** Разобранная геометрия; null, пока грузится. */
|
|
1895
|
+
mesh: ModelMesh | null;
|
|
1896
|
+
/** Готовые картинки базового цвета — по индексу из `mesh.images`. */
|
|
1897
|
+
bitmaps: (ImageBitmap | null)[];
|
|
1898
|
+
error: string | null;
|
|
1899
|
+
private loading;
|
|
1900
|
+
/**
|
|
1901
|
+
* Адрес меша — он же ключ ОБЩИХ ресурсов GPU.
|
|
1902
|
+
*
|
|
1903
|
+
* У замены по типу сотни копий одного файла (на замере 634 правила из 653 размещений), и меш у
|
|
1904
|
+
* них буквально один объект: кеш ассетов отдаёт его по этому же адресу. Значит и буферы с
|
|
1905
|
+
* текстурами обязаны быть общими, иначе каждая копия везёт в память свою.
|
|
1906
|
+
*/
|
|
1907
|
+
readonly url: string;
|
|
1908
|
+
private readonly fetchBytes?;
|
|
1909
|
+
private matrix;
|
|
1910
|
+
/**
|
|
1911
|
+
* Появление: доля роста по вертикали, 0..1.
|
|
1912
|
+
*
|
|
1913
|
+
* Ровно то же, что у зданий (`u_height_scale`): объект вырастает из земли, а
|
|
1914
|
+
* не возникает целиком. Для модели это не отдельный проход, а множитель к
|
|
1915
|
+
* вертикальной оси базиса — та же матрица уходит и в карту теней, поэтому
|
|
1916
|
+
* тень растёт вместе с объектом.
|
|
1917
|
+
*/
|
|
1918
|
+
grow: number;
|
|
1919
|
+
/** Куда идёт появление: 1 — вырастает, 0 — уходит в землю. */
|
|
1920
|
+
growTarget: 0 | 1;
|
|
1921
|
+
private animateIn;
|
|
1922
|
+
private shrinkFrom;
|
|
1923
|
+
private growStart;
|
|
1924
|
+
/**
|
|
1925
|
+
* Прозрачность, 0..1 — ею объект уступает место плану этажа и возвращается.
|
|
1926
|
+
*
|
|
1927
|
+
* Отдельно от `grow`, хотя оба про «появился/пропал». Рост — это ПОЯВЛЕНИЕ
|
|
1928
|
+
* объекта на карте (он вырастает из земли, как здание), и он уместен, когда
|
|
1929
|
+
* объект действительно приехал или уехал. А здесь объект никуда не девается:
|
|
1930
|
+
* камера просто зашла внутрь дома, и модель обязана уйти, не привлекая к
|
|
1931
|
+
* себе внимания, — вырастающая обратно из земли башня при выходе из плана
|
|
1932
|
+
* читается как новое событие на карте, которого не было.
|
|
1933
|
+
*/
|
|
1934
|
+
fade: number;
|
|
1935
|
+
/**
|
|
1936
|
+
* Непрозрачность из СТИЛЯ (`osmgl-model-opacity` слоя моделей), 0..1. Множится на `fade`: стиль
|
|
1937
|
+
* задаёт облик, а `fade` — уступку плану этажа, и одно не должно перетирать другое.
|
|
1938
|
+
*/
|
|
1939
|
+
styleOpacity: number;
|
|
1940
|
+
/** Отбрасывает ли модель тень (`osmgl-model-cast-shadows`). Получает её в любом случае. */
|
|
1941
|
+
castShadows: boolean;
|
|
1942
|
+
/**
|
|
1943
|
+
* ПОВЕРХ СЦЕНЫ — значок, а не предмет города (машина навигатора, `NavCarOverlay`).
|
|
1944
|
+
*
|
|
1945
|
+
* Такая модель не рисуется в проходе объёма: слой отдаёт её отдельному проходу в самом конце
|
|
1946
|
+
* кадра (`renderOnTop`), после подписей и с ОЧИЩЕННОЙ глубиной. Дом впереди её не закрывает, а
|
|
1947
|
+
* свои грани она по-прежнему прячет друг за другом. Так ведёт себя курсор Google: машина
|
|
1948
|
+
* крупнее настоящей (держит размер значка), и в честной глубине она бы втыкалась в дома на
|
|
1949
|
+
* каждом повороте. В карту теней и в пачку (`renderInstanced`) такие модели не идут.
|
|
1950
|
+
*/
|
|
1951
|
+
onTop: boolean;
|
|
1952
|
+
/** Итоговая непрозрачность в кадре. */
|
|
1953
|
+
get alpha(): number;
|
|
1954
|
+
private fadeTarget;
|
|
1955
|
+
private fadeFrom;
|
|
1956
|
+
private fadeStart;
|
|
1957
|
+
private matrix32;
|
|
1958
|
+
private nodeMatrix32;
|
|
1959
|
+
private normal32;
|
|
1960
|
+
constructor(options: ModelOptions);
|
|
1961
|
+
get isLoaded(): boolean;
|
|
1962
|
+
/** Скачивает и разбирает модель. Повторные вызовы возвращают ту же работу. */
|
|
1963
|
+
load(onDone?: () => void): Promise<void>;
|
|
1964
|
+
/**
|
|
1965
|
+
* Продвинуть появление. Возвращает true, пока рост не закончен, — это сигнал
|
|
1966
|
+
* карте просить следующий кадр.
|
|
1967
|
+
*
|
|
1968
|
+
* Отсчёт начинается с ПЕРВОГО кадра, где модель готова: до этого её ещё нет
|
|
1969
|
+
* на экране, и стартуй мы от начала загрузки, объект просто возникал бы уже
|
|
1970
|
+
* выросшим.
|
|
1971
|
+
*/
|
|
1972
|
+
advanceGrow(durationSec: number): boolean;
|
|
1973
|
+
/**
|
|
1974
|
+
* Начать уход в землю. Повторный вызов ничего не сбрасывает — иначе объект
|
|
1975
|
+
* на границе радиуса дёргался бы, начиная исчезновение заново каждый кадр.
|
|
1976
|
+
*/
|
|
1977
|
+
retire(): void;
|
|
1978
|
+
/**
|
|
1979
|
+
* Спрятать или показать объект ПЛАВНО.
|
|
1980
|
+
*
|
|
1981
|
+
* Повторный вызов с той же целью ничего не сбрасывает: решение принимается
|
|
1982
|
+
* каждый кадр (план этажа то открыт, то нет), и перезапуск отсчёта оставил бы
|
|
1983
|
+
* прозрачность навсегда замершей у начального значения.
|
|
1984
|
+
*/
|
|
1985
|
+
setHidden(hidden: boolean): void;
|
|
1986
|
+
/**
|
|
1987
|
+
* Продвинуть исчезновение. Возвращает true, пока оно идёт, — карте это сигнал
|
|
1988
|
+
* просить следующий кадр.
|
|
1989
|
+
*/
|
|
1990
|
+
advanceFade(durationSec: number): boolean;
|
|
1991
|
+
/** Объект уже стоит: показать сразу, без повторного роста. */
|
|
1992
|
+
markGrown(): void;
|
|
1993
|
+
/** Вернуть к жизни объект, который уже начал уходить. */
|
|
1994
|
+
revive(): void;
|
|
1995
|
+
/** Эффективный масштаб с учётом `fitHeight`. */
|
|
1996
|
+
effectiveScale(): number;
|
|
1997
|
+
/**
|
|
1998
|
+
* Радиус охватывающей сферы в МЕТРАХ — для отсечения по пирамиде видимости.
|
|
1999
|
+
*
|
|
2000
|
+
* Берём габарит из glTF и умножаем на действующий масштаб. Сфера заведомо больше настоящей формы,
|
|
2001
|
+
* и это правильная сторона ошибки: лишний раз нарисовать дальнее дерево дешевле, чем срезать
|
|
2002
|
+
* половину дворца, попавшую в кадр краем.
|
|
2003
|
+
*/
|
|
2004
|
+
boundingRadiusM(): number;
|
|
2005
|
+
/** Начало координат модели в нормализованном меркаторе. */
|
|
2006
|
+
origin(): {
|
|
2007
|
+
x: number;
|
|
2008
|
+
y: number;
|
|
2009
|
+
};
|
|
2010
|
+
/**
|
|
2011
|
+
* Матрица «единицы модели → локальные координаты объекта».
|
|
2012
|
+
*
|
|
2013
|
+
* Смена системы координат здесь и есть всё содержание: в glTF оси
|
|
2014
|
+
* `x = вправо, y = вверх, z = на зрителя`, у нас — `x = восток, y = ЮГ,
|
|
2015
|
+
* z = вверх`. Отсюда перестановка `(x, y, z) → (x, z, y)`: она же меняет
|
|
2016
|
+
* рукость системы с правой на левую, а наша именно левая.
|
|
2017
|
+
*
|
|
2018
|
+
* Горизонталь переводится в нормализованный меркатор (делением на длину
|
|
2019
|
+
* параллели), вертикаль остаётся В МЕТРАХ: матрица камеры сама домножит её на
|
|
2020
|
+
* `pixelsPerMeter`. Перепутать эти две — классический способ получить модель,
|
|
2021
|
+
* которая с зумом растёт или тонет в земле.
|
|
2022
|
+
*/
|
|
2023
|
+
private localMatrix;
|
|
2024
|
+
/**
|
|
2025
|
+
* Матрица модели в ЗАДАННОМ пространстве объекта.
|
|
2026
|
+
*
|
|
2027
|
+
* Отдельный метод, потому что пространств два: камера и источник света. Обе
|
|
2028
|
+
* матрицы обязаны строиться из одной локальной — иначе глубина, записанная в
|
|
2029
|
+
* карту теней, разойдётся с той, по которой модель потом сравнивают, и модель
|
|
2030
|
+
* начнёт затенять сама себя.
|
|
2031
|
+
*/
|
|
2032
|
+
matrixInSpace(objectMatrix: Float32Array, out32?: Float32Array): Float32Array;
|
|
2033
|
+
/**
|
|
2034
|
+
* Позиция камеры В ЕДИНИЦАХ МОДЕЛИ — для отражений.
|
|
2035
|
+
*
|
|
2036
|
+
* Отражению нужен вектор взгляда, а вершины и собственные светильники модели живут в её
|
|
2037
|
+
* собственном пространстве. Переводить их в мировые координаты пришлось бы на каждый экземпляр;
|
|
2038
|
+
* дешевле перевести туда одну камеру.
|
|
2039
|
+
*
|
|
2040
|
+
* Общей инверсии матрицы здесь нет и не нужно. `localMatrix` — это перестановка осей
|
|
2041
|
+
* (glTF x,y,z → восток, вверх, юг) с ОДНИМ масштабом плюс поворот, а такое обращается
|
|
2042
|
+
* транспонированием: сначала снимаем масштаб, потом поворот, потом перестановку.
|
|
2043
|
+
*
|
|
2044
|
+
* Тонкость с единицами: по горизонтали объектное пространство измеряется в долях меркатора, по
|
|
2045
|
+
* вертикали — в метрах. Поэтому горизонталь домножается на длину параллели, приводя всё к метрам,
|
|
2046
|
+
* и только затем делится на масштаб модели.
|
|
2047
|
+
*/
|
|
2048
|
+
cameraInModelSpace(tr: Transform): [number, number, number];
|
|
2049
|
+
/** Полная матрица модели для камеры. */
|
|
2050
|
+
matrixFor(tr: Transform): Float32Array;
|
|
2051
|
+
/**
|
|
2052
|
+
* Накрывает ли модель эту точку по горизонтали (нормализованный меркатор).
|
|
2053
|
+
*
|
|
2054
|
+
* Нужно маске заменённых зданий: модель дворца стоит не над одним домом, а
|
|
2055
|
+
* над целым комплексом, и «спрятать дом под точкой привязки» оставляет на
|
|
2056
|
+
* карте все его крылья. Пятно берём как ПОВЁРНУТЫЙ прямоугольник габарита:
|
|
2057
|
+
* настоящий контур модели нам взять неоткуда, а прямоугольник её габарита
|
|
2058
|
+
* ошибается наружу лишь на углах.
|
|
2059
|
+
*/
|
|
2060
|
+
coversPoint(mercX: number, mercY: number): boolean;
|
|
2061
|
+
/** Есть ли у модели клип — по нему карта решает, просить ли следующий кадр. */
|
|
2062
|
+
get isAnimated(): boolean;
|
|
2063
|
+
/**
|
|
2064
|
+
* Матрица ПОДВИЖНОГО узла для камеры: та же модельная, домноженная справа на
|
|
2065
|
+
* положение узла в этот момент.
|
|
2066
|
+
*
|
|
2067
|
+
* Отдельный метод, а не поле: узлов у клипа единицы, а считается это на
|
|
2068
|
+
* каждый кадр и на каждую модель — держать кэш дороже, чем перемножить.
|
|
2069
|
+
*/
|
|
2070
|
+
matrixForNode(tr: Transform, node: number, timeSec: number): Float32Array;
|
|
2071
|
+
/**
|
|
2072
|
+
* Матрица подвижного узла в ЗАДАННОМ пространстве объекта.
|
|
2073
|
+
*
|
|
2074
|
+
* Пространств два — камера и источник света, — и обе матрицы обязаны
|
|
2075
|
+
* строиться из одной и той же позы узла: разойдись они, и анимированная
|
|
2076
|
+
* часть начнёт отбрасывать тень от другого своего положения.
|
|
2077
|
+
*/
|
|
2078
|
+
matrixInSpaceForNode(objectMatrix: Float32Array, node: number, timeSec: number, out32: Float32Array): Float32Array;
|
|
2079
|
+
/**
|
|
2080
|
+
* Матрица нормалей: те же оси, но без масштаба и без переноса.
|
|
2081
|
+
* Масштаб у нас неравномерный (горизонталь в меркаторе, вертикаль в метрах),
|
|
2082
|
+
* поэтому нормали нельзя гонять основной матрицей — они перестанут быть
|
|
2083
|
+
* перпендикулярными, и свет поедет.
|
|
2084
|
+
*/
|
|
2085
|
+
normalMatrix(): Float32Array;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
/** Что оверлею нужно от карты (`Map` движка подходит как есть). */
|
|
2089
|
+
interface NavCarHost {
|
|
2090
|
+
addModel(model: Model): unknown;
|
|
2091
|
+
removeModel(model: Model): unknown;
|
|
2092
|
+
triggerRepaint(): unknown;
|
|
2093
|
+
/**
|
|
2094
|
+
* Фокус окон домов (`Map.setFacadeFocus`): в ведении окна стен рисуются только около машины (вариант A владельца,
|
|
2095
|
+
* 22.10.2026). Необязателен — хозяину без домов он не нужен.
|
|
2096
|
+
*/
|
|
2097
|
+
setFacadeFocus?(pos: [number, number] | null): unknown;
|
|
2098
|
+
}
|
|
2099
|
+
declare class NavCarOverlay {
|
|
2100
|
+
private readonly map;
|
|
2101
|
+
/** Модели по вариантам (`navCarModelId`) — создаются при первом выборе и живут, пока жив оверлей. */
|
|
2102
|
+
private models;
|
|
2103
|
+
/** Какая модель сейчас на карте (`null` — никакой). */
|
|
2104
|
+
private shown;
|
|
2105
|
+
private _kind;
|
|
2106
|
+
private _night;
|
|
2107
|
+
constructor(map: NavCarHost);
|
|
2108
|
+
/**
|
|
2109
|
+
* Вариант значка. `'arrow'` — машины нет (стрелку рисует приложение фигурами `navCursorShapes`).
|
|
2110
|
+
* Смена варианта снимает прежнюю модель сразу, новая встаёт со следующего `update`.
|
|
2111
|
+
*/
|
|
2112
|
+
get kind(): NavCarKind;
|
|
2113
|
+
set kind(value: NavCarKind);
|
|
2114
|
+
/**
|
|
2115
|
+
* Ночные цвета значка (тёмная тема карты). Меняет только объёмную стрелку: её модель снимается сразу,
|
|
2116
|
+
* ночная (или дневная) встаёт со следующего `update`.
|
|
2117
|
+
*/
|
|
2118
|
+
get night(): boolean;
|
|
2119
|
+
set night(value: boolean);
|
|
2120
|
+
/** Видна ли машина сейчас (после `update` с вариантом-машиной). */
|
|
2121
|
+
get visible(): boolean;
|
|
2122
|
+
/**
|
|
2123
|
+
* Поставить машину в точку `pos` (`[lon, lat]`) с курсом `bearing` (градусы по часовой) на зуме
|
|
2124
|
+
* карты `zoom`; `roadLiftM` — отметка полотна под машиной (эстакада), 0 на земле. Возвращает
|
|
2125
|
+
* `false`, если выбрана стрелка — тогда рисует приложение.
|
|
2126
|
+
*/
|
|
2127
|
+
update(pos: [number, number], bearing: number, zoom: number, roadLiftM?: number): boolean;
|
|
2128
|
+
/** Убрать машину с карты (выбор остаётся). */
|
|
2129
|
+
clear(): void;
|
|
2130
|
+
/** Снять все модели с карты — оверлей больше не нужен. */
|
|
2131
|
+
destroy(): void;
|
|
2132
|
+
/** Машину убрали — ведения нет: окна снова везде. */
|
|
2133
|
+
private hide;
|
|
2134
|
+
private hideModel;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
/**
|
|
2138
|
+
* ЛЕНТА МАРШРУТА ПО ПОЛОСАМ — геометрия (как у Baidu: на детальной улице линия маршрута становится
|
|
2139
|
+
* полупрозрачной лентой по рекомендуемым полосам).
|
|
2140
|
+
*
|
|
2141
|
+
* Чистые функции без движка и сети (проверка — `lane-ribbon-geom.test.ts`). Вход — план полос
|
|
2142
|
+
* (`api/lanes.ts`, контракт `POST /tiles/ldroads/guidance`) и та же трасса, что ушла в запрос:
|
|
2143
|
+
* все метры плана считаются ВДОЛЬ неё.
|
|
2144
|
+
*
|
|
2145
|
+
* ★ ВСЁ ПАРАМЕТРИЗУЕТСЯ МЕТРОМ МАРШРУТА. Осевая полосы — своя ломаная (её даёт сервер, смещённую по
|
|
2146
|
+
* профилю), но каждая её точка получает «станцию» — проекцию на трассу. Тогда «полоса на метре s»
|
|
2147
|
+
* — интерполяция по станциям, и всё остальное становится сложением точек на одной станции:
|
|
2148
|
+
* лента на две соседние полосы — середина их осевых, S-переход из полосы в полосу — смесь двух
|
|
2149
|
+
* точек с весом smoothstep, стык с обычной линией маршрута — смесь с точкой самой трассы. Своего
|
|
2150
|
+
* смещения кривых для этого не нужно, и лента не «срезает» повороты осевой.
|
|
2151
|
+
*
|
|
2152
|
+
* ★ ПЛОСКАЯ СИСТЕМА МЕТРОВ вокруг трассы (равнопромежуточная): на маршруте в несколько километров
|
|
2153
|
+
* ошибка — доли процента, а разница с серверными метрами ровно такая же, как у любых двух
|
|
2154
|
+
* реализаций длины.
|
|
2155
|
+
*/
|
|
2156
|
+
|
|
2157
|
+
type LonLat = [number, number];
|
|
2158
|
+
interface XY {
|
|
2159
|
+
x: number;
|
|
2160
|
+
y: number;
|
|
2161
|
+
}
|
|
2162
|
+
/** Плоская система метров вокруг точки. */
|
|
2163
|
+
interface Frame {
|
|
2164
|
+
toXY(p: LonLat): XY;
|
|
2165
|
+
toLonLat(p: XY): LonLat;
|
|
2166
|
+
}
|
|
2167
|
+
declare function frameAt(origin: LonLat): Frame;
|
|
2168
|
+
/** Гладкая ступенька 0→1: нулевая производная на концах — S-переход без излома на входе и выходе. */
|
|
2169
|
+
declare const smoothstep: (t: number) => number;
|
|
2170
|
+
interface Measured {
|
|
2171
|
+
pts: XY[];
|
|
2172
|
+
/** Накопленная длина к каждой точке, метры. */
|
|
2173
|
+
cum: number[];
|
|
2174
|
+
length: number;
|
|
2175
|
+
}
|
|
2176
|
+
declare function measure(pts: XY[], lengths?: number[]): Measured;
|
|
2177
|
+
/**
|
|
2178
|
+
* Длина звена — ТОЙ ЖЕ ФОРМУЛОЙ, ЧТО У СЕРВЕРА (`LdLaneGraph.DistanceM`, `LD-LANE-GUIDANCE.md`,
|
|
2179
|
+
* «Метры»): плоская на каждом звене, косинус СРЕДНЕЙ широты звена. Все метры плана — вдоль трассы
|
|
2180
|
+
* этой мерой; гаверсинус или общий косинус дали бы расхождение до 10 м на 10 км.
|
|
2181
|
+
*/
|
|
2182
|
+
declare function linkM(a: LonLat, b: LonLat): number;
|
|
2183
|
+
/** Трасса `[lon, lat]` в метрах: точки — в плоской системе, накопленная длина — мерой сервера. */
|
|
2184
|
+
declare function measureRoute(geometry: LonLat[], frame: Frame): Measured;
|
|
2185
|
+
/** Точка на метре `s` (за концами — продолжение крайнего отрезка). */
|
|
2186
|
+
declare function pointAt(m: Measured, s: number): XY;
|
|
2187
|
+
/** Курс на метре `s`: единичный вектор по ходу. */
|
|
2188
|
+
declare function tangentAt(m: Measured, s: number): XY;
|
|
2189
|
+
/** Курс в градусах по часовой от севера — для камеры. */
|
|
2190
|
+
declare function bearingAt(m: Measured, s: number, ahead?: number): number;
|
|
2191
|
+
/**
|
|
2192
|
+
* Метр трассы, ближайший к точке. `from`/`to` — окно поиска: трасса может пройти мимо одного
|
|
2193
|
+
* места дважды (разворот, петля), и полоса одного участка не должна «прилипнуть» к другому.
|
|
2194
|
+
*/
|
|
2195
|
+
declare function project(m: Measured, p: XY, from?: number, to?: number): {
|
|
2196
|
+
s: number;
|
|
2197
|
+
d: number;
|
|
2198
|
+
};
|
|
2199
|
+
/** Дробление ломаной: точки не реже `step` метров. */
|
|
2200
|
+
declare function densify(pts: XY[], step: number): XY[];
|
|
2201
|
+
/** Кусок ломаной между метрами `from` и `to` её СОБСТВЕННОЙ длины. */
|
|
2202
|
+
declare function sliceLine(pts: XY[], from: number, to: number): XY[];
|
|
2203
|
+
/**
|
|
2204
|
+
* СМЕЩЁННАЯ КРИВАЯ: каждая точка сдвигается на свой `offsets[i]` метров вправо по ходу.
|
|
2205
|
+
*
|
|
2206
|
+
* Нормаль в вершине — биссектриса соседних отрезков, длина поправлена на угол (как у стыка
|
|
2207
|
+
* «miter»), но не больше чем вдвое: на остром изломе честный miter улетает на десятки метров.
|
|
2208
|
+
*/
|
|
2209
|
+
declare function offsetLine(pts: XY[], offsets: number[] | number): XY[];
|
|
2210
|
+
/** Боковой сдвиг точки от трассы на метре `s`, метры вправо по ходу. */
|
|
2211
|
+
declare function lateralAt(route: Measured, s: number, p: XY): number;
|
|
2212
|
+
/** Осевая полосы со станциями: `s[i]` — метр трассы точки `pts[i]`, строго растёт. */
|
|
2213
|
+
interface Track {
|
|
2214
|
+
s: number[];
|
|
2215
|
+
pts: XY[];
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Осевая полосы → станции. Точки, станция которых не растёт (осевая шире трассы на развороте,
|
|
2219
|
+
* хвост за перекрёстком, загнутый назад), выкидываются: функция «точка по метру» обязана быть
|
|
2220
|
+
* однозначной.
|
|
2221
|
+
*/
|
|
2222
|
+
declare function laneTrack(route: Measured, centre: XY[], window: [number, number]): Track | null;
|
|
2223
|
+
/**
|
|
2224
|
+
* Точка полосы на метре `s`. За концами осевой — тот же боковой сдвиг от трассы, что у крайней
|
|
2225
|
+
* точки: лента не обрывается и не загибается к оси, если осевая чуть короче участка.
|
|
2226
|
+
*/
|
|
2227
|
+
declare function trackAt(route: Measured, tr: Track, s: number): XY;
|
|
2228
|
+
/** Точка, сдвинутая от трассы на метре `s` на `off` метров вправо. */
|
|
2229
|
+
declare function offsetPoint(route: Measured, s: number, off: number): XY;
|
|
2230
|
+
/** Отступ ленты от краёв полосы (на обе стороны вместе): лента не должна лечь на разметку. */
|
|
2231
|
+
declare const RIBBON_MARGIN_M = 0.7;
|
|
2232
|
+
/** Шаг ленты вдоль маршрута. */
|
|
2233
|
+
declare const RIBBON_STEP_M = 2;
|
|
2234
|
+
/** Длина стыка ленты с обычной линией маршрута на краях покрытия. */
|
|
2235
|
+
declare const JOIN_M = 25;
|
|
2236
|
+
/**
|
|
2237
|
+
* Скругление оси ленты на изломах (сигма гауссова окна, метры). Излом — поворот трассы на
|
|
2238
|
+
* перекрёстке или скачок между осевыми соседних отрезков улицы: без скругления контур ленты на
|
|
2239
|
+
* внутренней стороне угла выворачивался петлёй. Радиус дуги на повороте 90° — около 1.6 сигмы.
|
|
2240
|
+
*/
|
|
2241
|
+
declare const CORNER_SMOOTH_M = 4.5;
|
|
2242
|
+
/** Излом — поворот направления в точке оси больше этого (градусы). */
|
|
2243
|
+
declare const CORNER_DEG = 8;
|
|
2244
|
+
/**
|
|
2245
|
+
* `band-passed`/`allowed-passed` — пройденная в ведении часть ленты (серая, `lib/route-progress.ts`);
|
|
2246
|
+
* `buildRibbon` их не строит.
|
|
2247
|
+
*/
|
|
2248
|
+
type RibbonKind = 'band' | 'allowed' | 'chevron' | 'band-passed' | 'allowed-passed';
|
|
2249
|
+
/**
|
|
2250
|
+
* ФОРМА КОНЦА ЛЕНТЫ там, где она кончается без продолжения (начало и конец маршрута, края дыр в
|
|
2251
|
+
* плане; не стык с линией маршрута и не разрез в точке машины или по кускам):
|
|
2252
|
+
* - `round` — полукруг радиусом полуширины, касательный к кромкам (как `line-cap: round`);
|
|
2253
|
+
* - `flat` — прямой срез поперёк ленты в последней точке оси (как было до скруглений);
|
|
2254
|
+
* - `arrow` — заострённый конец-стрелка на КОНЦЕ маршрута (как у Baidu на точке назначения):
|
|
2255
|
+
* кромки сходятся в вершину впереди оси на `ARROW_TIP_K` полуширины; начало при этом — `round`.
|
|
2256
|
+
* Скругление и стрелка выходят за последнюю точку оси (на полуширину и `ARROW_TIP_K` полуширины).
|
|
2257
|
+
*/
|
|
2258
|
+
type RibbonCap = 'round' | 'flat' | 'arrow';
|
|
2259
|
+
declare const RIBBON_CAPS: readonly RibbonCap[];
|
|
2260
|
+
declare const DEFAULT_RIBBON_CAP: RibbonCap;
|
|
2261
|
+
declare const isRibbonCap: (v: unknown) => v is RibbonCap;
|
|
2262
|
+
/** Отрезков на полукруг конца: излом на стыке кромки и полукруга — π/(2·N), 2.8°. */
|
|
2263
|
+
declare const CAP_SEGMENTS = 32;
|
|
2264
|
+
/** Длина острия стрелки за последней точкой оси, в полуширинах ленты (угол при вершине ≈ 67°). */
|
|
2265
|
+
declare const ARROW_TIP_K = 1.5;
|
|
2266
|
+
interface RibbonFeature {
|
|
2267
|
+
kind: RibbonKind;
|
|
2268
|
+
/** Осевая куска, `[lon, lat]` (у ленты — уже скруглённая на изломах). */
|
|
2269
|
+
line: LonLat[];
|
|
2270
|
+
/** Ширина, метры: у ленты — наибольшая по куску (сама форма — в `outline`). */
|
|
2271
|
+
width: number;
|
|
2272
|
+
/** Уровень (эстакада 1, земля 0, тоннель −1) у каждой точки `line` — по нему берётся высота. */
|
|
2273
|
+
levels: number[];
|
|
2274
|
+
/**
|
|
2275
|
+
* Контур площадной фичи (`band`, `allowed`) — замкнутое кольцо `[lon, lat]` против
|
|
2276
|
+
* часовой, без самопересечений. Рисуется готовым многоугольником: форму ленты считает клиент, а
|
|
2277
|
+
* не движок из оси и ширины (у той сборки на изломе внутренняя кромка выворачивалась, и earcut
|
|
2278
|
+
* заливал треугольник через квартал).
|
|
2279
|
+
*/
|
|
2280
|
+
outline?: LonLat[];
|
|
2281
|
+
/**
|
|
2282
|
+
* Метр трассы у каждой точки `line` (не убывает) — по нему пройденная часть ленты отрезается в
|
|
2283
|
+
* ведении (`lib/route-progress.ts`). У шеврона — метры его двух концов.
|
|
2284
|
+
*/
|
|
2285
|
+
stations?: number[];
|
|
2286
|
+
/** Ширина ленты у каждой точки `line`, метры (площадные фичи) — контур отрезанной части. */
|
|
2287
|
+
widths?: number[];
|
|
2288
|
+
/**
|
|
2289
|
+
* Форма начала и конца (площадные фичи), `RibbonCap`: уже с учётом стыков — где лента переходит
|
|
2290
|
+
* в линию маршрута, там `flat`. Куски ленты (ведение, резка по `CHUNK_M`) берут форму только у
|
|
2291
|
+
* своих настоящих концов, на разрезах — прямо.
|
|
2292
|
+
*/
|
|
2293
|
+
caps?: [RibbonCap, RibbonCap];
|
|
2294
|
+
}
|
|
2295
|
+
interface Ribbon {
|
|
2296
|
+
features: RibbonFeature[];
|
|
2297
|
+
/** Где обычная линия маршрута уступает ленте: метры вдоль трассы. */
|
|
2298
|
+
hidden: [number, number][];
|
|
2299
|
+
/**
|
|
2300
|
+
* Ось ОСНОВНОЙ ленты для позы машины навигатора (`laneCarTrack` в `nav-car.ts`): те же точки и метры, что у её
|
|
2301
|
+
* фич `band` (`line`, `stations`), и доля плана в позе машины. Нет основной ленты — поля нет.
|
|
2302
|
+
*/
|
|
2303
|
+
car?: RibbonCar;
|
|
2304
|
+
}
|
|
2305
|
+
/**
|
|
2306
|
+
* МАШИНА ЕДЕТ ПО ЛЕНТЕ (этап 1 «как обычный режим полос Baidu/Amap», 23.10.2026): ось основной ленты — та самая, что
|
|
2307
|
+
* нарисована (`band.line` после скругления изломов), с метрами трассы и ВЕСОМ ПЛАНА в каждой точке:
|
|
2308
|
+
* · 1 — на куске покрытия плана (машина на оси ленты, в том числе пока лента переходит в полосу из ленты вне
|
|
2309
|
+
* покрытия по `RAMP_SLOPE`);
|
|
2310
|
+
* · вне покрытия — `CAR_OUTSIDE_WEIGHT` = 1 (решение владельца 24.10.2026): лента там — догадка «полполосы справа»
|
|
2311
|
+
* (`OUTSIDE_LANE_M`), и машина едет по ней же, а не по линии маршрута на 1,65 м левее. Плавность на краях куска
|
|
2312
|
+
* даёт сама ось ленты: она входит в полосу и выходит из неё по `RAMP_SLOPE`;
|
|
2313
|
+
* · при весе вне покрытия меньше 1 между ними — подъезд на `JOIN_M` до края куска (`carWeight`, smoothstep).
|
|
2314
|
+
* Метры не убывают; кусков основной ленты несколько (лента только на покрытии, `outside: false`) — они идут подряд.
|
|
2315
|
+
*/
|
|
2316
|
+
interface RibbonCar {
|
|
2317
|
+
stations: number[];
|
|
2318
|
+
line: LonLat[];
|
|
2319
|
+
weights: number[];
|
|
2320
|
+
}
|
|
2321
|
+
/**
|
|
2322
|
+
* Склеить пересекающиеся и близкие отрезки покрытия: щель меньше `gap` — это перекрёсток между
|
|
2323
|
+
* двумя участками, и лента через него идёт непрерывно, а не прыгает в линию маршрута и обратно.
|
|
2324
|
+
*/
|
|
2325
|
+
declare function mergeRanges(ranges: [number, number][], gap?: number, limit?: [number, number]): [number, number][];
|
|
2326
|
+
/** Подряд идущие номера полос — группы ленты (две соседние полосы — одна лента во всю ширину). */
|
|
2327
|
+
declare function contiguousGroups(lanes: PlanLane[]): PlanLane[][];
|
|
2328
|
+
/**
|
|
2329
|
+
* ЛЕНТА ВНЕ ПОКРЫТИЯ: где детальных дорог нет, маршрут тоже идёт лентой — шириной в одну полосу,
|
|
2330
|
+
* в правой половине дороги по ходу (смещение от оси трассы на полполосы). Полос там никто не
|
|
2331
|
+
* знает; ширина и сторона — по умолчанию, свои можно передать (`BuildOptions.outside`).
|
|
2332
|
+
*/
|
|
2333
|
+
declare const OUTSIDE_LANE_M = 3.3;
|
|
2334
|
+
/**
|
|
2335
|
+
* Переход между лентой по полосам и лентой вне покрытия: ширина и смещение меняются не круче
|
|
2336
|
+
* этого (метров на метр; с запасом до 5 см на метр — сглаживание оси добавляет свои доли) — длина
|
|
2337
|
+
* перехода `1.5 · Δ / RAMP_SLOPE` (у smoothstep наибольший наклон в полтора раза больше среднего;
|
|
2338
|
+
* разница ширин считается вдвое — `evenWidths` у порога ровняет ширину вдвое круче), но не короче
|
|
2339
|
+
* `JOIN_M` и не длиннее половины куска покрытия.
|
|
2340
|
+
*/
|
|
2341
|
+
declare const RAMP_SLOPE = 0.045;
|
|
2342
|
+
interface OutsideOptions {
|
|
2343
|
+
/** Ширина полосы вне покрытия, метры (лента — она минус `RIBBON_MARGIN_M`). */
|
|
2344
|
+
laneM?: number;
|
|
2345
|
+
/** Смещение оси ленты от оси трассы вправо по ходу, метры (односторонняя улица — 0). */
|
|
2346
|
+
offsetM?: number;
|
|
2347
|
+
}
|
|
2348
|
+
interface BuildOptions {
|
|
2349
|
+
/** Склеивать покрытие через щель не длиннее (метры). */
|
|
2350
|
+
gapM?: number;
|
|
2351
|
+
/**
|
|
2352
|
+
* Лента и вне покрытия (по умолчанию — да, `OUTSIDE_LANE_M` справа от оси). `false` — как
|
|
2353
|
+
* раньше: лента только на покрытии, на его краях сходится к линии маршрута.
|
|
2354
|
+
*/
|
|
2355
|
+
outside?: OutsideOptions | false;
|
|
2356
|
+
/**
|
|
2357
|
+
* Форма концов ленты (`RibbonCap`, по умолчанию `round`). У основной ленты — начало (`arrow` там
|
|
2358
|
+
* — `round`) и конец; прочие группы и разрешённые полосы — `round`, у `flat` — прямые.
|
|
2359
|
+
*/
|
|
2360
|
+
cap?: RibbonCap;
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* ЛЕНТА ИЗ ПЛАНА.
|
|
2364
|
+
*
|
|
2365
|
+
* Главный путь — одна непрерывная лента на каждый кусок покрытия: по участку — на группу
|
|
2366
|
+
* рекомендуемых полос (той, куда ведёт перестроение, иначе — на самую широкую), в `changes` —
|
|
2367
|
+
* S-переход из полосы в группу, между участками — плавная смесь конца одного с началом другого.
|
|
2368
|
+
* Вне покрытия (`BuildOptions.outside`, по умолчанию) лента продолжается шириной в одну полосу
|
|
2369
|
+
* справа от оси трассы, и линия маршрута уступает ей целиком; на краях покрытия ширина и смещение
|
|
2370
|
+
* переходят плавно (`RAMP_SLOPE`). С `outside: false` — по-старому: на краях покрытия ось ленты
|
|
2371
|
+
* сходится к оси трассы, дальше идёт линия маршрута. Остальные
|
|
2372
|
+
* группы рекомендуемых — своими лентами по участку, `allowed` — бледной лентой, `other` — ничего.
|
|
2373
|
+
* Все площадные фичи — готовым контуром (`outline`), шевроны — отдельными прямыми.
|
|
2374
|
+
*/
|
|
2375
|
+
declare function buildRibbon(plan: LanePlan, geometry: LonLat[], opts?: BuildOptions): Ribbon;
|
|
2376
|
+
/**
|
|
2377
|
+
* ПОВОРОТ ЧЕРЕЗ УЗЕЛ — по графу полос, как скругление угла асфальта у стенда. Полоса приходящего
|
|
2378
|
+
* отрезка и полоса уходящего — две прямые (направления берутся за пределами узла, где осевые
|
|
2379
|
+
* чистые); ось ленты в окне `[lo, hi]` — прямая по приходящей полосе до точки их пересечения и
|
|
2380
|
+
* прямая по уходящей, а дугу на изломе делает `smoothCorners` (гауссово окно по длине: касательная
|
|
2381
|
+
* и кривизна непрерывны, кривизна — колоколом). Раньше ось в узле шла по осевым отрезков как
|
|
2382
|
+
* есть: конец приходящей полосы торчал за начало дуги (сервер ведёт осевую до середины узла, а
|
|
2383
|
+
* уходящая полоса начинается сбоку от неё), и кромки ленты выходили со ступенькой и горбом.
|
|
2384
|
+
*
|
|
2385
|
+
* Окно — до `TURN_WINDOW_M` в каждую сторону от стыка (не дальше половины отрезка), поворот
|
|
2386
|
+
* положе `TURN_MIN_DEG` не трогается: там стык отрезков — продолжение улицы. Точки окна
|
|
2387
|
+
* раскладываются по новой кривой пропорционально станциям; ширина переходит от приходящей к
|
|
2388
|
+
* уходящей плавно (smoothstep), уровень — прежний.
|
|
2389
|
+
*/
|
|
2390
|
+
declare const TURN_WINDOW_M = 14;
|
|
2391
|
+
declare const TURN_MIN_DEG = 20;
|
|
2392
|
+
/** Функция нормального распределения (Абрамовиц — Стиган 7.1.26, ошибка < 1.5e-7). */
|
|
2393
|
+
declare function normalCdf(z: number): number;
|
|
2394
|
+
/**
|
|
2395
|
+
* ВЕС ПЛАНА ВНЕ ПОКРЫТИЯ (решение владельца 24.10.2026): 1 — машина и вне покрытия на оси ленты «полполосы справа»,
|
|
2396
|
+
* как в обычном режиме полос Baidu/Amap; 0 — на линии маршрута (так было в этапе 1, 23.10.2026).
|
|
2397
|
+
*/
|
|
2398
|
+
declare const CAR_OUTSIDE_WEIGHT = 1;
|
|
2399
|
+
/**
|
|
2400
|
+
* ВЕС ПЛАНА В ПОЗЕ МАШИНЫ на метре `s` (`RibbonCar.weights`): на куске покрытия `[a, b]` — 1 (машина на ленте, в
|
|
2401
|
+
* том числе пока лента переходит из полосы в ленту вне покрытия), вне кусков — `CAR_OUTSIDE_WEIGHT`, за `JOIN_M` до
|
|
2402
|
+
* края куска — подъезд smoothstep к 1 (при весе вне покрытия меньше 1: машина съезжает на ленту по метрам).
|
|
2403
|
+
*/
|
|
2404
|
+
declare function carWeight(s: number, runs: [number, number][]): number;
|
|
2405
|
+
/**
|
|
2406
|
+
* Значение на метре `s` по точкам с метрами `st` (не убывают): линейно между соседними, за концами — крайнее. На
|
|
2407
|
+
* повторе метра (окно поворота, `filletTurn`) берётся первая точка.
|
|
2408
|
+
*/
|
|
2409
|
+
declare function valueAt(st: number[], v: number[], s: number): number;
|
|
2410
|
+
/** Станции от `a` до `b` через `step`, концы включены. */
|
|
2411
|
+
declare function stationsBetween(a: number, b: number, step: number): number[];
|
|
2412
|
+
/**
|
|
2413
|
+
* РОВНАЯ ШИРИНА: полосы соседних отрезков одной улицы расходятся на сантиметры (3.5 и 3.333 м), и
|
|
2414
|
+
* лента от этого шла бы ступеньками. Ширина, отличающаяся от средней по куску меньше чем на
|
|
2415
|
+
* `EVEN_WIDTH_M`, приравнивается к ней; настоящая смена (лента на две полосы) остаётся, а между
|
|
2416
|
+
* порогом и двумя порогами ширина непрерывно возвращается к своей.
|
|
2417
|
+
*/
|
|
2418
|
+
declare const EVEN_WIDTH_M = 0.5;
|
|
2419
|
+
declare function evenWidths(widths: number[]): number[];
|
|
2420
|
+
/** Допуск прореживания оси шевронов, метры. */
|
|
2421
|
+
declare const CHEVRON_TOLERANCE_M = 0.1;
|
|
2422
|
+
/** Прямой кусок оси короче этого шевронов не получает (метры). */
|
|
2423
|
+
declare const CHEVRON_MIN_RUN_M = 6;
|
|
2424
|
+
/** Дуглас — Пойкер: номера оставленных точек (концы — всегда). */
|
|
2425
|
+
declare function simplifyIdx(pts: XY[], tol: number): number[];
|
|
2426
|
+
/** Шаг точек ленты после пересборки по собственной длине оси, метры. */
|
|
2427
|
+
declare const OUTLINE_STEP_M = 1;
|
|
2428
|
+
/**
|
|
2429
|
+
* Ломаная с атрибутами → точки через равный шаг ПО СОБСТВЕННОЙ ДЛИНЕ. Станции маршрута для этого не
|
|
2430
|
+
* годятся: на внутренней стороне поворота полоса короче трассы, и точки там сбивались в кучу —
|
|
2431
|
+
* окно сглаживания сжималось, дуга выходила с радиусом в пару метров.
|
|
2432
|
+
*/
|
|
2433
|
+
declare function resampleLine(pts: XY[], attrs: number[][], step?: number): {
|
|
2434
|
+
pts: XY[];
|
|
2435
|
+
attrs: number[][];
|
|
2436
|
+
};
|
|
2437
|
+
/** Сглаживание ширины по точкам оси (сигма — в точках, т. е. метрах после `resampleLine`). */
|
|
2438
|
+
declare const WIDTH_SMOOTH_PTS = 2;
|
|
2439
|
+
/**
|
|
2440
|
+
* ШИРИНА БЕЗ ИЗЛОМОВ: ширина приходит по станциям через 2 м и после пересборки оси через метр
|
|
2441
|
+
* идёт ломаной — на переходе ширины (поворот на две полосы, `filletTurn`) кромка от этого шла
|
|
2442
|
+
* зубцами через точку. Гаусс по точкам; ровная ширина остаётся ровной (только там, где она
|
|
2443
|
+
* меняется, что-то и сглаживается), концы — на месте.
|
|
2444
|
+
*/
|
|
2445
|
+
declare function smoothWidths(w: number[], sigma?: number): number[];
|
|
2446
|
+
/**
|
|
2447
|
+
* СКРУГЛЕНИЕ ИЗЛОМОВ ОСИ. Точки идут через равный шаг (`resampleLine`); у излома ось сглаживается
|
|
2448
|
+
* гауссовым окном, и только там: в пределах двух сигм от излома берётся сглаженная точка, дальше
|
|
2449
|
+
* за полторы сигмы — плавный возврат к исходной. Прямые участки и S-переходы (они и так гладкие)
|
|
2450
|
+
* остаются точно в полосе.
|
|
2451
|
+
*
|
|
2452
|
+
* Сигма — ПО УГЛУ ИЗЛОМА: поворот на перекрёстке (90°) — `CORNER_SMOOTH_M`, дуга радиусом около
|
|
2453
|
+
* 1.6 сигмы; пологий излом улицы (20–30°) — пропорционально меньше, иначе лента срезала бы его и
|
|
2454
|
+
* съезжала с полосы на кромку. Изломы ближе трёх метров друг к другу — один (на повороте осевые
|
|
2455
|
+
* соседних отрезков стыкуются двумя изломами со скачком между ними).
|
|
2456
|
+
*
|
|
2457
|
+
* За концами ломаная продолжается ЦЕНТРАЛЬНЫМ отражением (`2·p₀ − pₖ`): прямая остаётся прямой, а
|
|
2458
|
+
* крайние точки — на месте, то есть лента по-прежнему начинается ровно на конце линии маршрута.
|
|
2459
|
+
*/
|
|
2460
|
+
declare function smoothCorners(pts: XY[], sigmaM?: number): XY[];
|
|
2461
|
+
/**
|
|
2462
|
+
* КОНТУР ЛЕНТЫ: правая кромка по ходу, форма конца (`caps[1]`), левая кромка обратно, форма начала
|
|
2463
|
+
* (`caps[0]`) — кольцо против часовой. Нормаль в точке — по хорде соседей (ось гладкая, «miter» не
|
|
2464
|
+
* нужен). Точка кромки, которая пошла бы назад относительно оси (кривизна круче полуширины),
|
|
2465
|
+
* выбрасывается: кромка не делает петлю. Концы кромок — ровно `centre ± normal · half` крайних
|
|
2466
|
+
* точек, от них и строится форма конца (`capPoints`).
|
|
2467
|
+
*/
|
|
2468
|
+
declare function outlineOf(centre: XY[], half: number[], normals?: XY[], caps?: [RibbonCap, RibbonCap]): XY[];
|
|
2469
|
+
/**
|
|
2470
|
+
* ФОРМА КОНЦА — точки контура МЕЖДУ концами кромок (сами концы кромок — в `edgesOf`). `c` — крайняя
|
|
2471
|
+
* точка оси, `nrm` — нормаль вправо в ней, `h` — полуширина. Конец (`atEnd`) — от правой кромки
|
|
2472
|
+
* через переднюю точку к левой, начало — от левой через заднюю к правой: кольцо остаётся против
|
|
2473
|
+
* часовой. Полукруг `c + h·(±n·cos θ ± t·sin θ)`, θ = kπ/`CAP_SEGMENTS`: в θ = 0 касательная к
|
|
2474
|
+
* нему — направление оси, то есть кромка переходит в дугу без излома (ось у конца — крайний отрезок,
|
|
2475
|
+
* по нему и нормаль, `normalsOf`).
|
|
2476
|
+
*/
|
|
2477
|
+
declare function capPoints(c: XY, nrm: XY, h: number, cap: RibbonCap, atEnd: boolean): XY[];
|
|
2478
|
+
/** Кромки ленты по ходу: правая и левая (то, из чего `outlineOf` собирает кольцо). */
|
|
2479
|
+
declare function edgesOf(centre: XY[], half: number[], normals?: XY[]): {
|
|
2480
|
+
right: XY[];
|
|
2481
|
+
left: XY[];
|
|
2482
|
+
};
|
|
2483
|
+
/**
|
|
2484
|
+
* Нормали оси вправо по ходу — по хорде соседей (у концов — по крайнему отрезку). Отдельно от
|
|
2485
|
+
* `outlineOf`: у двух половин ленты, разрезанной в точке машины (`lib/route-progress.ts`), нормаль
|
|
2486
|
+
* в точке разреза обязана быть одной — тогда кромки сходятся без щели и нахлёста.
|
|
2487
|
+
*/
|
|
2488
|
+
declare function normalsOf(centre: XY[]): XY[];
|
|
2489
|
+
interface RoutePiece {
|
|
2490
|
+
coords: LonLat[];
|
|
2491
|
+
/** Для каждой точки куска — номер отрезка исходной трассы, на котором она лежит. */
|
|
2492
|
+
source: number[];
|
|
2493
|
+
}
|
|
2494
|
+
/**
|
|
2495
|
+
* Трасса без скрытых отрезков (метры) — куски для обычной линии маршрута. На концах куска точка
|
|
2496
|
+
* ставится ровно на границу: линия упирается в начало ленты без щели и без нахлёста.
|
|
2497
|
+
*/
|
|
2498
|
+
declare function routePieces(geometry: LonLat[], hidden: [number, number][]): RoutePiece[];
|
|
2499
|
+
/** Метры трассы до точек (манёвров): проекция по порядку, каждая не раньше предыдущей. */
|
|
2500
|
+
declare function metresAlong(geometry: LonLat[], points: LonLat[]): number[];
|
|
2501
|
+
/** Трасса в метрах — для ведения (точка и курс по метру). */
|
|
2502
|
+
declare function routeMeasure(geometry: LonLat[]): {
|
|
2503
|
+
frame: Frame;
|
|
2504
|
+
route: Measured;
|
|
2505
|
+
};
|
|
2506
|
+
/** Высота полотна в точке на уровне, метры. */
|
|
2507
|
+
type HeightAt = (p: LonLat, level: number) => number;
|
|
2508
|
+
/** Этаж эстакады стенда (`LEVEL_HEIGHT` у `vendor/city.js`) — запасная высота без сцены. */
|
|
2509
|
+
declare const LEVEL_HEIGHT_M = 8;
|
|
2510
|
+
declare const fallbackHeight: HeightAt;
|
|
2511
|
+
/**
|
|
2512
|
+
* Свойства высоты фичи — как у краски стенда: ровная — `lift`, со склоном — `liftPath`
|
|
2513
|
+
* (`lon,lat,h;…`, разбирает движок, `parseLift`). Нулевая высота — без свойства вовсе.
|
|
2514
|
+
*/
|
|
2515
|
+
declare function liftProps(f: RibbonFeature, heightAt: HeightAt): Record<string, number | string>;
|
|
2516
|
+
/** То же по готовым отметкам точек `line` — ведение режет ленту, не спрашивая поле высот заново. */
|
|
2517
|
+
declare function liftPropsOf(line: LonLat[], hs: number[]): Record<string, number | string>;
|
|
2518
|
+
|
|
2519
|
+
/**
|
|
2520
|
+
* ПЛАН ПОЛОС ВДОЛЬ МАРШРУТА — `POST /tiles/ldroads/guidance` (под общим ключом шлюза, как остальные
|
|
2521
|
+
* `/tiles/ldroads/*`; контракт — `docs/map-v2/LD-LANE-GUIDANCE.md` монорепозитория).
|
|
2522
|
+
*
|
|
2523
|
+
* Перенесено из веб-карты (`src/WebMap/src/api/lanes.ts` + `map/lane-plan.ts`) без изменения
|
|
2524
|
+
* поведения: типы, тело запроса (`lanePlanRequest`, как `requestFor` веба) и разбор ответа
|
|
2525
|
+
* (`parseLanePlan`). Пара у портов — `LanePlan.kt` / `LanePlan.swift`.
|
|
2526
|
+
*
|
|
2527
|
+
* ★ Сервис НЕОБЯЗАТЕЛЕН: нет ручки (404/405/501), ошибка, мусор в ответе, пустое покрытие — всё это
|
|
2528
|
+
* `null`, и карта рисует обычную линию маршрута. Сеть — у приложения: `fetchLanePlan` берёт функцию
|
|
2529
|
+
* `fetch` снаружи (и адрес — `Endpoints`/своя база), сам движок в сеть не ходит.
|
|
2530
|
+
*
|
|
2531
|
+
* ★ Заглушка веба `?laneMock=1` (план по опубликованным улицам на клиенте) — НЕ здесь: она про
|
|
2532
|
+
* отладку сайта и тянет его данные.
|
|
2533
|
+
*/
|
|
2534
|
+
|
|
2535
|
+
type LaneState = 'recommended' | 'allowed' | 'other';
|
|
2536
|
+
interface PlanLane {
|
|
2537
|
+
laneId: string;
|
|
2538
|
+
/** 0 — левая по ходу. */
|
|
2539
|
+
index: number;
|
|
2540
|
+
widthM: number;
|
|
2541
|
+
/** Осевая полосы ПО ХОДУ, `[lon, lat]`. */
|
|
2542
|
+
centre: [number, number][];
|
|
2543
|
+
state: LaneState;
|
|
2544
|
+
turns: string[];
|
|
2545
|
+
}
|
|
2546
|
+
interface PlanSegment {
|
|
2547
|
+
fromM: number;
|
|
2548
|
+
toM: number;
|
|
2549
|
+
wayId: number | string;
|
|
2550
|
+
dir: string;
|
|
2551
|
+
/** 1 — эстакада, 0 — земля, −1 — тоннель. */
|
|
2552
|
+
level: number;
|
|
2553
|
+
/** В какой полосе лента в начале отрезка (добавочное поле сервера; у заглушки может не быть). */
|
|
2554
|
+
ribbonLaneId?: string;
|
|
2555
|
+
lanes: PlanLane[];
|
|
2556
|
+
}
|
|
2557
|
+
interface PlanChange {
|
|
2558
|
+
fromLane: string;
|
|
2559
|
+
toLane: string;
|
|
2560
|
+
startM: number;
|
|
2561
|
+
endM: number;
|
|
2562
|
+
}
|
|
2563
|
+
interface PlanManeuver {
|
|
2564
|
+
atM: number;
|
|
2565
|
+
kind: string;
|
|
2566
|
+
/** Полосы, с которых манёвр выполняется по плану. */
|
|
2567
|
+
laneIds: string[];
|
|
2568
|
+
/** Стрелки полос подхода: что разрешено с каждой. */
|
|
2569
|
+
arrows: {
|
|
2570
|
+
laneId: string;
|
|
2571
|
+
turns: string[];
|
|
2572
|
+
}[];
|
|
2573
|
+
}
|
|
2574
|
+
interface LanePlan {
|
|
2575
|
+
version: string;
|
|
2576
|
+
/** Как сопоставлено: `edges` | `geometry` | `mixed` | `null` (покрытия нет). Добавочное поле. */
|
|
2577
|
+
matchedBy?: string | null;
|
|
2578
|
+
coverage: [number, number][];
|
|
2579
|
+
segments: PlanSegment[];
|
|
2580
|
+
changes: PlanChange[];
|
|
2581
|
+
maneuvers: PlanManeuver[];
|
|
2582
|
+
}
|
|
2583
|
+
interface LanePlanRequest {
|
|
2584
|
+
geometry: [number, number][];
|
|
2585
|
+
edges?: (number | string)[];
|
|
2586
|
+
graphVersion: number | null;
|
|
2587
|
+
maneuvers: {
|
|
2588
|
+
atM: number;
|
|
2589
|
+
type: string;
|
|
2590
|
+
modifier: string;
|
|
2591
|
+
}[];
|
|
2592
|
+
}
|
|
2593
|
+
declare const GUIDANCE_URL = "/tiles/ldroads/guidance";
|
|
2594
|
+
/** Манёвр маршрута: где он и что это (как шаг `/route`: `type` + `modifier`). */
|
|
2595
|
+
interface RouteManeuver {
|
|
2596
|
+
at: LonLat;
|
|
2597
|
+
type: string;
|
|
2598
|
+
modifier?: string;
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Маршрут для плана полос: трасса по ходу (`[lon, lat]`) и её манёвры. `edges` — `edgeId` шагов
|
|
2602
|
+
* `/route`, если они есть у приложения: с ними сервис сопоставляет точнее.
|
|
2603
|
+
*/
|
|
2604
|
+
interface LaneRoute {
|
|
2605
|
+
geometry: LonLat[];
|
|
2606
|
+
maneuvers?: RouteManeuver[];
|
|
2607
|
+
edges?: (number | string)[];
|
|
2608
|
+
graphVersion?: number | null;
|
|
2609
|
+
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Тело запроса плана: трасса и манёвры с метрами вдоль неё (как `requestFor` веб-карты). Рёбер
|
|
2612
|
+
* графа нет — поле опущено.
|
|
2613
|
+
*/
|
|
2614
|
+
declare function lanePlanRequest(route: LaneRoute): LanePlanRequest;
|
|
2615
|
+
/** Ручки нет (404/405/501) — не спрашиваем снова столько времени. */
|
|
2616
|
+
declare const ABSENT_RETRY_MS: number;
|
|
2617
|
+
/** Минимум от `fetch`, который нужен запросу плана (подходит `globalThis.fetch`). */
|
|
2618
|
+
type FetchLike = (url: string, init: {
|
|
2619
|
+
method: string;
|
|
2620
|
+
headers: Record<string, string>;
|
|
2621
|
+
body: string;
|
|
2622
|
+
signal?: AbortSignal;
|
|
2623
|
+
}) => Promise<{
|
|
2624
|
+
ok: boolean;
|
|
2625
|
+
status: number;
|
|
2626
|
+
json(): Promise<unknown>;
|
|
2627
|
+
}>;
|
|
2628
|
+
interface FetchLanePlanOptions {
|
|
2629
|
+
/** Адрес ручки (по умолчанию — `GUIDANCE_URL` на своём сайте). */
|
|
2630
|
+
url?: string;
|
|
2631
|
+
/** Функция запроса (по умолчанию — `globalThis.fetch`). */
|
|
2632
|
+
fetch?: FetchLike;
|
|
2633
|
+
signal?: AbortSignal;
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Запрос плана у сервиса. Любая ошибка — `null`; нет ручки — `null` и `ABSENT_RETRY_MS` без
|
|
2637
|
+
* запросов (на каждый `url` свой счётчик).
|
|
2638
|
+
*/
|
|
2639
|
+
declare function fetchLanePlan(req: LanePlanRequest, opts?: FetchLanePlanOptions): Promise<LanePlan | null>;
|
|
2640
|
+
/** Сбросить память об отсутствующей ручке (тесты, смена стенда). */
|
|
2641
|
+
declare function resetLanePlanAbsence(): void;
|
|
2642
|
+
/**
|
|
2643
|
+
* Ответ → план. Отбрасывается всё, что не описывает полосу или участок: рисовать по этому будет
|
|
2644
|
+
* движок, и мусор дешевле поймать здесь. Нет покрытия или участков — `null` (полос нет).
|
|
2645
|
+
*/
|
|
2646
|
+
declare function parseLanePlan(raw: unknown): LanePlan | null;
|
|
2647
|
+
|
|
2648
|
+
/** Зум тайлов источника ленты: `maxzoom` geojson-источника osmgl по умолчанию — 18. */
|
|
2649
|
+
declare const RIBBON_TILE_Z = 18;
|
|
2650
|
+
/** Единиц в тайле (`EXTENT` geojson-источника osmgl). */
|
|
2651
|
+
declare const TILE_EXTENT = 4096;
|
|
2652
|
+
/** Выдвиг вершины на разрезе, единиц тайла (`SURFACE_CUT_BLEED` osmgl). */
|
|
2653
|
+
declare const CUT_BLEED = 1;
|
|
2654
|
+
/** `[lon, lat]` → единицы тайла z18 во всём мире (та же меркаторская формула, что у движка). */
|
|
2655
|
+
declare function toUnits(p: LonLat): [number, number];
|
|
2656
|
+
declare function fromUnits(u: [number, number]): LonLat;
|
|
2657
|
+
/**
|
|
2658
|
+
* Отступ от границы тайла номер `k` (её координата — `k · 4096` единиц z18) у куска СПРАВА/СНИЗУ от
|
|
2659
|
+
* неё. Сосед слева/сверху касается границы, и движок выдвигает его на единицу своего тайла; кусок
|
|
2660
|
+
* справа обязан начаться ровно там же — после округления координат тайла (geojson-источник
|
|
2661
|
+
* округляет до целых) — и не лечь на границу сам (иначе выдвинется и он):
|
|
2662
|
+
* - тайл z18: 1.2 и 0.6 округляются до 1 = выдвиг соседа;
|
|
2663
|
+
* - тайл z17 (зум карты 17.5–18): у чётной границы (она же граница z17) 1.2 единицы z18 — это 0.6
|
|
2664
|
+
* единицы z17, округляется до 1 = выдвиг соседа на его z17; нечётная граница лежит ВНУТРИ тайла
|
|
2665
|
+
* z17, сосед там не выдвигается, и 0.6 единицы z18 = 0.3 единицы z17 округляются до нуля — кусок
|
|
2666
|
+
* сходится с соседом в той же точке.
|
|
2667
|
+
*/
|
|
2668
|
+
declare const cutInset: (k: number) => number;
|
|
2669
|
+
/**
|
|
2670
|
+
* Многоугольник (`[outer, ...holes]`, кольца `[lon, lat]`) → многоугольники по тайлам z18 с правилом
|
|
2671
|
+
* стыка `cutInset`. Целиком внутри тайла и далеко от его левой и верхней границ — как есть.
|
|
2672
|
+
*/
|
|
2673
|
+
declare function tileParts(poly: LonLat[][]): LonLat[][][];
|
|
2674
|
+
/** Маска: многоугольники в единицах z18 с рамками — вычитаются только задетые. */
|
|
2675
|
+
interface Mask {
|
|
2676
|
+
polys: {
|
|
2677
|
+
box: [number, number, number, number];
|
|
2678
|
+
geom: Geom;
|
|
2679
|
+
}[];
|
|
2680
|
+
}
|
|
2681
|
+
declare function maskOf(rings: LonLat[][]): Mask;
|
|
2682
|
+
/** Кольцо минус маска → кольца-многоугольники (`[outer, ...holes]`). Не задето — как есть. */
|
|
2683
|
+
declare function minusMask(ring: LonLat[], mask: Mask): LonLat[][][];
|
|
2684
|
+
/** Длина куска ленты по станциям, метры: столько пересчитывается при движении машины. */
|
|
2685
|
+
declare const CHUNK_M = 40;
|
|
2686
|
+
/** Кусок площадной фичи: вид и дробный диапазон её точек; `cut` — один из концов в точке машины. */
|
|
2687
|
+
interface AreaSpan {
|
|
2688
|
+
kind: RibbonKind;
|
|
2689
|
+
from: number;
|
|
2690
|
+
to: number;
|
|
2691
|
+
cut?: boolean;
|
|
2692
|
+
}
|
|
2693
|
+
/**
|
|
2694
|
+
* Разбиение площадной фичи на куски: границы — станции, кратные `CHUNK_M` (от ведения не
|
|
2695
|
+
* зависят — куски без машины одни и те же, их можно хранить), и точка машины `s` (позади — `*-passed`).
|
|
2696
|
+
*/
|
|
2697
|
+
declare function areaSpans(f: RibbonFeature, s: number | null, chunkM?: number): AreaSpan[];
|
|
2698
|
+
/**
|
|
2699
|
+
* Ось фичи в плоских метрах с нормалями — одна на фичу, общая для всех её кусков. `caps` — форма
|
|
2700
|
+
* её настоящих концов (`RibbonFeature.caps`): её получают только куски, доходящие до конца фичи.
|
|
2701
|
+
*/
|
|
2702
|
+
interface AreaFrame {
|
|
2703
|
+
frame: Frame;
|
|
2704
|
+
xy: XY[];
|
|
2705
|
+
normals: XY[];
|
|
2706
|
+
widths: number[];
|
|
2707
|
+
caps: [RibbonCap, RibbonCap];
|
|
2708
|
+
}
|
|
2709
|
+
declare function areaFrame(f: RibbonFeature): AreaFrame;
|
|
2710
|
+
/**
|
|
2711
|
+
* Контур куска `[from, to]` фичи: точки, нормали и ширины на концах — интерполяция соседних. Конец
|
|
2712
|
+
* куска на конце фичи — её формы (`AreaFrame.caps`: скругление, стрелка), на разрезе — прямой: там
|
|
2713
|
+
* кусок встык продолжает соседний. Форма конца — часть того же кольца, дальше — то же вычитание и
|
|
2714
|
+
* резка по тайлам, что у всей ленты.
|
|
2715
|
+
*/
|
|
2716
|
+
declare function spanOutline(af: AreaFrame, from: number, to: number): LonLat[];
|
|
2717
|
+
/** Ось куска `[from, to]` — `[lon, lat]`, для свойств высоты. */
|
|
2718
|
+
declare const spanLine: (f: RibbonFeature, from: number, to: number) => LonLat[];
|
|
2719
|
+
|
|
2720
|
+
/**
|
|
2721
|
+
* ПРОЙДЕННАЯ ЧАСТЬ МАРШРУТА — СЕРАЯ (как у Baidu: позади машины линия и лента по полосам серые,
|
|
2722
|
+
* впереди — синие или цветом пробок; граница — ровно в точке машины).
|
|
2723
|
+
*
|
|
2724
|
+
* Чистые функции без движка (проверка — `route-progress.test.ts` и общие фикстуры
|
|
2725
|
+
* `tests/fixtures/ld-lane-ribbon/`, их же гоняют порты Android и iOS). Метр прогресса — метр
|
|
2726
|
+
* трассы той же мерой, что у плана полос и ведения: плоская формула на звено (`linkM`).
|
|
2727
|
+
*
|
|
2728
|
+
* ★ ЛИНИЯ: синяя линия маршрута остаётся ЦЕЛОЙ, серая пройденная кладётся ПОВЕРХ неё (кусками вне
|
|
2729
|
+
* ленты по полосам). Резать синюю нельзя: шевроны движок ставит от начала линии, и у линии,
|
|
2730
|
+
* укорачиваемой с начала, стрелки ехали бы вместе с машиной. Под серой (непрозрачной) синие
|
|
2731
|
+
* шевроны не видны.
|
|
2732
|
+
*
|
|
2733
|
+
* ★ ЛЕНТА: площадные фичи (`band`, `allowed`) режутся в точке машины на две — пройденную
|
|
2734
|
+
* (`*-passed`, серая) и оставшуюся, с ОДНОЙ нормалью в точке разреза: кромки сходятся без щели и
|
|
2735
|
+
* нахлёста. Шевроны целиком позади выбрасываются; шеврон, на котором машина, остаётся целым (по той
|
|
2736
|
+
* же причине, что у линии) — его пройденную часть закрывает серая лента, слой которой выше
|
|
2737
|
+
* шевронов, — там стрелки бледные.
|
|
2738
|
+
*/
|
|
2739
|
+
|
|
2740
|
+
/** Трасса с накопленными метрами к каждой точке (мерой сервера, `linkM`). */
|
|
2741
|
+
interface RouteLine {
|
|
2742
|
+
geometry: LonLat[];
|
|
2743
|
+
cum: number[];
|
|
2744
|
+
length: number;
|
|
2745
|
+
}
|
|
2746
|
+
declare function routeLine(geometry: LonLat[]): RouteLine;
|
|
2747
|
+
/**
|
|
2748
|
+
* Точка трассы на метре `s`. Доля по звену — по его длине мерой сервера, сама точка — в градусах:
|
|
2749
|
+
* плоская система метров аффинна градусам, так что это та же точка, что у `routePieces`.
|
|
2750
|
+
*/
|
|
2751
|
+
declare function pointOn(line: RouteLine, s: number): LonLat;
|
|
2752
|
+
/** Кусок трассы между метрами (концы — точно на метрах). Короче `minM` — пусто. */
|
|
2753
|
+
declare function lineBetween(line: RouteLine, from: number, to: number, minM?: number): LonLat[];
|
|
2754
|
+
/** Пройденный кусок короче этого не рисуется (его и так закрывает метка машины). */
|
|
2755
|
+
declare const PASSED_MIN_M = 0.5;
|
|
2756
|
+
/**
|
|
2757
|
+
* Пройденные куски ОБЫЧНОЙ линии: трасса вне ленты по полосам (`hidden`, как у `routePieces`) от
|
|
2758
|
+
* начала до метра `s`. Пусто — ничего не пройдено (или всё пройденное — под лентой).
|
|
2759
|
+
*/
|
|
2760
|
+
declare function passedPieces(line: RouteLine, hidden: [number, number][], s: number): LonLat[][];
|
|
2761
|
+
/**
|
|
2762
|
+
* Положение метра `s` среди станций (не убывают): дробный номер точки в `[0, n − 1]`. На ровном
|
|
2763
|
+
* месте (повтор станции) — первая точка после него.
|
|
2764
|
+
*/
|
|
2765
|
+
declare function fracIndex(stations: number[], s: number): number;
|
|
2766
|
+
/**
|
|
2767
|
+
* Значения точек между дробными номерами `a` и `b`: значение на `a`, все целые строго между,
|
|
2768
|
+
* значение на `b`. Так режутся и ось, и атрибуты точек (ширина, уровень, станция, высота).
|
|
2769
|
+
*/
|
|
2770
|
+
declare function sliceAt<T>(values: T[], a: number, b: number, mix: (x: T, y: T, t: number) => T): T[];
|
|
2771
|
+
declare const mixNum: (x: number, y: number, t: number) => number;
|
|
2772
|
+
declare const mixLevel: (x: number, y: number, t: number) => number;
|
|
2773
|
+
declare const mixLonLat: (x: LonLat, y: LonLat, t: number) => LonLat;
|
|
2774
|
+
/** Пройденный вид площадной фичи. */
|
|
2775
|
+
declare const passedKind: (k: RibbonKind) => RibbonKind;
|
|
2776
|
+
/** Кусок ленты на карте: фича и откуда она взята — номер исходной и дробный диапазон её точек. */
|
|
2777
|
+
interface RibbonPiece {
|
|
2778
|
+
feature: RibbonFeature;
|
|
2779
|
+
source: number;
|
|
2780
|
+
from: number;
|
|
2781
|
+
to: number;
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* ЛЕНТА НА МЕТРЕ ВЕДЕНИЯ `s`: позади — серая, впереди — как была. `null` — ведения нет, лента
|
|
2785
|
+
* целиком цветная. Фичи без станций остаются как есть.
|
|
2786
|
+
*/
|
|
2787
|
+
declare function ribbonAtProgress(features: RibbonFeature[], s: number | null): RibbonPiece[];
|
|
2788
|
+
/**
|
|
2789
|
+
* Что из данных ленты меняется на метре `s`: число фич целиком позади и есть ли фича, которую
|
|
2790
|
+
* режет точка машины. Пока это не меняется, данные источника заново не заливаются.
|
|
2791
|
+
*/
|
|
2792
|
+
declare function ribbonProgressState(features: RibbonFeature[], s: number | null): {
|
|
2793
|
+
passed: number;
|
|
2794
|
+
cut: boolean;
|
|
2795
|
+
};
|
|
2796
|
+
|
|
2797
|
+
/**
|
|
2798
|
+
* ВЕДЕНИЕ ПО ПОЛОСАМ — что показать на метре маршрута: следующий манёвр, ряд стрелок полос и
|
|
2799
|
+
* камеру (наклон и приближение у манёвра внутри покрытия, как у Baidu).
|
|
2800
|
+
*
|
|
2801
|
+
* Чистые функции (проверка — `lane-guidance.test.ts`). ★ Подсказка говорит, КУДА ВСТАТЬ, и никогда
|
|
2802
|
+
* — «вы в полосе N»: своей полосы карта не знает (точности приёмника на это не хватает).
|
|
2803
|
+
*/
|
|
2804
|
+
|
|
2805
|
+
/** Ближе этого к манёвру внутри покрытия камера наклоняется и приближается. */
|
|
2806
|
+
declare const CLOSE_M = 300;
|
|
2807
|
+
declare const FAR_VIEW: {
|
|
2808
|
+
pitch: number;
|
|
2809
|
+
zoom: number;
|
|
2810
|
+
};
|
|
2811
|
+
declare const CLOSE_VIEW: {
|
|
2812
|
+
pitch: number;
|
|
2813
|
+
zoom: number;
|
|
2814
|
+
};
|
|
2815
|
+
/** Ряд стрелок подсказки дальше этого не показывается: заранее он только отвлекает. */
|
|
2816
|
+
declare const HINT_M = 600;
|
|
2817
|
+
/** Манёвр плана впереди (или только что пройденный — пока не отъехали `AFTER_M`). */
|
|
2818
|
+
declare function upcoming(plan: LanePlan | null, s: number): PlanManeuver | null;
|
|
2819
|
+
/** Доля «близкого вида» 0..1 на метре `s`. */
|
|
2820
|
+
declare function closeness(plan: LanePlan | null, s: number): number;
|
|
2821
|
+
/** Наклон и зум камеры ведения на метре `s`. */
|
|
2822
|
+
declare function guidanceCamera(plan: LanePlan | null, s: number): {
|
|
2823
|
+
pitch: number;
|
|
2824
|
+
zoom: number;
|
|
2825
|
+
close: number;
|
|
2826
|
+
};
|
|
2827
|
+
interface HintLane {
|
|
2828
|
+
laneId: string;
|
|
2829
|
+
turns: string[];
|
|
2830
|
+
/** Сюда вставать: полоса из `laneIds` манёвра. */
|
|
2831
|
+
recommended: boolean;
|
|
2832
|
+
/** Тоже годится, но план её не выбрал (`allowed` на подходе). */
|
|
2833
|
+
allowed: boolean;
|
|
2834
|
+
}
|
|
2835
|
+
interface LaneHint {
|
|
2836
|
+
atM: number;
|
|
2837
|
+
kind: string;
|
|
2838
|
+
lanes: HintLane[];
|
|
2839
|
+
}
|
|
2840
|
+
/**
|
|
2841
|
+
* Ряд стрелок для манёвра: полосы подхода (участок, на котором стоит манёвр) слева направо,
|
|
2842
|
+
* стрелки — из `arrows` манёвра, иначе — у самой полосы.
|
|
2843
|
+
*/
|
|
2844
|
+
declare function hintFor(plan: LanePlan | null, m: PlanManeuver | null): LaneHint | null;
|
|
2845
|
+
/** Подсказка на метре `s`: только если манёвр ближе `HINT_M`. */
|
|
2846
|
+
declare function hintAt(plan: LanePlan | null, s: number): (LaneHint & {
|
|
2847
|
+
distanceM: number;
|
|
2848
|
+
}) | null;
|
|
2849
|
+
/**
|
|
2850
|
+
* Ряд стрелок у точки манёвра (шаг списка манёвров приложения): манёвр плана в 25 м от точки по трассе
|
|
2851
|
+
* `geometry` (`hintForStep` веб-карты, `laneHintAt` портов). `null` — у шага полос нет.
|
|
2852
|
+
*/
|
|
2853
|
+
declare function hintNear(plan: LanePlan | null, geometry: LonLat[], at: LonLat): LaneHint | null;
|
|
2854
|
+
/** Кадр ведения: камера и что показать (`guideStep` веб-карты). */
|
|
2855
|
+
interface GuideFrame {
|
|
2856
|
+
center: LonLat;
|
|
2857
|
+
zoom: number;
|
|
2858
|
+
pitch: number;
|
|
2859
|
+
bearing: number;
|
|
2860
|
+
/** Доля «близкого вида» 0..1. */
|
|
2861
|
+
close: number;
|
|
2862
|
+
/** Курс по маршруту, градусы, — для своей метки «я здесь». */
|
|
2863
|
+
courseDeg: number;
|
|
2864
|
+
/** Метр камеры (сглаженный метр ведения): до него линия и лента серые, на нём — значок. */
|
|
2865
|
+
s: number;
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* ВЕДЕНИЕ ПО ПЛАНУ — метр маршрута и камера, как `stores/lanes.ts` (`advance`, `locate`) и `guideStep`
|
|
2869
|
+
* (`MapView.vue`) веб-карты, одним классом без карты и часов: приложение зовёт `locate` на каждую
|
|
2870
|
+
* геолокацию или `advance` в демо-проезде, а на каждый кадр — `step(dt)` и ставит камеру из ответа.
|
|
2871
|
+
* Камера сглаживается по кадрам: скачок наклона на пороге читался бы рывком. Пара у портов — `LaneGuide`
|
|
2872
|
+
* (Kotlin) и `navigram/lane_guide` натива; сверка — `kmp/fixtures/lane-guide.json`.
|
|
2873
|
+
*/
|
|
2874
|
+
declare class LaneGuide {
|
|
2875
|
+
readonly plan: LanePlan | null;
|
|
2876
|
+
readonly measure: {
|
|
2877
|
+
frame: Frame;
|
|
2878
|
+
route: Measured;
|
|
2879
|
+
} | null;
|
|
2880
|
+
/** Метр маршрута, на котором «мы». */
|
|
2881
|
+
progressM: number;
|
|
2882
|
+
private cam;
|
|
2883
|
+
private started;
|
|
2884
|
+
constructor(plan: LanePlan | null, geometry: LonLat[]);
|
|
2885
|
+
get lengthM(): number;
|
|
2886
|
+
/** Начать с текущей камеры (наклон, зум): вход в ведение плавный, курс — по ходу с упреждением 30 м. */
|
|
2887
|
+
start(pitch: number, zoom: number, fromM?: number): void;
|
|
2888
|
+
/** Демо-проезд: шаг времени (секунды); у манёвра притормаживает, как машина на повороте. false — доехали. */
|
|
2889
|
+
advance(dt: number, speedMps?: number): boolean;
|
|
2890
|
+
/** Геолокация → метр маршрута (окно вперёд: назад по трассе не прыгаем). false — дальше 60 м от трассы. */
|
|
2891
|
+
locate(pos: LonLat): boolean;
|
|
2892
|
+
/** Подсказка полос на метре ведения — для панели приложения. */
|
|
2893
|
+
hint(): (LaneHint & {
|
|
2894
|
+
distanceM: number;
|
|
2895
|
+
}) | null;
|
|
2896
|
+
/** Камера на кадр. `follow` — метр камеры догоняет метр ведения сразу (демо), иначе плавно. */
|
|
2897
|
+
step(dtSec: number, follow?: boolean): GuideFrame | null;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
/**
|
|
2901
|
+
* ДЕТАЛЬНЫЕ ДОРОГИ И ЛЕНТА ПОЛОС В СТИЛЕ КАРТЫ — чистая часть: разбор ответов сервиса
|
|
2902
|
+
* (`/tiles/ldroads/index`, `/tiles/ldroads/style.json`), вставка их слоёв в стиль карты, слои и
|
|
2903
|
+
* данные ленты по полосам. Пара у портов — `LdRoads.kt` / `LdRoads.swift` (те же имена).
|
|
2904
|
+
*
|
|
2905
|
+
* ★ СЦЕНА ЗДЕСЬ НЕ СТРОИТСЯ. Веб-карта (`src/WebMap/src/map/ld-roads.ts`) собирает полотно улиц
|
|
2906
|
+
* редактора у себя (воркер стенда) и режет асфальт своим протоколом — это остаётся у неё. Здесь —
|
|
2907
|
+
* контракт для приложений (`docs/map-v2/LD-MOBILE.md`): слои — `style.json` сервиса (формат osmgl
|
|
2908
|
+
* v1, id `ld-*`, источник `ld-roads`), данные — векторные тайлы z14–16, асфальт — уже обрезанный
|
|
2909
|
+
* под скрытыми дорогами (`/tiles/ldroads/roadbed/…` вместо URL источника `roadbed`). Сеть — у
|
|
2910
|
+
* приложения.
|
|
2911
|
+
*
|
|
2912
|
+
* ★ СЛОИ ВСТАЮТ СРАЗУ ЗА ПОЛОТНОМ КАРТЫ (за `roadbed`; до 23.10.2026 — за его краской `roadarrow`,
|
|
2913
|
+
* которой больше нет) — под дома, подписи и пробки; лента полос — сразу за ними (на их асфальте).
|
|
2914
|
+
* Разметка карты — ТОЛЬКО их (`ld-mark-*`): краски полотна в стиле нет (`ROADMARK_LAYER_IDS`).
|
|
2915
|
+
*
|
|
2916
|
+
* Слои ленты, `withLaneRibbon`, поле высот `heightsFromScene` и данные источника (`RibbonGeoJson`)
|
|
2917
|
+
* перенесены из `src/WebMap/src/map/lane-ribbon.ts` без изменения поведения.
|
|
2918
|
+
*/
|
|
2919
|
+
|
|
2920
|
+
/** Отметка полотна в точке `(lng, lat)`, метры (`MapObjectOptions.liftAt` объектов карты). */
|
|
2921
|
+
type LiftField = (lng: number, lat: number) => number;
|
|
2922
|
+
|
|
2923
|
+
/**
|
|
2924
|
+
* ЛЕНТА ПО ПОЛОСАМ — ЕДИНЫМ ПОЛИГОНОМ, НА ВЫСОТЕ НАСТОЯЩЕГО ПОЛОТНА.
|
|
2925
|
+
*
|
|
2926
|
+
* Разбор и источники — `docs/lane-ribbon-mesh.md`. Коротко:
|
|
2927
|
+
*
|
|
2928
|
+
* ★ ОДИН ПИКСЕЛЬ — ОДНА КРАСКА. Лента полупрозрачная, и любой нахлёст двух кусков даёт пятно
|
|
2929
|
+
* двойной яркости. Поэтому краска не накладывается кусками, а строится НЕПЕРЕКРЫВАЮЩАЯСЯ геометрия:
|
|
2930
|
+
* куски одного вида и одной полосы метров (`CHUNK_M`) сливаются объединением (polygon-clipping —
|
|
2931
|
+
* Мартинес–Руэда в изводе Майка Фогеля), а из каждой группы вычитается всё, что нарисовано раньше
|
|
2932
|
+
* (основная лента главнее разрешённых полос, ближняя к началу трассы — дальней). Итог — набор
|
|
2933
|
+
* многоугольников, которые касаются, но не перекрываются; каждый — один объект карты, earcut
|
|
2934
|
+
* разбивает его на треугольники один раз. Сумма площадей треугольников равна площади ленты — это и
|
|
2935
|
+
* проверяют тесты (`tests/navigram-lane-ribbon-mesh.test.mjs`).
|
|
2936
|
+
*
|
|
2937
|
+
* ★ ВЕДЕНИЕ. Группа, которую режет точка машины, делится на пройденную (серую) и оставшуюся части
|
|
2938
|
+
* пересечением и разностью с пройденными кусками своей группы — две маленькие операции на шаг ведения;
|
|
2939
|
+
* остальные группы берутся из кэша как есть.
|
|
2940
|
+
*
|
|
2941
|
+
* ★ ВЫСОТА — ОТ НАСТОЯЩЕГО ПОЛОТНА ПОД ТОЧКОЙ (`meshSurface`, `routeSurfaceField`): каждая вершина
|
|
2942
|
+
* ленты, линии маршрута и стрелок поворотов берёт отметку верха сетки детальных дорог ровно под собой
|
|
2943
|
+
* (треугольник с нормалью вверх; из нескольких — ближайший к ожидаемой отметке трассы, чтобы дорога
|
|
2944
|
+
* под эстакадой не взлетела на настил). Там, где сетка загружена, но под точкой её нет, — земля;
|
|
2945
|
+
* где сетки нет вовсе — отметка трассы (`routeLiftField`).
|
|
2946
|
+
*/
|
|
2947
|
+
|
|
2948
|
+
/** Кусок ленты для отрисовки: вид, многоугольник (`[outer, ...holes]`, кольца `[lon, lat]`, замкнуты). */
|
|
2949
|
+
interface RibbonArea {
|
|
2950
|
+
/** Постоянный ключ: тот же кусок на следующем шаге ведения — тот же объект карты. */
|
|
2951
|
+
key: string;
|
|
2952
|
+
kind: RibbonKind;
|
|
2953
|
+
polygon: LonLat[][];
|
|
2954
|
+
/**
|
|
2955
|
+
* Какие кромки сглаживать (`RouteArea.feather`): `feather[r][i]` — кромка `i → i+1` кольца `r`
|
|
2956
|
+
* (кольца — без замыкающей точки). Сглаживается только край ленты; шов с соседним куском ленты —
|
|
2957
|
+
* нет: там с обеих сторон краска, и полоса сглаживания легла бы на соседа второй краской.
|
|
2958
|
+
*/
|
|
2959
|
+
feather: boolean[][];
|
|
2960
|
+
}
|
|
2961
|
+
type Box = [number, number, number, number];
|
|
2962
|
+
/**
|
|
2963
|
+
* ПРОЙДЕННОЕ РАСТВОРЯЕТСЯ, А НЕ ОБРЫВАЕТСЯ (22.10.2026, просьба владельца «синий свет плавно уменьшался позади
|
|
2964
|
+
* машины»). Раньше группу с точкой машины резало пересечением: синяя кончалась ровно под машиной, а дальше шла
|
|
2965
|
+
* серая — ступенька на всю ширину ленты, и перекладывалась она шагом ведения (≤ 10 Гц). Теперь (`passAt`) группы
|
|
2966
|
+
* не режутся: у группы рядом с машиной рисуются ЦЕЛИКОМ и синяя, и серая, а какая где видна, решает шейдер по
|
|
2967
|
+
* метру трассы в вершине (`stationsFor`) и метру машины в униформе (`MapObject.along`): синяя гаснет на
|
|
2968
|
+
* `RIBBON_PASS_FADE_M` позади машины, серая на том же отрезке проявляется. Метр машины — каждым кадром, без
|
|
2969
|
+
* пересборки; набор объектов меняется, только когда группа входит в окно у машины или выходит из него.
|
|
2970
|
+
*/
|
|
2971
|
+
/** Длина растворения синей ленты позади машины, метры. */
|
|
2972
|
+
declare const RIBBON_PASS_FADE_M = 20;
|
|
2973
|
+
/**
|
|
2974
|
+
* Запас окна у машины, метры: группа рисуется обеими красками, пока задевает [машина − длина − запас,
|
|
2975
|
+
* машина + запас]. На границе окна та краска, что уходит, уже погашена шейдером, — смена набора не видна.
|
|
2976
|
+
*/
|
|
2977
|
+
declare const RIBBON_PASS_MARGIN_M = 5;
|
|
2978
|
+
/** Кусок ленты в ведении с растворением позади машины (`RibbonAreas.passAt`). */
|
|
2979
|
+
interface PassArea extends RibbonArea {
|
|
2980
|
+
/** Номер группы — для метров в точках (`RibbonAreas.stationsFor`). */
|
|
2981
|
+
group: number;
|
|
2982
|
+
/**
|
|
2983
|
+
* Сторона растворения (`AlongFade.dir`): +1 — синяя, гаснет позади машины; −1 — серая, проявляется позади
|
|
2984
|
+
* машины; 0 — у группы нет метров трассы, рисуется как есть.
|
|
2985
|
+
*/
|
|
2986
|
+
dir: 1 | -1 | 0;
|
|
2987
|
+
}
|
|
2988
|
+
/**
|
|
2989
|
+
* ПЛОЩАДЬ ЛЕНТЫ БЕЗ НАХЛЁСТОВ — по группам «категория × полоса метров `CHUNK_M`».
|
|
2990
|
+
*
|
|
2991
|
+
* Строится один раз на ленту; на шаге ведения пересчитывается только группа с точкой машины.
|
|
2992
|
+
*/
|
|
2993
|
+
declare class RibbonAreas {
|
|
2994
|
+
readonly ribbon: Ribbon;
|
|
2995
|
+
private readonly chunkM;
|
|
2996
|
+
/** Группы по порядку отрисовки: сначала основная лента, потом разрешённые полосы; внутри — по метрам. */
|
|
2997
|
+
private groups;
|
|
2998
|
+
private frames;
|
|
2999
|
+
private built;
|
|
3000
|
+
constructor(ribbon: Ribbon, chunkM?: number);
|
|
3001
|
+
private frame;
|
|
3002
|
+
/** Куски каждой фичи по полосам метров (без ведения) — сырьё групп. */
|
|
3003
|
+
private build;
|
|
3004
|
+
/** Лежит ли точка (единицы z18) внутри ленты — какой-нибудь группы. */
|
|
3005
|
+
private inside;
|
|
3006
|
+
/** Кромки многоугольника на краю ленты: с одной стороны лента, с другой — нет. */
|
|
3007
|
+
private featherOf;
|
|
3008
|
+
private featherCache;
|
|
3009
|
+
/** Метры трассы группы `[lo, hi]` (`null` — у её фич метров нет). */
|
|
3010
|
+
private spans;
|
|
3011
|
+
private spanOf;
|
|
3012
|
+
/** Что рисует группа на метре `progress`: `b` — синяя, `x` — синяя и серая (окно у машины), `g` — серая. */
|
|
3013
|
+
private passMode;
|
|
3014
|
+
/**
|
|
3015
|
+
* Отпечаток набора кусков `passAt` на метре `progress`: тот же — объекты те же, менять нужно только метр машины
|
|
3016
|
+
* у растворения (`MapObject.along`).
|
|
3017
|
+
*/
|
|
3018
|
+
passKey(progress: number | null, fadeM?: number): string;
|
|
3019
|
+
/**
|
|
3020
|
+
* Куски ленты в ведении С РАСТВОРЕНИЕМ ПОЗАДИ МАШИНЫ: группы целиком (без резки по точке машины), синие —
|
|
3021
|
+
* впереди и в окне у машины, серые — в окне и позади. Ключи — те же, что у целых групп `at` (`:whole`,
|
|
3022
|
+
* `:passed`): группа, перешедшая из одного состояния в другое, остаётся тем же объектом карты.
|
|
3023
|
+
*/
|
|
3024
|
+
passAt(progress: number | null, fadeM?: number): PassArea[];
|
|
3025
|
+
/**
|
|
3026
|
+
* МЕТР ТРАССЫ В ТОЧКАХ КУСКА группы `group` (кольца — как у `polygon`, в том числе после дробления кромок
|
|
3027
|
+
* `fadeRibbonArea`): метр ближайшей точки оси её фич (`stations` по точкам оси, между ними — линейно). Ищется
|
|
3028
|
+
* только по своей группе — трасса, вернувшаяся к тому же месту, чужого метра не даст. `null` — метров нет.
|
|
3029
|
+
*/
|
|
3030
|
+
stationsFor(group: number, polygon: LonLat[][]): number[][] | null;
|
|
3031
|
+
/** Куски ленты на метре ведения `progress` (`null` — ведения нет, лента целиком цветная). */
|
|
3032
|
+
at(progress: number | null): RibbonArea[];
|
|
3033
|
+
}
|
|
3034
|
+
/**
|
|
3035
|
+
* ШЕВРОНЫ ЛЕНТЫ — линиями маршрута (`Polyline`, та же программа и тот же рисунок стрелок, что у
|
|
3036
|
+
* линии), размер — в метрах на земле (как прежний слой: 6 px на z18 и вдвое на каждый зум).
|
|
3037
|
+
*/
|
|
3038
|
+
declare const RIBBON_CHEVRON: {
|
|
3039
|
+
readonly COLOR: "rgba(255,255,255,0)";
|
|
3040
|
+
readonly ARROW_COLOR: "rgba(255,255,255,0.95)";
|
|
3041
|
+
readonly WIDTH_M: 2.75;
|
|
3042
|
+
readonly PERIOD_M: 18.3;
|
|
3043
|
+
readonly SIZE_M: 1.37;
|
|
3044
|
+
/** Над лентой (16–17), под линией маршрута (20). */
|
|
3045
|
+
readonly Z: 18;
|
|
3046
|
+
};
|
|
3047
|
+
/** Шеврон ленты: постоянный ключ и ось. */
|
|
3048
|
+
interface RibbonChevron {
|
|
3049
|
+
key: string;
|
|
3050
|
+
line: LonLat[];
|
|
3051
|
+
}
|
|
3052
|
+
/** Шеврон в ведении с растворением: метры его концов (`null` — метров нет). */
|
|
3053
|
+
interface PassChevron extends RibbonChevron {
|
|
3054
|
+
stations: [number, number] | null;
|
|
3055
|
+
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Шевроны в ведении С РАСТВОРЕНИЕМ (`passAt`): шеврон гаснет позади машины вместе с синей лентой (`MapObject.along`
|
|
3058
|
+
* с началом `chevronRunBase`), поэтому снимается, только когда целиком ушёл за окно растворения, — не на машине.
|
|
3059
|
+
*/
|
|
3060
|
+
declare function ribbonPassChevrons(ribbon: Ribbon, progress: number | null, fadeM?: number): PassChevron[];
|
|
3061
|
+
/**
|
|
3062
|
+
* Метр трассы первой точки куска шеврона (`fadeRibbonLine` режет шеврон на куски): по доле пути вдоль оси шеврона
|
|
3063
|
+
* до ближайшей к ней точки, между метрами его концов. `null` — у шеврона метров нет.
|
|
3064
|
+
*/
|
|
3065
|
+
declare function chevronRunBase(chevron: PassChevron, run: LonLat[]): number | null;
|
|
3066
|
+
/** Шевроны на метре ведения: целиком позади машины не рисуются, тот, на котором машина, — целый. */
|
|
3067
|
+
declare function ribbonChevrons(ribbon: Ribbon, progress: number | null): RibbonChevron[];
|
|
3068
|
+
/** Сетка детальных дорог (тайл `ldtiles` после `decodeMeshTile`): точки — абсолютные `[lon, lat]`. */
|
|
3069
|
+
interface SurfaceMesh {
|
|
3070
|
+
lonlat: ArrayLike<number>;
|
|
3071
|
+
z: ArrayLike<number>;
|
|
3072
|
+
normals: ArrayLike<number>;
|
|
3073
|
+
indices: ArrayLike<number>;
|
|
3074
|
+
}
|
|
3075
|
+
/**
|
|
3076
|
+
* Отметка верха полотна в точке: число — отметка сетки под точкой (метры, как в тайле сетки, без
|
|
3077
|
+
* подъёма отрисовки); `null` — сетка здесь загружена, но дороги из неё под точкой нет (земля);
|
|
3078
|
+
* `undefined` — сетки здесь нет вовсе (судить не по чему). `hint` — ожидаемая отметка: из нескольких
|
|
3079
|
+
* поверхностей одна над другой берётся ближайшая к ней.
|
|
3080
|
+
*/
|
|
3081
|
+
type SurfaceAt = (lng: number, lat: number, hint?: number) => number | null | undefined;
|
|
3082
|
+
/**
|
|
3083
|
+
* Высота полотна по сеткам детальных дорог. `boxes` — рамки загруженных тайлов сетки `[w, s, e, n]`:
|
|
3084
|
+
* внутри них «нет треугольника» значит «земля», снаружи — «не знаю». Учитываются только грани, глядящие
|
|
3085
|
+
* вверх (верх плиты, а не борт и не низ).
|
|
3086
|
+
*/
|
|
3087
|
+
declare function meshSurface(meshes: readonly SurfaceMesh[], boxes: readonly Box[]): SurfaceAt;
|
|
3088
|
+
/**
|
|
3089
|
+
* ВЫСОТА ПОЛОТНА ДЛЯ ВСЕГО МАРШРУТА (лента, линия, пройденная часть, стрелки поворотов — одна функция
|
|
3090
|
+
* на всех): отметка сетки под точкой (`surface`, ближайшая к отметке трассы), в зоне загруженной сетки
|
|
3091
|
+
* без дороги под точкой — земля (0), вне сетки — отметка трассы (`route`, по метру трассы).
|
|
3092
|
+
*/
|
|
3093
|
+
declare function routeSurfaceField(surface: SurfaceAt | null, route: LiftField | null): LiftField | null;
|
|
3094
|
+
|
|
3095
|
+
/**
|
|
3096
|
+
* ЛЕНТА ПО ПОЛОСАМ И ЛИНИЯ МАРШРУТА СМЕНЯЮТ ДРУГ ДРУГА ПЕРЕХОДОМ, А НЕ СТУПЕНЬКОЙ (22.10.2026, просьба
|
|
3097
|
+
* владельца «чтобы синий свет плавно уменьшался» и «лента пропадает, а линия не появляется»).
|
|
3098
|
+
*
|
|
3099
|
+
* Раньше смена «линия ↔ лента» была ступенькой, а по зуму — ещё и дырой:
|
|
3100
|
+
* · объекты ленты гасли с `LANE_MIN_ZOOM` (`minZoom`), а линия уступала ей куски на ЛЮБОМ зуме — камера
|
|
3101
|
+
* ведения на прямой держит z17.4, и на всём покрытии плана не было ни ленты, ни линии;
|
|
3102
|
+
* · ВДОЛЬ ТРАССЫ на краю покрытия широкая синяя лента обрывалась, а с того же метра шла тонкая линия.
|
|
3103
|
+
*
|
|
3104
|
+
* Теперь:
|
|
3105
|
+
* · ОДНО СОСТОЯНИЕ ВИДИМОСТИ (`RibbonBlend`): цель «лента / линия» по зуму с гистерезисом
|
|
3106
|
+
* (`LANE_ON_ZOOM` / `LANE_OFF_ZOOM`) и значение, которое идёт к цели за `RIBBON_BLEND_MS` по гладкой
|
|
3107
|
+
* кривой. Лента рисуется с множителем `ribbonAlpha`, а куски линии ПОД лентой — с `lineAlpha`
|
|
3108
|
+
* (`MapObject.fade`): линия гаснет, пока лента проявляется, и обратно. Куски линии вне ленты не
|
|
3109
|
+
* трогаются никогда. Карта будит кадры, только пока переход идёт (`animating`);
|
|
3110
|
+
* · у концов ленты, где её сменяет линия (не у начала и конца трассы), лента на `RIBBON_FADE_M` гаснет
|
|
3111
|
+
* гладкой ступенькой (прозрачность в вершине, `RouteArea.alphas`), а линия на том же отрезке лежит
|
|
3112
|
+
* поверх всегда (`ribbonLineHidden` — куски под лентой без зон растворения).
|
|
3113
|
+
*
|
|
3114
|
+
* Метры здесь — мера трассы ленты (`measureRoute`, та же, что у `Ribbon.hidden`).
|
|
3115
|
+
*/
|
|
3116
|
+
|
|
3117
|
+
/** Длина растворения ленты у конца, где её сменяет линия маршрута, метры. */
|
|
3118
|
+
declare const RIBBON_FADE_M = 20;
|
|
3119
|
+
/** Длительность перехода «линия ↔ лента», мс. */
|
|
3120
|
+
declare const RIBBON_BLEND_MS = 300;
|
|
3121
|
+
/**
|
|
3122
|
+
* НАИБОЛЬШИЙ ШАГ ПЕРЕХОДА ЗА ОДИН КАДР, мс часов (24.10.2026). Переход меряется НАРИСОВАННЫМИ кадрами: долгий кадр
|
|
3123
|
+
* (включение ленты на пороге зума давало на Redmi 110–357 мс) двигает его не больше, чем на этот шаг, а не проматывает
|
|
3124
|
+
* целиком — иначе весь кросс-фейд и съезд машины в полосу (`laneCarPose`, доля `ribbonAlpha`) приходились на один кадр.
|
|
3125
|
+
* 34 мс — два периода развёртки при 60 Гц и один при 30 Гц: на ровных кадрах переход идёт как раньше, за 300 мс; хуже
|
|
3126
|
+
* 30 кадров/с — растягивается, но не меньше чем на 9 кадров (шаг доли ≤ 0.11, шаг съезда ≤ 0.17 сдвига полосы).
|
|
3127
|
+
*/
|
|
3128
|
+
declare const RIBBON_BLEND_STEP_MAX_MS = 34;
|
|
3129
|
+
/** С этого зума лента включается (если была выключена). */
|
|
3130
|
+
declare const LANE_ON_ZOOM: number;
|
|
3131
|
+
/** Ниже этого зума лента выключается (если была включена). Между порогами состояние держится. */
|
|
3132
|
+
declare const LANE_OFF_ZOOM: number;
|
|
3133
|
+
/** Кромка в зоне растворения дробится не реже (метры): прозрачность GPU смешивает между вершинами. */
|
|
3134
|
+
declare const RIBBON_FADE_STEP_M = 2;
|
|
3135
|
+
/**
|
|
3136
|
+
* Дальше от оси трассы (метры) точка к концу ленты не относится: широкая лента разрешённых полос
|
|
3137
|
+
* отходит от оси на 10–15 м, а трасса, вернувшаяся к тому же месту (разворот), — не её конец.
|
|
3138
|
+
*/
|
|
3139
|
+
declare const RIBBON_FADE_REACH_M = 30;
|
|
3140
|
+
/** Растворение концов ленты по трассе: куда и в какую сторону гаснет. */
|
|
3141
|
+
interface RibbonFade {
|
|
3142
|
+
frame: Frame;
|
|
3143
|
+
route: Measured;
|
|
3144
|
+
/** Метр конца ленты и сторона ленты от него: +1 — лента дальше по трассе, −1 — ближе. */
|
|
3145
|
+
ends: {
|
|
3146
|
+
at: number;
|
|
3147
|
+
dir: 1 | -1;
|
|
3148
|
+
}[];
|
|
3149
|
+
fadeM: number;
|
|
3150
|
+
}
|
|
3151
|
+
/**
|
|
3152
|
+
* Концы ленты, которые сменяет линия маршрута, — по кускам `hidden` трассы `geometry`. Нет таких концов
|
|
3153
|
+
* (или `fadeM` = 0) — `null`: лента рисуется как раньше.
|
|
3154
|
+
*/
|
|
3155
|
+
declare function ribbonFade(geometry: LonLat[], hidden: [number, number][], fadeM?: number): RibbonFade | null;
|
|
3156
|
+
/** Прозрачность ленты в точке `[lon, lat]`. */
|
|
3157
|
+
declare function ribbonFadeAlpha(fade: RibbonFade, p: LonLat): number;
|
|
3158
|
+
/**
|
|
3159
|
+
* Кусок ленты (`RibbonArea`: кольца без замыкающей точки и отметки кромок) — с прозрачностью в
|
|
3160
|
+
* вершинах. Кромки рядом с концами дробятся через `RIBBON_FADE_STEP_M` (отметка кромки — та же у
|
|
3161
|
+
* всех её частей): полигон ленты после объединения держит вершины только на изломах и стыках
|
|
3162
|
+
* кусков, и без дробления 20 м растворения размазались бы на всю прямую кромку. Кусок, весь
|
|
3163
|
+
* видимый (альфа 1 везде), возвращается как есть и без `alphas`.
|
|
3164
|
+
*/
|
|
3165
|
+
declare function fadeRibbonArea(fade: RibbonFade | null, polygon: LonLat[][], feather: boolean[][]): {
|
|
3166
|
+
polygon: LonLat[][];
|
|
3167
|
+
feather: boolean[][];
|
|
3168
|
+
alphas: number[][] | null;
|
|
3169
|
+
};
|
|
3170
|
+
/**
|
|
3171
|
+
* Шевроны ленты (линия по оси полосы) — без частей в зоне растворения: белые стрелки над
|
|
3172
|
+
* погасшей лентой висели бы в воздухе. Части с прозрачностью ниже 0.999 выбрасываются, кромка
|
|
3173
|
+
* режется с шагом `RIBBON_FADE_STEP_M`. Ничего не осталось — пусто.
|
|
3174
|
+
*/
|
|
3175
|
+
declare function fadeRibbonLine(fade: RibbonFade | null, line: LonLat[]): LonLat[][];
|
|
3176
|
+
/**
|
|
3177
|
+
* Куски трассы, что линия маршрута уступает ленте, — без зон растворения: у конца, где ленту
|
|
3178
|
+
* сменяет линия, линия заходит под гаснущую ленту на `fadeM` (и лежит поверх неё). У начала и конца
|
|
3179
|
+
* трассы куски не укорачиваются. Кусок, короче двух зон, пропадает: линия там цела.
|
|
3180
|
+
*/
|
|
3181
|
+
declare function ribbonLineHidden(geometry: LonLat[], hidden: [number, number][], fadeM?: number): [number, number][];
|
|
3182
|
+
/**
|
|
3183
|
+
* Дополнение кусков до трассы `[0, length]`: где линия рисуется ВНЕ кусков. Куски — по возрастанию и не
|
|
3184
|
+
* пересекаются (`ribbonLineHidden`, `mergeRanges`).
|
|
3185
|
+
*/
|
|
3186
|
+
declare function outsideRanges(ranges: [number, number][], length: number): [number, number][];
|
|
3187
|
+
/**
|
|
3188
|
+
* СОСТОЯНИЕ ВИДИМОСТИ «ЛЕНТА ↔ ЛИНИЯ» — одно на карту. `update` — каждым кадром (зум кадра, часы кадра,
|
|
3189
|
+
* есть ли лента); возвращает `true`, пока переход идёт (карте нужен следующий кадр).
|
|
3190
|
+
*
|
|
3191
|
+
* · цель: лента есть и зум не ниже `LANE_ON_ZOOM` — включить; ниже `LANE_OFF_ZOOM` — выключить; между —
|
|
3192
|
+
* как была (гистерезис: камера, стоящая на пороге, не мигает);
|
|
3193
|
+
* · значение `t` идёт к цели с постоянной скоростью `1 / RIBBON_BLEND_MS`, но за кадр — не больше
|
|
3194
|
+
* `RIBBON_BLEND_STEP_MAX_MS` часов: долгий кадр переход не проматывает; видимая доля — `smoothstep(t)`;
|
|
3195
|
+
* · ленту сняли (новая трасса, план кончился) — цель «линия»: прежняя лента ГАСНЕТ тем же переходом (её объекты держит
|
|
3196
|
+
* слой, пока `t` не дошло до 0 — `retiring`), а линия новой трассы уже целая;
|
|
3197
|
+
* · первый кадр с лентой (и пропуск кадров дольше секунды, и часы назад) не двигает `t`: переход меряется
|
|
3198
|
+
* только живыми кадрами — лента, пришедшая на крупном зуме, проявляется, а не встаёт сразу.
|
|
3199
|
+
*/
|
|
3200
|
+
declare class RibbonBlend {
|
|
3201
|
+
/** Цель: лента видна. */
|
|
3202
|
+
on: boolean;
|
|
3203
|
+
/** Значение перехода 0…1 (0 — линия, 1 — лента). */
|
|
3204
|
+
t: number;
|
|
3205
|
+
private at;
|
|
3206
|
+
update(zoom: number, nowMs: number, hasRibbon: boolean): boolean;
|
|
3207
|
+
/** Множитель прозрачности ленты. */
|
|
3208
|
+
get ribbonAlpha(): number;
|
|
3209
|
+
/** Множитель прозрачности кусков линии под лентой. */
|
|
3210
|
+
get lineAlpha(): number;
|
|
3211
|
+
/** Переход ещё идёт. */
|
|
3212
|
+
get animating(): boolean;
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
/**
|
|
3216
|
+
* СКРУГЛЁННАЯ ТРАССА — одна геометрия на линию маршрута, стрелки поворотов и машину навигатора.
|
|
3217
|
+
*
|
|
3218
|
+
* Зачем. Сервер отдаёт ломаную: на перекрёстке — прямой угол. Стрелка поворота раньше сглаживалась
|
|
3219
|
+
* сама (своим ядром, с радиусом от ширины), а линия под ней оставалась углом — на повороте они
|
|
3220
|
+
* расходились (жалоба пользователя 07.10.2026). Теперь скругляется САМА ТРАССА, одним радиусом
|
|
3221
|
+
* `ROUTE_ROUND.RADIUS_M`, и всё, что лежит на линии, берёт эту же геометрию: линия, пройденная
|
|
3222
|
+
* часть, стрелки (их ось — кусок той же скруглённой трассы, без своего сглаживания), машина.
|
|
3223
|
+
*
|
|
3224
|
+
* Как. Каждый узел с изломом заменяется дугой окружности радиуса `RADIUS_M`, касательной к обоим
|
|
3225
|
+
* звеньям (сопряжение). Касательная длина `R·tg(θ/2)` не больше половины каждого из соседних звеньев
|
|
3226
|
+
* — дуги соседних узлов не наезжают друг на друга; где места мало, радиус уменьшается. Дуга — точками
|
|
3227
|
+
* через `ARC_STEP`, их число чётное: середина дуги — отдельная точка, «якорь» исходного узла.
|
|
3228
|
+
*
|
|
3229
|
+
* МЕТРЫ. Всё, что меряется по исходной трассе (метр ведения, манёвры, участки пробок, куски под
|
|
3230
|
+
* лентой по полосам), переводится на скруглённую через якоря: метр внутри исходного звена `i` —
|
|
3231
|
+
* та же доля пути между якорями `i` и `i+1` (`roundedMetre`). Перевод монотонный и непрерывный,
|
|
3232
|
+
* стыки пробок встают на середины дуг, пройденная часть кончается ровно под машиной.
|
|
3233
|
+
*
|
|
3234
|
+
* Чистые функции; порты — `RouteRound.kt`, `RouteRound.swift`, сверка — `kmp/fixtures/route-line/round.json`.
|
|
3235
|
+
*/
|
|
3236
|
+
|
|
3237
|
+
/** Скруглённая трасса: точки, якоря исходных узлов и обе меры (исходная и скруглённая). */
|
|
3238
|
+
interface RoundedRoute {
|
|
3239
|
+
coords: LonLat[];
|
|
3240
|
+
/** Номер точки `coords` для каждого исходного узла (для узла с дугой — середина дуги). */
|
|
3241
|
+
anchors: number[];
|
|
3242
|
+
/** Скруглённая трасса с метрами. */
|
|
3243
|
+
line: RouteLine;
|
|
3244
|
+
/** Исходная трасса с метрами. */
|
|
3245
|
+
src: RouteLine;
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
/**
|
|
3249
|
+
* МАШИНА ПО ЛЕНТЕ ПОЛОС — этап 1 «как обычный режим полос Baidu/Amap» (23.10.2026): в навигаторе машина едет не по оси
|
|
3250
|
+
* линии маршрута, а по оси РЕКОМЕНДОВАННОЙ полосы — той, по которой идёт лента (`buildRibbon`), и перестраивается
|
|
3251
|
+
* вместе с лентой.
|
|
3252
|
+
*
|
|
3253
|
+
* ★ ТА ЖЕ ГЕОМЕТРИЯ, ЧТО РИСУЕТ ЛЕНТУ. Сдвиг берётся не из плана заново, а из оси нарисованной основной ленты
|
|
3254
|
+
* (`Ribbon.car` — точки `band.line` с метрами `band.stations`): на прямой машина стоит ровно на оси ленты, на
|
|
3255
|
+
* перестроении идёт её же S-переходом (smoothstep на длине перестроения сервера, 40 м на полосу), на повороте через
|
|
3256
|
+
* узел — по её же дуге.
|
|
3257
|
+
*
|
|
3258
|
+
* ★ СДВИГ — ВЕКТОРОМ ОТ СКРУГЛЁННОЙ ЛИНИИ МАРШРУТА (`roundedPoint`): машина без ленты стоит на скруглённой трассе
|
|
3259
|
+
* (`roundedPose` — та же дуга, что у линии и стрелки поворота), и вектор «ось ленты − точка скруглённой трассы» на
|
|
3260
|
+
* том же метре, прибавленный к ней, ставит машину ровно на ось ленты и на дуге поворота. Боковой сдвиг по нормали
|
|
3261
|
+
* дал бы на повороте метры расхождения: дуга трассы (радиус 15 м) и дуга ленты (гаусс оси полосы) — разные кривые.
|
|
3262
|
+
* Вектор непрерывен по метру (обе точки непрерывны), поэтому рывков нет и на изломах трассы.
|
|
3263
|
+
*
|
|
3264
|
+
* ★ КУРС С БОКОВОЙ СОСТАВЛЯЮЩЕЙ: направление движения = касательная трассы + производная сдвига по метру
|
|
3265
|
+
* (центральная разность на ±`LANE_CAR_SLOPE_M`, непрерывна для кусочно-линейного сдвига). На перестроении машина
|
|
3266
|
+
* поворачивает носом в сторону новой полосы (до ≈7° на 40 м перестроения в полосу 3.5 м) и выпрямляется к его концу;
|
|
3267
|
+
* на дуге с постоянным сдвигом поворота не добавляется (касательная параллельной кривой та же).
|
|
3268
|
+
*
|
|
3269
|
+
* ★ ВЕС ПЛАНА (`RibbonCar.weights`, `carWeight`): 1 на куске покрытия (машина на ленте и там, где лента ещё
|
|
3270
|
+
* переходит из ленты вне покрытия в полосу) и `CAR_OUTSIDE_WEIGHT` вне покрытия — с 24.10.2026 тоже 1: машина едет
|
|
3271
|
+
* по ленте «полполосы справа», а не по линии маршрута левее неё (решение владельца; было 0 с подъездом smoothstep на
|
|
3272
|
+
* `JOIN_M`). Плавность на краях покрытия — у самой оси ленты (`RAMP_SLOPE`). Плана нет вовсе (сервис не ответил) —
|
|
3273
|
+
* нет и дорожки: машина на линии.
|
|
3274
|
+
*
|
|
3275
|
+
* ★ ВМЕСТЕ С ПЕРЕХОДОМ «ЛЕНТА ↔ ЛИНИЯ» (`RibbonBlend`): сдвиг умножается на `ribbonAlpha` — ниже зума ленты машина на
|
|
3276
|
+
* линии маршрута, на пороге съезжает в полосу (и обратно) за те же `RIBBON_BLEND_MS`, что идёт кросс-фейд. Гаснущая
|
|
3277
|
+
* прежняя лента (новый план) — своим слоем с `ribbonAlpha` своего перехода: сумма долей — 1, машина не прыгает.
|
|
3278
|
+
*
|
|
3279
|
+
* ★ КАМЕРА ИДЁТ ЗА ЛИНИЕЙ МАРШРУТА, А НЕ ЗА СДВИНУТОЙ МАШИНОЙ. Центр и курс камеры ведения считаются, как раньше, по
|
|
3280
|
+
* скруглённой трассе: на перестроении дорога на экране стоит, а машина переезжает в соседнюю полосу (как у Baidu и в
|
|
3281
|
+
* жизни). Камера за машиной рыскала бы вбок на каждом перестроении и на каждом входе в ленту — на 3–5 м за полсекунды.
|
|
3282
|
+
*
|
|
3283
|
+
* Метры — исходной трассы мерой сервера (`linkM`), те же, что у `Ribbon.hidden`, пройденной части и подъёма ленты.
|
|
3284
|
+
* Чистые функции; порты — `LaneCar.kt`, `LaneCar.swift`, `navigram/lane_car.cpp`, сверка —
|
|
3285
|
+
* `kmp/fixtures/nav-car/lane.json` (`scripts/gen-car-models.mjs`).
|
|
3286
|
+
*/
|
|
3287
|
+
|
|
3288
|
+
/** Половина шага центральной разности для курса, метры (шаг точек ленты — метр, оси перехода — 2 м). */
|
|
3289
|
+
declare const LANE_CAR_SLOPE_M = 2;
|
|
3290
|
+
/**
|
|
3291
|
+
* Шаг дорожки по метру трассы, метры (плюс сами метры точек ленты). Сдвиг между точками дорожки линеен, а скруглённая
|
|
3292
|
+
* линия на дуге — ломаная через 11° (`ROUTE_ROUND.ARC_STEP`): у точек ленты (через метр её собственной длины) метр
|
|
3293
|
+
* трассы на повороте шагает до 5 м, и линейный сдвиг по такому шагу уводил машину с ленты на 12 см; сетка через
|
|
3294
|
+
* полметра без точек ленты срезала излом оси на повороте через узел (9 см). Сетка с точками ленты — до 2 см.
|
|
3295
|
+
*/
|
|
3296
|
+
declare const LANE_CAR_STEP_M = 0.5;
|
|
3297
|
+
/** Сдвиг машины от скруглённой линии маршрута по метрам трассы: метры на восток и на север, уже с весом плана. */
|
|
3298
|
+
interface LaneCarTrack {
|
|
3299
|
+
stations: number[];
|
|
3300
|
+
east: number[];
|
|
3301
|
+
north: number[];
|
|
3302
|
+
}
|
|
3303
|
+
/**
|
|
3304
|
+
* МЕТРЫ ТОЧЕК ЛЕНТЫ — СТРОГО ПО ВОЗРАСТАНИЮ. На дуге поворота через узел (`filletTurn`) несколько точек оси получают
|
|
3305
|
+
* один метр (проекция точки дуги на трассу пошла бы назад, и метр держится): «точка ленты на метре» там скакала бы через
|
|
3306
|
+
* эти точки — машина прыгала бы на 2 м. Такой повтор раскладывается по длине оси между соседними метрами.
|
|
3307
|
+
*/
|
|
3308
|
+
declare function spreadStations(stations: number[], line: LonLat[]): number[];
|
|
3309
|
+
/**
|
|
3310
|
+
* Дорожка машины из оси основной ленты (`Ribbon.car`) и скруглённой трассы `rr` той же геометрии (`roundRoute`): через
|
|
3311
|
+
* `LANE_CAR_STEP_M` по метру и в метрах точек ленты — вектор «точка ленты − точка скруглённой линии» с весом плана. Где сдвига нет (вне плана),
|
|
3312
|
+
* точки не хранятся — только нули на краях: дорожка весит столько, сколько покрытие. Нет основной ленты — `null`.
|
|
3313
|
+
*/
|
|
3314
|
+
declare function laneCarTrack(car: RibbonCar | null | undefined, rr: RoundedRoute): LaneCarTrack | null;
|
|
3315
|
+
/** Сдвиг на метре `s` и его производная по метру (центральная разность на ±`LANE_CAR_SLOPE_M`). */
|
|
3316
|
+
declare function laneCarShift(track: LaneCarTrack, s: number): {
|
|
3317
|
+
east: number;
|
|
3318
|
+
north: number;
|
|
3319
|
+
dEast: number;
|
|
3320
|
+
dNorth: number;
|
|
3321
|
+
};
|
|
3322
|
+
/** Слой дорожки: дорожка ленты и её доля в позе (`RibbonBlend.ribbonAlpha`). */
|
|
3323
|
+
interface LaneCarLayer {
|
|
3324
|
+
track: LaneCarTrack | null;
|
|
3325
|
+
alpha: number;
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* ПОЗА МАШИНЫ НА ЛЕНТЕ: `point`/`bearing` — поза на скруглённой линии маршрута на метре `s` (`roundedPose`), слои —
|
|
3329
|
+
* дорожки показанной и гаснущей лент с долями. Ни одна дорожка не участвует (нет ленты, доли нулевые, метр не число)
|
|
3330
|
+
* — поза как есть, бит в бит.
|
|
3331
|
+
*/
|
|
3332
|
+
declare function laneCarPose(point: LonLat, bearing: number, s: number, layers: readonly LaneCarLayer[]): {
|
|
3333
|
+
point: LonLat;
|
|
3334
|
+
bearing: number;
|
|
3335
|
+
};
|
|
3336
|
+
|
|
3337
|
+
/**
|
|
3338
|
+
* СТРЕЛКИ ПОВОРОТОВ НА ЛИНИИ МАРШРУТА — расположение и размеры как у навигатора `navigator-main`
|
|
3339
|
+
* (`sdk/.../map/RouteArrows.kt`, `RouteArrowLayers.kt`), а тот — по Organic Maps
|
|
3340
|
+
* (`libs/drape_frontend/route_renderer.cpp`, форма — `data/styles/default/light/symbols/route-arrow.svg`;
|
|
3341
|
+
* Apache-2.0, см. `porting/navigator-main.md`). Чистая геометрия, без GL: веб рисует её своими
|
|
3342
|
+
* многоугольниками (`Polygon`: тень, обводка, заливка), порты (`ManeuverArrows.kt`,
|
|
3343
|
+
* `ManeuverArrows.swift`) — так же; сверка — общие фикстуры `kmp/fixtures/maneuver-arrows`
|
|
3344
|
+
* (`node scripts/export-maneuver-arrows-fixture.mjs`).
|
|
3345
|
+
*
|
|
3346
|
+
* ★ РАСПОЛОЖЕНИЕ. Хвост — `BEFORE_M` (22 м) до узла манёвра, конец древка — `AFTER_M` (18 м) после
|
|
3347
|
+
* него: длина как у стрелки Яндекс.Навигатора (~40 м), по просьбе пользователя 08.10.2026 — стрелка
|
|
3348
|
+
* navigator-main / Organic Maps (36 + 54 = 90 м) на городских поворотах была слишком длинной. Близкие
|
|
3349
|
+
* манёвры (не дальше `NEARBY_M`, 50 м, друг от друга) — ОДНА стрелка от хвоста первого до головы
|
|
3350
|
+
* последнего: две короткие стрелки на одном перекрёстке читаются хуже одной изогнутой. Манёвр ближе
|
|
3351
|
+
* `END_MARGIN_M` к началу или концу трассы стрелки не получает.
|
|
3352
|
+
*
|
|
3353
|
+
* ★ РАЗМЕР — В ПИКСЕЛЯХ ЭКРАНА ПО ЗУМУ (как слои MapLibre у `navigator-main`), а не в метрах: древко
|
|
3354
|
+
* 5 → 10 → 12 px на z14/16/18, обводка 8 → 16 → 19 px, наконечник (треугольник 30×18 px с белым
|
|
3355
|
+
* 22×12 px внутри) — ×0.5 → ×1 → ×1.15; между узлами — линейно, за краями — край. Ниже `MIN_ZOOM` (14)
|
|
3356
|
+
* стрелок нет. Пиксели переводятся в метры по `metersPerPixel` на широте трассы; геометрию держит
|
|
3357
|
+
* кэш по ступени зума `ZOOM_STEP` (⅛: ширина прыгает не больше чем на 9 %).
|
|
3358
|
+
*
|
|
3359
|
+
* ★ ФОРМА. Древко идёт РОВНО ПО ТРАССЕ — своего сглаживания нет: скругляется сама трасса
|
|
3360
|
+
* (`route-round.ts`, радиус `ROUTE_ROUND.RADIUS_M`), и стрелка, построенная по скруглённой трассе,
|
|
3361
|
+
* лежит на линии маршрута по всей дуге (раньше ось сглаживалась своим ядром, и на повороте стрелка
|
|
3362
|
+
* уходила с угловатой линии). Сзади древко кончается полукругом, как круглый конец линии MapLibre. Наконечник —
|
|
3363
|
+
* прямой треугольник ЗА концом древка по его последнему направлению: основание обводки — ровно на
|
|
3364
|
+
* конце древка, белый — на `HEAD_FILL_INSET_PX` дальше (у `navigator-main` это значок с якорем
|
|
3365
|
+
* `bottom` в конце линии). Оформление: белая заливка, синяя обводка `#055FCD` и лёгкая тень (обводка,
|
|
3366
|
+
* раздутая на `SHADOW_PX`) — тень наша, на объёмной карте стрелка без неё «прилипает» к асфальту.
|
|
3367
|
+
*
|
|
3368
|
+
* ★ МЕТРЫ — МЕРОЙ ЛИНИИ МАРШРУТА (`routeLine`, `linkM`): манёвры приходят метрами той же трассы
|
|
3369
|
+
* (`metresAlong` по точкам шагов).
|
|
3370
|
+
*
|
|
3371
|
+
* ★ ГАСНЕТ ПЛАВНО, КАК ЛЕНТА (26.10.2026, просьба владельца «стрелки в поворотах исчезали плавно, как лента
|
|
3372
|
+
* маршрута»). Раньше стрелка снималась целиком на метре конца древка (`toM`): машина ещё не доехала до острия,
|
|
3373
|
+
* а стрелка пропадала ступенькой; смена трассы, вида (линия ↔ лента) и порог `MIN_ZOOM` — тоже ступенькой.
|
|
3374
|
+
* Теперь два закона, оба — общие с лентой:
|
|
3375
|
+
* · ПРОЙДЕННОЕ РАСТВОРЯЕТСЯ ПОЗАДИ МАШИНЫ ПО МЕТРУ ТРАССЫ: у каждой точки колец — метр трассы
|
|
3376
|
+
* (`fillStations`/`casingStations`/`shadowStations`, проекция на ось стрелки), площади рисуются с
|
|
3377
|
+
* `MapObject.along` (метр машины, `PASS_FADE_M` = `RIBBON_PASS_FADE_M`, сторона +1) — тот же шейдер и тот же
|
|
3378
|
+
* `alongFadeFactor`, что у ленты (`RibbonAreas.passAt`), без пересборки. Стрелка видна, пока её дальний край
|
|
3379
|
+
* (`endM`, острие тени) не ушёл за длину растворения (`shownManeuverArrows(…, passFadeM)`) — к этому метру она
|
|
3380
|
+
* уже прозрачна вся;
|
|
3381
|
+
* · СНЯТИЕ ЦЕЛИКОМ — ПО ВРЕМЕНИ (`ArrowFades`): стрелка, ушедшая из набора (другая трасса, вид линия ↔ лента,
|
|
3382
|
+
* порог зума, выключатель), гаснет за `RIBBON_BLEND_MS` тем же законом, что кросс-фейд «лента ↔ линия»
|
|
3383
|
+
* (`RibbonBlend`: шаг за кадр не больше `RIBBON_BLEND_STEP_MAX_MS`, доля `smoothstep`), а новая так же
|
|
3384
|
+
* проявляется. Смена СТУПЕНИ зума — тот же ключ: множитель переходит к стрелке новой ступени сразу (стрелки
|
|
3385
|
+
* соседних ступеней стоят наготове, 1ddcb20a).
|
|
3386
|
+
*/
|
|
3387
|
+
|
|
3388
|
+
/** Константы стрелки: метры (`_M`), пиксели экрана (`_PX`) и узлы «зум → пиксели» (`[зум, px]`). */
|
|
3389
|
+
declare const MANEUVER_ARROW: {
|
|
3390
|
+
/** Хвост — столько до узла (первого) манёвра. */
|
|
3391
|
+
readonly BEFORE_M: 22;
|
|
3392
|
+
/** Конец древка — столько после узла (последнего) манёвра; наконечник — за ним. */
|
|
3393
|
+
readonly AFTER_M: 18;
|
|
3394
|
+
/** Манёвры не дальше этого друг от друга — одна стрелка. */
|
|
3395
|
+
readonly NEARBY_M: 50;
|
|
3396
|
+
/** Ближе этого к началу или концу трассы манёвр стрелки не получает. */
|
|
3397
|
+
readonly END_MARGIN_M: 3;
|
|
3398
|
+
/** Ниже этого зума стрелок нет. */
|
|
3399
|
+
readonly MIN_ZOOM: 14;
|
|
3400
|
+
/** Ширина белого древка, px по зуму. */
|
|
3401
|
+
readonly BODY_PX: ReadonlyArray<readonly [number, number]>;
|
|
3402
|
+
/** Ширина синей обводки древка (вся, не кайма), px по зуму. */
|
|
3403
|
+
readonly BORDER_PX: ReadonlyArray<readonly [number, number]>;
|
|
3404
|
+
/** Множитель наконечника по зуму. */
|
|
3405
|
+
readonly HEAD_SCALE: ReadonlyArray<readonly [number, number]>;
|
|
3406
|
+
/** Наконечник обводки: полуширина основания и длина, px при множителе 1. */
|
|
3407
|
+
readonly HEAD_BORDER_HALF_PX: 15;
|
|
3408
|
+
readonly HEAD_BORDER_LEN_PX: 18;
|
|
3409
|
+
/** Белый наконечник: полуширина, длина и отступ основания от конца древка, px при множителе 1. */
|
|
3410
|
+
readonly HEAD_FILL_HALF_PX: 11;
|
|
3411
|
+
readonly HEAD_FILL_LEN_PX: 12;
|
|
3412
|
+
readonly HEAD_FILL_INSET_PX: 3;
|
|
3413
|
+
/** Скругление углов (остриё остаётся острым) и тень, px. */
|
|
3414
|
+
readonly ROUND_PX: 0.75;
|
|
3415
|
+
readonly SHADOW_PX: 2.5;
|
|
3416
|
+
/** Шаг дуги скругления, радианы. */
|
|
3417
|
+
readonly ARC_STEP: number;
|
|
3418
|
+
/** Шаг переразбивки оси, метры (своего сглаживания нет — ось = трасса). */
|
|
3419
|
+
readonly STEP_M: 1;
|
|
3420
|
+
/** Цвета и прозрачности: белая стрелка, синяя обводка (`navigator-main`), лёгкая тень. */
|
|
3421
|
+
readonly FILL: "#ffffff";
|
|
3422
|
+
readonly CASING: "#055fcd";
|
|
3423
|
+
readonly SHADOW: "#0b1a3a";
|
|
3424
|
+
readonly FILL_OPACITY: 1;
|
|
3425
|
+
readonly CASING_OPACITY: 1;
|
|
3426
|
+
readonly SHADOW_OPACITY: 0.18;
|
|
3427
|
+
/**
|
|
3428
|
+
* Высота над отметкой трассы — ТА ЖЕ, ЧТО У ЛИНИИ МАРШРУТА и ленты по полосам (`OVERLAY_ALTITUDE`,
|
|
3429
|
+
* `ROUTE_TOP` в `ld-roads.ts`): стрелка лежит на одной с ними поверхности, на эстакаде — на настиле
|
|
3430
|
+
* (отметка трассы под каждой вершиной, `routeLiftField`). Над линией её держит порядок (`Z_*`), над
|
|
3431
|
+
* асфальтом — сдвиг глубины прохода объектов. Было 0.4 — над лентой на 0.34.
|
|
3432
|
+
*/
|
|
3433
|
+
readonly LIFT_M: 0.28;
|
|
3434
|
+
/** Порядок среди объектов: над пройденной линией (21). */
|
|
3435
|
+
readonly Z_SHADOW: 23;
|
|
3436
|
+
readonly Z_CASING: 24;
|
|
3437
|
+
readonly Z_FILL: 25;
|
|
3438
|
+
/** Ступень зума для кэша геометрии. */
|
|
3439
|
+
readonly ZOOM_STEP: 0.125;
|
|
3440
|
+
/** Длина растворения пройденной части позади машины, метры — та же, что у ленты (`RIBBON_PASS_FADE_M`). */
|
|
3441
|
+
readonly PASS_FADE_M: 20;
|
|
3442
|
+
};
|
|
3443
|
+
/** Манёвр на трассе: метр (мерой линии маршрута) и вид шага (`type` + `modifier`, как у `/route`). */
|
|
3444
|
+
interface ManeuverAt {
|
|
3445
|
+
atM: number;
|
|
3446
|
+
type: string;
|
|
3447
|
+
modifier?: string;
|
|
3448
|
+
}
|
|
3449
|
+
/** Стрелка манёвра (или группы близких манёвров). Кольца замкнуты (первая точка повторена в конце), против часовой. */
|
|
3450
|
+
interface ManeuverArrow {
|
|
3451
|
+
/** Номер первого манёвра группы во входном списке. */
|
|
3452
|
+
index: number;
|
|
3453
|
+
/** Номера всех манёвров группы (по ходу). */
|
|
3454
|
+
indices: number[];
|
|
3455
|
+
/** Метр первого манёвра группы. */
|
|
3456
|
+
atM: number;
|
|
3457
|
+
/** Метры трассы, которые занимает древко: от хвоста до основания наконечника. */
|
|
3458
|
+
fromM: number;
|
|
3459
|
+
toM: number;
|
|
3460
|
+
/** Сглаженная ось древка от хвоста до основания наконечника. */
|
|
3461
|
+
line: LonLat[];
|
|
3462
|
+
/** Сама стрелка (белая). */
|
|
3463
|
+
fill: LonLat[];
|
|
3464
|
+
/** Синяя обводка — шире древко и больше наконечник. */
|
|
3465
|
+
casing: LonLat[];
|
|
3466
|
+
/** Лёгкая тень — обводка, раздутая на `SHADOW_PX`. */
|
|
3467
|
+
shadow: LonLat[];
|
|
3468
|
+
/**
|
|
3469
|
+
* Метр трассы в каждой точке колец (`fill`, `casing`, `shadow`; та же длина, замыкающая точка — тоже): проекция на
|
|
3470
|
+
* ось стрелки, доля её длины — в метры `fromM…toM`, за концами оси — продолжение по прямой (хвост, острие). По ним
|
|
3471
|
+
* площадь растворяется позади машины (`RouteArea.stations` + `MapObject.along`). Мера — та же, что у `fromM`/`toM`.
|
|
3472
|
+
*/
|
|
3473
|
+
fillStations: number[];
|
|
3474
|
+
casingStations: number[];
|
|
3475
|
+
shadowStations: number[];
|
|
3476
|
+
/** Дальний край стрелки по трассе — наибольший метр точек тени (острие с тенью). */
|
|
3477
|
+
endM: number;
|
|
3478
|
+
}
|
|
3479
|
+
interface ManeuverArrowOptions {
|
|
3480
|
+
/** Зум, под который строятся размеры (по умолчанию 16 — множитель наконечника 1). */
|
|
3481
|
+
zoom?: number;
|
|
3482
|
+
/** Метры в пикселе экрана; по умолчанию — `metersPerPixel` на широте начала трассы и зуме `zoom`. */
|
|
3483
|
+
metersPerPixel?: number;
|
|
3484
|
+
}
|
|
3485
|
+
/** Размеры стрелки в метрах на зуме. */
|
|
3486
|
+
interface ManeuverArrowSize {
|
|
3487
|
+
bodyHalfM: number;
|
|
3488
|
+
borderHalfM: number;
|
|
3489
|
+
headBorderHalfM: number;
|
|
3490
|
+
headBorderLenM: number;
|
|
3491
|
+
headFillHalfM: number;
|
|
3492
|
+
headFillLenM: number;
|
|
3493
|
+
headFillInsetM: number;
|
|
3494
|
+
roundM: number;
|
|
3495
|
+
shadowM: number;
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Нужна ли манёвру стрелка — `hasRouteArrow` у `navigator-main`: настоящие повороты, развилки, съезды
|
|
3499
|
+
* и кольцо — всегда; `continue`/`new name` — только если дорога меняет направление (модификатор не
|
|
3500
|
+
* «прямо»); старт, финиш, полосы, уведомления — нет.
|
|
3501
|
+
*/
|
|
3502
|
+
declare function maneuverNeedsArrow(type: string, modifier?: string): boolean;
|
|
3503
|
+
/** Значение узлов `[зум, значение]` на зуме: между узлами — линейно, за краями — край (как `interpolate` MapLibre). */
|
|
3504
|
+
declare function zoomStops(stops: ReadonlyArray<readonly [number, number]>, zoom: number): number;
|
|
3505
|
+
/** Ступень зума для кэша геометрии (`ZOOM_STEP`); `Math.round` веба. */
|
|
3506
|
+
declare function maneuverArrowZoomStep(zoom: number): number;
|
|
3507
|
+
/** Размеры стрелки в метрах: пиксели узлов на зуме `zoom`, умноженные на метры в пикселе. */
|
|
3508
|
+
declare function maneuverArrowSize(zoom: number, metersPerPixel: number): ManeuverArrowSize;
|
|
3509
|
+
/**
|
|
3510
|
+
* Контур, раздутый наружу на `d` со СКРУГЛЁННЫМИ выпуклыми углами (сумма Минковского с кругом —
|
|
3511
|
+
* то же, что сдвиг порога знакового расстояния у шейдера пробок). Кольцо против часовой, без повтора
|
|
3512
|
+
* первой точки. Вогнутые углы — пересечением сдвинутых сторон.
|
|
3513
|
+
*/
|
|
3514
|
+
declare function inflateRound(ring: XY[], d: number): XY[];
|
|
3515
|
+
/**
|
|
3516
|
+
* Стрелки манёвров на трассе `geometry` (`[lon, lat]` по ходу). `maneuvers` — метры манёвров мерой
|
|
3517
|
+
* линии маршрута (по возрастанию не обязательно). Стрелка — каждому, кому она нужна
|
|
3518
|
+
* (`maneuverNeedsArrow`); манёвры не дальше `NEARBY_M` друг от друга — одной стрелкой.
|
|
3519
|
+
*/
|
|
3520
|
+
declare function maneuverArrows(geometry: LonLat[], maneuvers: ManeuverAt[], opts?: ManeuverArrowOptions): ManeuverArrow[];
|
|
3521
|
+
/**
|
|
3522
|
+
* СТРЕЛКА ЛЕНТЫ — доли ширины полосы ленты (как стрелки полосного вида Apple Maps и Google): белое
|
|
3523
|
+
* древко ≈ 60 % ширины ленты, синяя обводка ≈ 72 %, наконечник — почти во всю ширину, но внутри неё.
|
|
3524
|
+
*/
|
|
3525
|
+
declare const RIBBON_ARROW: {
|
|
3526
|
+
readonly BODY: 0.3;
|
|
3527
|
+
readonly BORDER: 0.36;
|
|
3528
|
+
readonly HEAD_FILL_HALF: 0.4;
|
|
3529
|
+
readonly HEAD_BORDER_HALF: 0.47;
|
|
3530
|
+
readonly HEAD_FILL_LEN: 0.9;
|
|
3531
|
+
readonly HEAD_BORDER_LEN: 1.1;
|
|
3532
|
+
readonly HEAD_FILL_INSET: 0.15;
|
|
3533
|
+
readonly ROUND: 0.05;
|
|
3534
|
+
readonly SHADOW: 0.04;
|
|
3535
|
+
/** Лента должна идти за узлом манёвра хотя бы столько, чтобы у стрелки была голова на ленте. */
|
|
3536
|
+
readonly MIN_AFTER_M: 8;
|
|
3537
|
+
/**
|
|
3538
|
+
* Голова — на выходе из поворота: если у конца древка лента ещё поворачивает (разворот, крутой
|
|
3539
|
+
* поворот — направление на `SETTLE_PROBE_M` вперёд меняется больше `SETTLE_DEG`), конец сдвигается
|
|
3540
|
+
* вперёд шагом `SETTLE_STEP_M`, но не дальше `SETTLE_MAX_M` за последним узлом.
|
|
3541
|
+
*/
|
|
3542
|
+
readonly SETTLE_PROBE_M: 4;
|
|
3543
|
+
/** Вперёд направление берётся на `SETTLE_AHEAD_M` — прямой кусок посреди разворота (через разделитель) не считается выходом. */
|
|
3544
|
+
readonly SETTLE_AHEAD_M: 12;
|
|
3545
|
+
readonly SETTLE_TAIL_M: 6;
|
|
3546
|
+
readonly SETTLE_DEG: 15;
|
|
3547
|
+
readonly SETTLE_STEP_M: 1;
|
|
3548
|
+
readonly SETTLE_MAX_M: 40;
|
|
3549
|
+
};
|
|
3550
|
+
/**
|
|
3551
|
+
* СТРЕЛКИ МАНЁВРОВ НА ЛЕНТЕ ПО ПОЛОСАМ: ось — сама основная лента (`band`: та же дорожка по полосам,
|
|
3552
|
+
* что рисует лента, — с поворотом и разворотом), ширина — доли ширины ленты у узла манёвра (`RIBBON_ARROW`),
|
|
3553
|
+
* голова — у выхода, внутри ленты (ось не продлевается за её конец). Группы манёвров и длины — как у
|
|
3554
|
+
* стрелок линии (`BEFORE_M`, `AFTER_M`, `NEARBY_M`). `maneuvers` — метры мерой ИСХОДНОЙ трассы ленты
|
|
3555
|
+
* (как станции её фич). Манёвр, у которого ленты нет (узел вне основной ленты или лента кончается
|
|
3556
|
+
* раньше `MIN_AFTER_M` за ним), стрелки ленты не получает — у него остаётся стрелка линии.
|
|
3557
|
+
*/
|
|
3558
|
+
declare function ribbonManeuverArrows(ribbon: Ribbon, maneuvers: ManeuverAt[]): ManeuverArrow[];
|
|
3559
|
+
/**
|
|
3560
|
+
* Какие стрелки показывать: ниже `MIN_ZOOM` — никакие; выше — все, кроме пройденных (`null` — ведения нет).
|
|
3561
|
+
* Пройденная без растворения (`passFadeM` = 0) — конец древка позади `progressM`; с растворением позади машины
|
|
3562
|
+
* (`passFadeM` > 0, обычно `PASS_FADE_M`) — дальний край стрелки (`endM`) позади `progressM − passFadeM`: там она
|
|
3563
|
+
* прозрачна вся, и снятие не видно. Возвращает номера в списке `arrows`.
|
|
3564
|
+
*/
|
|
3565
|
+
declare function shownManeuverArrows(arrows: ManeuverArrow[], progressM: number | null, zoom: number, passFadeM?: number): number[];
|
|
3566
|
+
/**
|
|
3567
|
+
* ПОЯВЛЕНИЕ И СНЯТИЕ СТРЕЛОК ПО ВРЕМЕНИ — тем же законом, что кросс-фейд «лента ↔ линия» (`RibbonBlend`). Ключ —
|
|
3568
|
+
* стрелка в своём виде (слой решает сам: поколение трассы, вид линия/лента, первый манёвр группы; ступень зума в ключ
|
|
3569
|
+
* НЕ входит — смена ступени мгновенна). У ключа значение `t` 0…1 и цель (видна / снята):
|
|
3570
|
+
* · `show(keys)` — видны ровно эти; новый ключ заводится с `t` = 0 и проявляется, прочие гаснут;
|
|
3571
|
+
* · `tick(nowMs)` — кадр: `t` идёт к цели со скоростью `1 / RIBBON_BLEND_MS`, за кадр не больше
|
|
3572
|
+
* `RIBBON_BLEND_STEP_MAX_MS` часов; первый кадр, пропуск дольше секунды и часы назад — без шага. Снятый ключ,
|
|
3573
|
+
* дошедший до нуля, убирается (`gone` — их список за этот кадр: слою пора снять его объекты). true — переход идёт;
|
|
3574
|
+
* · `settle()` — сразу к цели, без перехода (слой без часов: проверки);
|
|
3575
|
+
* · `alpha(key)` — множитель прозрачности объектов ключа: `smoothstep(t)`; нет ключа — 0.
|
|
3576
|
+
*/
|
|
3577
|
+
declare class ArrowFades {
|
|
3578
|
+
private items;
|
|
3579
|
+
private at;
|
|
3580
|
+
/** Ключи, снятые до нуля последним `tick`/`settle`. */
|
|
3581
|
+
gone: string[];
|
|
3582
|
+
show(keys: Iterable<string>): void;
|
|
3583
|
+
tick(nowMs: number): boolean;
|
|
3584
|
+
settle(): void;
|
|
3585
|
+
alpha(key: string): number;
|
|
3586
|
+
has(key: string): boolean;
|
|
3587
|
+
/** Переход идёт хоть у одного ключа. */
|
|
3588
|
+
get animating(): boolean;
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
export { ABSENT_RETRY_MS, ARROW_ICONS, ARROW_ICON_STROKE, ARROW_PATHS, ARROW_TIP_K, type AreaFrame, type AreaSpan, ArrowFades, type BuildOptions, CAP_SEGMENTS, CAR_OUTSIDE_WEIGHT, CHEVRON_MIN_RUN_M, CHEVRON_TOLERANCE_M, CHUNK_M, CLASS_LIMIT, CLOSE_M, CLOSE_VIEW, CORNER_DEG, CORNER_SMOOTH_M, CURSOR, CURSOR_SCALE, CUT_BLEED, DEFAULT_RIBBON_CAP, EVEN_WIDTH_M, FAR_VIEW, FIX_ACC_MAX, FIX_ACC_ROUTE, FIX_MAX_AGE_MS, FIX_NET_ACC, FIX_NET_HOLD_MS, FIX_REJECT_RESET, FIX_SKEW_MIN_MS, FIX_STREAM_JITTER_MS, FIX_VMAX, FOLLOW_BACK_MS, type FetchLanePlanOptions, type FetchLike, type Frame, GUIDANCE_URL, type GuideFrame, HINT_M, type HeightAt, type HintLane, JOIN_M, LANE_CAR_SLOPE_M, LANE_CAR_STEP_M, LANE_OFF_ZOOM, LANE_ON_ZOOM, LANG_TAG, LEVEL_HEIGHT_M, type LaneCarLayer, type LaneCarTrack, LaneGuide, type LaneHint, type LanePlan, type LanePlanRequest, type LaneRoute, type LaneState, type LonLat, MANEUVER_ARROW, ManeuverAnnouncer, type ManeuverArrow, type ManeuverArrowOptions, type ManeuverArrowSize, type ManeuverAt, type Mask, type Measured, NAV_ARRIVE_M, NAV_ARROW, NAV_ARROW_PALETTES, NAV_BACK_M, NAV_BEARING_TAU, NAV_CAR, NAV_CAR_BODIES, NAV_CAR_CLEAR, NAV_CAR_KINDS, NAV_CAR_LABELS, NAV_COAST_TAU, NAV_COLORS, NAV_DEMO, NAV_LOOK_AHEAD_M, NAV_MAX_DT, NAV_MIN_VISIBLE, NAV_PITCH, NAV_PREDICT_MAX, NAV_PULL_TAU, NAV_ROUTE_PULL_TAU, NAV_SPEED_FALL_TAU, NAV_SPEED_RISE_TAU, NAV_SPOKEN_GRID, NAV_STILL_MPS, NAV_THRESHOLD_GRID, NAV_TILT, NAV_TOP_PAD, NAV_TRAFFIC_COLORS, NAV_TRAFFIC_LEVELS, NAV_VIA_SAY_M, NAV_VIEW, NAV_VOICE, NAV_ZOOM_STICK, type NavAim, type NavArrowPalette, type NavBestRoute, type NavCarBody, type NavCarHost, type NavCarIcon, type NavCarKind, type NavCarMaterial, type NavCarModelKind, NavCarOverlay, type NavCarPart, type NavCarPose, type NavFeature, type NavFix, NavFixFilter, type NavFixVerdict, type NavFollowMode, type NavLang, NavProgress, type NavProgressOut, NavPuck, NavRoute, type NavSayKey, type NavStep, NavTracker, type NavTrafficBand, type NavTrip, type NavUpcoming, type NavUtterance, NavViewCamera, type NavViewTarget, type NavVoiceCue, NavZoomStick, OUTLINE_STEP_M, OUTSIDE_LANE_M, OffRouteWatch, type OutsideOptions, PASSED_MIN_M, PROG_AHEAD, PROG_BACK_CONFIRM, PROG_BACK_M, PROG_BACK_MIN, PROG_COARSE_HOLD_MS, PROG_JUMP_CONFIRM, PROG_NEAR_M, PROG_NEAR_MPS, PROG_SWITCH_M, PROG_WIDE_M, PROG_WIDE_ON_M, PUCK_GLIDE_MAX_MS, PUCK_GLIDE_MIN_MS, PUCK_STILL_MAX_M, PUCK_STILL_MIN_M, PUCK_STILL_MPS, PUCK_TELEPORT_M, type PassArea, type PassChevron, type PlanChange, type PlanLane, type PlanManeuver, type PlanSegment, RAMP_SLOPE, RIBBON_ARROW, RIBBON_BLEND_MS, RIBBON_BLEND_STEP_MAX_MS, RIBBON_CAPS, RIBBON_CHEVRON, RIBBON_FADE_M, RIBBON_FADE_REACH_M, RIBBON_FADE_STEP_M, RIBBON_MARGIN_M, RIBBON_PASS_FADE_M, RIBBON_PASS_MARGIN_M, RIBBON_STEP_M, RIBBON_TILE_Z, type Ribbon, type RibbonArea, RibbonAreas, RibbonBlend, type RibbonCap, type RibbonCar, type RibbonChevron, type RibbonFade, type RibbonFeature, type RibbonKind, type RibbonPiece, type RouteLine, type RouteManeuver, type RoutePiece, SPEED_AROUND_TOLERANCE, SPEED_LIMIT_HOLD_MS, SPEED_QUERY_TOLERANCES, type SpeedLimit, SpeedLimitHold, type SpeedVerdict, SpeedWatch, type SurfaceAt, type SurfaceMesh, TILE_EXTENT, TURN_MIN_DEG, TURN_WINDOW_M, type Track, WIDTH_SMOOTH_PTS, type XY, ZOOM_BY_SPEED, areaFrame, areaSpans, arrowPath, bearing, bearingAt, buildRibbon, cameraCenterShift, capPoints, carWeight, chevronRunBase, closeness, contiguousGroups, cutInset, demoSpeedKmh, demoTargetKmh, densify, distM, distParts, edgesOf, evenWidths, fadeRibbonArea, fadeRibbonLine, fallbackHeight, farThreshold, fetchLanePlan, fmt1, fmt4, fmtDist, fmtDur, fracIndex, frameAt, fromUnits, guidanceCamera, hintAt, hintFor, hintNear, inflateRound, isNetworkFix, isOldFix, isRibbonCap, laneCarPose, laneCarShift, laneCarTrack, lanePlanRequest, laneTrack, lateralAt, liftProps, liftPropsOf, lineBetween, linkM, maneuverArrow, maneuverArrowSize, maneuverArrowZoomStep, maneuverArrows, maneuverNeedsArrow, maskOf, measure, measureRoute, mergeRanges, meshSurface, metresAlong, minusMask, mixLevel, mixLonLat, mixNum, navAdvanceM, navAheadPx, navArrowOutline, navCameraCenter, navCameraShiftPx, navCarColor, navCarGlb, navCarHash, navCarIcon, navCarIconSvg, navCarKind, navCarLiftAt, navCarMesh, navCarModelId, navCarPose, navCarScreenY, navCursorShapes, navDemoStep, navDt, navFarText, navFarThreshold, navFollowAim, navFollowModeOf, navFollowPitch, navFollowShiftPx, navHasFar, navHasSpokenDistance, navIsTurn, navLockedCenter, navManeuverPhrase, navNearThreshold, navPhrase, navRoundedForSpeech, navSay, navSnapSpoken, navSpeechText, navSpeeding, navSpokenDistance, navStartingLowercase, navStepSpeed, navStepText, navTargetZoom, navThen, navTiltForZoom, navTrafficLevel, navTurnWeight, navViewTarget, navVoicePhrases, navVoicePlan, navWithDistance, navZoomForAhead, normalCdf, normalsOf, offsetLine, offsetPoint, outlineOf, outsideRanges, parseLanePlan, parseMaxspeed, passedKind, passedPieces, pointAt, pointOn, project, projectOnSegment, resampleLine, resetLanePlanAbsence, ribbonAtProgress, ribbonChevrons, ribbonFade, ribbonFadeAlpha, ribbonLineHidden, ribbonManeuverArrows, ribbonPassChevrons, ribbonProgressState, roundDist, routeLine, routeMeasure, routePieces, routeSurfaceField, shownManeuverArrows, simplifyIdx, sliceAt, sliceLine, smoothCorners, smoothWidths, smoothstep, snapWindow, spanLine, spanOutline, speedLimitFromFeatures, spreadStations, stationsBetween, tangentAt, tileParts, toUnits, trackAt, turnTo, upcoming, valueAt, zoomStops };
|