@mapslibvn/core 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,71 @@
1
1
  # @mapslibvn/core
2
2
 
3
- Client TypeScript không phụ thuộc giao diện cho Places API MapsLibVN, kèm kiểu dữ liệu, chuẩn hóa
4
- tiếng Việt và chuỗi ghi nguồn dùng chung.
3
+ [![npm version](https://img.shields.io/npm/v/@mapslibvn/core.svg)](https://www.npmjs.com/package/@mapslibvn/core)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@mapslibvn/core.svg)](https://www.npmjs.com/package/@mapslibvn/core)
5
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/@mapslibvn/core)](https://bundlephobia.com/package/@mapslibvn/core)
6
+ [![license](https://img.shields.io/npm/l/@mapslibvn/core.svg)](https://www.npmjs.com/package/@mapslibvn/core)
7
+
8
+ **The Vietnam-first geodata client for TypeScript/JavaScript.** Framework-agnostic, no runtime dependencies, and built from day one to understand Vietnamese addresses the way people actually type them — no diacritics, abbreviated street types, old-vs-new administrative names, and hẻm/ngõ (alley) numbering that generic geocoders get wrong.
9
+
10
+ `@mapslibvn/core` is the foundation shared by [`@mapslibvn/web`](https://www.npmjs.com/package/@mapslibvn/web), [`@mapslibvn/react`](https://www.npmjs.com/package/@mapslibvn/react) and [`@mapslibvn/react-native`](https://www.npmjs.com/package/@mapslibvn/react-native) — but it works just as well on its own, in any Node.js service, CLI, or non-map UI (search bars, address forms, delivery apps, coverage checks…).
11
+
12
+ ## Why teams pick it
13
+
14
+ - 🇻🇳 **Vietnamese text handling, built in** — `normalizeVi`, `stripDiacritics`, `expandAbbrev`, `applyBrandAlias` normalize things like "chung cu", "P. Bến Thành" or "Cty" the way Vietnamese users actually search.
15
+ - 🏠 **Real address parsing** — `parseAddress` splits a raw Vietnamese address string into house number, hẻm/ngõ, street, ward, district and province, with a confidence score.
16
+ - 🌍 **Pick your POI sources** — mix OpenStreetMap, Overture Maps and Foursquare Open Places per request, or just use the sensible "all" default.
17
+ - 🧭 **Headless turn-by-turn navigation** — a pure state machine (`createNavigator`) handling rerouting, off-route detection, maneuver formatting and voice-announcement timing, with zero UI dependency. It's the same engine powering the web and React Native navigation layers.
18
+ - 📦 **Small and fully typed** — ESM-only, one flat export surface, gzip barrel kept under 20 kB by CI.
19
+ - ⚖️ **Attribution helpers included** — `attributionText()` / `attributionHtml()` so you stay compliant with OSM/Overture/Foursquare data licenses even outside a map view.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm install @mapslibvn/core
25
+ ```
26
+
27
+ Works with Node.js 22+ on the server, and any modern browser with `fetch`.
28
+
29
+ ## Quick start
30
+
31
+ ```ts
32
+ import { createClient } from '@mapslibvn/core';
33
+
34
+ const places = createClient({
35
+ apiKey: 'mlv_live_…',
36
+ baseUrl: 'https://api.ai-solutions.io.vn',
37
+ });
38
+
39
+ const { items } = await places.autocomplete('cho ben thanh', {
40
+ near: [106.7, 10.776],
41
+ limit: 5,
42
+ });
43
+ ```
44
+
45
+ The client covers autocomplete, search, nearby, geocode, reverse geocode, place details, directions, style URLs and edit suggestions. Never ship a secret key in client-side code or commit it to source control — browser-facing keys must be origin-restricted.
46
+
47
+ 📖 Full API reference: <https://mapslibvn-docs.pages.dev/sdk/>
48
+
49
+ 📦 Part of the MapsLibVN SDK family: [`@mapslibvn/web`](https://www.npmjs.com/package/@mapslibvn/web) · [`@mapslibvn/react`](https://www.npmjs.com/package/@mapslibvn/react) · [`@mapslibvn/react-native`](https://www.npmjs.com/package/@mapslibvn/react-native)
50
+
51
+ Source license: MIT. When you display map data, you must keep attribution as described in the docs and `THIRD_PARTY_NOTICES.md`.
52
+
53
+ ---
54
+
55
+ ## Tiếng Việt
56
+
57
+ **Client TypeScript/JavaScript "Việt Nam trước" cho dữ liệu địa lý.** Không phụ thuộc framework, không cần thư viện ngoài lúc chạy, và được xây dựng ngay từ đầu để hiểu địa chỉ tiếng Việt đúng như người dùng thật gõ — không dấu, viết tắt loại đường, tên hành chính cũ/mới, và cách đánh số hẻm/ngõ mà các bộ geocoder thông thường không xử lý đúng.
58
+
59
+ `@mapslibvn/core` là nền tảng dùng chung cho [`@mapslibvn/web`](https://www.npmjs.com/package/@mapslibvn/web), [`@mapslibvn/react`](https://www.npmjs.com/package/@mapslibvn/react) và [`@mapslibvn/react-native`](https://www.npmjs.com/package/@mapslibvn/react-native) — nhưng vẫn dùng tốt độc lập trong bất kỳ service Node.js, CLI hay giao diện không có bản đồ nào (ô tìm kiếm, form địa chỉ, app giao hàng, kiểm tra vùng phủ…).
60
+
61
+ ## Vì sao nên chọn
62
+
63
+ - 🇻🇳 **Xử lý tiếng Việt có sẵn** — `normalizeVi`, `stripDiacritics`, `expandAbbrev`, `applyBrandAlias` chuẩn hoá "chung cư", "P. Bến Thành", "Cty" đúng cách người Việt hay tìm.
64
+ - 🏠 **Phân tích địa chỉ thật** — `parseAddress` tách một chuỗi địa chỉ tiếng Việt thành số nhà, hẻm/ngõ, đường, phường, quận, tỉnh, kèm điểm tin cậy (`confidence`).
65
+ - 🌍 **Tự chọn nguồn POI** — trộn OpenStreetMap, Overture Maps và Foursquare Open Places theo từng request, hoặc dùng mặc định "cả ba" (`all`).
66
+ - 🧭 **Dẫn đường không giao diện** — một máy trạng thái thuần (`createNavigator`) lo việc tính lại tuyến, phát hiện lệch tuyến, định dạng chỉ dẫn rẽ và thời điểm đọc thoại — không phụ thuộc UI, dùng chung cho cả lớp dẫn đường web lẫn React Native.
67
+ - 📦 **Nhỏ gọn và có kiểu đầy đủ** — chỉ ESM, một điểm export duy nhất, CI giữ trần gzip dưới 20 kB.
68
+ - ⚖️ **Có sẵn hàm ghi nguồn** — `attributionText()` / `attributionHtml()` để tuân thủ giấy phép dữ liệu OSM/Overture/Foursquare kể cả khi không hiển thị bản đồ.
5
69
 
6
70
  ## Cài đặt
7
71
 
@@ -11,7 +75,7 @@ npm install @mapslibvn/core
11
75
 
12
76
  Yêu cầu Node.js 22+ khi chạy phía máy chủ. Trình duyệt hiện đại có sẵn `fetch` cũng dùng được.
13
77
 
14
- ## dụ
78
+ ## Bắt đầu nhanh
15
79
 
16
80
  ```ts
17
81
  import { createClient } from '@mapslibvn/core';
@@ -27,11 +91,10 @@ const { items } = await places.autocomplete('cho ben thanh', {
27
91
  });
28
92
  ```
29
93
 
30
- API gồm autocomplete, search, nearby, geocode, reverse geocode, chi tiết địa điểm, style URL và
31
- gửi đề xuất chỉnh sửa. Không đưa khóa bí mật vào mã nguồn hoặc commit; khóa trình duyệt phải giới
32
- hạn đúng origin.
94
+ API gồm autocomplete, search, nearby, geocode, reverse geocode, chi tiết địa điểm, chỉ đường (directions), style URL và gửi đề xuất chỉnh sửa. Không đưa khóa bí mật vào mã nguồn hoặc commit; khóa trình duyệt phải giới hạn đúng origin.
95
+
96
+ 📖 Tài liệu API đầy đủ: <https://mapslibvn-docs.pages.dev/sdk/>
33
97
 
34
- Tài liệu: <https://mapslibvn-docs.pages.dev/sdk/>
98
+ 📦 Nằm trong họ SDK MapsLibVN: [`@mapslibvn/web`](https://www.npmjs.com/package/@mapslibvn/web) · [`@mapslibvn/react`](https://www.npmjs.com/package/@mapslibvn/react) · [`@mapslibvn/react-native`](https://www.npmjs.com/package/@mapslibvn/react-native)
35
99
 
36
- Giấy phép mã nguồn: MIT. Khi hiển thị dữ liệu bản đồ, phải giữ attribution theo tài liệu và
37
- `THIRD_PARTY_NOTICES.md`.
100
+ Giấy phép mã nguồn: MIT. Khi hiển thị dữ liệu bản đồ, phải giữ attribution theo tài liệu và `THIRD_PARTY_NOTICES.md`.
@@ -9,7 +9,7 @@ MapsLibVN **không liên kết với, không được tài trợ hay chứng th
9
9
  nhãn hiệu của bên thứ ba; tên đó xuất hiện ở đây và trong tài liệu chỉ để mô tả nguồn gốc kỹ
10
10
  thuật của thư viện mà MapsLibVN sử dụng.
11
11
 
12
- Cập nhật: 04/09/2026.
12
+ Cập nhật: 12/09/2026.
13
13
 
14
14
  ## 1. Thư viện được đóng gói hoặc là peer dependency của SDK
15
15
 
@@ -20,6 +20,8 @@ Cập nhật: 04/09/2026.
20
20
  | react, react-dom | 18.3.1 (peer, chỉ `@mapslibvn/react`) | MIT | |
21
21
  | @maplibre/maplibre-react-native | 11.3.8 (peer, chỉ `@mapslibvn/react-native`) | MIT | bộ vẽ bản đồ native iOS/Android |
22
22
  | react, react-native | react ≥ 19.1, react-native ≥ 0.80 (peer, chỉ `@mapslibvn/react-native`) | MIT | |
23
+ | expo-location, expo-task-manager | 57.0.17 (peer **tuỳ chọn**, chỉ entry `@mapslibvn/react-native/expo`) | MIT | định vị tiền cảnh và nền cho dẫn đường |
24
+ | expo-speech, expo-audio, expo-keep-awake | 57.0.3 / 57.0.5 / 57.0.1 (peer **tuỳ chọn**, chỉ entry `@mapslibvn/react-native/expo`) | MIT | đọc câu chỉ dẫn, phiên âm thanh khi nền, giữ màn hình sáng |
23
25
 
24
26
  Nguyên văn giấy phép ở mục 4.
25
27
 
@@ -385,9 +387,38 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO
385
387
  Gói này nhúng MapLibre Native (Android, iOS) khi build app — xem thông báo giấy phép trong
386
388
  chính gói đó cho các thành phần native.
387
389
 
390
+ ### 4.9 expo-location, expo-task-manager, expo-speech, expo-audio, expo-keep-awake — MIT
391
+
392
+ ```
393
+ The MIT License (MIT)
394
+
395
+ Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
396
+
397
+ Permission is hereby granted, free of charge, to any person obtaining a copy
398
+ of this software and associated documentation files (the "Software"), to deal
399
+ in the Software without restriction, including without limitation the rights
400
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
401
+ copies of the Software, and to permit persons to whom the Software is
402
+ furnished to do so, subject to the following conditions:
403
+
404
+ The above copyright notice and this permission notice shall be included in all
405
+ copies or substantial portions of the Software.
406
+
407
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
408
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
409
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
410
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
411
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
412
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
413
+ SOFTWARE.
414
+ ```
415
+
416
+ Các gói này là peer dependency **tuỳ chọn**: chỉ app import `@mapslibvn/react-native/expo` (dẫn
417
+ đường) mới cài; SDK không đóng gói mã của chúng.
418
+
388
419
  ## 5. Công cụ phía máy chủ (không phân phối, liệt kê để minh bạch)
389
420
 
390
421
  Planetiler (Apache-2.0), tippecanoe (BSD-2-Clause), osmium-tool (GPL-3.0 — dùng như công cụ
391
422
  dòng lệnh, không liên kết mã), pyosmium (BSD-2-Clause), DuckDB (MIT), PostgreSQL
392
- (PostgreSQL License), PostGIS (GPL-2.0 — chạy như dịch vụ), Hono (MIT), cloudflared
423
+ (PostgreSQL License), PostGIS (GPL-2.0 — chạy như dịch vụ), Valhalla (MIT — engine chỉ đường, chạy như dịch vụ riêng trên máy chủ, không liên kết mã), Hono (MIT), cloudflared
393
424
  (Apache-2.0), Astro Starlight (MIT), Vitest (MIT), Playwright (Apache-2.0).
package/dist/index.d.ts CHANGED
@@ -177,6 +177,68 @@ interface PoiFeature {
177
177
  group: string;
178
178
  lngLat: [number, number];
179
179
  }
180
+ /** Phương tiện cho `GET /v1/directions` (spec dẫn đường A). */
181
+ type TravelMode = 'motorbike' | 'car' | 'walk';
182
+ type DirectionsLang = 'vi' | 'en';
183
+ /** Loại bước rẽ theo tập cố định của MapsLibVN — không phụ thuộc engine. */
184
+ type ManeuverKind = 'depart' | 'arrive' | 'continue' | 'slight_right' | 'slight_left' | 'turn_right' | 'turn_left' | 'sharp_right' | 'sharp_left' | 'uturn_right' | 'uturn_left' | 'ramp_straight' | 'ramp_right' | 'ramp_left' | 'exit_right' | 'exit_left' | 'keep_right' | 'keep_left' | 'merge' | 'merge_right' | 'merge_left' | 'roundabout_enter' | 'roundabout_exit' | 'ferry_enter' | 'ferry_exit' | 'elevator' | 'steps' | 'escalator' | 'building_enter' | 'building_exit' | 'other';
185
+ interface RouteStep {
186
+ kind: ManeuverKind;
187
+ instruction: string;
188
+ /** Câu rẽ ngắn gọn để đọc lúc còn cách xa (spec B); Valhalla không trả → null. */
189
+ verbal_alert: string | null;
190
+ verbal_pre: string | null;
191
+ verbal_post: string | null;
192
+ street_names: string[];
193
+ distance_m: number;
194
+ duration_s: number;
195
+ /** Chỉ số điểm trong polyline của CẢ tuyến (đã dịch qua các leg). */
196
+ shape_begin: number;
197
+ shape_end: number;
198
+ /** [lng, lat] điểm bắt đầu bước. */
199
+ location: [number, number];
200
+ /** Số lối ra khi `kind = roundabout_enter`, còn lại null. */
201
+ roundabout_exit: number | null;
202
+ }
203
+ interface RouteLeg {
204
+ distance_m: number;
205
+ duration_s: number;
206
+ /** Chỉ số điểm đầu của leg trong polyline tuyến. */
207
+ shape_offset: number;
208
+ steps: RouteStep[];
209
+ }
210
+ interface Route {
211
+ mode: TravelMode;
212
+ distance_m: number;
213
+ duration_s: number;
214
+ /** [minLng, minLat, maxLng, maxLat] */
215
+ bbox: [number, number, number, number];
216
+ /** polyline6 của cả tuyến — giải mã bằng `decodePolyline6` → `[lng, lat][]`. */
217
+ geometry: string;
218
+ legs: RouteLeg[];
219
+ flags: {
220
+ toll: boolean;
221
+ highway: boolean;
222
+ ferry: boolean;
223
+ };
224
+ }
225
+ interface Waypoint {
226
+ /** [lng, lat] điểm người dùng gửi. */
227
+ location: [number, number];
228
+ /** [lng, lat] điểm trên tuyến gần nhất (đầu leg tương ứng). */
229
+ snapped: [number, number];
230
+ name: string | null;
231
+ }
232
+ interface DirectionsResponse {
233
+ routes: Route[];
234
+ waypoints: Waypoint[];
235
+ attribution: string;
236
+ /** Thông tin chẩn đoán, không phải hợp đồng ổn định. */
237
+ engine?: {
238
+ name: string;
239
+ graph: string | null;
240
+ };
241
+ }
180
242
 
181
243
  type Theme = 'light' | 'dark';
182
244
  interface ClientOptions {
@@ -206,6 +268,19 @@ interface AttributionResponse {
206
268
  license?: string;
207
269
  }[];
208
270
  }
271
+ interface DirectionsOptions {
272
+ /** [lat, lng] — vĩ độ trước, cùng quy ước với `near`. */
273
+ from: [number, number];
274
+ to: [number, number];
275
+ /** Tối đa 5 điểm dừng, mỗi điểm [lat, lng]. */
276
+ via?: [number, number][];
277
+ /** Mặc định máy chủ: `motorbike`. */
278
+ mode?: TravelMode;
279
+ /** Mặc định máy chủ: `vi`. */
280
+ lang?: DirectionsLang;
281
+ /** Xin thêm một tuyến thay thế (bị bỏ qua khi có `via`). */
282
+ alternatives?: boolean;
283
+ }
209
284
  declare function createClient(options: ClientOptions): {
210
285
  baseUrl: string;
211
286
  attribution: () => Promise<AttributionResponse>;
@@ -245,6 +320,8 @@ declare function createClient(options: ClientOptions): {
245
320
  items: GeocodeItem[];
246
321
  }>;
247
322
  reverse: (lat: number, lng: number) => Promise<ReverseResponse>;
323
+ /** Chỉ đường (spec dẫn đường A). Response dùng [lng, lat]; tham số vào dùng [lat, lng]. */
324
+ directions: (opts: DirectionsOptions) => Promise<DirectionsResponse>;
248
325
  /** Gửi đóng góp/sửa POI (spec 6.1). Khoá phải có scope edits:write. */
249
326
  suggestEdit: (edit: SuggestEditRequest) => Promise<SuggestEditResponse>;
250
327
  };
@@ -300,6 +377,7 @@ declare function adminAliasKeys(input: AdminAliasKeyInput): string[];
300
377
  * không có API đổi layout property của lớp có sẵn. Web dùng `applyLanguage` lúc chạy nhưng
301
378
  * chia sẻ `nameExpression`/`isNameLabelLayer` từ đây.
302
379
  */
380
+
303
381
  type Lang = 'vi' | 'en';
304
382
  interface StyleLayerLike {
305
383
  id: string;
@@ -311,6 +389,11 @@ interface StyleLike {
311
389
  layers?: readonly StyleLayerLike[] | undefined;
312
390
  }
313
391
  declare const POI_LAYER_ID = "poi";
392
+ /**
393
+ * Lớp symbol đầu tiên của từng theme MapsLibVN — chèn tuyến dẫn đường trước lớp này để nhãn đường
394
+ * nằm trên tuyến (spec C mục 4). `packages/style/src/first-symbol-layer.test.ts` bảo vệ giá trị.
395
+ */
396
+ declare const FIRST_SYMBOL_LAYER_ID: Readonly<Record<Theme, string>>;
314
397
  declare function isPoiStyleLayer(layer: StyleLayerLike): boolean;
315
398
  declare function nameExpression(lang: Lang): unknown[];
316
399
  /** Lớp nhãn tên (symbol có `text-field` tham chiếu `name`), trừ lớp chủ quyền luôn tiếng Việt. */
@@ -421,4 +504,324 @@ declare function applyToponymAlias(normalized: string): string;
421
504
  */
422
505
  declare function viKey(normalized: string): string;
423
506
 
424
- export { ATTRIBUTION_LINKS, type AdminAliasKeyInput, type AlleyKeyword, type AttributionLink, type AttributionResponse, type AutocompleteItem, type AutocompleteType, type ClientOptions, DEFAULT_POI_SOURCES, type EditChanges, type EditKind, type GeocodeItem, type GeocodeMatched, type GeocodePrecision, type Lang, type MapsLibVNClient, MapsLibVNError, NAME_FILLERS, POI_LAYER_ID, POI_SOURCES, POI_SOURCE_PROFILES, type ParsedAddress, type Place, type PlaceAddress, type PlaceCategory, type PlaceDetails, type PlaceSource, type PoiFeature, type PoiSource, type PoiSourceProfile, type ReverseAddress, type ReverseResponse, type SearchKeys, type StyleLayerLike, type StyleLike, type SuggestEditRequest, type SuggestEditResponse, TOPONYM_ALIAS, type Theme, type ToponymEntry, adminAliasKeys, applyBrandAlias, applyToponymAlias, attributionHtml, attributionText, createClient, expandAbbrev, filterNameAlt, foldTelex, hidePoiLayer, isNameLabelLayer, isPoiStyleLayer, localizeStyle, looksLikeTelex, nameCore, nameExpression, normalizePoiSources, normalizeVi, parseAddress, parsePoiSourcesCsv, poiSourceClause, poiSourcesKey, profileForSources, searchKeys, stripDiacritics, viKey };
507
+ /**
508
+ * Polyline mã hoá kiểu Google với precision 1e6 (Valhalla `legs[].shape`).
509
+ * Toạ độ trả về theo thứ tự GeoJSON `[lng, lat]`.
510
+ */
511
+ declare function decodePolyline6(encoded: string): [number, number][];
512
+ /** Mã hoá `[lng, lat][]` thành polyline6; Worker dùng để nối shape các leg thành một chuỗi. */
513
+ declare function encodePolyline6(coords: readonly (readonly [number, number])[]): string;
514
+
515
+ declare const MANEUVER_KINDS: readonly ManeuverKind[];
516
+ /**
517
+ * Mã maneuver Valhalla (0–43, `TripDirections_Maneuver_Type`) → kind MapsLibVN. Đặt ở core để Worker
518
+ * và SDK dùng một bản (spec A mục 6); bảng chỉ là dữ liệu, không phải logic gọi engine.
519
+ * Mã transit 30–36 và 0 (kNone) không có trong bảng → `other`.
520
+ */
521
+ declare const VALHALLA_MANEUVER_KIND: Readonly<Record<number, ManeuverKind>>;
522
+ declare function maneuverKindFromValhalla(type: number): ManeuverKind;
523
+
524
+ /** Một điểm định vị. Lớp dán (web/RN) đổi từ API nền tảng sang dạng này. */
525
+ interface GeoFix {
526
+ lng: number;
527
+ lat: number;
528
+ /** Bán kính sai số (m). Thiếu → coi là 10 m. */
529
+ accuracy_m?: number;
530
+ /** Độ so với bắc, thuận chiều kim đồng hồ; null/undefined khi đứng yên hoặc không có. */
531
+ heading?: number | null;
532
+ speed_mps?: number | null;
533
+ /** ms epoch — nguồn thời gian duy nhất của máy trạng thái. */
534
+ timestamp: number;
535
+ }
536
+ /** Nguồn tuyến. `MapsLibVNClient` của `createClient` thoả kiểu này không cần bọc. */
537
+ interface RouteProvider {
538
+ directions(opts: DirectionsOptions): Promise<DirectionsResponse>;
539
+ }
540
+ interface PositionError {
541
+ code: 'denied' | 'unavailable' | 'timeout';
542
+ message: string;
543
+ raw?: unknown;
544
+ }
545
+ /** Nguồn vị trí do lớp dán cung cấp; core chỉ định nghĩa kiểu để web và RN cùng hình. */
546
+ interface PositionSource {
547
+ subscribe(onFix: (fix: GeoFix) => void, onError?: (error: PositionError) => void): () => void;
548
+ }
549
+ type NavigationStatus = 'idle' | 'navigating' | 'off_route' | 'rerouting' | 'arrived' | 'stopped';
550
+ interface NavigationThresholds {
551
+ /** Ngưỡng lệch cơ sở; hiệu dụng = max(offRoute_m, 1.5 × accuracy_m). */
552
+ offRoute_m: number;
553
+ /** Số fix liên tiếp ngoài ngưỡng để xác nhận lệch. */
554
+ offRouteFixes: number;
555
+ /** … và đã kéo dài ít nhất bấy nhiêu giây (theo timestamp fix). */
556
+ offRouteSeconds: number;
557
+ /** Fix kém hơn thì bỏ, không cập nhật gì. */
558
+ maxAccuracy_m: number;
559
+ /** Đọc "Trong X nữa, …" khi còn ≤. */
560
+ approach_m: number;
561
+ /** Đọc câu rẽ khi còn ≤. */
562
+ pre_m: number;
563
+ /** Bán kính đến nơi / qua điểm via. */
564
+ arrive_m: number;
565
+ rerouteCooldown_s: number;
566
+ rerouteMaxFailures: number;
567
+ }
568
+ /** Số khởi điểm theo spec B mục 4.3; chốt lại sau thực địa (Task 20). */
569
+ declare const NAVIGATION_THRESHOLDS: Readonly<Record<TravelMode, NavigationThresholds>>;
570
+ interface NavigationProgress {
571
+ status: NavigationStatus;
572
+ route: Route;
573
+ routeIndex: number;
574
+ legIndex: number;
575
+ /** Chỉ số step PHẲNG qua mọi leg (spec B 4.4). */
576
+ stepIndex: number;
577
+ step: RouteStep;
578
+ /** Step có điểm rẽ kế tiếp; null khi step hiện tại là arrive cuối. */
579
+ nextStep: RouteStep | null;
580
+ /** [lng, lat] điểm đã bám lên tuyến. */
581
+ snapped: [number, number];
582
+ /** Hướng đi (độ): heading GPS khi speed_mps > 1, còn không thì hướng đoạn tuyến. */
583
+ bearing: number;
584
+ /** Chỉ số đỉnh đầu của đoạn polyline đang ở. */
585
+ shapeIndex: number;
586
+ traveled_m: number;
587
+ remaining_m: number;
588
+ /** ETA: phần còn lại của step hiện tại theo tỉ lệ + tổng duration các step sau. */
589
+ remaining_s: number;
590
+ /** Tới điểm rẽ của nextStep; 0 khi không còn. */
591
+ distanceToStep_m: number;
592
+ /** Khoảng cách vuông góc từ fix tới tuyến. */
593
+ offRoute_m: number;
594
+ fix: GeoFix;
595
+ }
596
+ interface Announcement {
597
+ text: string;
598
+ kind: 'depart' | 'post' | 'approach' | 'pre' | 'arrive';
599
+ stepIndex: number;
600
+ /** 3 = câu rẽ / đến nơi / khởi hành, 2 = "Trong X nữa", 1 = verbal_post. */
601
+ priority: 1 | 2 | 3;
602
+ }
603
+ interface NavigationEvents {
604
+ status: {
605
+ status: NavigationStatus;
606
+ previous: NavigationStatus;
607
+ };
608
+ progress: NavigationProgress;
609
+ step: {
610
+ stepIndex: number;
611
+ step: RouteStep;
612
+ };
613
+ waypoint: {
614
+ legIndex: number;
615
+ waypoint: Waypoint;
616
+ };
617
+ offRoute: {
618
+ distance_m: number;
619
+ fix: GeoFix;
620
+ };
621
+ reroute: {
622
+ reason: 'off_route' | 'manual';
623
+ response: DirectionsResponse;
624
+ };
625
+ rerouteFailed: {
626
+ error: unknown;
627
+ attempts: number;
628
+ final: boolean;
629
+ };
630
+ announce: Announcement;
631
+ arrive: {
632
+ waypoint: Waypoint;
633
+ fix: GeoFix;
634
+ };
635
+ }
636
+ interface NavigatorOptions {
637
+ response: DirectionsResponse;
638
+ /** Mặc định 0. */
639
+ routeIndex?: number;
640
+ /** Bắt buộc khi `reroute` là 'auto' (mặc định). */
641
+ provider?: RouteProvider;
642
+ /** Mặc định 'auto'. */
643
+ reroute?: 'auto' | 'manual';
644
+ /** Mặc định 'vi'; dùng cho câu "Trong X nữa" và request tính lại. */
645
+ lang?: DirectionsLang;
646
+ thresholds?: Partial<NavigationThresholds>;
647
+ }
648
+ interface Navigator {
649
+ readonly status: NavigationStatus;
650
+ readonly progress: NavigationProgress | null;
651
+ /** Đồng bộ. Fix kém accuracy hoặc timestamp không tăng bị bỏ. */
652
+ update(fix: GeoFix): void;
653
+ /** Thay tuyến: reset bước, lịch đọc, bộ đếm lệch; fix kế tiếp bám trên toàn tuyến. */
654
+ setRoute(response: DirectionsResponse, routeIndex?: number): void;
655
+ /** Gọi provider ngay (bỏ cooldown và trần lỗi). */
656
+ reroute(): Promise<void>;
657
+ stop(): void;
658
+ on<K extends keyof NavigationEvents>(event: K, handler: (e: NavigationEvents[K]) => void): void;
659
+ off<K extends keyof NavigationEvents>(event: K, handler: (e: NavigationEvents[K]) => void): void;
660
+ }
661
+
662
+ /** Hình học cầu và chiếu điểm cho dẫn đường (spec B 4.1). Toạ độ luôn `[lng, lat]`. */
663
+ type LngLat = readonly [number, number];
664
+ declare function haversineM(a: LngLat, b: LngLat): number;
665
+ /** Hướng từ a tới b, độ [0, 360) thuận chiều kim đồng hồ từ bắc. */
666
+ declare function bearingDeg(a: LngLat, b: LngLat): number;
667
+ /** Chênh góc nhỏ nhất giữa hai hướng, [0, 180]. */
668
+ declare function angleDiffDeg(a: number, b: number): number;
669
+ interface Projection {
670
+ /** Vị trí trên đoạn, 0 = a, 1 = b (đã kẹp). */
671
+ t: number;
672
+ point: [number, number];
673
+ /** Khoảng cách từ p tới điểm chiếu (m). */
674
+ distance_m: number;
675
+ }
676
+ /**
677
+ * Chiếu p lên đoạn ab trong mặt phẳng cục bộ quanh a (equirectangular, đủ chính xác cho đoạn dưới
678
+ * vài km). Đoạn suy biến (a = b) → t = 0.
679
+ */
680
+ declare function projectOnSegment(p: LngLat, a: LngLat, b: LngLat): Projection;
681
+ /** Khoảng cách cộng dồn (m) tại từng đỉnh; `cum[0] = 0`; mảng rỗng → rỗng. */
682
+ declare function cumulativeDistances(coords: readonly LngLat[]): number[];
683
+
684
+ interface FlatStep {
685
+ step: RouteStep;
686
+ legIndex: number;
687
+ indexInLeg: number;
688
+ /** Mét từ đầu tuyến tới điểm rẽ của step (đầu step). */
689
+ begin_m: number;
690
+ /** = begin_m của step kế, hoặc tổng chiều dài với step cuối. */
691
+ end_m: number;
692
+ }
693
+ interface RouteIndex {
694
+ coords: [number, number][];
695
+ /** cum[i] = mét từ đầu tuyến tới đỉnh i. */
696
+ cum: number[];
697
+ total_m: number;
698
+ /** Step phẳng qua mọi leg, theo thứ tự đi. */
699
+ steps: FlatStep[];
700
+ /** Mét tại điểm đầu từng leg (`shape_offset`). */
701
+ legBegin_m: number[];
702
+ }
703
+ declare function buildRouteIndex(route: Route): RouteIndex;
704
+ /**
705
+ * Step đang ở tại along_m: step có begin_m ≤ along < end_m. Step dài 0 (arrive tại via) không bao
706
+ * giờ là "đang ở" trừ step cuối cùng khi along ≥ total.
707
+ */
708
+ declare function stepAt(index: RouteIndex, along_m: number): number;
709
+ interface ProgressAt {
710
+ stepIndex: number;
711
+ legIndex: number;
712
+ distanceToStep_m: number;
713
+ remaining_m: number;
714
+ remaining_s: number;
715
+ }
716
+ declare function progressAt(index: RouteIndex, along_m: number): ProgressAt;
717
+
718
+ interface SnapResult {
719
+ /** Đỉnh đầu của đoạn được chọn. */
720
+ shapeIndex: number;
721
+ t: number;
722
+ point: [number, number];
723
+ /** Khoảng cách vuông góc fix → tuyến (m). */
724
+ distance_m: number;
725
+ /** Mét từ đầu tuyến tới điểm chiếu. */
726
+ along_m: number;
727
+ }
728
+ interface SnapOptions {
729
+ /** null → tìm trên toàn tuyến (fix đầu sau start/setRoute). */
730
+ fromShapeIndex: number | null;
731
+ /** Đoạn có mốc đầu ≤ cum[from] + window_m mới được xét. */
732
+ window_m: number;
733
+ heading?: number | null | undefined;
734
+ }
735
+ declare function snapToRoute(index: RouteIndex, p: LngLat, opts: SnapOptions): SnapResult | null;
736
+
737
+ /** "85 mét", "1,2 ki-lô-mét" (viết đủ để giọng đọc phát âm đúng), "12 ki-lô-mét"; en tương ứng. */
738
+ declare function formatDistance(m: number, lang?: DirectionsLang): string;
739
+ /** Bản ngắn cho UI: "85 m", "1,2 km", "12 km". */
740
+ declare function formatDistanceShort(m: number): string;
741
+ /** ≥ 200 m làm tròn 50; dưới 200 m làm tròn 10; tối thiểu 10. */
742
+ declare function roundForSpeech(m: number): number;
743
+ declare function lowerFirst(text: string): string;
744
+ /** "Trong 190 mét nữa, rẽ phải vào Nguyễn Du." — Valhalla 3.8.3 không tự ghép khoảng cách vào alert. */
745
+ declare function composeApproach(distance_m: number, step: RouteStep, lang: DirectionsLang): string | null;
746
+ /**
747
+ * Lịch đọc spec B mục 4.5. `announced` giữ khoá `${stepIndex}:${kind}` đã đọc (navigator xoá khi đổi
748
+ * tuyến). `stepChanged` = fix này vừa đổi step (hoặc là fix đầu).
749
+ */
750
+ declare function planAnnouncements(p: NavigationProgress, th: NavigationThresholds, lang: DirectionsLang, announced: Set<string>, stepChanged: boolean): Announcement[];
751
+
752
+ interface SimulateOptions {
753
+ /** Mặc định theo mode: walk 1.4, motorbike 8, car 12. */
754
+ speed_mps?: number;
755
+ /** Mặc định 1. */
756
+ interval_s?: number;
757
+ /** Mặc định 8. */
758
+ accuracy_m?: number;
759
+ /** Nhiễu vị trí đều trong hình tròn bán kính này; mặc định 0. */
760
+ jitter_m?: number;
761
+ /** Mặc định 1. */
762
+ seed?: number;
763
+ /** Mặc định 1_700_000_000_000. */
764
+ start_ms?: number;
765
+ }
766
+ declare const SIMULATE_DEFAULT_SPEED_MPS: Readonly<Record<TravelMode, number>>;
767
+ /** PRNG xác định (mulberry32) để test và demo lặp lại được. */
768
+ declare function mulberry32(seed: number): () => number;
769
+ /** Đi dọc polyline với vận tốc hằng; heading = hướng đoạn; thuần và xác định. */
770
+ declare function simulateFixes(route: Route, opts?: SimulateOptions): GeoFix[];
771
+
772
+ /** Máy trạng thái dẫn đường thuần (spec B mục 4.4–4.5): không DOM, không timer, thời gian từ fix. */
773
+ declare function createNavigator(opts: NavigatorOptions): Navigator;
774
+
775
+ /** Vai của từng feature trong source tuyến — web và RN cùng lọc theo `properties.kind`. */
776
+ type RouteFeatureKind = 'alt' | 'active' | 'traveled' | 'puck';
777
+ interface RouteLineFeature {
778
+ type: 'Feature';
779
+ geometry: {
780
+ type: 'LineString';
781
+ coordinates: [number, number][];
782
+ };
783
+ properties: {
784
+ kind: 'alt' | 'active' | 'traveled';
785
+ index: number;
786
+ };
787
+ }
788
+ interface RoutePuckFeature {
789
+ type: 'Feature';
790
+ geometry: {
791
+ type: 'Point';
792
+ coordinates: [number, number];
793
+ };
794
+ properties: {
795
+ kind: 'puck';
796
+ bearing: number;
797
+ };
798
+ }
799
+ type RouteFeature = RouteLineFeature | RoutePuckFeature;
800
+ interface RouteFeatureCollection {
801
+ type: 'FeatureCollection';
802
+ features: RouteFeature[];
803
+ }
804
+ /** Điểm cắt tuyến chính: trước là đã đi, sau là còn lại. */
805
+ interface RouteProgressCut {
806
+ shapeIndex: number;
807
+ snapped: [number, number];
808
+ /** Hướng đi (độ) cho puck; thiếu → 0. */
809
+ bearing?: number;
810
+ }
811
+ interface RouteFeaturesOptions {
812
+ active: number;
813
+ progress?: RouteProgressCut | null;
814
+ /** Thêm feature Point `puck` tại `snapped` khi có `progress`. Mặc định false. */
815
+ puck?: boolean;
816
+ }
817
+ declare const EMPTY_ROUTE_FEATURES: RouteFeatureCollection;
818
+ /** Giải mã polyline6 của mọi tuyến trong response — làm một lần rồi cache ở lớp dán. */
819
+ declare function decodeRoutes(response: DirectionsResponse): [number, number][][];
820
+ /**
821
+ * Dựng FeatureCollection cho source tuyến: tuyến khác `active` là `alt`; tuyến `active` bị cắt tại
822
+ * `progress` thành `traveled` + `active` (cả hai đi qua điểm bám); không có `progress` thì nguyên
823
+ * tuyến là `active`. `puck` thêm một Point tại điểm bám mang `bearing`. Không đột biến `coords`.
824
+ */
825
+ declare function routeFeatures(coords: readonly (readonly [number, number][])[], opts: RouteFeaturesOptions): RouteFeatureCollection;
826
+
827
+ export { ATTRIBUTION_LINKS, type AdminAliasKeyInput, type AlleyKeyword, type Announcement, type AttributionLink, type AttributionResponse, type AutocompleteItem, type AutocompleteType, type ClientOptions, DEFAULT_POI_SOURCES, type DirectionsLang, type DirectionsOptions, type DirectionsResponse, EMPTY_ROUTE_FEATURES, type EditChanges, type EditKind, FIRST_SYMBOL_LAYER_ID, type FlatStep, type GeoFix, type GeocodeItem, type GeocodeMatched, type GeocodePrecision, type Lang, type LngLat, MANEUVER_KINDS, type ManeuverKind, type MapsLibVNClient, MapsLibVNError, NAME_FILLERS, NAVIGATION_THRESHOLDS, type NavigationEvents, type NavigationProgress, type NavigationStatus, type NavigationThresholds, type Navigator, type NavigatorOptions, POI_LAYER_ID, POI_SOURCES, POI_SOURCE_PROFILES, type ParsedAddress, type Place, type PlaceAddress, type PlaceCategory, type PlaceDetails, type PlaceSource, type PoiFeature, type PoiSource, type PoiSourceProfile, type PositionError, type PositionSource, type ProgressAt, type Projection, type ReverseAddress, type ReverseResponse, type Route, type RouteFeature, type RouteFeatureCollection, type RouteFeatureKind, type RouteFeaturesOptions, type RouteIndex, type RouteLeg, type RouteLineFeature, type RouteProgressCut, type RouteProvider, type RoutePuckFeature, type RouteStep, SIMULATE_DEFAULT_SPEED_MPS, type SearchKeys, type SimulateOptions, type SnapOptions, type SnapResult, type StyleLayerLike, type StyleLike, type SuggestEditRequest, type SuggestEditResponse, TOPONYM_ALIAS, type Theme, type ToponymEntry, type TravelMode, VALHALLA_MANEUVER_KIND, type Waypoint, adminAliasKeys, angleDiffDeg, applyBrandAlias, applyToponymAlias, attributionHtml, attributionText, bearingDeg, buildRouteIndex, composeApproach, createClient, createNavigator, cumulativeDistances, decodePolyline6, decodeRoutes, encodePolyline6, expandAbbrev, filterNameAlt, foldTelex, formatDistance, formatDistanceShort, haversineM, hidePoiLayer, isNameLabelLayer, isPoiStyleLayer, localizeStyle, looksLikeTelex, lowerFirst, maneuverKindFromValhalla, mulberry32, nameCore, nameExpression, normalizePoiSources, normalizeVi, parseAddress, parsePoiSourcesCsv, planAnnouncements, poiSourceClause, poiSourcesKey, profileForSources, progressAt, projectOnSegment, roundForSpeech, routeFeatures, searchKeys, simulateFixes, snapToRoute, stepAt, stripDiacritics, viKey };