@mapslibvn/react-native 0.4.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/LICENSE +21 -0
- package/README.md +33 -0
- package/THIRD_PARTY_NOTICES.md +393 -0
- package/dist/index.d.ts +303 -0
- package/dist/index.js +742 -0
- package/package.json +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/map.tsx
|
|
6
|
+
import {
|
|
7
|
+
Camera,
|
|
8
|
+
Map as NativeMap
|
|
9
|
+
} from "@maplibre/maplibre-react-native";
|
|
10
|
+
|
|
11
|
+
// ../core/dist/index.js
|
|
12
|
+
var ATTRIBUTION_LINKS = [
|
|
13
|
+
{
|
|
14
|
+
text: "\xA9 MapsLibVN",
|
|
15
|
+
href: "https://github.com/dotienphong/maps-library-vietnam"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
text: "\xA9 OpenStreetMap contributors",
|
|
19
|
+
href: "https://www.openstreetmap.org/copyright",
|
|
20
|
+
license: "ODbL"
|
|
21
|
+
},
|
|
22
|
+
{ text: "\xA9 OpenMapTiles", href: "https://openmaptiles.org/" },
|
|
23
|
+
{
|
|
24
|
+
text: "Places: Overture Maps Foundation",
|
|
25
|
+
href: "https://overturemaps.org/",
|
|
26
|
+
license: "CDLA-Permissive 2.0"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
text: "Foursquare OS Places",
|
|
30
|
+
href: "https://opensource.foursquare.com/os-places/",
|
|
31
|
+
license: "Apache-2.0"
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
var withLicense = (link, text) => link.license ? `${text} (${link.license})` : text;
|
|
35
|
+
function join(parts) {
|
|
36
|
+
const [mapsLibVN, osm, openMapTiles, overture, foursquare] = parts;
|
|
37
|
+
return `${mapsLibVN} \xB7 ${osm} \xB7 ${openMapTiles} \xB7 ${overture}, ${foursquare}`;
|
|
38
|
+
}
|
|
39
|
+
function attributionText() {
|
|
40
|
+
return join(ATTRIBUTION_LINKS.map((link) => withLicense(link, link.text)));
|
|
41
|
+
}
|
|
42
|
+
var MapsLibVNError = class extends Error {
|
|
43
|
+
constructor(status, code, message, requestId) {
|
|
44
|
+
super(message);
|
|
45
|
+
__publicField(this, "status");
|
|
46
|
+
__publicField(this, "code");
|
|
47
|
+
__publicField(this, "requestId");
|
|
48
|
+
this.name = "MapsLibVNError";
|
|
49
|
+
this.status = status;
|
|
50
|
+
this.code = code;
|
|
51
|
+
this.requestId = requestId;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var POI_SOURCES = ["osm", "overture", "fsq"];
|
|
55
|
+
var POI_SOURCE_PROFILES = {
|
|
56
|
+
all: ["osm", "overture", "fsq"],
|
|
57
|
+
osm: ["osm"],
|
|
58
|
+
"osm-fsq": ["osm", "fsq"],
|
|
59
|
+
"overture-fsq": ["overture", "fsq"],
|
|
60
|
+
overture: ["overture"],
|
|
61
|
+
fsq: ["fsq"]
|
|
62
|
+
};
|
|
63
|
+
var DEFAULT_POI_SOURCES = POI_SOURCE_PROFILES.all;
|
|
64
|
+
var isPoiSource = (value) => POI_SOURCES.includes(value);
|
|
65
|
+
function normalizePoiSources(list) {
|
|
66
|
+
if (list.length === 0 || !list.every(isPoiSource)) return null;
|
|
67
|
+
const set = new Set(list);
|
|
68
|
+
return POI_SOURCES.filter((source) => set.has(source));
|
|
69
|
+
}
|
|
70
|
+
function poiSourcesKey(sources) {
|
|
71
|
+
return (normalizePoiSources(sources) ?? []).join(",");
|
|
72
|
+
}
|
|
73
|
+
function createClient(options) {
|
|
74
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
75
|
+
const poiSources = normalizePoiSources(options.poiSources ?? DEFAULT_POI_SOURCES);
|
|
76
|
+
if (!poiSources) {
|
|
77
|
+
throw new Error(`poiSources kh\xF4ng h\u1EE3p l\u1EC7: ${JSON.stringify(options.poiSources)}`);
|
|
78
|
+
}
|
|
79
|
+
const sources = poiSourcesKey(poiSources);
|
|
80
|
+
const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
81
|
+
const baseHeaders = () => ({
|
|
82
|
+
...options.headers ?? {},
|
|
83
|
+
"X-Api-Key": options.apiKey
|
|
84
|
+
});
|
|
85
|
+
async function parseOrThrow(response) {
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
let body = {};
|
|
88
|
+
try {
|
|
89
|
+
body = await response.json();
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
throw new MapsLibVNError(
|
|
93
|
+
response.status,
|
|
94
|
+
body.error?.code ?? "http_error",
|
|
95
|
+
body.error?.message ?? `HTTP ${response.status}`,
|
|
96
|
+
body.error?.request_id
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return await response.json();
|
|
100
|
+
}
|
|
101
|
+
async function get(path, params = {}) {
|
|
102
|
+
const url = new URL(baseUrl + path);
|
|
103
|
+
for (const [key, value] of Object.entries(params)) {
|
|
104
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
105
|
+
}
|
|
106
|
+
const response = await doFetch(url, {
|
|
107
|
+
headers: baseHeaders()
|
|
108
|
+
});
|
|
109
|
+
return parseOrThrow(response);
|
|
110
|
+
}
|
|
111
|
+
async function post(path, body) {
|
|
112
|
+
const response = await doFetch(new URL(baseUrl + path), {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { ...baseHeaders(), "content-type": "application/json" },
|
|
115
|
+
body: JSON.stringify(body)
|
|
116
|
+
});
|
|
117
|
+
return parseOrThrow(response);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
baseUrl,
|
|
121
|
+
attribution: () => get("/v1/attribution"),
|
|
122
|
+
styleUrl: (theme) => `${baseUrl}/v1/styles/${theme}.json?key=${encodeURIComponent(options.apiKey)}&sources=${encodeURIComponent(sources)}`,
|
|
123
|
+
autocomplete: (q, opts = {}) => get("/v1/autocomplete", {
|
|
124
|
+
q,
|
|
125
|
+
near: opts.near?.join(","),
|
|
126
|
+
limit: opts.limit,
|
|
127
|
+
types: opts.types?.join(","),
|
|
128
|
+
sources
|
|
129
|
+
}),
|
|
130
|
+
search: (q, opts = {}) => get("/v1/search", {
|
|
131
|
+
q,
|
|
132
|
+
category: opts.category,
|
|
133
|
+
near: opts.near?.join(","),
|
|
134
|
+
radius: opts.radius,
|
|
135
|
+
bbox: opts.bbox?.join(","),
|
|
136
|
+
limit: opts.limit,
|
|
137
|
+
offset: opts.offset,
|
|
138
|
+
sources
|
|
139
|
+
}),
|
|
140
|
+
nearby: (opts) => get("/v1/nearby", {
|
|
141
|
+
lat: opts.lat,
|
|
142
|
+
lng: opts.lng,
|
|
143
|
+
radius: opts.radius,
|
|
144
|
+
category: opts.category,
|
|
145
|
+
limit: opts.limit,
|
|
146
|
+
sources
|
|
147
|
+
}),
|
|
148
|
+
getPlace: (id) => get(`/v1/places/${encodeURIComponent(id)}`),
|
|
149
|
+
geocode: (q, opts = {}) => get("/v1/geocode", {
|
|
150
|
+
q,
|
|
151
|
+
near: opts.near?.join(","),
|
|
152
|
+
limit: opts.limit
|
|
153
|
+
}),
|
|
154
|
+
reverse: (lat, lng) => get("/v1/reverse", { lat, lng, sources }),
|
|
155
|
+
/** Gửi đóng góp/sửa POI (spec 6.1). Khoá phải có scope edits:write. */
|
|
156
|
+
suggestEdit: (edit) => post("/v1/edits", edit)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
var abbrev_default = {
|
|
160
|
+
"tp.": "thanh pho",
|
|
161
|
+
"tt.": "thi tran",
|
|
162
|
+
"tx.": "thi xa",
|
|
163
|
+
"p.": "phuong",
|
|
164
|
+
"q.": "quan",
|
|
165
|
+
"h.": "huyen",
|
|
166
|
+
"x.": "xa",
|
|
167
|
+
"d.": "duong",
|
|
168
|
+
"ng.": "nguyen",
|
|
169
|
+
kp: "khu pho",
|
|
170
|
+
cty: "cong ty",
|
|
171
|
+
cp: "co phan"
|
|
172
|
+
};
|
|
173
|
+
var brand_alias_default = {
|
|
174
|
+
highlands: ["highlands coffee", "highland coffee", "highland"],
|
|
175
|
+
cong: ["cong caphe", "cong ca phe", "cong cafe", "cong coffee"],
|
|
176
|
+
"the coffee house": ["tch", "coffee house"],
|
|
177
|
+
"phuc long": ["phuc long coffee tea", "phuc long tea coffee", "phuc long coffee"],
|
|
178
|
+
"trung nguyen": ["trung nguyen legend", "trung nguyen e-coffee", "trung nguyen coffee"],
|
|
179
|
+
starbucks: ["starbucks coffee", "starbucks reserve"],
|
|
180
|
+
"circle k": ["circlek"],
|
|
181
|
+
winmart: ["vinmart", "vinmart plus", "winmart plus"],
|
|
182
|
+
coopmart: ["co opmart", "co op mart", "coop mart", "saigon co op"],
|
|
183
|
+
"bach hoa xanh": ["bhx"],
|
|
184
|
+
"family mart": ["familymart"],
|
|
185
|
+
gs25: ["gs 25"],
|
|
186
|
+
"pho 24": ["pho24"],
|
|
187
|
+
kfc: ["kentucky fried chicken"],
|
|
188
|
+
vietcombank: ["ngan hang vietcombank", "ngan hang tmcp ngoai thuong viet nam", "vcb"],
|
|
189
|
+
techcombank: ["ngan hang techcombank", "tcb"],
|
|
190
|
+
agribank: ["ngan hang agribank", "ngan hang nong nghiep va phat trien nong thon"],
|
|
191
|
+
bidv: ["ngan hang bidv", "ngan hang dau tu va phat trien viet nam"],
|
|
192
|
+
vietinbank: ["ngan hang vietinbank", "ngan hang cong thuong viet nam"],
|
|
193
|
+
pharmacity: ["nha thuoc pharmacity"],
|
|
194
|
+
"long chau": ["nha thuoc long chau", "fpt long chau"],
|
|
195
|
+
"an khang": ["nha thuoc an khang"],
|
|
196
|
+
"the gioi di dong": ["tgdd"],
|
|
197
|
+
"dien may xanh": ["dmx"],
|
|
198
|
+
"fpt shop": ["fptshop"]
|
|
199
|
+
};
|
|
200
|
+
var ABBREV = abbrev_default;
|
|
201
|
+
var BRAND_ALIAS = brand_alias_default;
|
|
202
|
+
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
203
|
+
var ABBREV_RULES = Object.entries(ABBREV).map(([key, value]) => {
|
|
204
|
+
const after = key.endsWith(".") ? "" : "(?=[\\s\\d,.]|$)";
|
|
205
|
+
return {
|
|
206
|
+
re: new RegExp(`(^|[\\s,.(])${escapeRegExp(key)}${after}`, "g"),
|
|
207
|
+
replacement: `$1${value} `
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
var ALIAS_RULES = Object.entries(BRAND_ALIAS).flatMap(([canonical, variants]) => variants.map((variant) => ({ variant, canonical }))).sort((a, b) => b.variant.length - a.variant.length);
|
|
211
|
+
function stripDiacritics(s) {
|
|
212
|
+
return s.normalize("NFD").replace(/\p{M}/gu, "").replace(/đ/g, "d").replace(/Đ/g, "D");
|
|
213
|
+
}
|
|
214
|
+
function expandAbbrev(s) {
|
|
215
|
+
let out = s;
|
|
216
|
+
for (const { re, replacement } of ABBREV_RULES) out = out.replace(re, replacement);
|
|
217
|
+
out = out.replace(/(^|\s)f\.?(?=\s*\d)/g, "$1phuong ");
|
|
218
|
+
return out.replace(/ {2,}/g, " ").trim();
|
|
219
|
+
}
|
|
220
|
+
function normalizeVi(input) {
|
|
221
|
+
let s = stripDiacritics(input.normalize("NFC").toLowerCase());
|
|
222
|
+
s = expandAbbrev(s);
|
|
223
|
+
s = s.replace(/[^a-z0-9/\-\s]/g, " ");
|
|
224
|
+
s = s.replace(/\s*\/\s*/g, "/");
|
|
225
|
+
s = s.replace(/\s+-\s+/g, " ");
|
|
226
|
+
return s.replace(/\s+/g, " ").trim();
|
|
227
|
+
}
|
|
228
|
+
var provinces_default = {
|
|
229
|
+
"H\xE0 N\u1ED9i": ["ha noi", "hanoi", "hn"],
|
|
230
|
+
Hu\u1EBF: ["hue", "thua thien hue", "thua thien-hue"],
|
|
231
|
+
"Lai Ch\xE2u": ["lai chau"],
|
|
232
|
+
"\u0110i\u1EC7n Bi\xEAn": ["dien bien"],
|
|
233
|
+
"S\u01A1n La": ["son la"],
|
|
234
|
+
"L\u1EA1ng S\u01A1n": ["lang son"],
|
|
235
|
+
"Qu\u1EA3ng Ninh": ["quang ninh", "ha long"],
|
|
236
|
+
"Thanh H\xF3a": ["thanh hoa"],
|
|
237
|
+
"Ngh\u1EC7 An": ["nghe an", "vinh"],
|
|
238
|
+
"H\xE0 T\u0129nh": ["ha tinh"],
|
|
239
|
+
"Cao B\u1EB1ng": ["cao bang"],
|
|
240
|
+
"Tuy\xEAn Quang": ["tuyen quang", "ha giang"],
|
|
241
|
+
"L\xE0o Cai": ["lao cai", "yen bai", "sa pa", "sapa"],
|
|
242
|
+
"Th\xE1i Nguy\xEAn": ["thai nguyen", "bac kan", "bac can"],
|
|
243
|
+
"Ph\xFA Th\u1ECD": ["phu tho", "vinh phuc", "hoa binh", "viet tri"],
|
|
244
|
+
"B\u1EAFc Ninh": ["bac ninh", "bac giang"],
|
|
245
|
+
"H\u01B0ng Y\xEAn": ["hung yen", "thai binh"],
|
|
246
|
+
"H\u1EA3i Ph\xF2ng": ["hai phong", "hai duong"],
|
|
247
|
+
"Ninh B\xECnh": ["ninh binh", "ha nam", "nam dinh"],
|
|
248
|
+
"Qu\u1EA3ng Tr\u1ECB": ["quang tri", "quang binh", "dong hoi"],
|
|
249
|
+
"\u0110\xE0 N\u1EB5ng": ["da nang", "danang", "quang nam", "hoi an", "tam ky"],
|
|
250
|
+
"Qu\u1EA3ng Ng\xE3i": ["quang ngai", "kon tum"],
|
|
251
|
+
"Gia Lai": ["gia lai", "binh dinh", "quy nhon", "pleiku"],
|
|
252
|
+
"Kh\xE1nh H\xF2a": ["khanh hoa", "ninh thuan", "nha trang", "cam ranh", "phan rang"],
|
|
253
|
+
"L\xE2m \u0110\u1ED3ng": ["lam dong", "dak nong", "dac nong", "binh thuan", "da lat", "dalat", "phan thiet"],
|
|
254
|
+
"\u0110\u1EAFk L\u1EAFk": ["dak lak", "dac lac", "daklak", "phu yen", "buon ma thuot", "tuy hoa"],
|
|
255
|
+
"Th\xE0nh ph\u1ED1 H\u1ED3 Ch\xED Minh": ["ho chi minh", "hcm", "tphcm", "sai gon", "saigon", "binh duong", "ba ria vung tau", "ba ria-vung tau", "ba ria", "vung tau", "thu dau mot", "di an"],
|
|
256
|
+
"\u0110\u1ED3ng Nai": ["dong nai", "binh phuoc", "bien hoa", "dong xoai"],
|
|
257
|
+
"T\xE2y Ninh": ["tay ninh", "long an", "tan an"],
|
|
258
|
+
"C\u1EA7n Th\u01A1": ["can tho", "soc trang", "hau giang", "vi thanh"],
|
|
259
|
+
"V\u0129nh Long": ["vinh long", "ben tre", "tra vinh"],
|
|
260
|
+
"\u0110\u1ED3ng Th\xE1p": ["dong thap", "tien giang", "my tho", "cao lanh", "sa dec"],
|
|
261
|
+
"C\xE0 Mau": ["ca mau", "bac lieu"],
|
|
262
|
+
"An Giang": ["an giang", "kien giang", "rach gia", "phu quoc", "long xuyen", "chau doc"]
|
|
263
|
+
};
|
|
264
|
+
var PROVINCE_BY_ALIAS = /* @__PURE__ */ new Map();
|
|
265
|
+
for (const [name, aliases] of Object.entries(provinces_default)) {
|
|
266
|
+
PROVINCE_BY_ALIAS.set(normalizeVi(name).replace(/^thanh pho /, ""), name);
|
|
267
|
+
for (const a of aliases) PROVINCE_BY_ALIAS.set(a, name);
|
|
268
|
+
}
|
|
269
|
+
var NUM = String.raw`(?:\d+(?:[a-z](?![a-z])\d*)?(?:-\d+[a-z]?)?|[a-z]{1,2}\d+[a-z]?)`;
|
|
270
|
+
var NUM_LEAD = String.raw`(?:\d+(?:[a-z](?![a-z])\d*)?(?:-\d+[a-z]?)?|(?![pqf]\d|tp\d|ql\d|tl\d|dt\d|hl\d)[a-z]{1,2}\d+[a-z]?)`;
|
|
271
|
+
var HN = String.raw`${NUM_LEAD}(?:/${NUM})*`;
|
|
272
|
+
var ALLEY_KW = String.raw`(?:hem|ngo|ngach|kiet)`;
|
|
273
|
+
var PREFIX = String.raw`(?:(?:so(?:\s*nha)?|sn|lo|can|kiot)\.?\s*)?`;
|
|
274
|
+
var RE_PREFIX = new RegExp(`^${PREFIX}`);
|
|
275
|
+
var RE_STREET = new RegExp(
|
|
276
|
+
String.raw`^${PREFIX}(${HN})?\s*((?:${ALLEY_KW}\s*${HN}\s*)*)((?:duong|d\.|pho)\s+(?!so\b|[a-z]{1,2}\d))?(.*)$`
|
|
277
|
+
);
|
|
278
|
+
var RE_ALLEY_EACH = new RegExp(String.raw`(${ALLEY_KW})\s*(${HN})`, "g");
|
|
279
|
+
var RE_STREET_LIKE = new RegExp(
|
|
280
|
+
String.raw`^${PREFIX}${NUM_LEAD}(?:/|\s|$)|\b${ALLEY_KW}\s*\d|^(?:duong|d\.|pho)\s`
|
|
281
|
+
);
|
|
282
|
+
var POI_LAYER_ID = "poi";
|
|
283
|
+
var SOVEREIGNTY_LABEL_ID = "sovereignty-label";
|
|
284
|
+
function isPoiStyleLayer(layer) {
|
|
285
|
+
return layer.id === POI_LAYER_ID || layer.source === "poi";
|
|
286
|
+
}
|
|
287
|
+
function nameExpression(lang) {
|
|
288
|
+
return ["coalesce", ["get", `name:${lang}`], ["get", "name"]];
|
|
289
|
+
}
|
|
290
|
+
function isNameLabelLayer(layer) {
|
|
291
|
+
if (layer.type !== "symbol" || layer.id === SOVEREIGNTY_LABEL_ID) return false;
|
|
292
|
+
const textField = layer.layout?.["text-field"];
|
|
293
|
+
return textField !== void 0 && JSON.stringify(textField).includes("name");
|
|
294
|
+
}
|
|
295
|
+
function mapLayers(style, fn) {
|
|
296
|
+
return { ...style, layers: (style.layers ?? []).map(fn) };
|
|
297
|
+
}
|
|
298
|
+
function localizeStyle(style, lang) {
|
|
299
|
+
if (lang === "vi") return style;
|
|
300
|
+
return mapLayers(
|
|
301
|
+
style,
|
|
302
|
+
(layer) => isNameLabelLayer(layer) ? { ...layer, layout: { ...layer.layout, "text-field": nameExpression(lang) } } : layer
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
function hidePoiLayer(style) {
|
|
306
|
+
return mapLayers(
|
|
307
|
+
style,
|
|
308
|
+
(layer) => isPoiStyleLayer(layer) ? { ...layer, layout: { ...layer.layout, visibility: "none" } } : layer
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
var toponym_alias_default = {
|
|
312
|
+
"quy nhon": {
|
|
313
|
+
variants: [
|
|
314
|
+
"qui nhon"
|
|
315
|
+
],
|
|
316
|
+
source: "osm:node/369487010 alt_name"
|
|
317
|
+
},
|
|
318
|
+
"dak lak": {
|
|
319
|
+
variants: [
|
|
320
|
+
"darlac",
|
|
321
|
+
"daklak"
|
|
322
|
+
],
|
|
323
|
+
source: "osm:relation/1884034 alt_name"
|
|
324
|
+
},
|
|
325
|
+
"buon ma thuot": {
|
|
326
|
+
variants: [
|
|
327
|
+
"buon me thuot",
|
|
328
|
+
"ban me thuot",
|
|
329
|
+
"buon ma thot"
|
|
330
|
+
],
|
|
331
|
+
source: "osm:node/2502425156 alt_name"
|
|
332
|
+
},
|
|
333
|
+
"da nang": {
|
|
334
|
+
variants: [
|
|
335
|
+
"tourane"
|
|
336
|
+
],
|
|
337
|
+
source: "osm:relation/1891418 old_name"
|
|
338
|
+
},
|
|
339
|
+
hue: {
|
|
340
|
+
variants: [
|
|
341
|
+
"thua thien hue",
|
|
342
|
+
"thua thien"
|
|
343
|
+
],
|
|
344
|
+
source: "osm:relation/1891483 old_name"
|
|
345
|
+
},
|
|
346
|
+
"tan son nhat": {
|
|
347
|
+
variants: [
|
|
348
|
+
"tan son nhut"
|
|
349
|
+
],
|
|
350
|
+
source: "osm:node/13097604035 old_name"
|
|
351
|
+
},
|
|
352
|
+
"hoi an": {
|
|
353
|
+
variants: [
|
|
354
|
+
"faifo"
|
|
355
|
+
],
|
|
356
|
+
source: "osm:node/110506070 old_name"
|
|
357
|
+
},
|
|
358
|
+
"soc trang": {
|
|
359
|
+
variants: [
|
|
360
|
+
"khanh hung"
|
|
361
|
+
],
|
|
362
|
+
source: "osm:node/369487102 old_name"
|
|
363
|
+
},
|
|
364
|
+
"can tho": {
|
|
365
|
+
variants: [
|
|
366
|
+
"tay do",
|
|
367
|
+
"phong dinh"
|
|
368
|
+
],
|
|
369
|
+
source: "osm:node/948325197 old_name"
|
|
370
|
+
},
|
|
371
|
+
"ca mau": {
|
|
372
|
+
variants: [
|
|
373
|
+
"an xuyen"
|
|
374
|
+
],
|
|
375
|
+
source: "osm:relation/1873490 old_name"
|
|
376
|
+
},
|
|
377
|
+
"ha noi": {
|
|
378
|
+
variants: [
|
|
379
|
+
"thang long"
|
|
380
|
+
],
|
|
381
|
+
source: "osm:relation/1903516 old_name"
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
var isEntry = (value) => typeof value === "object" && value !== null && "variants" in value && "source" in value;
|
|
385
|
+
var TOPONYM_ALIAS = Object.fromEntries(
|
|
386
|
+
Object.entries(toponym_alias_default).filter(
|
|
387
|
+
(pair) => !pair[0].startsWith("$") && isEntry(pair[1])
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
var escapeRegExp2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
391
|
+
var RULES = Object.entries(TOPONYM_ALIAS).flatMap(([canonical, { variants }]) => variants.map((variant) => ({ variant, canonical }))).sort((a, b) => b.variant.length - a.variant.length).map(({ variant, canonical }) => ({
|
|
392
|
+
re: new RegExp(`(^|\\s)${escapeRegExp2(variant)}(?=\\s|$)`, "g"),
|
|
393
|
+
replacement: `$1${canonical}`
|
|
394
|
+
}));
|
|
395
|
+
var vi_key_rules_default = {
|
|
396
|
+
$comment: [
|
|
397
|
+
"B\u1EA3ng lu\u1EADt kho\xE1 ng\u1EEF \xE2m \u2014 spec 05/09 m\u1EE5c 6.2. \u0110\u1EB7t \u1EDF d\u1EEF li\u1EC7u \u0111\u1EC3 ch\u1EC9nh m\xE0 kh\xF4ng s\u1EEDa logic.",
|
|
398
|
+
"\xC1p SAU normalizeVi v\xE0 applyToponymAlias, theo T\u1EEANG T\u1EEA, r\u1ED3i n\u1ED1i kh\xF4ng kho\u1EA3ng tr\u1EAFng.",
|
|
399
|
+
"Th\u1EE9 t\u1EF1 c\xF3 \xFD ngh\u0129a: \u0111\u1EA7u t\u1EEB \u2192 \xE2m cu\u1ED1i \u2192 i/y. `ngh` ph\u1EA3i tr\u01B0\u1EDBc `gh`; `gi` ph\u1EA3i tr\u01B0\u1EDBc `r`/`d`.",
|
|
400
|
+
"KH\xD4NG \xE1p l/n: 'H\xE0 N\u1ED9i' \u2194 'H\xE0 L\u1ED9i' va ch\u1EA1m t\xEAn ri\xEAng th\u1EADt qu\xE1 nhi\u1EC1u (spec n\xF3i r\xF5).",
|
|
401
|
+
"Ch\u1EC9 d\xF9ng \u1EDF b\u1EADc 3 n\xEAn va ch\u1EA1m ch\u1EC9 \u1EA3nh h\u01B0\u1EDFng th\u1EE9 t\u1EF1 trong nh\xF3m m\u1EDD, kh\xF4ng l\xE0m sai b\u1EADc 1/2."
|
|
402
|
+
],
|
|
403
|
+
wordStart: [
|
|
404
|
+
["^ngh", "ng"],
|
|
405
|
+
["^gh", "g"],
|
|
406
|
+
["^ph", "f"],
|
|
407
|
+
["^gi(?=[aeiouy])", "d"],
|
|
408
|
+
["^r", "d"],
|
|
409
|
+
["^tr", "c"],
|
|
410
|
+
["^ch", "c"],
|
|
411
|
+
["^x", "s"],
|
|
412
|
+
["^k(?=[aou])", "c"]
|
|
413
|
+
],
|
|
414
|
+
wordEnd: [
|
|
415
|
+
["ng$", "n"],
|
|
416
|
+
["nh$", "n"],
|
|
417
|
+
["t$", "c"]
|
|
418
|
+
],
|
|
419
|
+
anywhere: [
|
|
420
|
+
["quy", "qui"],
|
|
421
|
+
["([bcdfghklmnpqrstvx])y$", "$1i"]
|
|
422
|
+
]
|
|
423
|
+
};
|
|
424
|
+
var RULES2 = vi_key_rules_default;
|
|
425
|
+
var compile = (rules) => rules.map(([pattern, replacement]) => ({ re: new RegExp(pattern), replacement }));
|
|
426
|
+
var WORD_START = compile(RULES2.wordStart);
|
|
427
|
+
var WORD_END = compile(RULES2.wordEnd);
|
|
428
|
+
var ANYWHERE = compile(RULES2.anywhere);
|
|
429
|
+
|
|
430
|
+
// src/map.tsx
|
|
431
|
+
import { useContext, useEffect as useEffect2, useMemo, useRef } from "react";
|
|
432
|
+
import {
|
|
433
|
+
StyleSheet as StyleSheet2,
|
|
434
|
+
View
|
|
435
|
+
} from "react-native";
|
|
436
|
+
|
|
437
|
+
// src/attribution.tsx
|
|
438
|
+
import { Pressable, StyleSheet, Text } from "react-native";
|
|
439
|
+
import { jsx } from "react/jsx-runtime";
|
|
440
|
+
var COMPACT_ATTRIBUTION = "\xA9 MapsLibVN \xB7 \xA9 OpenStreetMap contributors";
|
|
441
|
+
function Attribution({ compact, onPress }) {
|
|
442
|
+
return /* @__PURE__ */ jsx(
|
|
443
|
+
Pressable,
|
|
444
|
+
{
|
|
445
|
+
onPress,
|
|
446
|
+
accessibilityRole: "link",
|
|
447
|
+
accessibilityLabel: "Ghi ngu\u1ED3n b\u1EA3n \u0111\u1ED3",
|
|
448
|
+
style: styles.box,
|
|
449
|
+
testID: "mapslibvn-attribution",
|
|
450
|
+
children: /* @__PURE__ */ jsx(Text, { style: styles.text, numberOfLines: compact ? 1 : 2, children: compact ? COMPACT_ATTRIBUTION : attributionText() })
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
var styles = StyleSheet.create({
|
|
455
|
+
box: {
|
|
456
|
+
position: "absolute",
|
|
457
|
+
left: 8,
|
|
458
|
+
bottom: 8,
|
|
459
|
+
maxWidth: "72%",
|
|
460
|
+
backgroundColor: "rgba(255,255,255,0.82)",
|
|
461
|
+
borderRadius: 4,
|
|
462
|
+
paddingHorizontal: 6,
|
|
463
|
+
paddingVertical: 2
|
|
464
|
+
},
|
|
465
|
+
text: { fontSize: 10, color: "#333333" }
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// src/context.ts
|
|
469
|
+
import { createContext } from "react";
|
|
470
|
+
var MapContext = createContext(null);
|
|
471
|
+
|
|
472
|
+
// src/to-poi-feature.ts
|
|
473
|
+
function toPoiFeature(feature) {
|
|
474
|
+
if (!feature || feature.geometry.type !== "Point") return null;
|
|
475
|
+
const [lng, lat] = feature.geometry.coordinates;
|
|
476
|
+
if (lng === void 0 || lat === void 0) return null;
|
|
477
|
+
const p = feature.properties ?? {};
|
|
478
|
+
return {
|
|
479
|
+
id: String(p.id ?? feature.id ?? ""),
|
|
480
|
+
name: String(p.name ?? ""),
|
|
481
|
+
category: String(p.cat ?? ""),
|
|
482
|
+
group: String(p.grp ?? ""),
|
|
483
|
+
lngLat: [lng, lat]
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/use-style.ts
|
|
488
|
+
import { useEffect, useState } from "react";
|
|
489
|
+
var isTheme = (s) => s === "light" || s === "dark";
|
|
490
|
+
function styleUrlFor(places, style) {
|
|
491
|
+
return isTheme(style) ? places.styleUrl(style) : style;
|
|
492
|
+
}
|
|
493
|
+
function needsTransform(o) {
|
|
494
|
+
return o.lang !== "vi" || !o.poiLayer;
|
|
495
|
+
}
|
|
496
|
+
function transformStyle(json, o) {
|
|
497
|
+
const localized = localizeStyle(json, o.lang);
|
|
498
|
+
return o.poiLayer ? localized : hidePoiLayer(localized);
|
|
499
|
+
}
|
|
500
|
+
function useResolvedStyle(places, options, doFetch = globalThis.fetch) {
|
|
501
|
+
const url = styleUrlFor(places, options.style);
|
|
502
|
+
const transform = needsTransform(options);
|
|
503
|
+
const [state, setState] = useState(
|
|
504
|
+
() => transform ? { status: "loading" } : { status: "ready", mapStyle: url }
|
|
505
|
+
);
|
|
506
|
+
useEffect(() => {
|
|
507
|
+
if (!transform) {
|
|
508
|
+
setState({ status: "ready", mapStyle: url });
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
let cancelled = false;
|
|
512
|
+
setState({ status: "loading" });
|
|
513
|
+
(async () => {
|
|
514
|
+
try {
|
|
515
|
+
const res = await doFetch(url);
|
|
516
|
+
if (!res.ok) throw new Error(`Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c style (HTTP ${res.status})`);
|
|
517
|
+
const json = await res.json();
|
|
518
|
+
if (!cancelled) {
|
|
519
|
+
setState({
|
|
520
|
+
status: "ready",
|
|
521
|
+
mapStyle: transformStyle(json, { lang: options.lang, poiLayer: options.poiLayer })
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
} catch (cause) {
|
|
525
|
+
if (!cancelled) {
|
|
526
|
+
setState({
|
|
527
|
+
status: "error",
|
|
528
|
+
error: cause instanceof Error ? cause : new Error("Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c style")
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
})();
|
|
533
|
+
return () => {
|
|
534
|
+
cancelled = true;
|
|
535
|
+
};
|
|
536
|
+
}, [url, transform, options.lang, options.poiLayer, doFetch]);
|
|
537
|
+
return state;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/map.tsx
|
|
541
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
542
|
+
var DEFAULT_CENTER = [106.7, 10.776];
|
|
543
|
+
var DEFAULT_ZOOM = 12;
|
|
544
|
+
function MapsLibVNMap({
|
|
545
|
+
apiKey,
|
|
546
|
+
apiBase,
|
|
547
|
+
style = "light",
|
|
548
|
+
center = DEFAULT_CENTER,
|
|
549
|
+
zoom = DEFAULT_ZOOM,
|
|
550
|
+
lang = "vi",
|
|
551
|
+
poiLayer = true,
|
|
552
|
+
poiSources,
|
|
553
|
+
compactAttribution = false,
|
|
554
|
+
bundleId,
|
|
555
|
+
containerStyle,
|
|
556
|
+
onLoad,
|
|
557
|
+
onPoiClick,
|
|
558
|
+
onError,
|
|
559
|
+
testID,
|
|
560
|
+
children
|
|
561
|
+
}) {
|
|
562
|
+
const poiSourcesKey2 = poiSources?.join(",") ?? "";
|
|
563
|
+
const places = useMemo(
|
|
564
|
+
() => createClient({
|
|
565
|
+
apiKey,
|
|
566
|
+
baseUrl: apiBase,
|
|
567
|
+
...bundleId ? { headers: { "X-Bundle-Id": bundleId } } : {},
|
|
568
|
+
...poiSourcesKey2 ? { poiSources: poiSourcesKey2.split(",") } : {}
|
|
569
|
+
}),
|
|
570
|
+
[apiKey, apiBase, bundleId, poiSourcesKey2]
|
|
571
|
+
);
|
|
572
|
+
const native = useRef(null);
|
|
573
|
+
const camera = useRef(null);
|
|
574
|
+
const handlers = useRef({ onLoad, onPoiClick, onError });
|
|
575
|
+
handlers.current = { onLoad, onPoiClick, onError };
|
|
576
|
+
const handle = useMemo(
|
|
577
|
+
() => ({
|
|
578
|
+
native,
|
|
579
|
+
camera,
|
|
580
|
+
places,
|
|
581
|
+
flyTo(c, z) {
|
|
582
|
+
camera.current?.flyTo(z === void 0 ? { center: c } : { center: c, zoom: z });
|
|
583
|
+
},
|
|
584
|
+
fitBounds(bbox, padding = 40) {
|
|
585
|
+
camera.current?.fitBounds(bbox, {
|
|
586
|
+
padding: { top: padding, right: padding, bottom: padding, left: padding }
|
|
587
|
+
});
|
|
588
|
+
},
|
|
589
|
+
async getBounds() {
|
|
590
|
+
const b = await native.current?.getBounds();
|
|
591
|
+
if (!b) throw new Error("B\u1EA3n \u0111\u1ED3 ch\u01B0a s\u1EB5n s\xE0ng");
|
|
592
|
+
return b;
|
|
593
|
+
}
|
|
594
|
+
}),
|
|
595
|
+
[places]
|
|
596
|
+
);
|
|
597
|
+
const resolved = useResolvedStyle(places, { style, lang, poiLayer });
|
|
598
|
+
useEffect2(() => {
|
|
599
|
+
if (resolved.status === "error") handlers.current.onError?.(resolved.error);
|
|
600
|
+
}, [resolved]);
|
|
601
|
+
const mapKey = `${apiKey}|${apiBase}|${style}|${lang}|${poiLayer}|${poiSourcesKey2}`;
|
|
602
|
+
const loadedFor = useRef(null);
|
|
603
|
+
const onPress = async (e) => {
|
|
604
|
+
if (!poiLayer || !handlers.current.onPoiClick) return;
|
|
605
|
+
const features = await native.current?.queryRenderedFeatures(e.nativeEvent.point, {
|
|
606
|
+
layers: [POI_LAYER_ID]
|
|
607
|
+
});
|
|
608
|
+
const poi = toPoiFeature(features?.[0]);
|
|
609
|
+
if (poi) handlers.current.onPoiClick?.(poi);
|
|
610
|
+
};
|
|
611
|
+
return /* @__PURE__ */ jsxs(View, { style: [styles2.container, containerStyle], ...testID ? { testID } : {}, children: [
|
|
612
|
+
resolved.status === "ready" ? /* @__PURE__ */ jsxs(
|
|
613
|
+
NativeMap,
|
|
614
|
+
{
|
|
615
|
+
ref: native,
|
|
616
|
+
style: styles2.map,
|
|
617
|
+
mapStyle: resolved.mapStyle,
|
|
618
|
+
attribution: true,
|
|
619
|
+
attributionPosition: { bottom: 8, right: 8 },
|
|
620
|
+
logo: false,
|
|
621
|
+
onPress,
|
|
622
|
+
onDidFinishLoadingStyle: () => {
|
|
623
|
+
if (loadedFor.current === mapKey) return;
|
|
624
|
+
loadedFor.current = mapKey;
|
|
625
|
+
handlers.current.onLoad?.(handle);
|
|
626
|
+
},
|
|
627
|
+
onDidFailLoadingMap: () => handlers.current.onError?.(new Error("Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c b\u1EA3n \u0111\u1ED3")),
|
|
628
|
+
children: [
|
|
629
|
+
/* @__PURE__ */ jsx2(Camera, { ref: camera, initialViewState: { center, zoom } }),
|
|
630
|
+
/* @__PURE__ */ jsx2(MapContext.Provider, { value: handle, children })
|
|
631
|
+
]
|
|
632
|
+
},
|
|
633
|
+
mapKey
|
|
634
|
+
) : null,
|
|
635
|
+
/* @__PURE__ */ jsx2(
|
|
636
|
+
Attribution,
|
|
637
|
+
{
|
|
638
|
+
compact: compactAttribution,
|
|
639
|
+
onPress: () => {
|
|
640
|
+
void native.current?.showAttribution();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
)
|
|
644
|
+
] });
|
|
645
|
+
}
|
|
646
|
+
function useMap() {
|
|
647
|
+
const map = useContext(MapContext);
|
|
648
|
+
if (!map) throw new Error("useMap ph\u1EA3i \u0111\u01B0\u1EE3c g\u1ECDi b\xEAn trong <MapsLibVNMap>");
|
|
649
|
+
return map;
|
|
650
|
+
}
|
|
651
|
+
var styles2 = StyleSheet2.create({
|
|
652
|
+
container: { flex: 1, position: "relative" },
|
|
653
|
+
map: { flex: 1 }
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// src/marker.tsx
|
|
657
|
+
import { Marker as NativeMarker } from "@maplibre/maplibre-react-native";
|
|
658
|
+
import { StyleSheet as StyleSheet3, View as View2 } from "react-native";
|
|
659
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
660
|
+
var DEFAULT_MARKER_COLOR = "#3FB1CE";
|
|
661
|
+
function Marker({
|
|
662
|
+
lng,
|
|
663
|
+
lat,
|
|
664
|
+
color = DEFAULT_MARKER_COLOR,
|
|
665
|
+
anchor = "center",
|
|
666
|
+
onPress,
|
|
667
|
+
children,
|
|
668
|
+
testID
|
|
669
|
+
}) {
|
|
670
|
+
useMap();
|
|
671
|
+
const pin = children ?? /* @__PURE__ */ jsx3(View2, { style: [styles3.pin, { backgroundColor: color }], testID: "mapslibvn-marker-pin" });
|
|
672
|
+
return /* @__PURE__ */ jsx3(
|
|
673
|
+
NativeMarker,
|
|
674
|
+
{
|
|
675
|
+
lngLat: [lng, lat],
|
|
676
|
+
anchor,
|
|
677
|
+
...onPress ? { onPress: () => onPress() } : {},
|
|
678
|
+
...testID ? { testID } : {},
|
|
679
|
+
children: pin
|
|
680
|
+
}
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
var styles3 = StyleSheet3.create({
|
|
684
|
+
pin: {
|
|
685
|
+
width: 22,
|
|
686
|
+
height: 22,
|
|
687
|
+
borderRadius: 11,
|
|
688
|
+
borderWidth: 3,
|
|
689
|
+
borderColor: "#ffffff"
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// src/use-places.ts
|
|
694
|
+
import { useContext as useContext2, useEffect as useEffect3, useState as useState2 } from "react";
|
|
695
|
+
function usePlaces(query, options = {}) {
|
|
696
|
+
const mapClient = useContext2(MapContext)?.places ?? null;
|
|
697
|
+
const client = options.client ?? mapClient;
|
|
698
|
+
const [items, setItems] = useState2([]);
|
|
699
|
+
const [loading, setLoading] = useState2(false);
|
|
700
|
+
const [error, setError] = useState2(null);
|
|
701
|
+
const nearKey = options.near?.join(",") ?? "";
|
|
702
|
+
useEffect3(() => {
|
|
703
|
+
const normalizedQuery = query.trim();
|
|
704
|
+
if (!client || normalizedQuery.length < 2) {
|
|
705
|
+
setItems([]);
|
|
706
|
+
setLoading(false);
|
|
707
|
+
setError(null);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
let cancelled = false;
|
|
711
|
+
setLoading(true);
|
|
712
|
+
setError(null);
|
|
713
|
+
const timer = setTimeout(async () => {
|
|
714
|
+
try {
|
|
715
|
+
const requestOptions = {};
|
|
716
|
+
if (nearKey) requestOptions.near = nearKey.split(",").map(Number);
|
|
717
|
+
if (options.limit !== void 0) requestOptions.limit = options.limit;
|
|
718
|
+
const response = await client.autocomplete(normalizedQuery, requestOptions);
|
|
719
|
+
if (!cancelled) setItems(response.items);
|
|
720
|
+
} catch (cause) {
|
|
721
|
+
if (!cancelled) setError(cause instanceof Error ? cause : new Error("Kh\xF4ng th\u1EC3 t\u1EA3i g\u1EE3i \xFD"));
|
|
722
|
+
} finally {
|
|
723
|
+
if (!cancelled) setLoading(false);
|
|
724
|
+
}
|
|
725
|
+
}, options.debounceMs ?? 200);
|
|
726
|
+
return () => {
|
|
727
|
+
cancelled = true;
|
|
728
|
+
clearTimeout(timer);
|
|
729
|
+
};
|
|
730
|
+
}, [client, query, nearKey, options.limit, options.debounceMs]);
|
|
731
|
+
return { items, loading, error };
|
|
732
|
+
}
|
|
733
|
+
export {
|
|
734
|
+
COMPACT_ATTRIBUTION,
|
|
735
|
+
DEFAULT_CENTER,
|
|
736
|
+
DEFAULT_MARKER_COLOR,
|
|
737
|
+
DEFAULT_ZOOM,
|
|
738
|
+
MapsLibVNMap,
|
|
739
|
+
Marker,
|
|
740
|
+
useMap,
|
|
741
|
+
usePlaces
|
|
742
|
+
};
|