642 Commits

Author SHA1 Message Date
vcoppe
8e397ce662 New translations en.json (French) 2026-03-30 00:26:39 +02:00
vcoppe
a8ceb7d48d New translations en.json (Dutch) 2026-03-30 00:26:36 +02:00
vcoppe
0944e81ecc New translations en.json (Serbian (Latin)) 2026-03-29 23:23:28 +02:00
vcoppe
090114a51e New translations en.json (Chinese Traditional, Hong Kong) 2026-03-29 23:23:27 +02:00
vcoppe
d275710e37 New translations en.json (Latvian) 2026-03-29 23:23:27 +02:00
vcoppe
f1817f4a4b New translations en.json (Thai) 2026-03-29 23:23:26 +02:00
vcoppe
1304264879 New translations en.json (Indonesian) 2026-03-29 23:23:25 +02:00
vcoppe
a4fe16b37e New translations en.json (Portuguese, Brazilian) 2026-03-29 23:23:24 +02:00
vcoppe
d1200027b0 New translations en.json (Vietnamese) 2026-03-29 23:23:23 +02:00
vcoppe
b91cdd28f6 New translations en.json (Chinese Simplified) 2026-03-29 23:23:22 +02:00
vcoppe
acc89a01e2 New translations en.json (Ukrainian) 2026-03-29 23:23:21 +02:00
vcoppe
9748a920dc New translations en.json (Turkish) 2026-03-29 23:23:20 +02:00
vcoppe
dd31974fab New translations en.json (Swedish) 2026-03-29 23:23:18 +02:00
vcoppe
cb32b4772b New translations en.json (Russian) 2026-03-29 23:23:17 +02:00
vcoppe
fa71659334 New translations en.json (Portuguese) 2026-03-29 23:23:16 +02:00
vcoppe
3711c284ca New translations en.json (Polish) 2026-03-29 23:23:15 +02:00
vcoppe
d937e1887c New translations en.json (Norwegian) 2026-03-29 23:23:14 +02:00
vcoppe
fb77ef3ef6 New translations en.json (Lithuanian) 2026-03-29 23:23:13 +02:00
vcoppe
6ded1eba77 New translations en.json (Korean) 2026-03-29 23:23:12 +02:00
vcoppe
819e9f2e5e New translations en.json (Italian) 2026-03-29 23:23:11 +02:00
vcoppe
c7c1a26157 New translations en.json (Hungarian) 2026-03-29 23:23:10 +02:00
vcoppe
f59b21b1dc New translations en.json (Hebrew) 2026-03-29 23:23:09 +02:00
vcoppe
1308e60c04 New translations en.json (Finnish) 2026-03-29 23:23:08 +02:00
vcoppe
9665137e15 New translations en.json (Greek) 2026-03-29 23:23:07 +02:00
vcoppe
43c9997581 New translations en.json (German) 2026-03-29 23:23:06 +02:00
vcoppe
fa6670ff0a New translations en.json (Danish) 2026-03-29 23:23:05 +02:00
vcoppe
3913bb0c5a New translations en.json (Czech) 2026-03-29 23:23:04 +02:00
vcoppe
719101f8e6 New translations en.json (Belarusian) 2026-03-29 23:23:03 +02:00
vcoppe
8486d022ec New translations en.json (Spanish) 2026-03-29 23:23:02 +02:00
vcoppe
f21db8f683 New translations en.json (French) 2026-03-29 23:23:01 +02:00
vcoppe
6cfd6202c6 New translations en.json (Romanian) 2026-03-29 23:23:00 +02:00
vcoppe
96810c5c52 New translations en.json (Catalan) 2026-03-29 23:22:59 +02:00
vcoppe
e4b4452bfe New translations en.json (Dutch) 2026-03-29 23:22:58 +02:00
vcoppe
c2e2030536 New translations en.json (Basque) 2026-03-29 23:22:57 +02:00
vcoppe
5bffdaee4c New translations merge.mdx (Dutch) 2026-03-29 23:01:45 +02:00
vcoppe
a9f1e7e8c8 New translations map-controls.mdx (Dutch) 2026-03-29 22:59:01 +02:00
vcoppe
bb47ef5aca New translations map-controls.mdx (Spanish) 2026-03-29 22:58:56 +02:00
vcoppe
8c632b06e1 New translations integration.mdx (Dutch) 2026-03-29 22:58:46 +02:00
vcoppe
c2921cd395 New translations en.json (Serbian (Latin)) 2026-03-29 22:57:49 +02:00
vcoppe
8d0a443e2f New translations en.json (Chinese Traditional, Hong Kong) 2026-03-29 22:57:48 +02:00
vcoppe
cfbe4ddee9 New translations en.json (Latvian) 2026-03-29 22:57:47 +02:00
vcoppe
4b8e7607cd New translations en.json (Thai) 2026-03-29 22:57:46 +02:00
vcoppe
9c4baed8f0 New translations en.json (Indonesian) 2026-03-29 22:57:45 +02:00
vcoppe
b7d3a0aa87 New translations en.json (Portuguese, Brazilian) 2026-03-29 22:57:44 +02:00
vcoppe
938c86543a New translations en.json (Vietnamese) 2026-03-29 22:57:43 +02:00
vcoppe
ceab276e02 New translations en.json (Chinese Simplified) 2026-03-29 22:57:42 +02:00
vcoppe
643dc906fc New translations en.json (Ukrainian) 2026-03-29 22:57:41 +02:00
vcoppe
d6adc2d2e5 New translations en.json (Turkish) 2026-03-29 22:57:40 +02:00
vcoppe
bea6304347 New translations en.json (Swedish) 2026-03-29 22:57:39 +02:00
vcoppe
a121c6d9ac New translations en.json (Russian) 2026-03-29 22:57:38 +02:00
vcoppe
abbac0d1d0 New translations en.json (Portuguese) 2026-03-29 22:57:37 +02:00
vcoppe
e74bae0c8f New translations en.json (Polish) 2026-03-29 22:57:36 +02:00
vcoppe
f1af9af1c3 New translations en.json (Norwegian) 2026-03-29 22:57:35 +02:00
vcoppe
a2dcf0b8ca New translations en.json (Lithuanian) 2026-03-29 22:57:34 +02:00
vcoppe
691d6cd0f6 New translations en.json (Korean) 2026-03-29 22:57:33 +02:00
vcoppe
b603c4d28b New translations en.json (Italian) 2026-03-29 22:57:32 +02:00
vcoppe
38f2896e6d New translations en.json (Hungarian) 2026-03-29 22:57:31 +02:00
vcoppe
e70ee418fe New translations en.json (Hebrew) 2026-03-29 22:57:30 +02:00
vcoppe
27ce4ee26e New translations en.json (Finnish) 2026-03-29 22:57:29 +02:00
vcoppe
e3f6af0497 New translations en.json (Greek) 2026-03-29 22:57:28 +02:00
vcoppe
110e341efb New translations en.json (German) 2026-03-29 22:57:27 +02:00
vcoppe
a436c4f24d New translations en.json (Danish) 2026-03-29 22:57:26 +02:00
vcoppe
227c4c9f42 New translations en.json (Czech) 2026-03-29 22:57:25 +02:00
vcoppe
98bffae341 New translations en.json (Belarusian) 2026-03-29 22:57:24 +02:00
vcoppe
b1e0567515 New translations en.json (Spanish) 2026-03-29 22:57:23 +02:00
vcoppe
f64dfadf29 New translations en.json (French) 2026-03-29 22:57:22 +02:00
vcoppe
1333d27563 New translations en.json (Romanian) 2026-03-29 22:57:21 +02:00
vcoppe
0785629f40 New translations en.json (Catalan) 2026-03-29 22:57:20 +02:00
vcoppe
067b692083 New translations en.json (Dutch) 2026-03-29 22:57:20 +02:00
vcoppe
4bcccab6d3 New translations en.json (Basque) 2026-03-29 22:57:18 +02:00
vcoppe
5867737fd5 New translations elevation.mdx (French) 2026-03-29 21:37:18 +02:00
vcoppe
cba4d1b2c6 New translations merge.mdx (French) 2026-03-29 21:37:17 +02:00
vcoppe
30e07d8487 New translations map-controls.mdx (French) 2026-03-29 21:35:42 +02:00
vcoppe
2bd036a456 New translations integration.mdx (French) 2026-03-29 21:35:30 +02:00
vcoppe
47c0ab7368 New translations en.json (French) 2026-03-29 21:34:30 +02:00
vcoppe
a7e1f9f400 New translations map-controls.mdx (Serbian (Latin)) 2026-03-29 20:39:24 +02:00
vcoppe
f976318c80 New translations map-controls.mdx (Chinese Traditional, Hong Kong) 2026-03-29 20:39:24 +02:00
vcoppe
5d91ccbd74 New translations map-controls.mdx (Latvian) 2026-03-29 20:39:23 +02:00
vcoppe
7cb6f1a8a9 New translations map-controls.mdx (Thai) 2026-03-29 20:39:22 +02:00
vcoppe
d123fb98c9 New translations map-controls.mdx (Indonesian) 2026-03-29 20:39:21 +02:00
vcoppe
ec6bab3382 New translations map-controls.mdx (Portuguese, Brazilian) 2026-03-29 20:39:20 +02:00
vcoppe
04d13fc023 New translations map-controls.mdx (Vietnamese) 2026-03-29 20:39:19 +02:00
vcoppe
c47cacb9dd New translations map-controls.mdx (Chinese Simplified) 2026-03-29 20:39:18 +02:00
vcoppe
5805743b3d New translations map-controls.mdx (Ukrainian) 2026-03-29 20:39:17 +02:00
vcoppe
9e6459dc25 New translations map-controls.mdx (Turkish) 2026-03-29 20:39:16 +02:00
vcoppe
26bf26e04c New translations map-controls.mdx (Swedish) 2026-03-29 20:39:15 +02:00
vcoppe
626429bf7d New translations map-controls.mdx (Russian) 2026-03-29 20:39:14 +02:00
vcoppe
170e746323 New translations map-controls.mdx (Portuguese) 2026-03-29 20:39:13 +02:00
vcoppe
17c718c3ce New translations map-controls.mdx (Polish) 2026-03-29 20:39:12 +02:00
vcoppe
5fef8916d9 New translations map-controls.mdx (Norwegian) 2026-03-29 20:39:11 +02:00
vcoppe
c58ba03181 New translations map-controls.mdx (Dutch) 2026-03-29 20:39:10 +02:00
vcoppe
b2ef670006 New translations map-controls.mdx (Lithuanian) 2026-03-29 20:39:09 +02:00
vcoppe
a90ea2abb8 New translations map-controls.mdx (Korean) 2026-03-29 20:39:08 +02:00
vcoppe
7d2e971aca New translations map-controls.mdx (Italian) 2026-03-29 20:39:07 +02:00
vcoppe
3a1c44f513 New translations map-controls.mdx (Hungarian) 2026-03-29 20:39:06 +02:00
vcoppe
47d3f12302 New translations map-controls.mdx (Hebrew) 2026-03-29 20:39:05 +02:00
vcoppe
9be9a8b00c New translations map-controls.mdx (Finnish) 2026-03-29 20:39:04 +02:00
vcoppe
404aa48382 New translations map-controls.mdx (Basque) 2026-03-29 20:39:03 +02:00
vcoppe
9eea3c68e3 New translations map-controls.mdx (Greek) 2026-03-29 20:39:02 +02:00
vcoppe
dc623c39af New translations map-controls.mdx (German) 2026-03-29 20:39:01 +02:00
vcoppe
e94f21e1ba New translations map-controls.mdx (Danish) 2026-03-29 20:39:00 +02:00
vcoppe
fd54b865a3 New translations map-controls.mdx (Czech) 2026-03-29 20:38:59 +02:00
vcoppe
0c15c9e28f New translations map-controls.mdx (Catalan) 2026-03-29 20:38:58 +02:00
vcoppe
69a0b20a4c New translations map-controls.mdx (Belarusian) 2026-03-29 20:38:57 +02:00
vcoppe
a5892cf21e New translations map-controls.mdx (Spanish) 2026-03-29 20:38:56 +02:00
vcoppe
b64de543ba New translations map-controls.mdx (French) 2026-03-29 20:38:55 +02:00
vcoppe
00224f5131 New translations map-controls.mdx (Romanian) 2026-03-29 20:38:54 +02:00
vcoppe
20c0342f0c New translations merge.mdx (Serbian (Latin)) 2026-03-29 20:33:49 +02:00
vcoppe
bd9ca4271b New translations merge.mdx (Chinese Traditional, Hong Kong) 2026-03-29 20:33:48 +02:00
vcoppe
9da8f5cac6 New translations merge.mdx (Latvian) 2026-03-29 20:33:47 +02:00
vcoppe
199731417c New translations merge.mdx (Thai) 2026-03-29 20:33:46 +02:00
vcoppe
fe4b617176 New translations merge.mdx (Indonesian) 2026-03-29 20:33:46 +02:00
vcoppe
5437ed57cf New translations merge.mdx (Portuguese, Brazilian) 2026-03-29 20:33:45 +02:00
vcoppe
096dd10b55 New translations merge.mdx (Vietnamese) 2026-03-29 20:33:44 +02:00
vcoppe
f3cabd4812 New translations merge.mdx (Chinese Simplified) 2026-03-29 20:33:43 +02:00
vcoppe
d7179c76f7 New translations merge.mdx (Ukrainian) 2026-03-29 20:33:42 +02:00
vcoppe
6465dc447d New translations merge.mdx (Turkish) 2026-03-29 20:33:41 +02:00
vcoppe
7459582be8 New translations merge.mdx (Swedish) 2026-03-29 20:33:40 +02:00
vcoppe
3a0d2ba4b3 New translations merge.mdx (Russian) 2026-03-29 20:33:39 +02:00
vcoppe
ee62fc453a New translations merge.mdx (Portuguese) 2026-03-29 20:33:38 +02:00
vcoppe
d00ebb96ec New translations merge.mdx (Polish) 2026-03-29 20:33:37 +02:00
vcoppe
b20c2cd61d New translations merge.mdx (Norwegian) 2026-03-29 20:33:36 +02:00
vcoppe
6a6d5deda5 New translations merge.mdx (Dutch) 2026-03-29 20:33:35 +02:00
vcoppe
c411c7e5c1 New translations merge.mdx (Lithuanian) 2026-03-29 20:33:35 +02:00
vcoppe
b853cd1932 New translations merge.mdx (Korean) 2026-03-29 20:33:34 +02:00
vcoppe
417bb421d2 New translations merge.mdx (Italian) 2026-03-29 20:33:33 +02:00
vcoppe
0c6186c2ca New translations merge.mdx (Hungarian) 2026-03-29 20:33:32 +02:00
vcoppe
33dbd56e86 New translations merge.mdx (Hebrew) 2026-03-29 20:33:31 +02:00
vcoppe
35197616eb New translations merge.mdx (Finnish) 2026-03-29 20:33:30 +02:00
vcoppe
784f77f769 New translations merge.mdx (Basque) 2026-03-29 20:33:29 +02:00
vcoppe
a51a3a99d7 New translations merge.mdx (Greek) 2026-03-29 20:33:28 +02:00
vcoppe
4c3f23a906 New translations merge.mdx (German) 2026-03-29 20:33:27 +02:00
vcoppe
e75889855e New translations merge.mdx (Danish) 2026-03-29 20:33:26 +02:00
vcoppe
016e25120a New translations merge.mdx (Czech) 2026-03-29 20:33:25 +02:00
vcoppe
7482c28f87 New translations merge.mdx (Catalan) 2026-03-29 20:33:24 +02:00
vcoppe
f6c1765335 New translations merge.mdx (Belarusian) 2026-03-29 20:33:23 +02:00
vcoppe
0229b1a1f4 New translations merge.mdx (Spanish) 2026-03-29 20:33:22 +02:00
vcoppe
8ec227ff12 New translations merge.mdx (French) 2026-03-29 20:33:21 +02:00
vcoppe
4e2dc309c3 New translations merge.mdx (Romanian) 2026-03-29 20:33:20 +02:00
vcoppe
a267f2195b New translations integration.mdx (Serbian (Latin)) 2026-03-29 20:30:48 +02:00
vcoppe
5f111f58e4 New translations integration.mdx (Chinese Traditional, Hong Kong) 2026-03-29 20:30:47 +02:00
vcoppe
3505430671 New translations integration.mdx (Latvian) 2026-03-29 20:30:45 +02:00
vcoppe
49e154c208 New translations integration.mdx (Thai) 2026-03-29 20:30:44 +02:00
vcoppe
99ada17c5c New translations integration.mdx (Indonesian) 2026-03-29 20:30:44 +02:00
vcoppe
c34a29df07 New translations integration.mdx (Portuguese, Brazilian) 2026-03-29 20:30:43 +02:00
vcoppe
ab4d46c5c0 New translations integration.mdx (Vietnamese) 2026-03-29 20:30:42 +02:00
vcoppe
9dba37f76e New translations integration.mdx (Chinese Simplified) 2026-03-29 20:30:41 +02:00
vcoppe
7e8f6cae5d New translations integration.mdx (Ukrainian) 2026-03-29 20:30:40 +02:00
vcoppe
397d7d8d1d New translations integration.mdx (Turkish) 2026-03-29 20:30:39 +02:00
vcoppe
8d11a878d5 New translations integration.mdx (Swedish) 2026-03-29 20:30:38 +02:00
vcoppe
5ed09553f5 New translations integration.mdx (Russian) 2026-03-29 20:30:37 +02:00
vcoppe
7c08b79160 New translations integration.mdx (Portuguese) 2026-03-29 20:30:36 +02:00
vcoppe
90b4b4949e New translations integration.mdx (Polish) 2026-03-29 20:30:35 +02:00
vcoppe
a6eca76558 New translations integration.mdx (Norwegian) 2026-03-29 20:30:34 +02:00
vcoppe
ae5d5d1d9d New translations integration.mdx (Dutch) 2026-03-29 20:30:33 +02:00
vcoppe
315447fd3c New translations integration.mdx (Lithuanian) 2026-03-29 20:30:32 +02:00
vcoppe
ec39177844 New translations integration.mdx (Korean) 2026-03-29 20:30:31 +02:00
vcoppe
e1b18be666 New translations integration.mdx (Italian) 2026-03-29 20:30:30 +02:00
vcoppe
c8ae59446a New translations integration.mdx (Hungarian) 2026-03-29 20:30:29 +02:00
vcoppe
942b5aa4a8 New translations integration.mdx (Hebrew) 2026-03-29 20:30:28 +02:00
vcoppe
d31c814f70 New translations integration.mdx (Finnish) 2026-03-29 20:30:27 +02:00
vcoppe
6c7a64fe2c New translations integration.mdx (Basque) 2026-03-29 20:30:26 +02:00
vcoppe
3b304bfeee New translations integration.mdx (Greek) 2026-03-29 20:30:25 +02:00
vcoppe
57cee732dc New translations integration.mdx (German) 2026-03-29 20:30:24 +02:00
vcoppe
3ea4c317dd New translations integration.mdx (Danish) 2026-03-29 20:30:23 +02:00
vcoppe
8e2df27ac1 New translations integration.mdx (Czech) 2026-03-29 20:30:22 +02:00
vcoppe
4fa37144e6 New translations integration.mdx (Catalan) 2026-03-29 20:30:22 +02:00
vcoppe
0ddbf645d8 New translations integration.mdx (Belarusian) 2026-03-29 20:30:21 +02:00
vcoppe
8a0c4d8052 New translations integration.mdx (Spanish) 2026-03-29 20:30:20 +02:00
vcoppe
2590d17ec1 New translations integration.mdx (French) 2026-03-29 20:30:19 +02:00
vcoppe
fea3040d44 New translations integration.mdx (Romanian) 2026-03-29 20:30:18 +02:00
vcoppe
f2344dd188 New translations files-and-stats.mdx (Serbian (Latin)) 2026-03-29 20:29:43 +02:00
vcoppe
87db6c3a2b New translations files-and-stats.mdx (Chinese Traditional, Hong Kong) 2026-03-29 20:29:42 +02:00
vcoppe
a0dd6a0c7a New translations files-and-stats.mdx (Latvian) 2026-03-29 20:29:41 +02:00
vcoppe
f5772a3227 New translations files-and-stats.mdx (Thai) 2026-03-29 20:29:40 +02:00
vcoppe
3f51b7f01b New translations files-and-stats.mdx (Indonesian) 2026-03-29 20:29:40 +02:00
vcoppe
a9115e67d8 New translations files-and-stats.mdx (Portuguese, Brazilian) 2026-03-29 20:29:39 +02:00
vcoppe
3863ba99fb New translations files-and-stats.mdx (Vietnamese) 2026-03-29 20:29:38 +02:00
vcoppe
d0ac53f4ab New translations files-and-stats.mdx (Chinese Simplified) 2026-03-29 20:29:37 +02:00
vcoppe
6b8b903efa New translations files-and-stats.mdx (Ukrainian) 2026-03-29 20:29:36 +02:00
vcoppe
b212aeeaf6 New translations files-and-stats.mdx (Turkish) 2026-03-29 20:29:35 +02:00
vcoppe
4d0cbb1632 New translations files-and-stats.mdx (Swedish) 2026-03-29 20:29:34 +02:00
vcoppe
8e013e0050 New translations files-and-stats.mdx (Russian) 2026-03-29 20:29:33 +02:00
vcoppe
7049e37a4d New translations files-and-stats.mdx (Portuguese) 2026-03-29 20:29:32 +02:00
vcoppe
8034602e7a New translations files-and-stats.mdx (Polish) 2026-03-29 20:29:31 +02:00
vcoppe
7abf3b406e New translations files-and-stats.mdx (Norwegian) 2026-03-29 20:29:30 +02:00
vcoppe
7438f09194 New translations files-and-stats.mdx (Dutch) 2026-03-29 20:29:29 +02:00
vcoppe
0ee12a12d2 New translations files-and-stats.mdx (Lithuanian) 2026-03-29 20:29:28 +02:00
vcoppe
c65145bc0f New translations files-and-stats.mdx (Korean) 2026-03-29 20:29:27 +02:00
vcoppe
86220ba5bb New translations files-and-stats.mdx (Italian) 2026-03-29 20:29:26 +02:00
vcoppe
0006b74dfe New translations files-and-stats.mdx (Hungarian) 2026-03-29 20:29:25 +02:00
vcoppe
14fcace3bf New translations files-and-stats.mdx (Hebrew) 2026-03-29 20:29:24 +02:00
vcoppe
0898b24e83 New translations files-and-stats.mdx (Finnish) 2026-03-29 20:29:23 +02:00
vcoppe
170ed8ecde New translations files-and-stats.mdx (Basque) 2026-03-29 20:29:22 +02:00
vcoppe
523b6918f9 New translations files-and-stats.mdx (Greek) 2026-03-29 20:29:21 +02:00
vcoppe
910fbed5cd New translations files-and-stats.mdx (German) 2026-03-29 20:29:19 +02:00
vcoppe
b41179f085 New translations files-and-stats.mdx (Danish) 2026-03-29 20:29:18 +02:00
vcoppe
52f73ac690 New translations files-and-stats.mdx (Czech) 2026-03-29 20:29:17 +02:00
vcoppe
63b35a14ab New translations files-and-stats.mdx (Catalan) 2026-03-29 20:29:15 +02:00
vcoppe
21c54d2d7e New translations files-and-stats.mdx (Belarusian) 2026-03-29 20:29:14 +02:00
vcoppe
92bee784ce New translations files-and-stats.mdx (Spanish) 2026-03-29 20:29:13 +02:00
vcoppe
d35eb04f26 New translations files-and-stats.mdx (French) 2026-03-29 20:29:12 +02:00
vcoppe
fd23ef677d New translations files-and-stats.mdx (Romanian) 2026-03-29 20:29:11 +02:00
vcoppe
0e4b8c214f New translations en.json (Serbian (Latin)) 2026-03-29 20:29:10 +02:00
vcoppe
7ecc29f200 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-29 20:29:09 +02:00
vcoppe
1ff6293cb4 New translations en.json (Latvian) 2026-03-29 20:29:08 +02:00
vcoppe
262e580451 New translations en.json (Thai) 2026-03-29 20:29:07 +02:00
vcoppe
79431e3f7e New translations en.json (Indonesian) 2026-03-29 20:29:06 +02:00
vcoppe
4c66afc40d New translations en.json (Portuguese, Brazilian) 2026-03-29 20:29:05 +02:00
vcoppe
3708b8e445 New translations en.json (Vietnamese) 2026-03-29 20:29:04 +02:00
vcoppe
caf8621c07 New translations en.json (Chinese Simplified) 2026-03-29 20:29:03 +02:00
vcoppe
8aab0a0c48 New translations en.json (Ukrainian) 2026-03-29 20:29:02 +02:00
vcoppe
da7d3a5a08 New translations en.json (Turkish) 2026-03-29 20:29:01 +02:00
vcoppe
e9f0d9b810 New translations en.json (Swedish) 2026-03-29 20:29:00 +02:00
vcoppe
96063fc946 New translations en.json (Russian) 2026-03-29 20:28:59 +02:00
vcoppe
1c69f76e71 New translations en.json (Portuguese) 2026-03-29 20:28:58 +02:00
vcoppe
4981c80d8e New translations en.json (Polish) 2026-03-29 20:28:57 +02:00
vcoppe
f59ecd322b New translations en.json (Norwegian) 2026-03-29 20:28:56 +02:00
vcoppe
c4d7984d87 New translations en.json (Lithuanian) 2026-03-29 20:28:55 +02:00
vcoppe
6e71227e9c New translations en.json (Korean) 2026-03-29 20:28:54 +02:00
vcoppe
8c26fe4eac New translations en.json (Italian) 2026-03-29 20:28:53 +02:00
vcoppe
0c0a6e4f63 New translations en.json (Hungarian) 2026-03-29 20:28:52 +02:00
vcoppe
b90f4e946f New translations en.json (Hebrew) 2026-03-29 20:28:51 +02:00
vcoppe
39c05ecb0f New translations en.json (Finnish) 2026-03-29 20:28:50 +02:00
vcoppe
c338e024a9 New translations en.json (Greek) 2026-03-29 20:28:49 +02:00
vcoppe
bdbafd9b9d New translations en.json (German) 2026-03-29 20:28:49 +02:00
vcoppe
ff869acd8e New translations en.json (Danish) 2026-03-29 20:28:48 +02:00
vcoppe
a74a46cd5e New translations en.json (Czech) 2026-03-29 20:28:47 +02:00
vcoppe
f600df9c8e New translations en.json (Belarusian) 2026-03-29 20:28:46 +02:00
vcoppe
f63a7cdb5b New translations en.json (Spanish) 2026-03-29 20:28:45 +02:00
vcoppe
ba5f548bbf New translations en.json (French) 2026-03-29 20:28:44 +02:00
vcoppe
9b62acb819 New translations en.json (Romanian) 2026-03-29 20:28:43 +02:00
vcoppe
34219fa9d9 New translations en.json (Catalan) 2026-03-29 20:28:41 +02:00
vcoppe
892a4fc6d2 New translations en.json (Dutch) 2026-03-29 20:28:40 +02:00
vcoppe
fd35f80c33 New translations en.json (Basque) 2026-03-29 20:28:39 +02:00
vcoppe
66666e4570 New translations en.json (Spanish) 2026-03-28 07:48:27 +01:00
vcoppe
77b1de0a45 New translations en.json (Dutch) 2026-03-27 21:57:18 +01:00
vcoppe
6f08560888 New translations funding.mdx (Dutch) 2026-03-27 20:53:20 +01:00
vcoppe
ffbb74e539 New translations en.json (Serbian (Latin)) 2026-03-27 20:52:31 +01:00
vcoppe
803d691398 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-27 20:52:30 +01:00
vcoppe
03e33ace34 New translations en.json (Latvian) 2026-03-27 20:52:29 +01:00
vcoppe
377dfce412 New translations en.json (Thai) 2026-03-27 20:52:27 +01:00
vcoppe
e6a5387f08 New translations en.json (Indonesian) 2026-03-27 20:52:26 +01:00
vcoppe
92f793ab0e New translations en.json (Portuguese, Brazilian) 2026-03-27 20:52:25 +01:00
vcoppe
0085ddf4c2 New translations en.json (Vietnamese) 2026-03-27 20:52:24 +01:00
vcoppe
2c3e86cdcd New translations en.json (Chinese Simplified) 2026-03-27 20:52:23 +01:00
vcoppe
38502fff3a New translations en.json (Ukrainian) 2026-03-27 20:52:21 +01:00
vcoppe
39a666e433 New translations en.json (Turkish) 2026-03-27 20:52:20 +01:00
vcoppe
f07851929c New translations en.json (Swedish) 2026-03-27 20:52:19 +01:00
vcoppe
e3ebbb88c9 New translations en.json (Russian) 2026-03-27 20:52:18 +01:00
vcoppe
dca88a254d New translations en.json (Portuguese) 2026-03-27 20:52:09 +01:00
vcoppe
78730760e3 New translations en.json (Polish) 2026-03-27 20:52:08 +01:00
vcoppe
1f7b1fc12b New translations en.json (Norwegian) 2026-03-27 20:52:06 +01:00
vcoppe
5b12c85271 New translations en.json (Lithuanian) 2026-03-27 20:52:05 +01:00
vcoppe
7f160730c9 New translations en.json (Korean) 2026-03-27 20:52:04 +01:00
vcoppe
712259dc82 New translations en.json (Italian) 2026-03-27 20:52:03 +01:00
vcoppe
f9477556c5 New translations en.json (Hungarian) 2026-03-27 20:52:02 +01:00
vcoppe
874ed75e76 New translations en.json (Hebrew) 2026-03-27 20:52:01 +01:00
vcoppe
159199fc6a New translations en.json (Finnish) 2026-03-27 20:52:00 +01:00
vcoppe
416262e185 New translations en.json (Greek) 2026-03-27 20:51:59 +01:00
vcoppe
32add9f48c New translations en.json (German) 2026-03-27 20:51:58 +01:00
vcoppe
457a62a84d New translations en.json (Danish) 2026-03-27 20:51:57 +01:00
vcoppe
918ee6177b New translations en.json (Czech) 2026-03-27 20:51:56 +01:00
vcoppe
7b73aa00de New translations en.json (Belarusian) 2026-03-27 20:51:55 +01:00
vcoppe
d6b8645f52 New translations en.json (Catalan) 2026-03-27 20:51:53 +01:00
vcoppe
d3eba662e6 New translations en.json (Dutch) 2026-03-27 20:51:52 +01:00
vcoppe
5c3476dff3 New translations en.json (Basque) 2026-03-27 20:51:51 +01:00
vcoppe
46f88ef854 New translations en.json (Spanish) 2026-03-27 20:51:45 +01:00
vcoppe
b5c956ffe5 New translations en.json (French) 2026-03-27 20:51:44 +01:00
vcoppe
db0fe84c8c New translations en.json (Romanian) 2026-03-27 20:51:43 +01:00
vcoppe
420efae194 New translations en.json (Serbian (Latin)) 2026-03-27 19:42:41 +01:00
vcoppe
de6bda6d7d New translations en.json (Chinese Traditional, Hong Kong) 2026-03-27 19:42:39 +01:00
vcoppe
eff9ce4783 New translations en.json (Latvian) 2026-03-27 19:42:37 +01:00
vcoppe
0675fbfda9 New translations en.json (Thai) 2026-03-27 19:42:35 +01:00
vcoppe
bbebed7c98 New translations en.json (Indonesian) 2026-03-27 19:42:34 +01:00
vcoppe
303fa41405 New translations en.json (Portuguese, Brazilian) 2026-03-27 19:42:32 +01:00
vcoppe
de134ec56b New translations en.json (Vietnamese) 2026-03-27 19:42:31 +01:00
vcoppe
984ab7debf New translations en.json (Chinese Simplified) 2026-03-27 19:42:30 +01:00
vcoppe
fa6691821b New translations en.json (Ukrainian) 2026-03-27 19:42:26 +01:00
vcoppe
f949fdf4d4 New translations en.json (Turkish) 2026-03-27 19:42:24 +01:00
vcoppe
bd72166112 New translations en.json (Swedish) 2026-03-27 19:42:22 +01:00
vcoppe
ac9103668a New translations en.json (Russian) 2026-03-27 19:42:21 +01:00
vcoppe
3905a366bd New translations en.json (Portuguese) 2026-03-27 19:42:20 +01:00
vcoppe
bf804eba2c New translations en.json (Polish) 2026-03-27 19:42:18 +01:00
vcoppe
8522584711 New translations en.json (Norwegian) 2026-03-27 19:42:17 +01:00
vcoppe
2a948584dc New translations en.json (Lithuanian) 2026-03-27 19:42:15 +01:00
vcoppe
4812556ab4 New translations en.json (Korean) 2026-03-27 19:42:13 +01:00
vcoppe
c43d10d680 New translations en.json (Italian) 2026-03-27 19:42:12 +01:00
vcoppe
6e6e12bc19 New translations en.json (Hungarian) 2026-03-27 19:42:10 +01:00
vcoppe
8e020d5d10 New translations en.json (Hebrew) 2026-03-27 19:42:09 +01:00
vcoppe
e94b1ea1b9 New translations en.json (Finnish) 2026-03-27 19:42:08 +01:00
vcoppe
663f485782 New translations en.json (Greek) 2026-03-27 19:42:07 +01:00
vcoppe
e67b5e886f New translations en.json (German) 2026-03-27 19:42:06 +01:00
vcoppe
062e214538 New translations en.json (Dutch) 2026-03-27 19:42:03 +01:00
vcoppe
fb7ecaf925 New translations en.json (Basque) 2026-03-27 19:42:02 +01:00
vcoppe
e5d6282c2e New translations en.json (Danish) 2026-03-27 19:41:58 +01:00
vcoppe
0757fb1726 New translations en.json (Czech) 2026-03-27 19:41:57 +01:00
vcoppe
aeffe2663c New translations en.json (Belarusian) 2026-03-27 19:41:56 +01:00
vcoppe
85a1c365c2 New translations en.json (Spanish) 2026-03-27 19:41:55 +01:00
vcoppe
9b1639e065 New translations en.json (French) 2026-03-27 19:41:53 +01:00
vcoppe
da62040800 New translations en.json (Romanian) 2026-03-27 19:41:51 +01:00
vcoppe
8f700921ee New translations en.json (Catalan) 2026-03-27 19:41:48 +01:00
vcoppe
6e80fb6bdb New translations elevation.mdx (Serbian (Latin)) 2026-03-27 18:46:58 +01:00
vcoppe
d0c61cf91f New translations elevation.mdx (Chinese Traditional, Hong Kong) 2026-03-27 18:46:57 +01:00
vcoppe
03a4c7c58e New translations elevation.mdx (Latvian) 2026-03-27 18:46:55 +01:00
vcoppe
15753a9fc0 New translations elevation.mdx (Thai) 2026-03-27 18:46:54 +01:00
vcoppe
1b31112677 New translations elevation.mdx (Indonesian) 2026-03-27 18:46:53 +01:00
vcoppe
ff71ce1292 New translations elevation.mdx (Portuguese, Brazilian) 2026-03-27 18:46:51 +01:00
vcoppe
e45cd1a2ab New translations elevation.mdx (Vietnamese) 2026-03-27 18:46:50 +01:00
vcoppe
c722c25020 New translations elevation.mdx (Chinese Simplified) 2026-03-27 18:46:49 +01:00
vcoppe
88cc331ff2 New translations elevation.mdx (Ukrainian) 2026-03-27 18:46:48 +01:00
vcoppe
52792a99fa New translations elevation.mdx (Turkish) 2026-03-27 18:46:47 +01:00
vcoppe
8f1f53ef62 New translations elevation.mdx (Swedish) 2026-03-27 18:46:46 +01:00
vcoppe
6accef46d7 New translations elevation.mdx (Russian) 2026-03-27 18:46:45 +01:00
vcoppe
e18a9f3100 New translations elevation.mdx (Portuguese) 2026-03-27 18:46:44 +01:00
vcoppe
e6bd23ef6f New translations elevation.mdx (Polish) 2026-03-27 18:46:43 +01:00
vcoppe
359fcac564 New translations elevation.mdx (Norwegian) 2026-03-27 18:46:42 +01:00
vcoppe
2418eddafc New translations elevation.mdx (Dutch) 2026-03-27 18:46:41 +01:00
vcoppe
9019c44790 New translations elevation.mdx (Lithuanian) 2026-03-27 18:46:39 +01:00
vcoppe
6159e4665b New translations elevation.mdx (Korean) 2026-03-27 18:46:38 +01:00
vcoppe
9aba2fa798 New translations elevation.mdx (Italian) 2026-03-27 18:46:30 +01:00
vcoppe
d7b2db507b New translations elevation.mdx (Hungarian) 2026-03-27 18:46:29 +01:00
vcoppe
465c687717 New translations elevation.mdx (Hebrew) 2026-03-27 18:46:28 +01:00
vcoppe
b6611e10fd New translations elevation.mdx (Finnish) 2026-03-27 18:46:27 +01:00
vcoppe
709e740527 New translations elevation.mdx (Basque) 2026-03-27 18:46:26 +01:00
vcoppe
ac1603eb03 New translations elevation.mdx (Greek) 2026-03-27 18:46:24 +01:00
vcoppe
b198d51925 New translations elevation.mdx (German) 2026-03-27 18:46:14 +01:00
vcoppe
3f29509b24 New translations elevation.mdx (Danish) 2026-03-27 18:46:13 +01:00
vcoppe
a5e1d27d5f New translations elevation.mdx (Czech) 2026-03-27 18:46:12 +01:00
vcoppe
1e727c2009 New translations elevation.mdx (Catalan) 2026-03-27 18:46:11 +01:00
vcoppe
9826a450b0 New translations elevation.mdx (Belarusian) 2026-03-27 18:46:10 +01:00
vcoppe
4a0a3a1b50 New translations elevation.mdx (Spanish) 2026-03-27 18:46:09 +01:00
vcoppe
b898c5f24a New translations elevation.mdx (French) 2026-03-27 18:46:07 +01:00
vcoppe
673df15b3e New translations elevation.mdx (Romanian) 2026-03-27 18:46:06 +01:00
vcoppe
2d8eb78d0c New translations map-controls.mdx (Serbian (Latin)) 2026-03-27 18:41:55 +01:00
vcoppe
8b3eaa0e87 New translations map-controls.mdx (Chinese Traditional, Hong Kong) 2026-03-27 18:41:54 +01:00
vcoppe
aef0b50550 New translations map-controls.mdx (Latvian) 2026-03-27 18:41:53 +01:00
vcoppe
65cacbfc5b New translations map-controls.mdx (Thai) 2026-03-27 18:41:51 +01:00
vcoppe
491d187e5f New translations map-controls.mdx (Indonesian) 2026-03-27 18:41:49 +01:00
vcoppe
1a8d30bc5a New translations map-controls.mdx (Portuguese, Brazilian) 2026-03-27 18:41:48 +01:00
vcoppe
c28322b8ee New translations map-controls.mdx (Vietnamese) 2026-03-27 18:41:46 +01:00
vcoppe
34bbae120c New translations map-controls.mdx (Chinese Simplified) 2026-03-27 18:41:45 +01:00
vcoppe
7553d1a33f New translations integration.mdx (Serbian (Latin)) 2026-03-27 18:41:24 +01:00
vcoppe
ef99717afa New translations integration.mdx (Chinese Traditional, Hong Kong) 2026-03-27 18:41:22 +01:00
vcoppe
2c83be9a38 New translations integration.mdx (Latvian) 2026-03-27 18:41:21 +01:00
vcoppe
07517ae60a New translations integration.mdx (Thai) 2026-03-27 18:41:19 +01:00
vcoppe
64005e3ee6 New translations integration.mdx (Indonesian) 2026-03-27 18:41:18 +01:00
vcoppe
609cb01b6a New translations integration.mdx (Portuguese, Brazilian) 2026-03-27 18:41:17 +01:00
vcoppe
04457f7e0a New translations integration.mdx (Vietnamese) 2026-03-27 18:41:15 +01:00
vcoppe
d33651ea67 New translations integration.mdx (Chinese Simplified) 2026-03-27 18:41:13 +01:00
vcoppe
658bd30f2f New translations map-controls.mdx (Ukrainian) 2026-03-27 18:41:11 +01:00
vcoppe
bd4fd0153a New translations map-controls.mdx (Turkish) 2026-03-27 18:41:10 +01:00
vcoppe
fac3d2ac78 New translations map-controls.mdx (Swedish) 2026-03-27 18:41:08 +01:00
vcoppe
91f984bc41 New translations map-controls.mdx (Russian) 2026-03-27 18:41:06 +01:00
vcoppe
c2f3aa1c69 New translations map-controls.mdx (Portuguese) 2026-03-27 18:41:05 +01:00
vcoppe
962dbba5da New translations map-controls.mdx (Polish) 2026-03-27 18:41:04 +01:00
vcoppe
0ca058b796 New translations map-controls.mdx (Norwegian) 2026-03-27 18:41:03 +01:00
vcoppe
97db24eb92 New translations map-controls.mdx (Dutch) 2026-03-27 18:41:01 +01:00
vcoppe
e1c7e54e0e New translations map-controls.mdx (Lithuanian) 2026-03-27 18:41:00 +01:00
vcoppe
0e9bc1f9ed New translations map-controls.mdx (Korean) 2026-03-27 18:40:59 +01:00
vcoppe
659b854988 New translations map-controls.mdx (Italian) 2026-03-27 18:40:58 +01:00
vcoppe
247db8c9a2 New translations map-controls.mdx (Hungarian) 2026-03-27 18:40:56 +01:00
vcoppe
7d4e1251f7 New translations map-controls.mdx (Hebrew) 2026-03-27 18:40:55 +01:00
vcoppe
88be2b5b6a New translations map-controls.mdx (Finnish) 2026-03-27 18:40:54 +01:00
vcoppe
d730e8f12e New translations map-controls.mdx (Basque) 2026-03-27 18:40:53 +01:00
vcoppe
1315e43104 New translations map-controls.mdx (Greek) 2026-03-27 18:40:52 +01:00
vcoppe
67d02464ae New translations integration.mdx (Ukrainian) 2026-03-27 18:40:50 +01:00
vcoppe
4889d73d81 New translations integration.mdx (Turkish) 2026-03-27 18:40:49 +01:00
vcoppe
ab68fb387c New translations integration.mdx (Swedish) 2026-03-27 18:40:48 +01:00
vcoppe
5d5feabe12 New translations integration.mdx (Russian) 2026-03-27 18:40:47 +01:00
vcoppe
fd7d7a1812 New translations integration.mdx (Portuguese) 2026-03-27 18:40:46 +01:00
vcoppe
74185f44ef New translations integration.mdx (Polish) 2026-03-27 18:40:45 +01:00
vcoppe
c4f3ade767 New translations integration.mdx (Norwegian) 2026-03-27 18:40:43 +01:00
vcoppe
70a68ac3bc New translations integration.mdx (Dutch) 2026-03-27 18:40:42 +01:00
vcoppe
58f2fc472c New translations integration.mdx (Lithuanian) 2026-03-27 18:40:41 +01:00
vcoppe
d498b5590a New translations integration.mdx (Korean) 2026-03-27 18:40:39 +01:00
vcoppe
586f03abcf New translations integration.mdx (Italian) 2026-03-27 18:40:38 +01:00
vcoppe
c7fc0d0b1c New translations integration.mdx (Hungarian) 2026-03-27 18:40:37 +01:00
vcoppe
62a795dcaa New translations integration.mdx (Hebrew) 2026-03-27 18:40:36 +01:00
vcoppe
f318532538 New translations integration.mdx (Finnish) 2026-03-27 18:40:34 +01:00
vcoppe
366fadc1c6 New translations integration.mdx (Basque) 2026-03-27 18:40:33 +01:00
vcoppe
062e86b62d New translations integration.mdx (Greek) 2026-03-27 18:40:32 +01:00
vcoppe
fdd29d9708 New translations map-controls.mdx (German) 2026-03-27 18:40:31 +01:00
vcoppe
5e22a05b2e New translations map-controls.mdx (Danish) 2026-03-27 18:40:30 +01:00
vcoppe
81342950a5 New translations map-controls.mdx (Czech) 2026-03-27 18:40:29 +01:00
vcoppe
1dbdfcfe9c New translations map-controls.mdx (Catalan) 2026-03-27 18:40:28 +01:00
vcoppe
bb259cbe1c New translations map-controls.mdx (Belarusian) 2026-03-27 18:40:26 +01:00
vcoppe
4fa60ac46f New translations map-controls.mdx (Spanish) 2026-03-27 18:40:25 +01:00
vcoppe
be0d8cb59d New translations map-controls.mdx (French) 2026-03-27 18:40:24 +01:00
vcoppe
71b4e93bee New translations map-controls.mdx (Romanian) 2026-03-27 18:40:23 +01:00
vcoppe
86b70f0f19 New translations integration.mdx (German) 2026-03-27 18:40:22 +01:00
vcoppe
e83a296889 New translations integration.mdx (Danish) 2026-03-27 18:40:21 +01:00
vcoppe
f6694755a0 New translations funding.mdx (Serbian (Latin)) 2026-03-27 18:40:15 +01:00
vcoppe
ca9a43917f New translations funding.mdx (Chinese Traditional, Hong Kong) 2026-03-27 18:40:14 +01:00
vcoppe
2a4ea390ab New translations funding.mdx (Latvian) 2026-03-27 18:40:13 +01:00
vcoppe
b474298320 New translations funding.mdx (Thai) 2026-03-27 18:40:11 +01:00
vcoppe
25a5872ff7 New translations funding.mdx (Indonesian) 2026-03-27 18:40:10 +01:00
vcoppe
745477187d New translations funding.mdx (Portuguese, Brazilian) 2026-03-27 18:40:09 +01:00
vcoppe
d232a3ad19 New translations funding.mdx (Vietnamese) 2026-03-27 18:40:08 +01:00
vcoppe
563b21fffe New translations funding.mdx (Chinese Simplified) 2026-03-27 18:39:58 +01:00
vcoppe
802ab68280 New translations funding.mdx (Turkish) 2026-03-27 18:39:57 +01:00
vcoppe
b0d4078085 New translations funding.mdx (Swedish) 2026-03-27 18:39:56 +01:00
vcoppe
7fdc2d2bf0 New translations funding.mdx (Russian) 2026-03-27 18:39:55 +01:00
vcoppe
39380f3764 New translations funding.mdx (Portuguese) 2026-03-27 18:39:54 +01:00
vcoppe
c8a10f6fde New translations funding.mdx (Polish) 2026-03-27 18:39:52 +01:00
vcoppe
4471ef605b New translations integration.mdx (Czech) 2026-03-27 18:39:51 +01:00
vcoppe
e60a7d1f3a New translations integration.mdx (Catalan) 2026-03-27 18:39:50 +01:00
vcoppe
e5fdf7ed4a New translations integration.mdx (Belarusian) 2026-03-27 18:39:49 +01:00
vcoppe
2d640df936 New translations integration.mdx (Spanish) 2026-03-27 18:39:48 +01:00
vcoppe
029437ba01 New translations integration.mdx (French) 2026-03-27 18:39:46 +01:00
vcoppe
9c59e0b536 New translations integration.mdx (Romanian) 2026-03-27 18:39:45 +01:00
vcoppe
1e9fa89911 New translations funding.mdx (Norwegian) 2026-03-27 18:39:36 +01:00
vcoppe
b2d0fc17a1 New translations funding.mdx (Dutch) 2026-03-27 18:39:35 +01:00
vcoppe
67af6325c2 New translations funding.mdx (Lithuanian) 2026-03-27 18:39:34 +01:00
vcoppe
5ff9d6a21e New translations funding.mdx (Korean) 2026-03-27 18:39:32 +01:00
vcoppe
8fc8627998 New translations funding.mdx (Italian) 2026-03-27 18:39:31 +01:00
vcoppe
02044a40c6 New translations funding.mdx (Hungarian) 2026-03-27 18:39:30 +01:00
vcoppe
bf4fd993ec New translations funding.mdx (Hebrew) 2026-03-27 18:39:29 +01:00
vcoppe
13c75a4d1e New translations funding.mdx (Finnish) 2026-03-27 18:39:27 +01:00
vcoppe
1932f03e23 New translations funding.mdx (Basque) 2026-03-27 18:39:26 +01:00
vcoppe
bd1d5a8e3b New translations funding.mdx (Greek) 2026-03-27 18:39:25 +01:00
vcoppe
d8d189ee51 New translations funding.mdx (German) 2026-03-27 18:39:14 +01:00
vcoppe
7d30551e5a New translations funding.mdx (Danish) 2026-03-27 18:39:13 +01:00
vcoppe
0efbcc5b58 New translations funding.mdx (Czech) 2026-03-27 18:39:12 +01:00
vcoppe
d658ca4bff New translations funding.mdx (Catalan) 2026-03-27 18:39:11 +01:00
vcoppe
445b8fe09e New translations funding.mdx (Belarusian) 2026-03-27 18:39:10 +01:00
vcoppe
3796418070 New translations funding.mdx (Spanish) 2026-03-27 18:39:09 +01:00
vcoppe
abe5e1bff0 New translations funding.mdx (French) 2026-03-27 18:39:08 +01:00
vcoppe
1c301be470 New translations funding.mdx (Romanian) 2026-03-27 18:39:06 +01:00
vcoppe
bd6aa43928 New translations files-and-stats.mdx (Serbian (Latin)) 2026-03-27 18:38:56 +01:00
vcoppe
346a22d7a5 New translations files-and-stats.mdx (Chinese Traditional, Hong Kong) 2026-03-27 18:38:55 +01:00
vcoppe
a1ce711d50 New translations files-and-stats.mdx (Latvian) 2026-03-27 18:38:54 +01:00
vcoppe
81dc9c6c11 New translations files-and-stats.mdx (Thai) 2026-03-27 18:38:53 +01:00
vcoppe
802573cb25 New translations files-and-stats.mdx (Indonesian) 2026-03-27 18:38:52 +01:00
vcoppe
dae0f39496 New translations files-and-stats.mdx (Portuguese, Brazilian) 2026-03-27 18:38:51 +01:00
vcoppe
ec59423ad1 New translations files-and-stats.mdx (Vietnamese) 2026-03-27 18:38:49 +01:00
vcoppe
e671ee1867 New translations files-and-stats.mdx (Chinese Simplified) 2026-03-27 18:38:39 +01:00
vcoppe
6682ba9fff New translations files-and-stats.mdx (Ukrainian) 2026-03-27 18:38:38 +01:00
vcoppe
37993d92ed New translations files-and-stats.mdx (Turkish) 2026-03-27 18:38:36 +01:00
vcoppe
3c2c79c007 New translations files-and-stats.mdx (Swedish) 2026-03-27 18:38:35 +01:00
vcoppe
7fe2e71be8 New translations files-and-stats.mdx (Russian) 2026-03-27 18:38:34 +01:00
vcoppe
7378676449 New translations files-and-stats.mdx (Portuguese) 2026-03-27 18:38:33 +01:00
vcoppe
cae88d5ed8 New translations files-and-stats.mdx (Polish) 2026-03-27 18:38:32 +01:00
vcoppe
9d38fe59aa New translations files-and-stats.mdx (Norwegian) 2026-03-27 18:38:31 +01:00
vcoppe
6d6215efee New translations files-and-stats.mdx (Dutch) 2026-03-27 18:38:29 +01:00
vcoppe
d75866b83e New translations files-and-stats.mdx (Lithuanian) 2026-03-27 18:38:28 +01:00
vcoppe
72595cf63a New translations files-and-stats.mdx (Korean) 2026-03-27 18:38:27 +01:00
vcoppe
ed5d4e0aa0 New translations en.json (Serbian (Latin)) 2026-03-27 18:38:21 +01:00
vcoppe
eb006b166d New translations en.json (Chinese Traditional, Hong Kong) 2026-03-27 18:38:20 +01:00
vcoppe
5019a7bd29 New translations en.json (Latvian) 2026-03-27 18:38:19 +01:00
vcoppe
6b4cd246a6 New translations en.json (Thai) 2026-03-27 18:38:17 +01:00
vcoppe
0a6d649cd8 New translations en.json (Indonesian) 2026-03-27 18:38:16 +01:00
vcoppe
61176b5ec0 New translations en.json (Portuguese, Brazilian) 2026-03-27 18:38:15 +01:00
vcoppe
61956549d3 New translations en.json (Vietnamese) 2026-03-27 18:38:14 +01:00
vcoppe
7064465871 New translations files-and-stats.mdx (Italian) 2026-03-27 18:38:13 +01:00
vcoppe
702a80038e New translations files-and-stats.mdx (Hungarian) 2026-03-27 18:38:11 +01:00
vcoppe
5f4bd199d6 New translations files-and-stats.mdx (Hebrew) 2026-03-27 18:38:10 +01:00
vcoppe
4141c947b2 New translations files-and-stats.mdx (Finnish) 2026-03-27 18:38:09 +01:00
vcoppe
48b3bdf2c6 New translations files-and-stats.mdx (Basque) 2026-03-27 18:38:08 +01:00
vcoppe
794f30be44 New translations files-and-stats.mdx (Greek) 2026-03-27 18:38:06 +01:00
vcoppe
23deb5ff97 New translations en.json (Chinese Simplified) 2026-03-27 18:38:04 +01:00
vcoppe
e5fb6899ad New translations en.json (Ukrainian) 2026-03-27 18:38:02 +01:00
vcoppe
40c77643fe New translations en.json (Turkish) 2026-03-27 18:38:01 +01:00
vcoppe
8aba4c2ede New translations en.json (Swedish) 2026-03-27 18:37:59 +01:00
vcoppe
d993a70ad3 New translations en.json (Russian) 2026-03-27 18:37:58 +01:00
vcoppe
51018469be New translations en.json (Portuguese) 2026-03-27 18:37:57 +01:00
vcoppe
4c29ef6f39 New translations en.json (Polish) 2026-03-27 18:37:56 +01:00
vcoppe
a569ca377f New translations en.json (Norwegian) 2026-03-27 18:37:55 +01:00
vcoppe
28c2a0e821 New translations en.json (Lithuanian) 2026-03-27 18:37:53 +01:00
vcoppe
39ab789a45 New translations en.json (Korean) 2026-03-27 18:37:52 +01:00
vcoppe
afba797e72 New translations en.json (Italian) 2026-03-27 18:37:50 +01:00
vcoppe
a4efa994be New translations en.json (Hungarian) 2026-03-27 18:37:48 +01:00
vcoppe
d3b24747d3 New translations en.json (Hebrew) 2026-03-27 18:37:46 +01:00
vcoppe
a079ca740c New translations en.json (Finnish) 2026-03-27 18:37:45 +01:00
vcoppe
dec79f17f4 New translations en.json (Greek) 2026-03-27 18:37:43 +01:00
vcoppe
99ab420b46 New translations funding.mdx (Ukrainian) 2026-03-27 18:37:41 +01:00
vcoppe
b0ad085342 New translations en.json (Dutch) 2026-03-27 18:37:40 +01:00
vcoppe
12b67c017b New translations en.json (Basque) 2026-03-27 18:37:39 +01:00
vcoppe
ad4a029679 New translations files-and-stats.mdx (German) 2026-03-27 18:37:38 +01:00
vcoppe
98cf88864f New translations files-and-stats.mdx (Danish) 2026-03-27 18:37:36 +01:00
vcoppe
bd2634efe5 New translations files-and-stats.mdx (Czech) 2026-03-27 18:37:35 +01:00
vcoppe
8a6b7e6974 New translations files-and-stats.mdx (Catalan) 2026-03-27 18:37:33 +01:00
vcoppe
9dd52551ee New translations files-and-stats.mdx (Belarusian) 2026-03-27 18:37:31 +01:00
vcoppe
f8722c15e5 New translations files-and-stats.mdx (Spanish) 2026-03-27 18:37:30 +01:00
vcoppe
9a8c15b810 New translations files-and-stats.mdx (French) 2026-03-27 18:37:29 +01:00
vcoppe
a953e7f7ff New translations files-and-stats.mdx (Romanian) 2026-03-27 18:37:28 +01:00
vcoppe
e384fb0722 New translations en.json (German) 2026-03-27 18:37:26 +01:00
vcoppe
cc8f2e9bd8 New translations en.json (Danish) 2026-03-27 18:37:25 +01:00
vcoppe
793e55be7a New translations en.json (Czech) 2026-03-27 18:37:23 +01:00
vcoppe
7e8ea121b6 New translations en.json (Belarusian) 2026-03-27 18:37:21 +01:00
vcoppe
337e81be9f New translations en.json (Spanish) 2026-03-27 18:37:20 +01:00
vcoppe
9af1ec0459 New translations en.json (French) 2026-03-27 18:37:18 +01:00
vcoppe
368a45726e New translations en.json (Romanian) 2026-03-27 18:37:17 +01:00
vcoppe
2513753311 New translations en.json (Catalan) 2026-03-27 18:37:16 +01:00
vcoppe
40f2400057 New translations clean.mdx (Greek) 2026-03-27 16:07:17 +01:00
vcoppe
8da3e6f65d New translations settings.mdx (Greek) 2026-03-27 16:07:14 +01:00
vcoppe
958ecab6fe New translations translation.mdx (Greek) 2026-03-27 16:07:13 +01:00
vcoppe
254ec37b24 New translations en.json (Chinese Simplified) 2026-03-27 09:31:56 +01:00
vcoppe
2b7e297010 New translations en.json (Spanish) 2026-03-26 09:04:20 +01:00
vcoppe
672d68f88b New translations en.json (Dutch) 2026-03-26 04:01:21 +01:00
vcoppe
739dec1c9e New translations en.json (Czech) 2026-03-25 23:06:47 +01:00
vcoppe
d2c0f674c1 New translations en.json (Serbian (Latin)) 2026-03-25 21:56:16 +01:00
vcoppe
f189d9c525 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-25 21:56:15 +01:00
vcoppe
7f0cd488d2 New translations en.json (Latvian) 2026-03-25 21:56:14 +01:00
vcoppe
4b36866a03 New translations en.json (Thai) 2026-03-25 21:56:13 +01:00
vcoppe
645c616151 New translations en.json (Indonesian) 2026-03-25 21:56:11 +01:00
vcoppe
6bff0c9b31 New translations en.json (Portuguese, Brazilian) 2026-03-25 21:56:10 +01:00
vcoppe
1f88cfed0d New translations en.json (Vietnamese) 2026-03-25 21:56:09 +01:00
vcoppe
4b8545c9d3 New translations en.json (Chinese Simplified) 2026-03-25 21:56:08 +01:00
vcoppe
51c45e95a5 New translations en.json (Ukrainian) 2026-03-25 21:56:07 +01:00
vcoppe
17a7538391 New translations en.json (Turkish) 2026-03-25 21:56:06 +01:00
vcoppe
08bd6de1cb New translations en.json (Swedish) 2026-03-25 21:56:04 +01:00
vcoppe
c5dddcf711 New translations en.json (Russian) 2026-03-25 21:56:03 +01:00
vcoppe
116e74185f New translations en.json (Portuguese) 2026-03-25 21:56:02 +01:00
vcoppe
862e20f553 New translations en.json (Polish) 2026-03-25 21:56:01 +01:00
vcoppe
5c90fbb35b New translations en.json (Norwegian) 2026-03-25 21:55:59 +01:00
vcoppe
08314adb51 New translations en.json (Lithuanian) 2026-03-25 21:55:58 +01:00
vcoppe
8706e5dbc5 New translations en.json (Dutch) 2026-03-25 21:55:55 +01:00
vcoppe
ac25896e8f New translations en.json (Korean) 2026-03-25 21:55:44 +01:00
vcoppe
8db9cb68d3 New translations en.json (Italian) 2026-03-25 21:55:43 +01:00
vcoppe
3be79c3cb0 New translations en.json (Hungarian) 2026-03-25 21:55:41 +01:00
vcoppe
730e8a9c8a New translations en.json (Hebrew) 2026-03-25 21:55:38 +01:00
vcoppe
9910c3d853 New translations en.json (Finnish) 2026-03-25 21:55:37 +01:00
vcoppe
2fb09d5341 New translations en.json (Greek) 2026-03-25 21:55:35 +01:00
vcoppe
6776f6fa0d New translations en.json (German) 2026-03-25 21:55:33 +01:00
vcoppe
5a4afb9432 New translations en.json (Danish) 2026-03-25 21:55:31 +01:00
vcoppe
6721552564 New translations en.json (Czech) 2026-03-25 21:55:28 +01:00
vcoppe
1bf0fbf5a7 New translations en.json (Belarusian) 2026-03-25 21:55:25 +01:00
vcoppe
1093385b21 New translations en.json (Spanish) 2026-03-25 21:55:22 +01:00
vcoppe
555e9a00df New translations en.json (French) 2026-03-25 21:55:18 +01:00
vcoppe
752afedee1 New translations en.json (Romanian) 2026-03-25 21:55:14 +01:00
vcoppe
e0a0e96ccd New translations en.json (Catalan) 2026-03-25 21:55:07 +01:00
vcoppe
a3fe492b89 New translations en.json (Basque) 2026-03-25 21:55:02 +01:00
vcoppe
738dc377b3 New translations file.mdx (German) 2026-03-23 14:39:20 +01:00
vcoppe
1040c10eaf New translations en.json (Spanish) 2026-03-23 11:28:14 +01:00
vcoppe
eab58b9e14 New translations elevation.mdx (Polish) 2026-03-22 14:03:42 +01:00
vcoppe
d528120181 New translations view.mdx (Polish) 2026-03-22 14:03:41 +01:00
vcoppe
ca2be90b1e New translations map-controls.mdx (Polish) 2026-03-22 14:03:40 +01:00
vcoppe
449a230b2a New translations elevation.mdx (Polish) 2026-03-22 12:31:26 +01:00
vcoppe
3878a57e37 New translations en.json (Italian) 2026-03-20 17:53:13 +01:00
vcoppe
6d52ee2cc8 New translations faq.mdx (Polish) 2026-03-19 23:52:04 +01:00
vcoppe
a7bc9186a2 New translations time.mdx (Polish) 2026-03-19 23:52:03 +01:00
vcoppe
a0388bf5c9 New translations scissors.mdx (Polish) 2026-03-19 23:52:02 +01:00
vcoppe
2f4831397d New translations routing.mdx (Polish) 2026-03-19 23:52:01 +01:00
vcoppe
744fa0234a New translations view.mdx (Polish) 2026-03-19 23:52:00 +01:00
vcoppe
235b75a6b7 New translations settings.mdx (Polish) 2026-03-19 23:51:59 +01:00
vcoppe
ee3ecc1864 New translations file.mdx (Polish) 2026-03-19 23:51:58 +01:00
vcoppe
efc3a373f3 New translations map-controls.mdx (Polish) 2026-03-19 23:51:57 +01:00
vcoppe
14b10065ab New translations getting-started.mdx (Polish) 2026-03-19 23:51:55 +01:00
vcoppe
3b906bddf3 New translations file.mdx (Polish) 2026-03-19 22:44:25 +01:00
vcoppe
e77c7fb41f New translations translation.mdx (Polish) 2026-03-19 22:44:24 +01:00
vcoppe
0c6ccef007 New translations funding.mdx (Polish) 2026-03-19 22:44:22 +01:00
vcoppe
998d20f3e9 New translations en.json (Polish) 2026-03-19 22:44:21 +01:00
vcoppe
a918f24352 New translations translation.mdx (Polish) 2026-03-19 21:38:18 +01:00
vcoppe
64139fb355 New translations en.json (Chinese Simplified) 2026-03-19 06:56:31 +01:00
vcoppe
a0bfc17723 New translations en.json (Dutch) 2026-03-18 23:41:40 +01:00
vcoppe
c9b26dbc1c New translations en.json (French) 2026-03-18 19:59:04 +01:00
vcoppe
92e658376d New translations en.json (Serbian (Latin)) 2026-03-18 18:42:14 +01:00
vcoppe
55b7f17cd4 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-18 18:42:12 +01:00
vcoppe
fc030ecd4d New translations en.json (Latvian) 2026-03-18 18:42:10 +01:00
vcoppe
a76f6f0e0a New translations en.json (Thai) 2026-03-18 18:42:08 +01:00
vcoppe
03b20ea067 New translations en.json (Indonesian) 2026-03-18 18:42:06 +01:00
vcoppe
259f0d66c7 New translations en.json (Portuguese, Brazilian) 2026-03-18 18:42:04 +01:00
vcoppe
30f272c404 New translations en.json (Vietnamese) 2026-03-18 18:42:03 +01:00
vcoppe
7a02f1d5b1 New translations en.json (Chinese Simplified) 2026-03-18 18:42:02 +01:00
vcoppe
c421c2a404 New translations en.json (Ukrainian) 2026-03-18 18:42:01 +01:00
vcoppe
2e58d270b9 New translations en.json (Turkish) 2026-03-18 18:41:59 +01:00
vcoppe
d087fed76b New translations en.json (Swedish) 2026-03-18 18:41:58 +01:00
vcoppe
ad0efd3372 New translations en.json (Russian) 2026-03-18 18:41:57 +01:00
vcoppe
1414f0a7f0 New translations en.json (Portuguese) 2026-03-18 18:41:56 +01:00
vcoppe
19b0b33944 New translations en.json (Polish) 2026-03-18 18:41:54 +01:00
vcoppe
d0c3bfb3d3 New translations en.json (Norwegian) 2026-03-18 18:41:53 +01:00
vcoppe
b8a17c8ffe New translations en.json (Lithuanian) 2026-03-18 18:41:52 +01:00
vcoppe
e69c03f6fb New translations en.json (Korean) 2026-03-18 18:41:51 +01:00
vcoppe
aafe7df561 New translations en.json (Italian) 2026-03-18 18:41:50 +01:00
vcoppe
818e07df93 New translations en.json (Hungarian) 2026-03-18 18:41:48 +01:00
vcoppe
23b19e0367 New translations en.json (Hebrew) 2026-03-18 18:41:47 +01:00
vcoppe
6e23b01434 New translations en.json (Finnish) 2026-03-18 18:41:46 +01:00
vcoppe
1c5180aca7 New translations en.json (Greek) 2026-03-18 18:41:44 +01:00
vcoppe
b968f8d28f New translations en.json (German) 2026-03-18 18:41:42 +01:00
vcoppe
b1f1adcc9d New translations en.json (Danish) 2026-03-18 18:41:40 +01:00
vcoppe
49277340ed New translations en.json (Czech) 2026-03-18 18:41:37 +01:00
vcoppe
6d489e279c New translations en.json (Belarusian) 2026-03-18 18:41:36 +01:00
vcoppe
a2eb7ae9c3 New translations en.json (Spanish) 2026-03-18 18:41:33 +01:00
vcoppe
892074fd1b New translations en.json (French) 2026-03-18 18:41:31 +01:00
vcoppe
11eec6cf15 New translations en.json (Romanian) 2026-03-18 18:41:30 +01:00
vcoppe
f17d412a22 New translations en.json (Catalan) 2026-03-18 18:41:27 +01:00
vcoppe
892db21e8f New translations en.json (Dutch) 2026-03-18 18:41:25 +01:00
vcoppe
e4ba56ff0f New translations en.json (Basque) 2026-03-18 18:41:21 +01:00
vcoppe
edfe28d61f New translations en.json (Serbian (Latin)) 2026-03-18 18:32:23 +01:00
vcoppe
188197ab15 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-18 18:32:22 +01:00
vcoppe
3429af3f33 New translations en.json (Latvian) 2026-03-18 18:32:20 +01:00
vcoppe
232f13d2e0 New translations en.json (Thai) 2026-03-18 18:32:19 +01:00
vcoppe
0eb2d7543e New translations en.json (Indonesian) 2026-03-18 18:32:18 +01:00
vcoppe
c2caa68268 New translations en.json (Portuguese, Brazilian) 2026-03-18 18:32:16 +01:00
vcoppe
0033e16d4a New translations en.json (Vietnamese) 2026-03-18 18:32:15 +01:00
vcoppe
34ccb86f8a New translations en.json (Chinese Simplified) 2026-03-18 18:32:12 +01:00
vcoppe
3800fab9a8 New translations en.json (Ukrainian) 2026-03-18 18:32:05 +01:00
vcoppe
0216e27a22 New translations en.json (Turkish) 2026-03-18 18:32:03 +01:00
vcoppe
1325f58cab New translations en.json (Swedish) 2026-03-18 18:32:01 +01:00
vcoppe
7e50594ada New translations en.json (Russian) 2026-03-18 18:32:00 +01:00
vcoppe
3fabc250e9 New translations en.json (Portuguese) 2026-03-18 18:31:59 +01:00
vcoppe
ec4d4e0cb5 New translations en.json (Polish) 2026-03-18 18:31:58 +01:00
vcoppe
cc88ea5cdf New translations en.json (Norwegian) 2026-03-18 18:31:56 +01:00
vcoppe
4452f2fc75 New translations en.json (Lithuanian) 2026-03-18 18:31:55 +01:00
vcoppe
3cefe39cd7 New translations en.json (Korean) 2026-03-18 18:31:54 +01:00
vcoppe
a5fcc95299 New translations en.json (Italian) 2026-03-18 18:31:53 +01:00
vcoppe
a5d5c85fdd New translations en.json (Hungarian) 2026-03-18 18:31:51 +01:00
vcoppe
36c3d7dd9e New translations en.json (Hebrew) 2026-03-18 18:31:50 +01:00
vcoppe
9022974cf4 New translations en.json (Finnish) 2026-03-18 18:31:48 +01:00
vcoppe
3744fac4ad New translations en.json (Greek) 2026-03-18 18:31:47 +01:00
vcoppe
ee208c0191 New translations en.json (German) 2026-03-18 18:31:46 +01:00
vcoppe
6562ef643b New translations en.json (Danish) 2026-03-18 18:31:41 +01:00
vcoppe
3ca4845d34 New translations en.json (Czech) 2026-03-18 18:31:36 +01:00
vcoppe
dabb014689 New translations en.json (Belarusian) 2026-03-18 18:31:32 +01:00
vcoppe
6a233fd695 New translations en.json (Spanish) 2026-03-18 18:31:29 +01:00
vcoppe
d2ce6d0297 New translations en.json (French) 2026-03-18 18:31:27 +01:00
vcoppe
203d9de289 New translations en.json (Romanian) 2026-03-18 18:31:23 +01:00
vcoppe
a624144e66 New translations en.json (Catalan) 2026-03-18 18:31:16 +01:00
vcoppe
6a58e5044e New translations en.json (Dutch) 2026-03-18 18:31:13 +01:00
vcoppe
54779beeff New translations en.json (Basque) 2026-03-18 18:31:08 +01:00
vcoppe
9d5fc48286 New translations file.mdx (Ukrainian) 2026-03-11 19:04:38 +01:00
vcoppe
fa0339ed9f New translations translation.mdx (Ukrainian) 2026-03-11 17:56:19 +01:00
vcoppe
f6a89784b8 New translations funding.mdx (Ukrainian) 2026-03-11 17:56:18 +01:00
vcoppe
6301df55d5 New translations file.mdx (Catalan) 2026-03-10 09:10:17 +01:00
vcoppe
378f66de7a New translations en.json (Catalan) 2026-03-10 09:10:16 +01:00
vcoppe
0c48b52b5b New translations en.json (Dutch) 2026-03-05 14:53:55 +01:00
vcoppe
3a1e81467f New translations en.json (Basque) 2026-03-04 10:42:12 +01:00
vcoppe
40422b9059 New translations files-and-stats.mdx (Portuguese, Brazilian) 2026-03-01 17:36:41 +01:00
vcoppe
767fdbd773 New translations file.mdx (Portuguese, Brazilian) 2026-03-01 16:34:19 +01:00
vcoppe
1473886f54 New translations file.mdx (French) 2026-02-26 09:27:01 +01:00
vcoppe
daeb3d4f57 New translations en.json (Indonesian) 2026-02-19 06:29:33 +01:00
vcoppe
65bad83635 New translations en.json (Norwegian) 2026-02-12 21:05:16 +01:00
vcoppe
c2ac4fb7d9 New translations en.json (Hungarian) 2026-02-08 09:31:02 +01:00
vcoppe
c52fa0001a New translations mapbox.mdx (German) 2026-02-02 18:59:24 +01:00
vcoppe
dfad2ef3ef New translations en.json (German) 2026-02-02 18:59:22 +01:00
vcoppe
9c6e03f4a8 improve layer stacking 2026-01-30 21:30:37 +01:00
vcoppe
2a4dfe010e improve color management 2026-01-30 21:17:59 +01:00
vcoppe
f42a916c25 remove unused parameter 2026-01-30 21:17:11 +01:00
vcoppe
772b810fa8 simplify initialization 2026-01-30 21:16:56 +01:00
vcoppe
4d4d10d5c2 small UI tweaks 2026-01-30 21:16:32 +01:00
vcoppe
0e4c7dbe64 New translations en.json (Chinese Simplified) (#306) 2026-01-30 21:02:21 +01:00
309 changed files with 4116 additions and 3553 deletions

View File

@@ -31,7 +31,7 @@ jobs:
- name: Create env file
run: |
touch website/.env
echo PUBLIC_MAPTILER_KEY=${{ secrets.PUBLIC_MAPTILER_KEY }} >> website/.env
echo PUBLIC_MAPBOX_TOKEN=${{ secrets.PUBLIC_MAPBOX_TOKEN }} >> website/.env
cat website/.env
- name: Build website

View File

@@ -42,11 +42,11 @@ npm run build
### Running the website
To be able to load the map, you will need to create your own <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> and store it in a `.env` file in the `website` directory.
To be able to load the map, you will need to create your own <a href="https://account.mapbox.com/auth/signup" target="_blank">Mapbox access token</a> and store it in a `.env` file in the `website` directory.
```bash
cd website
echo PUBLIC_MAPTILER_KEY={YOUR_MAPTILER_KEY} >> .env
echo PUBLIC_MAPBOX_TOKEN={YOUR_MAPBOX_TOKEN} >> .env
npm install
npm run dev
```
@@ -69,9 +69,9 @@ This project has been made possible thanks to the following open source projects
- [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) — fast GPX file parsing
- [SortableJS](https://github.com/SortableJS/Sortable) — creating a sortable file tree
- Mapping:
- [MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js) — beautiful and fast interactive maps
- [Mapbox GL JS](https://github.com/mapbox/mapbox-gl-js) — beautiful and fast interactive maps
- [brouter](https://github.com/abrensch/brouter) — routing engine
- [OpenStreetMap](https://www.openstreetmap.org) — map data used by most of the map layers, and by the routing engine
- [OpenStreetMap](https://www.openstreetmap.org) — map data used by Mapbox and brouter
- Search:
- [DocSearch](https://github.com/algolia/docsearch) — search engine for the documentation

View File

@@ -1 +1 @@
PUBLIC_MAPTILER_KEY=YOUR_MAPTILER_KEY
PUBLIC_MAPBOX_TOKEN=YOUR_MAPBOX_TOKEN

1222
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,9 +23,10 @@
"@types/eslint": "^9.6.1",
"@types/events": "^3.0.3",
"@types/file-saver": "^2.0.7",
"@types/mapbox__sphericalmercator": "^1.2.3",
"@types/mapbox__tilebelt": "^1.0.4",
"@types/mapbox-gl": "^3.4.1",
"@types/node": "^22.15.30",
"@types/png.js": "^0.2.3",
"@types/sanitize-html": "^2.16.0",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.1",
@@ -61,9 +62,10 @@
"dependencies": {
"@docsearch/js": "^3.9.0",
"@internationalized/date": "^3.8.2",
"@mapbox/mapbox-gl-geocoder": "^5.0.3",
"@mapbox/sphericalmercator": "^2.0.1",
"@mapbox/tilebelt": "^2.0.2",
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
"@types/mapbox__sphericalmercator": "^1.2.3",
"chart.js": "^4.5.1",
"chartjs-plugin-zoom": "^2.2.0",
"clsx": "^2.1.1",
@@ -72,8 +74,9 @@
"gpx": "file:../gpx",
"immer": "^10.1.1",
"jszip": "^3.10.1",
"mapbox-gl": "^3.17.0",
"mapillary-js": "^4.1.2",
"maplibre-gl": "^5.16.0",
"png.js": "^0.2.1",
"sanitize-html": "^2.17.0",
"sortablejs": "^1.15.6",
"tailwind-merge": "^3.3.0"

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 3.6 MiB

After

Width:  |  Height:  |  Size: 3.6 MiB

View File

@@ -22,18 +22,15 @@ import {
Binoculars,
Toilet,
} from 'lucide-static';
import { type RasterDEMSourceSpecification, type StyleSpecification } from 'maplibre-gl';
import { type RasterDEMSourceSpecification, type StyleSpecification } from 'mapbox-gl';
import ignFrTopo from './custom/ign-fr-topo.json';
import ignFrPlan from './custom/ign-fr-plan.json';
import ignFrSatellite from './custom/ign-fr-satellite.json';
import bikerouterGravel from './custom/bikerouter-gravel.json';
export const maptilerKeyPlaceHolder = 'MAPTILER_KEY';
export const basemaps: { [key: string]: string | StyleSpecification } = {
maptilerTopo: `https://api.maptiler.com/maps/topo-v4/style.json?key=${maptilerKeyPlaceHolder}`,
maptilerOutdoors: `https://api.maptiler.com/maps/outdoor-v4/style.json?key=${maptilerKeyPlaceHolder}`,
maptilerSatellite: `https://api.maptiler.com/maps/hybrid-v4/style.json?key=${maptilerKeyPlaceHolder}`,
mapboxOutdoors: 'mapbox://styles/mapbox/outdoors-v12',
mapboxSatellite: 'mapbox://styles/mapbox/satellite-streets-v12',
openStreetMap: {
version: 8,
sources: {
@@ -776,9 +773,8 @@ export type LayerTreeType = { [key: string]: LayerTreeType | boolean };
export const basemapTree: LayerTreeType = {
basemaps: {
world: {
maptilerTopo: true,
maptilerOutdoors: true,
maptilerSatellite: true,
mapboxOutdoors: true,
mapboxSatellite: true,
openStreetMap: true,
openTopoMap: true,
openHikingMap: true,
@@ -911,7 +907,7 @@ export const overpassTree: LayerTreeType = {
};
// Default basemap used
export const defaultBasemap = 'maptilerTopo';
export const defaultBasemap = 'mapboxOutdoors';
// Default overlays used (none)
export const defaultOverlays: LayerTreeType = {
@@ -1000,9 +996,8 @@ export const defaultOverpassQueries: LayerTreeType = {
export const defaultBasemapTree: LayerTreeType = {
basemaps: {
world: {
maptilerTopo: true,
maptilerOutdoors: true,
maptilerSatellite: true,
mapboxOutdoors: true,
mapboxSatellite: true,
openStreetMap: true,
openTopoMap: true,
openHikingMap: true,
@@ -1141,7 +1136,7 @@ export type CustomLayer = {
maxZoom: number;
layerType: 'basemap' | 'overlay';
resourceType: 'raster' | 'vector';
value: string | maplibregl.StyleSpecification;
value: string | {};
};
type OverpassQueryData = {
@@ -1460,9 +1455,11 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
};
export const terrainSources: { [key: string]: RasterDEMSourceSpecification } = {
'maptiler-dem': {
'mapbox-dem': {
type: 'raster-dem',
url: `https://api.maptiler.com/tiles/terrain-rgb-v2/tiles.json?key=${maptilerKeyPlaceHolder}`,
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
tileSize: 512,
maxzoom: 14,
},
mapterhorn: {
type: 'raster-dem',
@@ -1470,4 +1467,4 @@ export const terrainSources: { [key: string]: RasterDEMSourceSpecification } = {
},
};
export const defaultTerrainSource = 'maptiler-dem';
export const defaultTerrainSource = 'mapbox-dem';

View File

@@ -31,13 +31,13 @@
<Card.Root
class="h-full {orientation === 'vertical'
? 'min-w-40 sm:min-w-44'
: 'w-full h-10'} border-none shadow-none p-0 text-sm sm:text-base"
? 'min-w-40 sm:min-w-44 text-sm sm:text-base'
: 'w-full'} border-none shadow-none p-0"
>
<Card.Content
class="h-full flex {orientation === 'vertical'
? 'flex-col justify-center'
: 'flex-row w-full justify-evenly'} gap-4 p-0"
: 'flex-row w-full justify-between'} gap-4 p-0"
>
<Tooltip label={i18n._('quantities.distance')}>
<span class="flex flex-row items-center">

View File

@@ -8,7 +8,7 @@
...others
}: {
iconOnly?: boolean;
company?: 'gpx.studio' | 'maptiler' | 'github' | 'crowdin' | 'facebook' | 'reddit';
company?: 'gpx.studio' | 'mapbox' | 'github' | 'crowdin' | 'facebook' | 'reddit';
[key: string]: any;
} = $props();
</script>
@@ -19,10 +19,10 @@
alt="Logo of gpx.studio."
{...others}
/>
{:else if company === 'maptiler'}
{:else if company === 'mapbox'}
<img
src="{base}/maptiler-logo{mode.current === 'dark' ? '-dark' : ''}.svg"
alt="Logo of Maptiler."
src="{base}/mapbox-logo-{mode.current === 'dark' ? 'white' : 'black'}.svg"
alt="Logo of Mapbox."
{...others}
/>
{:else if company === 'github'}

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import maptilerTopoMap from '$lib/assets/img/home/maptiler-topo.png?enhanced';
import mapboxOutdoorsMap from '$lib/assets/img/home/mapbox-outdoors.png?enhanced';
import waymarkedMap from '$lib/assets/img/home/waymarked.png?enhanced';
</script>
<div class="relative h-80 aspect-square rounded-2xl shadow-xl overflow-clip">
<enhanced:img src={maptilerTopoMap} alt="MapTiler Topo map screenshot." class="absolute" />
<enhanced:img src={mapboxOutdoorsMap} alt="Mapbox Outdoors map screenshot." class="absolute" />
<enhanced:img
src={waymarkedMap}
alt="Waymarked Trails map screenshot."

View File

@@ -18,7 +18,7 @@
Construction,
} from '@lucide/svelte';
import type { Readable, Writable } from 'svelte/store';
import type { Coordinates, GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
import type { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
import { settings } from '$lib/logic/settings';
import { i18n } from '$lib/i18n.svelte';
import { ElevationProfile } from '$lib/components/elevation-profile/elevation-profile';
@@ -28,14 +28,12 @@
let {
gpxStatistics,
slicedGPXStatistics,
hoveredPoint,
additionalDatasets,
elevationFill,
showControls = true,
}: {
gpxStatistics: Readable<GPXStatisticsGroup>;
slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>;
hoveredPoint: Writable<Coordinates | null>;
additionalDatasets: Writable<string[]>;
elevationFill: Writable<'slope' | 'surface' | 'highway' | undefined>;
showControls?: boolean;
@@ -49,7 +47,6 @@
elevationProfile = new ElevationProfile(
gpxStatistics,
slicedGPXStatistics,
hoveredPoint,
additionalDatasets,
elevationFill,
canvas,
@@ -64,7 +61,7 @@
});
</script>
<div class="h-full grow min-w-0 min-h-0 relative">
<div class="h-full grow min-w-0 relative py-2">
<canvas bind:this={overlay} class="w-full h-full absolute pointer-events-none"></canvas>
<canvas bind:this={canvas} class="w-full h-full absolute"></canvas>
{#if showControls}

View File

@@ -20,8 +20,10 @@ import Chart, {
type ScriptableLineSegmentContext,
type TooltipItem,
} from 'chart.js/auto';
import mapboxgl from 'mapbox-gl';
import { get, type Readable, type Writable } from 'svelte/store';
import type { Coordinates, GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
import { map } from '$lib/components/map/map';
import type { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
import { mode } from 'mode-watcher';
import { getHighwayColor, getSlopeColor, getSurfaceColor } from '$lib/assets/colors';
@@ -40,7 +42,7 @@ interface ElevationProfilePoint {
length: number;
};
extensions: Record<string, any>;
coordinates: Coordinates;
coordinates: [number, number];
index: number;
}
@@ -48,19 +50,18 @@ export class ElevationProfile {
private _chart: Chart | null = null;
private _canvas: HTMLCanvasElement;
private _overlay: HTMLCanvasElement;
private _marker: mapboxgl.Marker | null = null;
private _dragging = false;
private _panning = false;
private _gpxStatistics: Readable<GPXStatisticsGroup>;
private _slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>;
private _hoveredPoint: Writable<Coordinates | null>;
private _additionalDatasets: Readable<string[]>;
private _elevationFill: Readable<'slope' | 'surface' | 'highway' | undefined>;
constructor(
gpxStatistics: Readable<GPXStatisticsGroup>,
slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>,
hoveredPoint: Writable<Coordinates | null>,
additionalDatasets: Readable<string[]>,
elevationFill: Readable<'slope' | 'surface' | 'highway' | undefined>,
canvas: HTMLCanvasElement,
@@ -68,12 +69,17 @@ export class ElevationProfile {
) {
this._gpxStatistics = gpxStatistics;
this._slicedGPXStatistics = slicedGPXStatistics;
this._hoveredPoint = hoveredPoint;
this._additionalDatasets = additionalDatasets;
this._elevationFill = elevationFill;
this._canvas = canvas;
this._overlay = overlay;
let element = document.createElement('div');
element.className = 'h-4 w-4 rounded-full bg-cyan-500 border-2 border-white';
this._marker = new mapboxgl.Marker({
element,
});
import('chartjs-plugin-zoom').then((module) => {
Chart.register(module.default);
this.initialize();
@@ -156,10 +162,14 @@ export class ElevationProfile {
label: (context: TooltipItem<'line'>) => {
let point = context.raw as ElevationProfilePoint;
if (context.datasetIndex === 0) {
const map_ = get(map);
if (map_ && this._marker) {
if (this._dragging) {
this._hoveredPoint.set(null);
this._marker.remove();
} else {
this._hoveredPoint.set(point.coordinates);
this._marker.setLngLat(point.coordinates);
this._marker.addTo(map_);
}
}
return `${i18n._('quantities.elevation')}: ${getElevationWithUnits(point.y, false)}`;
} else if (context.datasetIndex === 1) {
@@ -302,7 +312,10 @@ export class ElevationProfile {
events: ['mouseout'],
afterEvent: (chart: Chart, args: { event: ChartEvent }) => {
if (args.event.type === 'mouseout') {
this._hoveredPoint.set(null);
const map_ = get(map);
if (map_ && this._marker) {
this._marker.remove();
}
}
},
},
@@ -624,5 +637,8 @@ export class ElevationProfile {
this._chart.destroy();
this._chart = null;
}
if (this._marker) {
this._marker.remove();
}
}
}

View File

@@ -16,7 +16,7 @@
import { setMode } from 'mode-watcher';
import { settings } from '$lib/logic/settings';
import { fileStateCollection } from '$lib/logic/file-state';
import { gpxStatistics, hoveredPoint, slicedGPXStatistics } from '$lib/logic/statistics';
import { gpxStatistics, slicedGPXStatistics } from '$lib/logic/statistics';
import { loadFile } from '$lib/logic/file-actions';
import { selection } from '$lib/logic/selection';
import { untrack } from 'svelte';
@@ -102,7 +102,7 @@
<div class="grow relative">
<Map
class="h-full {$fileStateCollection.size > 1 ? 'horizontal' : ''}"
maptilerKey={options.key}
accessToken={options.token}
geocoder={false}
geolocate={true}
hash={useHash}
@@ -130,7 +130,6 @@
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
showControls={options.elevation.controls}

View File

@@ -22,7 +22,7 @@
getCleanedEmbeddingOptions,
getMergedEmbeddingOptions,
} from './embedding';
import { PUBLIC_MAPTILER_KEY } from '$env/static/public';
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
import Embedding from './Embedding.svelte';
import { onDestroy } from 'svelte';
import { base } from '$app/paths';
@@ -32,7 +32,7 @@
let options = $state(
getMergedEmbeddingOptions(
{
key: 'YOUR_MAPTILER_KEY',
token: 'YOUR_MAPBOX_TOKEN',
theme: mode.current,
},
defaultEmbeddingOptions
@@ -46,10 +46,10 @@
let iframeOptions = $derived(
getMergedEmbeddingOptions(
{
key:
options.key.length === 0 || options.key === 'YOUR_MAPTILER_KEY'
? PUBLIC_MAPTILER_KEY
: options.key,
token:
options.token.length === 0 || options.token === 'YOUR_MAPBOX_TOKEN'
? PUBLIC_MAPBOX_TOKEN
: options.token,
files: files.split(',').filter((url) => url.length > 0),
ids: driveIds.split(',').filter((id) => id.length > 0),
elevation: {
@@ -102,8 +102,8 @@
</Card.Header>
<Card.Content>
<fieldset class="flex flex-col gap-3">
<Label for="key">{i18n._('embedding.maptiler_key')}</Label>
<Input id="key" type="text" class="h-8" bind:value={options.key} />
<Label for="token">{i18n._('embedding.mapbox_token')}</Label>
<Input id="token" type="text" class="h-8" bind:value={options.token} />
<Label for="file_urls">{i18n._('embedding.file_urls')}</Label>
<Input id="file_urls" type="text" class="h-8" bind:value={files} />
<Label for="drive_ids">{i18n._('embedding.drive_ids')}</Label>

View File

@@ -1,8 +1,8 @@
import { PUBLIC_MAPTILER_KEY } from '$env/static/public';
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
import { basemaps } from '$lib/assets/layers';
export type EmbeddingOptions = {
key: string;
token: string;
files: string[];
ids: string[];
basemap: string;
@@ -26,10 +26,10 @@ export type EmbeddingOptions = {
};
export const defaultEmbeddingOptions = {
key: '',
token: '',
files: [],
ids: [],
basemap: 'maptilerTopo',
basemap: 'mapboxOutdoors',
elevation: {
show: true,
height: 170,
@@ -107,7 +107,7 @@ export function getURLForGoogleDriveFile(fileId: string): string {
export function convertOldEmbeddingOptions(options: URLSearchParams): any {
let newOptions: any = {
key: PUBLIC_MAPTILER_KEY,
token: PUBLIC_MAPBOX_TOKEN,
files: [],
ids: [],
};
@@ -123,7 +123,7 @@ export function convertOldEmbeddingOptions(options: URLSearchParams): any {
if (options.has('source')) {
let basemap = options.get('source')!;
if (basemap === 'satellite') {
newOptions.basemap = 'maptilerSatellite';
newOptions.basemap = 'mapboxSatellite';
} else if (basemap === 'otm') {
newOptions.basemap = 'openTopoMap';
} else if (basemap === 'ohm') {

View File

@@ -5,16 +5,6 @@
map.onLoad((map_) => {
map_.on('contextmenu', (e) => {
if (
map_.queryRenderedFeatures(e.point, {
layers: map_
.getLayersOrder()
.filter((layerId) => layerId.startsWith('routing-controls')),
}).length
) {
// Clicked on routing control, ignoring
return;
}
trackpointPopup?.setItem({
item: new TrackPoint({
attributes: {

View File

@@ -1,25 +1,30 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css';
import { Button } from '$lib/components/ui/button';
import { i18n } from '$lib/i18n.svelte';
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
import { page } from '$app/state';
import { map } from '$lib/components/map/map';
import { PUBLIC_MAPTILER_KEY } from '$env/static/public';
let {
maptilerKey = PUBLIC_MAPTILER_KEY,
accessToken = PUBLIC_MAPBOX_TOKEN,
geolocate = true,
geocoder = true,
hash = true,
class: className = '',
}: {
maptilerKey?: string;
accessToken?: string;
geolocate?: boolean;
geocoder?: boolean;
hash?: boolean;
class?: string;
} = $props();
mapboxgl.accessToken = accessToken;
let webgl2Supported = $state(true);
let embeddedApp = $state(false);
@@ -43,7 +48,7 @@
language = 'en';
}
map.init(maptilerKey, language, hash, geocoder, geolocate);
map.init(language, hash, geocoder, geolocate);
});
onDestroy(() => {
@@ -76,21 +81,21 @@
<style lang="postcss">
@reference "../../../app.css";
div :global(.maplibregl-map) {
div :global(.mapboxgl-map) {
@apply font-sans;
}
div :global(.maplibregl-ctrl-top-right > .maplibregl-ctrl) {
div :global(.mapboxgl-ctrl-top-right > .mapboxgl-ctrl) {
@apply shadow-md;
@apply bg-background;
@apply text-foreground;
}
div :global(.maplibregl-ctrl-icon) {
div :global(.mapboxgl-ctrl-icon) {
@apply dark:brightness-[4.7];
}
div :global(.maplibregl-ctrl-geocoder) {
div :global(.mapboxgl-ctrl-geocoder) {
@apply flex;
@apply flex-row;
@apply w-fit;
@@ -105,45 +110,36 @@
@apply text-foreground;
}
div :global(.maplibregl-ctrl-geocoder .suggestions > li > a) {
div :global(.mapboxgl-ctrl-geocoder .suggestions > li > a) {
@apply text-foreground;
@apply hover:text-accent-foreground;
@apply hover:bg-accent;
}
div :global(.maplibregl-ctrl-geocoder .suggestions > .active > a) {
div :global(.mapboxgl-ctrl-geocoder .suggestions > .active > a) {
@apply bg-background;
}
div :global(.maplibregl-ctrl-geocoder--button) {
div :global(.mapboxgl-ctrl-geocoder--button) {
@apply bg-transparent;
@apply hover:bg-transparent;
}
div :global(.maplibregl-ctrl-geocoder--icon) {
div :global(.mapboxgl-ctrl-geocoder--icon) {
@apply fill-foreground;
@apply hover:fill-accent-foreground;
}
div :global(.maplibregl-ctrl-geocoder--icon-search) {
div :global(.mapboxgl-ctrl-geocoder--icon-search) {
@apply relative;
@apply top-0;
@apply left-0;
@apply my-2;
@apply w-[29px];
}
div :global(.maplibregl-ctrl-geocoder--icon-loading) {
@apply -mt-1;
@apply mb-0;
}
div :global(.maplibregl-ctrl-geocoder--icon-close) {
@apply my-0;
}
div :global(.maplibregl-ctrl-geocoder--input) {
div :global(.mapboxgl-ctrl-geocoder--input) {
@apply relative;
@apply h-8;
@apply w-64;
@apply py-0;
@apply pl-2;
@@ -153,12 +149,12 @@
@apply text-foreground;
}
div :global(.maplibregl-ctrl-geocoder--collapsed .maplibregl-ctrl-geocoder--input) {
div :global(.mapboxgl-ctrl-geocoder--collapsed .mapboxgl-ctrl-geocoder--input) {
@apply w-0;
@apply p-0;
}
div :global(.maplibregl-ctrl-top-right) {
div :global(.mapboxgl-ctrl-top-right) {
@apply z-40;
@apply flex;
@apply flex-col;
@@ -167,76 +163,77 @@
@apply overflow-hidden;
}
.horizontal :global(.maplibregl-ctrl-bottom-left) {
.horizontal :global(.mapboxgl-ctrl-bottom-left) {
@apply bottom-[42px];
}
.horizontal :global(.maplibregl-ctrl-bottom-right) {
.horizontal :global(.mapboxgl-ctrl-bottom-right) {
@apply bottom-[42px];
}
div :global(.maplibregl-ctrl-attrib) {
div :global(.mapboxgl-ctrl-attrib) {
@apply dark:bg-transparent;
}
div :global(.maplibregl-compact-show.maplibregl-ctrl-attrib) {
div :global(.mapboxgl-compact-show.mapboxgl-ctrl-attrib) {
@apply dark:bg-background;
}
div :global(.maplibregl-ctrl-attrib-button) {
div :global(.mapboxgl-ctrl-attrib-button) {
@apply dark:bg-foreground;
}
div :global(.maplibregl-compact-show .maplibregl-ctrl-attrib-button) {
div :global(.mapboxgl-compact-show .mapboxgl-ctrl-attrib-button) {
@apply dark:bg-foreground;
}
div :global(.maplibregl-ctrl-attrib a) {
div :global(.mapboxgl-ctrl-attrib a) {
@apply text-foreground;
}
div :global(.maplibregl-popup) {
div :global(.mapboxgl-popup) {
@apply w-fit;
@apply z-50;
}
div :global(.maplibregl-popup-content) {
div :global(.mapboxgl-popup-content) {
@apply p-0;
@apply bg-transparent;
@apply shadow-none;
}
div :global(.maplibregl-popup-anchor-top .maplibregl-popup-tip) {
div :global(.mapboxgl-popup-anchor-top .mapboxgl-popup-tip) {
@apply border-b-background;
}
div :global(.maplibregl-popup-anchor-top-left .maplibregl-popup-tip) {
div :global(.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip) {
@apply border-b-background;
}
div :global(.maplibregl-popup-anchor-top-right .maplibregl-popup-tip) {
div :global(.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip) {
@apply border-b-background;
}
div :global(.maplibregl-popup-anchor-bottom .maplibregl-popup-tip) {
div :global(.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip) {
@apply border-t-background;
@apply drop-shadow-md;
}
div :global(.maplibregl-popup-anchor-bottom-left .maplibregl-popup-tip) {
div :global(.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip) {
@apply border-t-background;
@apply drop-shadow-md;
}
div :global(.maplibregl-popup-anchor-bottom-right .maplibregl-popup-tip) {
div :global(.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip) {
@apply border-t-background;
@apply drop-shadow-md;
}
div :global(.maplibregl-popup-anchor-left .maplibregl-popup-tip) {
div :global(.mapboxgl-popup-anchor-left .mapboxgl-popup-tip) {
@apply border-r-background;
}
div :global(.maplibregl-popup-anchor-right .maplibregl-popup-tip) {
div :global(.mapboxgl-popup-anchor-right .mapboxgl-popup-tip) {
@apply border-l-background;
}
</style>

View File

@@ -17,7 +17,7 @@
let control: CustomControl | null = null;
onMount(() => {
map.onLoad((map: maplibregl.Map) => {
map.onLoad((map: mapboxgl.Map) => {
if (position.includes('right')) container.classList.add('float-right');
else container.classList.add('float-left');
container.classList.remove('hidden');

View File

@@ -1,4 +1,4 @@
import { type Map, type IControl } from 'maplibre-gl';
import { type Map, type IControl } from 'mapbox-gl';
export default class CustomControl implements IControl {
_map: Map | undefined;

View File

@@ -1,11 +1,10 @@
import { settings } from '$lib/logic/settings';
import { gpxStatistics } from '$lib/logic/statistics';
import { getConvertedDistanceToKilometers } from '$lib/units';
import type { GeoJSONSource } from 'mapbox-gl';
import { get } from 'svelte/store';
import { map } from '$lib/components/map/map';
import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { allHidden } from '$lib/logic/hidden';
import type { GeoJSONSource } from 'maplibre-gl';
import { ANCHOR_LAYER_KEY } from '../style';
const { distanceMarkers, distanceUnits } = settings;
@@ -23,7 +22,7 @@ export class DistanceMarkers {
this.unsubscribes.push(
map.subscribe((map_) => {
if (map_) {
map_.on('style.load', this.updateBinded);
map_.on('style.import.load', this.updateBinded);
}
})
);

View File

@@ -3,14 +3,13 @@ import { MapPopup } from '$lib/components/map/map-popup';
export let waypointPopup: MapPopup | null = null;
export let trackpointPopup: MapPopup | null = null;
export function createPopups(map: maplibregl.Map) {
export function createPopups(map: mapboxgl.Map) {
removePopups();
waypointPopup = new MapPopup(map, {
closeButton: false,
focusAfterOpen: false,
maxWidth: undefined,
offset: {
center: [0, 0],
top: [0, 0],
'top-left': [0, 0],
'top-right': [0, 0],

View File

@@ -1,11 +1,6 @@
import { get, type Readable } from 'svelte/store';
import maplibregl, {
type GeoJSONSource,
type FilterSpecification,
type MapLayerMouseEvent,
type MapLayerTouchEvent,
} from 'maplibre-gl';
import { map } from '$lib/components/map/map';
import mapboxgl, { type FilterSpecification } from 'mapbox-gl';
import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { waypointPopup, trackpointPopup } from './gpx-layer-popup';
import {
ListTrackSegmentItem,
@@ -15,7 +10,7 @@ import {
ListFileItem,
ListRootItem,
} from '$lib/components/file-list/file-list';
import { getClosestLinePoint, getElevation, loadSVGIcon } from '$lib/utils';
import { getClosestLinePoint, getElevation } from '$lib/utils';
import { selectedWaypoint } from '$lib/components/toolbar/tools/waypoint/waypoint';
import { MapPin, Square } from 'lucide-static';
import { getSymbolKey, symbols } from '$lib/assets/symbols';
@@ -27,8 +22,7 @@ import { fileActionManager } from '$lib/logic/file-action-manager';
import { fileActions } from '$lib/logic/file-actions';
import { splitAs } from '$lib/components/toolbar/tools/scissors/scissors';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
import { gpxColors } from './gpx-layers';
import { gpxColors } from '$lib/components/map/gpx-layer/gpx-layers';
const colors = [
'#ff0000',
@@ -120,28 +114,28 @@ export class GPXLayer {
selected: boolean = false;
currentWaypointData: GeoJSON.FeatureCollection | null = null;
draggedWaypointIndex: number | null = null;
draggingStartingPosition: maplibregl.Point = new maplibregl.Point(0, 0);
draggingStartingPosition: mapboxgl.Point = new mapboxgl.Point(0, 0);
unsubscribe: Function[] = [];
updateBinded: () => void = this.update.bind(this);
layerOnMouseEnterBinded: (e: any) => void = this.layerOnMouseEnter.bind(this);
layerOnMouseLeaveBinded: () => void = this.layerOnMouseLeave.bind(this);
layerOnMouseMoveBinded: (e: any) => void = this.layerOnMouseMove.bind(this);
layerOnClickBinded: (e: MapLayerMouseEvent) => void = this.layerOnClick.bind(this);
layerOnContextMenuBinded: (e: MapLayerMouseEvent) => void = this.layerOnContextMenu.bind(this);
waypointLayerOnMouseEnterBinded: (e: MapLayerMouseEvent) => void =
layerOnClickBinded: (e: any) => void = this.layerOnClick.bind(this);
layerOnContextMenuBinded: (e: any) => void = this.layerOnContextMenu.bind(this);
waypointLayerOnMouseEnterBinded: (e: mapboxgl.MapMouseEvent) => void =
this.waypointLayerOnMouseEnter.bind(this);
waypointLayerOnMouseLeaveBinded: (e: MapLayerMouseEvent) => void =
waypointLayerOnMouseLeaveBinded: (e: mapboxgl.MapMouseEvent) => void =
this.waypointLayerOnMouseLeave.bind(this);
waypointLayerOnClickBinded: (e: MapLayerMouseEvent) => void =
waypointLayerOnClickBinded: (e: mapboxgl.MapMouseEvent) => void =
this.waypointLayerOnClick.bind(this);
waypointLayerOnMouseDownBinded: (e: MapLayerMouseEvent) => void =
waypointLayerOnMouseDownBinded: (e: mapboxgl.MapMouseEvent) => void =
this.waypointLayerOnMouseDown.bind(this);
waypointLayerOnTouchStartBinded: (e: MapLayerTouchEvent) => void =
waypointLayerOnTouchStartBinded: (e: mapboxgl.MapTouchEvent) => void =
this.waypointLayerOnTouchStart.bind(this);
waypointLayerOnMouseMoveBinded: (e: MapLayerMouseEvent | MapLayerTouchEvent) => void =
waypointLayerOnMouseMoveBinded: (e: mapboxgl.MapMouseEvent | mapboxgl.MapTouchEvent) => void =
this.waypointLayerOnMouseMove.bind(this);
waypointLayerOnMouseUpBinded: (e: MapLayerMouseEvent | MapLayerTouchEvent) => void =
waypointLayerOnMouseUpBinded: (e: mapboxgl.MapMouseEvent | mapboxgl.MapTouchEvent) => void =
this.waypointLayerOnMouseUp.bind(this);
constructor(fileId: string, file: Readable<GPXFileWithStatistics | undefined>) {
@@ -151,7 +145,7 @@ export class GPXLayer {
this.unsubscribe.push(
map.subscribe(($map) => {
if ($map) {
$map.on('style.load', this.updateBinded);
$map.on('style.import.load', this.updateBinded);
this.update();
}
})
@@ -174,9 +168,8 @@ export class GPXLayer {
update() {
const _map = get(map);
const layerEventManager = map.layerEventManager;
let file = get(this.file)?.file;
if (!_map || !layerEventManager || !file) {
if (!_map || !file) {
return;
}
@@ -192,7 +185,7 @@ export class GPXLayer {
this.loadIcons();
try {
let source = _map.getSource(this.fileId) as GeoJSONSource | undefined;
let source = _map.getSource(this.fileId) as mapboxgl.GeoJSONSource | undefined;
if (source) {
source.setData(this.getGeoJSON());
} else {
@@ -221,26 +214,65 @@ export class GPXLayer {
ANCHOR_LAYER_KEY.tracks
);
layerEventManager.on('click', this.fileId, this.layerOnClickBinded);
layerEventManager.on('contextmenu', this.fileId, this.layerOnContextMenuBinded);
layerEventManager.on('mouseenter', this.fileId, this.layerOnMouseEnterBinded);
layerEventManager.on('mouseleave', this.fileId, this.layerOnMouseLeaveBinded);
layerEventManager.on('mousemove', this.fileId, this.layerOnMouseMoveBinded);
_map.on('click', this.fileId, this.layerOnClickBinded);
_map.on('contextmenu', this.fileId, this.layerOnContextMenuBinded);
_map.on('mouseenter', this.fileId, this.layerOnMouseEnterBinded);
_map.on('mouseleave', this.fileId, this.layerOnMouseLeaveBinded);
_map.on('mousemove', this.fileId, this.layerOnMouseMoveBinded);
}
let visibleTrackSegmentIds: string[] = [];
file.forEachSegment((segment, trackIndex, segmentIndex) => {
if (!segment._data.hidden) {
visibleTrackSegmentIds.push(`${trackIndex}-${segmentIndex}`);
}
let waypointSource = _map.getSource(this.fileId + '-waypoints') as
| mapboxgl.GeoJSONSource
| undefined;
this.currentWaypointData = this.getWaypointsGeoJSON();
if (waypointSource) {
waypointSource.setData(this.currentWaypointData);
} else {
_map.addSource(this.fileId + '-waypoints', {
type: 'geojson',
data: this.currentWaypointData,
});
const segmentFilter: FilterSpecification = [
'in',
['get', 'trackSegmentId'],
['literal', visibleTrackSegmentIds],
];
}
_map.setFilter(this.fileId, segmentFilter, { validate: false });
if (!_map.getLayer(this.fileId + '-waypoints')) {
_map.addLayer(
{
id: this.fileId + '-waypoints',
type: 'symbol',
source: this.fileId + '-waypoints',
layout: {
'icon-image': ['get', 'icon'],
'icon-size': 0.3,
'icon-anchor': 'bottom',
'icon-padding': 0,
'icon-allow-overlap': true,
},
},
ANCHOR_LAYER_KEY.waypoints
);
_map.on(
'mouseenter',
this.fileId + '-waypoints',
this.waypointLayerOnMouseEnterBinded
);
_map.on(
'mouseleave',
this.fileId + '-waypoints',
this.waypointLayerOnMouseLeaveBinded
);
_map.on('click', this.fileId + '-waypoints', this.waypointLayerOnClickBinded);
_map.on(
'mousedown',
this.fileId + '-waypoints',
this.waypointLayerOnMouseDownBinded
);
_map.on(
'touchstart',
this.fileId + '-waypoints',
this.waypointLayerOnTouchStartBinded
);
}
if (get(directionMarkers)) {
if (!_map.getLayer(this.fileId + '-direction')) {
@@ -269,70 +301,28 @@ export class GPXLayer {
ANCHOR_LAYER_KEY.directionMarkers
);
}
_map.setFilter(this.fileId + '-direction', segmentFilter, { validate: false });
} else {
if (_map.getLayer(this.fileId + '-direction')) {
_map.removeLayer(this.fileId + '-direction');
}
}
let waypointSource = _map.getSource(this.fileId + '-waypoints') as
| GeoJSONSource
| undefined;
this.currentWaypointData = this.getWaypointsGeoJSON();
if (waypointSource) {
waypointSource.setData(this.currentWaypointData);
} else {
_map.addSource(this.fileId + '-waypoints', {
type: 'geojson',
data: this.currentWaypointData,
promoteId: 'waypointIndex',
});
let visibleTrackSegmentIds: string[] = [];
file.forEachSegment((segment, trackIndex, segmentIndex) => {
if (!segment._data.hidden) {
visibleTrackSegmentIds.push(`${trackIndex}-${segmentIndex}`);
}
});
const segmentFilter: FilterSpecification = [
'in',
['get', 'trackSegmentId'],
['literal', visibleTrackSegmentIds],
];
if (!_map.getLayer(this.fileId + '-waypoints')) {
_map.addLayer(
{
id: this.fileId + '-waypoints',
type: 'symbol',
source: this.fileId + '-waypoints',
layout: {
'icon-image': ['get', 'icon'],
'icon-size': 0.3,
'icon-anchor': 'bottom',
'icon-padding': 0,
'icon-allow-overlap': true,
},
},
ANCHOR_LAYER_KEY.waypoints
);
_map.setFilter(this.fileId, segmentFilter, { validate: false });
layerEventManager.on(
'mouseenter',
this.fileId + '-waypoints',
this.waypointLayerOnMouseEnterBinded
);
layerEventManager.on(
'mouseleave',
this.fileId + '-waypoints',
this.waypointLayerOnMouseLeaveBinded
);
layerEventManager.on(
'click',
this.fileId + '-waypoints',
this.waypointLayerOnClickBinded
);
layerEventManager.on(
'mousedown',
this.fileId + '-waypoints',
this.waypointLayerOnMouseDownBinded
);
layerEventManager.on(
'touchstart',
this.fileId + '-waypoints',
this.waypointLayerOnTouchStartBinded
);
if (_map.getLayer(this.fileId + '-direction')) {
_map.setFilter(this.fileId + '-direction', segmentFilter, { validate: false });
}
let visibleWaypoints: number[] = [];
@@ -355,47 +345,32 @@ export class GPXLayer {
remove() {
const _map = get(map);
if (_map) {
_map.off('style.load', this.updateBinded);
}
_map.off('click', this.fileId, this.layerOnClickBinded);
_map.off('contextmenu', this.fileId, this.layerOnContextMenuBinded);
_map.off('mouseenter', this.fileId, this.layerOnMouseEnterBinded);
_map.off('mouseleave', this.fileId, this.layerOnMouseLeaveBinded);
_map.off('mousemove', this.fileId, this.layerOnMouseMoveBinded);
_map.off('style.import.load', this.updateBinded);
const layerEventManager = map.layerEventManager;
if (layerEventManager) {
layerEventManager.off('click', this.fileId, this.layerOnClickBinded);
layerEventManager.off('contextmenu', this.fileId, this.layerOnContextMenuBinded);
layerEventManager.off('mouseenter', this.fileId, this.layerOnMouseEnterBinded);
layerEventManager.off('mouseleave', this.fileId, this.layerOnMouseLeaveBinded);
layerEventManager.off('mousemove', this.fileId, this.layerOnMouseMoveBinded);
layerEventManager.off(
_map.off(
'mouseenter',
this.fileId + '-waypoints',
this.waypointLayerOnMouseEnterBinded
);
layerEventManager.off(
_map.off(
'mouseleave',
this.fileId + '-waypoints',
this.waypointLayerOnMouseLeaveBinded
);
layerEventManager.off(
'click',
this.fileId + '-waypoints',
this.waypointLayerOnClickBinded
);
layerEventManager.off(
'mousedown',
this.fileId + '-waypoints',
this.waypointLayerOnMouseDownBinded
);
layerEventManager.off(
_map.off('click', this.fileId + '-waypoints', this.waypointLayerOnClickBinded);
_map.off('mousedown', this.fileId + '-waypoints', this.waypointLayerOnMouseDownBinded);
_map.off(
'touchstart',
this.fileId + '-waypoints',
this.waypointLayerOnTouchStartBinded
);
}
if (_map) {
if (_map.getLayer(this.fileId + '-direction')) {
_map.removeLayer(this.fileId + '-direction');
}
@@ -471,7 +446,7 @@ export class GPXLayer {
}
}
layerOnClick(e: MapLayerMouseEvent) {
layerOnClick(e: mapboxgl.MapMouseEvent) {
if (
get(currentTool) === Tool.ROUTING &&
get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints'])
@@ -529,7 +504,7 @@ export class GPXLayer {
}
}
waypointLayerOnMouseEnter(e: MapLayerMouseEvent) {
waypointLayerOnMouseEnter(e: mapboxgl.MapMouseEvent) {
if (this.draggedWaypointIndex !== null) {
return;
}
@@ -549,7 +524,7 @@ export class GPXLayer {
mapCursor.notify(MapCursorState.WAYPOINT_HOVER, false);
}
waypointLayerOnClick(e: MapLayerMouseEvent) {
waypointLayerOnClick(e: mapboxgl.MapMouseEvent) {
e.preventDefault();
let waypointIndex = e.features![0].properties!.waypointIndex;
@@ -591,7 +566,7 @@ export class GPXLayer {
}
}
waypointLayerOnMouseDown(e: MapLayerMouseEvent) {
waypointLayerOnMouseDown(e: mapboxgl.MapMouseEvent) {
if (get(currentTool) !== Tool.WAYPOINT || !this.selected) {
return;
}
@@ -601,7 +576,6 @@ export class GPXLayer {
}
e.preventDefault();
_map.dragPan.disable();
this.draggedWaypointIndex = e.features![0].properties!.waypointIndex;
this.draggingStartingPosition = e.point;
@@ -611,7 +585,7 @@ export class GPXLayer {
_map.once('mouseup', this.waypointLayerOnMouseUpBinded);
}
waypointLayerOnTouchStart(e: MapLayerTouchEvent) {
waypointLayerOnTouchStart(e: mapboxgl.MapTouchEvent) {
if (e.points.length !== 1 || get(currentTool) !== Tool.WAYPOINT || !this.selected) {
return;
}
@@ -625,13 +599,12 @@ export class GPXLayer {
waypointPopup?.hide();
e.preventDefault();
_map.dragPan.disable();
_map.on('touchmove', this.waypointLayerOnMouseMoveBinded);
_map.once('touchend', this.waypointLayerOnMouseUpBinded);
}
waypointLayerOnMouseMove(e: MapLayerMouseEvent | MapLayerTouchEvent) {
waypointLayerOnMouseMove(e: mapboxgl.MapMouseEvent | mapboxgl.MapTouchEvent) {
if (this.draggedWaypointIndex === null || e.point.equals(this.draggingStartingPosition)) {
return;
}
@@ -643,35 +616,18 @@ export class GPXLayer {
).coordinates = [e.lngLat.lng, e.lngLat.lat];
let waypointSource = get(map)?.getSource(this.fileId + '-waypoints') as
| GeoJSONSource
| mapboxgl.GeoJSONSource
| undefined;
if (waypointSource) {
waypointSource.updateData({
update: [
{
id: this.draggedWaypointIndex,
newGeometry: {
type: 'Point',
coordinates: [e.lngLat.lng, e.lngLat.lat],
},
},
],
});
waypointSource.setData(this.currentWaypointData!);
}
}
waypointLayerOnMouseUp(e: MapLayerMouseEvent | MapLayerTouchEvent) {
waypointLayerOnMouseUp(e: mapboxgl.MapMouseEvent | mapboxgl.MapTouchEvent) {
mapCursor.notify(MapCursorState.WAYPOINT_DRAGGING, false);
const _map = get(map);
if (!_map) {
return;
}
_map.dragPan.enable();
_map.off('mousemove', this.waypointLayerOnMouseMoveBinded);
_map.off('touchmove', this.waypointLayerOnMouseMoveBinded);
get(map)?.off('mousemove', this.waypointLayerOnMouseMoveBinded);
get(map)?.off('touchmove', this.waypointLayerOnMouseMoveBinded);
if (this.draggedWaypointIndex === null) {
return;
@@ -794,7 +750,20 @@ export class GPXLayer {
symbols.forEach((symbol) => {
const iconId = `waypoint-${symbol ?? 'default'}-${this.layerColor}`;
loadSVGIcon(_map, iconId, getSvgForSymbol(symbol, this.layerColor));
if (!_map.hasImage(iconId)) {
let icon = new Image(100, 100);
icon.onload = () => {
if (!_map.hasImage(iconId)) {
_map.addImage(iconId, icon);
}
};
// Lucide icons are SVG files with a 24x24 viewBox
// Create a new SVG with a 32x32 viewBox and center the icon in a circle
icon.src =
'data:image/svg+xml,' +
encodeURIComponent(getSvgForSymbol(symbol, this.layerColor));
}
});
}
}

View File

@@ -1,40 +1,30 @@
import { currentTool, Tool } from '$lib/components/toolbar/tools';
import { gpxStatistics, hoveredPoint, slicedGPXStatistics } from '$lib/logic/statistics';
import type { GeoJSONSource } from 'maplibre-gl';
import { gpxStatistics, slicedGPXStatistics } from '$lib/logic/statistics';
import mapboxgl from 'mapbox-gl';
import { get } from 'svelte/store';
import { map } from '$lib/components/map/map';
import { allHidden } from '$lib/logic/hidden';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
import { loadSVGIcon } from '$lib/utils';
const startMarkerSVG = `<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="6" fill="#22c55e" stroke="white" stroke-width="1.5"/>
</svg>`;
const endMarkerSVG = `<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="checkerboard" x="0" y="0" width="5" height="5" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="2.5" height="2.5" fill="white"/>
<rect x="2.5" y="2.5" width="2.5" height="2.5" fill="white"/>
<rect x="2.5" y="0" width="2.5" height="2.5" fill="black"/>
<rect x="0" y="2.5" width="2.5" height="2.5" fill="black"/>
</pattern>
</defs>
<circle cx="8" cy="8" r="6" fill="url(#checkerboard)" stroke="white" stroke-width="1.5"/>
</svg>`;
const hoverMarkerSVG = `<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="6" fill="#00b8db" stroke="white" stroke-width="1.5"/>
</svg>`;
export class StartEndMarkers {
start: mapboxgl.Marker;
end: mapboxgl.Marker;
updateBinded: () => void = this.update.bind(this);
unsubscribes: (() => void)[] = [];
constructor() {
let startElement = document.createElement('div');
let endElement = document.createElement('div');
startElement.className = `h-4 w-4 rounded-full bg-green-500 border-2 border-white`;
endElement.className = `h-4 w-4 rounded-full border-2 border-white`;
endElement.style.background =
'repeating-conic-gradient(#fff 0 90deg, #000 0 180deg) 0 0/8px 8px round';
this.start = new mapboxgl.Marker({ element: startElement });
this.end = new mapboxgl.Marker({ element: endElement });
map.onLoad(() => this.update());
this.unsubscribes.push(gpxStatistics.subscribe(this.updateBinded));
this.unsubscribes.push(slicedGPXStatistics.subscribe(this.updateBinded));
this.unsubscribes.push(hoveredPoint.subscribe(this.updateBinded));
this.unsubscribes.push(currentTool.subscribe(this.updateBinded));
this.unsubscribes.push(allHidden.subscribe(this.updateBinded));
}
@@ -43,115 +33,33 @@ export class StartEndMarkers {
const map_ = get(map);
if (!map_) return;
this.loadIcons();
const tool = get(currentTool);
const statistics = get(gpxStatistics);
const slicedStatistics = get(slicedGPXStatistics);
const hovered = get(hoveredPoint);
const hidden = get(allHidden);
if (!hidden) {
const data: GeoJSON.FeatureCollection = {
type: 'FeatureCollection',
features: [],
};
if (statistics.global.length > 0 && tool !== Tool.ROUTING) {
const start = statistics
.getTrackPoint(slicedStatistics?.[1] ?? 0)!
.trkpt.getCoordinates();
const end = statistics
if (statistics.global.length > 0 && tool !== Tool.ROUTING && !hidden) {
this.start
.setLngLat(
statistics.getTrackPoint(slicedStatistics?.[1] ?? 0)!.trkpt.getCoordinates()
)
.addTo(map_);
this.end
.setLngLat(
statistics
.getTrackPoint(slicedStatistics?.[2] ?? statistics.global.length - 1)!
.trkpt.getCoordinates();
data.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [start.lon, start.lat],
},
properties: {
icon: 'start-marker',
},
});
data.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [end.lon, end.lat],
},
properties: {
icon: 'end-marker',
},
});
}
if (hovered) {
data.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [hovered.lon, hovered.lat],
},
properties: {
icon: 'hover-marker',
},
});
}
let source = map_.getSource('start-end-markers') as GeoJSONSource | undefined;
if (source) {
source.setData(data);
.trkpt.getCoordinates()
)
.addTo(map_);
} else {
map_.addSource('start-end-markers', {
type: 'geojson',
data: data,
});
}
if (!map_.getLayer('start-end-markers')) {
map_.addLayer(
{
id: 'start-end-markers',
type: 'symbol',
source: 'start-end-markers',
layout: {
'icon-image': ['get', 'icon'],
'icon-size': 0.2,
'icon-allow-overlap': true,
},
},
ANCHOR_LAYER_KEY.startEndMarkers
);
}
} else {
if (map_.getLayer('start-end-markers')) {
map_.removeLayer('start-end-markers');
}
if (map_.getSource('start-end-markers')) {
map_.removeSource('start-end-markers');
}
this.start.remove();
this.end.remove();
}
}
remove() {
this.unsubscribes.forEach((unsubscribe) => unsubscribe());
const map_ = get(map);
if (!map_) return;
if (map_.getLayer('start-end-markers')) {
map_.removeLayer('start-end-markers');
}
if (map_.getSource('start-end-markers')) {
map_.removeSource('start-end-markers');
}
}
loadIcons() {
const map_ = get(map);
if (!map_) return;
loadSVGIcon(map_, 'start-marker', startMarkerSVG);
loadSVGIcon(map_, 'end-marker', endMarkerSVG);
loadSVGIcon(map_, 'hover-marker', hoverMarkerSVG);
this.start.remove();
this.end.remove();
}
}

View File

@@ -20,8 +20,9 @@
import { i18n } from '$lib/i18n.svelte';
import { defaultBasemap, type CustomLayer } from '$lib/assets/layers';
import { onMount } from 'svelte';
import { remove } from './utils';
import { customBasemapUpdate, isSelected, remove } from './utils';
import { settings } from '$lib/logic/settings';
import { map } from '$lib/components/map/map';
import { dndzone } from 'svelte-dnd-action';
const {
@@ -41,9 +42,14 @@
let maxZoom: number = $state(20);
let layerType: 'basemap' | 'overlay' = $state('basemap');
let resourceType: 'raster' | 'vector' = $derived.by(() => {
if (tileUrls[0].length > 0 && tileUrls[0].includes('.json')) {
if (tileUrls[0].length > 0) {
if (
tileUrls[0].includes('.json') ||
(tileUrls[0].includes('api.mapbox.com/styles') && !tileUrls[0].includes('tiles'))
) {
return 'vector';
}
}
return 'raster';
});
@@ -128,8 +134,8 @@
],
};
}
addLayer(layerId);
$customLayers[layerId] = layer;
addLayer(layerId);
selectedLayerId = undefined;
setDataFromSelectedLayer();
}
@@ -152,7 +158,9 @@
return $tree;
});
if ($currentBasemap !== layerId) {
if ($currentBasemap === layerId) {
$customBasemapUpdate++;
} else {
$currentBasemap = layerId;
}
@@ -168,6 +176,14 @@
return $tree;
});
if ($map && $currentOverlays && isSelected($currentOverlays, layerId)) {
try {
$map.removeImport(layerId);
} catch (e) {
// No reliable way to check if the map is ready to remove sources and layers
}
}
currentOverlays.update(($overlays) => {
if (!$overlays.overlays.hasOwnProperty('custom')) {
$overlays.overlays['custom'] = {};

View File

@@ -5,8 +5,12 @@
import { Separator } from '$lib/components/ui/separator';
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
import { Layers } from '@lucide/svelte';
import { basemaps, defaultBasemap, overlays } from '$lib/assets/layers';
import { settings } from '$lib/logic/settings';
import { map } from '$lib/components/map/map';
import { customBasemapUpdate, getLayers } from './utils';
import type { ImportSpecification, StyleSpecification } from 'mapbox-gl';
import { untrack } from 'svelte';
let container: HTMLDivElement;
let overpassLayer: OverpassLayer;
@@ -19,14 +23,125 @@
selectedBasemapTree,
selectedOverlayTree,
selectedOverpassTree,
customLayers,
opacities,
} = settings;
map.onLoad((_map: maplibregl.Map) => {
function setStyle() {
if (!$map) {
return;
}
let basemap = basemaps.hasOwnProperty($currentBasemap)
? basemaps[$currentBasemap]
: ($customLayers[$currentBasemap]?.value ?? basemaps[defaultBasemap]);
$map.removeImport('basemap');
if (typeof basemap === 'string') {
$map.addImport({ id: 'basemap', url: basemap }, 'overlays');
} else {
$map.addImport(
{
id: 'basemap',
url: '',
data: basemap as StyleSpecification,
},
'overlays'
);
}
}
$effect(() => {
if ($map && ($currentBasemap || $customBasemapUpdate)) {
untrack(() => setStyle());
}
});
function addOverlay(id: string) {
if (!$map) {
return;
}
try {
let overlay = $customLayers.hasOwnProperty(id) ? $customLayers[id].value : overlays[id];
if (typeof overlay === 'string') {
$map.addImport({ id, url: overlay });
} else {
if ($opacities.hasOwnProperty(id)) {
overlay = {
...overlay,
layers: (overlay as StyleSpecification).layers.map((layer) => {
if (layer.type === 'raster') {
if (!layer.paint) {
layer.paint = {};
}
layer.paint['raster-opacity'] = $opacities[id];
}
return layer;
}),
};
}
$map.addImport({
id,
url: '',
data: overlay as StyleSpecification,
});
}
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
}
}
function updateOverlays() {
if ($map && $currentOverlays && $opacities) {
let overlayLayers = getLayers($currentOverlays);
try {
let activeOverlays =
$map
.getStyle()
.imports?.reduce(
(
acc: Record<string, ImportSpecification>,
imprt: ImportSpecification
) => {
if (!['basemap', 'overlays'].includes(imprt.id)) {
acc[imprt.id] = imprt;
}
return acc;
},
{}
) || {};
let toRemove = Object.keys(activeOverlays).filter((id) => !overlayLayers[id]);
toRemove.forEach((id) => {
$map?.removeImport(id);
});
let toAdd = Object.entries(overlayLayers)
.filter(([id, selected]) => selected && !activeOverlays.hasOwnProperty(id))
.map(([id]) => id);
toAdd.forEach((id) => {
addOverlay(id);
});
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
}
}
}
$effect(() => {
if ($map && $currentOverlays && $opacities) {
untrack(() => updateOverlays());
}
});
map.onLoad((_map: mapboxgl.Map) => {
if (overpassLayer) {
overpassLayer.remove();
}
overpassLayer = new OverpassLayer(_map, map.layerEventManager!);
overpassLayer = new OverpassLayer(_map);
overpassLayer.add();
let first = true;
_map.on('style.import.load', () => {
if (!first) return;
first = false;
updateOverlays();
});
});
let open = $state(false);

View File

@@ -167,11 +167,11 @@
{#if isSelected($selectedOverlayTree, selectedOverlay)}
{#if $isLayerFromExtension(selectedOverlay)}
{$getLayerName(selectedOverlay)}
{:else if $customLayers.hasOwnProperty(selectedOverlay)}
{$customLayers[selectedOverlay].name}
{:else}
{i18n._(`layers.label.${selectedOverlay}`)}
{/if}
{:else if $customLayers.hasOwnProperty(selectedOverlay)}
{$customLayers[selectedOverlay].name}
{/if}
{/if}
</Select.Trigger>
@@ -213,9 +213,7 @@
isSelected($currentOverlays, selectedOverlay)
) {
try {
if ($map.getLayer(selectedOverlay)) {
$map.removeLayer(selectedOverlay);
}
$map.removeImport(selectedOverlay);
} catch (e) {
// No reliable way to check if the map is ready to remove sources and layers
}

View File

@@ -103,7 +103,7 @@ export class ExtensionAPI {
if (current && isSelected(current, overlay.id)) {
show = true;
try {
get(map)?.removeLayer(overlay.id);
get(map)?.removeImport(overlay.id);
} catch (e) {
// No reliable way to check if the map is ready to remove sources and layers
}

View File

@@ -6,10 +6,7 @@ import { overpassQueryData } from '$lib/assets/layers';
import { MapPopup } from '$lib/components/map/map-popup';
import { settings } from '$lib/logic/settings';
import { db } from '$lib/db';
import type { GeoJSONSource } from 'maplibre-gl';
import { ANCHOR_LAYER_KEY } from '../style';
import type { MapLayerEventManager } from '$lib/components/map/map-layer-event-manager';
import { loadSVGIcon } from '$lib/utils';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
const { currentOverpassQueries } = settings;
@@ -24,12 +21,11 @@ liveQuery(() => db.overpassdata.toArray()).subscribe((pois) => {
});
export class OverpassLayer {
overpassUrl = 'https://maps.mail.ru/osm/tools/overpass/api/interpreter';
overpassUrl = 'https://overpass.private.coffee/api/interpreter';
minZoom = 12;
queryZoom = 12;
expirationTime = 7 * 24 * 3600 * 1000;
map: maplibregl.Map;
layerEventManager: MapLayerEventManager;
map: mapboxgl.Map;
popup: MapPopup;
currentQueries: Set<string> = new Set();
@@ -40,9 +36,8 @@ export class OverpassLayer {
updateBinded = this.update.bind(this);
onHoverBinded = this.onHover.bind(this);
constructor(map: maplibregl.Map, layerEventManager: MapLayerEventManager) {
constructor(map: mapboxgl.Map) {
this.map = map;
this.layerEventManager = layerEventManager;
this.popup = new MapPopup(map, {
closeButton: false,
focusAfterOpen: false,
@@ -53,7 +48,7 @@ export class OverpassLayer {
add() {
this.map.on('moveend', this.queryIfNeededBinded);
this.map.on('style.load', this.updateBinded);
this.map.on('style.import.load', this.updateBinded);
this.unsubscribes.push(data.subscribe(this.updateBinded));
this.unsubscribes.push(
currentOverpassQueries.subscribe(() => {
@@ -77,17 +72,10 @@ export class OverpassLayer {
update() {
this.loadIcons();
const fullData = get(data);
const queries = getCurrentQueries();
const d: GeoJSON.FeatureCollection = {
type: 'FeatureCollection',
features: fullData.features.filter((feature) =>
queries.includes(feature.properties!.query)
),
};
let d = get(data);
try {
let source = this.map.getSource('overpass') as GeoJSONSource | undefined;
let source = this.map.getSource('overpass') as mapboxgl.GeoJSONSource | undefined;
if (source) {
source.setData(d);
} else {
@@ -113,9 +101,13 @@ export class OverpassLayer {
ANCHOR_LAYER_KEY.overpass
);
this.layerEventManager.on('mouseenter', 'overpass', this.onHoverBinded);
this.layerEventManager.on('click', 'overpass', this.onHoverBinded);
this.map.on('mouseenter', 'overpass', this.onHoverBinded);
this.map.on('click', 'overpass', this.onHoverBinded);
}
this.map.setFilter('overpass', ['in', 'query', ...getCurrentQueries()], {
validate: false,
});
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
}
@@ -123,9 +115,7 @@ export class OverpassLayer {
remove() {
this.map.off('moveend', this.queryIfNeededBinded);
this.map.off('style.load', this.updateBinded);
this.layerEventManager.off('mouseenter', 'overpass', this.onHoverBinded);
this.layerEventManager.off('click', 'overpass', this.onHoverBinded);
this.map.off('style.import.load', this.updateBinded);
this.unsubscribes.forEach((unsubscribe) => unsubscribe());
try {
@@ -258,16 +248,27 @@ export class OverpassLayer {
loadIcons() {
let currentQueries = getCurrentQueries();
currentQueries.forEach((query) => {
loadSVGIcon(
this.map,
`overpass-${query}`,
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
if (!this.map.hasImage(`overpass-${query}`)) {
let icon = new Image(100, 100);
icon.onload = () => {
if (!this.map.hasImage(`overpass-${query}`)) {
this.map.addImage(`overpass-${query}`, icon);
}
};
// Lucide icons are SVG files with a 24x24 viewBox
// Create a new SVG with a 32x32 viewBox and center the icon in a circle
icon.src =
'data:image/svg+xml,' +
encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
<circle cx="20" cy="20" r="20" fill="${overpassQueryData[query].icon.color}" />
<g transform="translate(8 8)">
${overpassQueryData[query].icon.svg.replace('stroke="currentColor"', 'stroke="white"')}
</g>
</svg>`
);
</svg>
`);
}
});
}
}

View File

@@ -76,3 +76,5 @@ export function removeAll(node: LayerTreeType, ids: string[]) {
});
return node;
}
export const customBasemapUpdate = writable(0);

View File

@@ -1,281 +0,0 @@
import { fileStateCollection } from '$lib/logic/file-state';
import maplibregl from 'maplibre-gl';
type MapLayerMouseEventListener = (e: maplibregl.MapLayerMouseEvent) => void;
type MapLayerTouchEventListener = (e: maplibregl.MapLayerTouchEvent) => void;
type MapLayerListener = {
features: maplibregl.MapGeoJSONFeature[];
mousemoves: MapLayerMouseEventListener[];
mouseenters: MapLayerMouseEventListener[];
mouseleaves: MapLayerMouseEventListener[];
mousedowns: MapLayerMouseEventListener[];
clicks: MapLayerMouseEventListener[];
contextmenus: MapLayerMouseEventListener[];
touchstarts: MapLayerTouchEventListener[];
};
export class MapLayerEventManager {
private _map: maplibregl.Map;
private _listeners: Record<string, MapLayerListener> = {};
constructor(map: maplibregl.Map) {
this._map = map;
this._map.on('mousemove', this._handleMouseMove.bind(this));
this._map.on('click', this._handleMouseClick.bind(this, 'click'));
this._map.on('contextmenu', this._handleMouseClick.bind(this, 'contextmenu'));
this._map.on('mousedown', this._handleMouseClick.bind(this, 'mousedown'));
this._map.on('touchstart', this._handleTouchStart.bind(this));
}
on(
eventType:
| 'mousemove'
| 'mouseenter'
| 'mouseleave'
| 'mousedown'
| 'click'
| 'contextmenu'
| 'touchstart',
layerId: string,
listener: MapLayerMouseEventListener | MapLayerTouchEventListener
) {
if (!this._listeners[layerId]) {
this._listeners[layerId] = {
features: [],
mousemoves: [],
mouseenters: [],
mouseleaves: [],
mousedowns: [],
clicks: [],
contextmenus: [],
touchstarts: [],
};
}
switch (eventType) {
case 'mousemove':
this._listeners[layerId].mousemoves.push(listener as MapLayerMouseEventListener);
break;
case 'mouseenter':
this._listeners[layerId].mouseenters.push(listener as MapLayerMouseEventListener);
break;
case 'mouseleave':
this._listeners[layerId].mouseleaves.push(listener as MapLayerMouseEventListener);
break;
case 'mousedown':
this._listeners[layerId].mousedowns.push(listener as MapLayerMouseEventListener);
break;
case 'click':
this._listeners[layerId].clicks.push(listener as MapLayerMouseEventListener);
break;
case 'contextmenu':
this._listeners[layerId].contextmenus.push(listener as MapLayerMouseEventListener);
break;
case 'touchstart':
this._listeners[layerId].touchstarts.push(listener as MapLayerTouchEventListener);
break;
}
}
off(
eventType:
| 'mousemove'
| 'mouseenter'
| 'mouseleave'
| 'mousedown'
| 'click'
| 'contextmenu'
| 'touchstart',
layerId: string,
listener: MapLayerMouseEventListener | MapLayerTouchEventListener
) {
if (this._listeners[layerId]) {
switch (eventType) {
case 'mousemove':
this._listeners[layerId].mousemoves = this._listeners[
layerId
].mousemoves.filter((l) => l !== listener);
break;
case 'mouseenter':
this._listeners[layerId].mouseenters = this._listeners[
layerId
].mouseenters.filter((l) => l !== listener);
break;
case 'mouseleave':
this._listeners[layerId].mouseleaves = this._listeners[
layerId
].mouseleaves.filter((l) => l !== listener);
break;
case 'mousedown':
this._listeners[layerId].mousedowns = this._listeners[
layerId
].mousedowns.filter((l) => l !== listener);
break;
case 'click':
this._listeners[layerId].clicks = this._listeners[layerId].clicks.filter(
(l) => l !== listener
);
break;
case 'contextmenu':
this._listeners[layerId].contextmenus = this._listeners[
layerId
].contextmenus.filter((l) => l !== listener);
break;
case 'touchstart':
this._listeners[layerId].touchstarts = this._listeners[
layerId
].touchstarts.filter((l) => l !== listener);
break;
}
if (
this._listeners[layerId].mousemoves.length === 0 &&
this._listeners[layerId].mouseenters.length === 0 &&
this._listeners[layerId].mouseleaves.length === 0 &&
this._listeners[layerId].mousedowns.length === 0 &&
this._listeners[layerId].clicks.length === 0 &&
this._listeners[layerId].contextmenus.length === 0 &&
this._listeners[layerId].touchstarts.length === 0
) {
delete this._listeners[layerId];
}
}
}
private _handleMouseMove(e: maplibregl.MapMouseEvent) {
const featuresByLayer = this._getRenderedFeaturesByLayer(e);
Object.keys(this._listeners).forEach((layerId) => {
const features = featuresByLayer[layerId] || [];
const listener = this._listeners[layerId];
if ((features.length == 0) != (listener.features.length == 0)) {
if (features.length > 0) {
if (listener.mouseenters.length > 0) {
const event = new maplibregl.MapMouseEvent(
'mouseenter',
e.target,
e.originalEvent,
{
features: featuresByLayer[layerId]!,
}
);
listener.mouseenters.forEach((l) => l(event));
}
} else {
if (listener.mouseleaves.length > 0) {
const event = new maplibregl.MapMouseEvent(
'mouseleave',
e.target,
e.originalEvent
);
listener.mouseleaves.forEach((l) => l(event));
}
}
}
if (features.length > 0 && listener.mousemoves.length > 0) {
const event = new maplibregl.MapMouseEvent('mousemove', e.target, e.originalEvent, {
features: featuresByLayer[layerId]!,
});
listener.mousemoves.forEach((l) => l(event));
}
listener.features = features;
});
}
private _handleMouseClick(type: string, e: maplibregl.MapMouseEvent) {
const featuresByLayer = this._getRenderedFeaturesByLayer(e);
Object.keys(this._listeners).forEach((layerId) => {
const features = featuresByLayer[layerId] || [];
const listener = this._listeners[layerId];
if (features.length > 0) {
if (type === 'click' && listener.clicks.length > 0) {
const event = new maplibregl.MapMouseEvent('click', e.target, e.originalEvent, {
features: features,
});
listener.clicks.forEach((l) => l(event));
} else if (type === 'contextmenu' && listener.contextmenus.length > 0) {
const event = new maplibregl.MapMouseEvent(
'contextmenu',
e.target,
e.originalEvent,
{
features: features,
}
);
listener.contextmenus.forEach((l) => l(event));
} else if (type === 'mousedown' && listener.mousedowns.length > 0) {
const event = new maplibregl.MapMouseEvent(
'mousedown',
e.target,
e.originalEvent,
{
features: features,
}
);
listener.mousedowns.forEach((l) => l(event));
}
}
});
}
private _handleTouchStart(e: maplibregl.MapTouchEvent) {
const featuresByLayer = this._getRenderedFeaturesByLayer(e);
Object.keys(this._listeners).forEach((layerId) => {
const features = featuresByLayer[layerId] || [];
const listener = this._listeners[layerId];
if (features.length > 0) {
const event: maplibregl.MapLayerTouchEvent = new maplibregl.MapTouchEvent(
'touchstart',
e.target,
e.originalEvent
);
event.features = featuresByLayer[layerId]!;
listener.touchstarts.forEach((l) => l(event));
}
});
}
private _getBounds(point: maplibregl.Point) {
const delta = 30;
return new maplibregl.LngLatBounds(
this._map.unproject([point.x - delta, point.y + delta]),
this._map.unproject([point.x + delta, point.y - delta])
);
}
private _filterLayersIntersectingBounds(
layerIds: string[],
bounds: maplibregl.LngLatBounds
): string[] {
let result = layerIds.filter((layerId) => {
if (!this._map.getLayer(layerId)) return false;
const fileId = layerId.replace('-waypoints', '');
if (fileId === layerId) {
return fileStateCollection.getStatistics(fileId)?.intersectsBBox(bounds) ?? true;
} else {
return (
fileStateCollection.getStatistics(fileId)?.intersectsWaypointBBox(bounds) ??
true
);
}
});
return result;
}
private _getRenderedFeaturesByLayer(e: maplibregl.MapMouseEvent | maplibregl.MapTouchEvent) {
const layerIds = this._filterLayersIntersectingBounds(
Object.keys(this._listeners),
this._getBounds(e.point)
);
const features =
layerIds.length > 0
? this._map.queryRenderedFeatures(e.point, { layers: layerIds })
: [];
const featuresByLayer: Record<string, maplibregl.MapGeoJSONFeature[]> = {};
features.forEach((f) => {
if (!featuresByLayer[f.layer.id]) {
featuresByLayer[f.layer.id] = [];
}
featuresByLayer[f.layer.id].push(f);
});
return featuresByLayer;
}
}

View File

@@ -1,5 +1,5 @@
import { TrackPoint, Waypoint } from 'gpx';
import maplibregl from 'maplibre-gl';
import mapboxgl from 'mapbox-gl';
import { mount, tick, unmount } from 'svelte';
import { get, writable, type Writable } from 'svelte/store';
import MapPopupComponent from '$lib/components/map/MapPopup.svelte';
@@ -11,15 +11,15 @@ export type PopupItem<T = Waypoint | TrackPoint | any> = {
};
export class MapPopup {
map: maplibregl.Map;
popup: maplibregl.Popup;
map: mapboxgl.Map;
popup: mapboxgl.Popup;
item: Writable<PopupItem | null> = writable(null);
component: ReturnType<typeof mount>;
maybeHideBinded = this.maybeHide.bind(this);
constructor(map: maplibregl.Map, options?: maplibregl.PopupOptions) {
constructor(map: mapboxgl.Map, options?: mapboxgl.PopupOptions) {
this.map = map;
this.popup = new maplibregl.Popup(options);
this.popup = new mapboxgl.Popup(options);
this.component = mount(MapPopupComponent, {
target: document.body,
props: {
@@ -51,7 +51,7 @@ export class MapPopup {
this.map.on('mousemove', this.maybeHideBinded);
}
maybeHide(e: maplibregl.MapMouseEvent) {
maybeHide(e: mapboxgl.MapMouseEvent) {
const item = get(this.item);
if (item === null) {
this.hide();
@@ -75,10 +75,10 @@ export class MapPopup {
getCoordinates() {
const item = get(this.item);
if (item === null) {
return new maplibregl.LngLat(0, 0);
return new mapboxgl.LngLat(0, 0);
}
return item.item instanceof Waypoint || item.item instanceof TrackPoint
? item.item.getCoordinates()
: new maplibregl.LngLat(item.item.lon, item.item.lat);
: new mapboxgl.LngLat(item.item.lon, item.item.lat);
}
}

View File

@@ -1,80 +1,110 @@
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import MaplibreGeocoder, {
type MaplibreGeocoderFeatureResults,
} from '@maplibre/maplibre-gl-geocoder';
import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css';
import mapboxgl from 'mapbox-gl';
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
import { get, writable, type Writable } from 'svelte/store';
import { settings } from '$lib/logic/settings';
import { tick } from 'svelte';
import { ANCHOR_LAYER_KEY, StyleManager } from '$lib/components/map/style';
import { MapLayerEventManager } from '$lib/components/map/map-layer-event-manager';
import { terrainSources } from '$lib/assets/layers';
const { treeFileView, elevationProfile, bottomPanelSize, rightPanelSize, distanceUnits } = settings;
const {
treeFileView,
elevationProfile,
bottomPanelSize,
rightPanelSize,
distanceUnits,
terrainSource,
} = settings;
let fitBoundsOptions: maplibregl.MapOptions['fitBoundsOptions'] = {
let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = {
maxZoom: 15,
linear: true,
easing: () => 1,
};
export class MapLibreGLMap {
private _maptilerKey: string = '';
private _map: maplibregl.Map | null = null;
private _mapStore: Writable<maplibregl.Map | null> = writable(null);
private _styleManager: StyleManager | null = null;
private _onLoadCallbacks: ((map: maplibregl.Map) => void)[] = [];
private _unsubscribes: (() => void)[] = [];
private callOnLoadBinded: () => void = this.callOnLoad.bind(this);
public layerEventManager: MapLayerEventManager | null = null;
const emptySource: mapboxgl.GeoJSONSourceSpecification = {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
};
export const ANCHOR_LAYER_KEY = {
mapillary: 'mapillary-end',
tracks: 'tracks-end',
directionMarkers: 'direction-markers-end',
distanceMarkers: 'distance-markers-end',
interactions: 'interactions-end',
overpass: 'overpass-end',
waypoints: 'waypoints-end',
};
const anchorLayers: mapboxgl.LayerSpecification[] = Object.values(ANCHOR_LAYER_KEY).map((id) => ({
id: id,
type: 'symbol',
source: 'empty-source',
}));
subscribe(run: (value: maplibregl.Map | null) => void, invalidate?: () => void) {
return this._mapStore.subscribe(run, invalidate);
export class MapboxGLMap {
private _map: Writable<mapboxgl.Map | null> = writable(null);
private _onLoadCallbacks: ((map: mapboxgl.Map) => void)[] = [];
private _unsubscribes: (() => void)[] = [];
subscribe(run: (value: mapboxgl.Map | null) => void, invalidate?: () => void) {
return this._map.subscribe(run, invalidate);
}
init(
maptilerKey: string,
language: string,
hash: boolean,
geocoder: boolean,
geolocate: boolean
) {
this._maptilerKey = maptilerKey;
this._styleManager = new StyleManager(this._mapStore, this._maptilerKey);
const map = new maplibregl.Map({
init(language: string, hash: boolean, geocoder: boolean, geolocate: boolean) {
const map = new mapboxgl.Map({
container: 'map',
style: {
version: 8,
projection: {
type: 'globe',
sources: {
'empty-source': emptySource,
},
sources: {},
layers: [],
layers: anchorLayers,
imports: [
{
id: 'basemap',
url: '',
},
{
id: 'overlays',
url: '',
},
],
},
projection: 'globe',
zoom: 0,
hash: hash,
language,
attributionControl: false,
logoPosition: 'bottom-right',
boxZoom: false,
maxPitch: 85,
});
this.layerEventManager = new MapLayerEventManager(map);
map.addControl(
new maplibregl.NavigationControl({
new mapboxgl.AttributionControl({
compact: true,
})
);
map.addControl(
new mapboxgl.NavigationControl({
visualizePitch: true,
})
);
if (geocoder) {
let geocoder = new MaplibreGeocoder(
{
forwardGeocode: async (config) => {
const results: MaplibreGeocoderFeatureResults = {
features: [],
type: 'FeatureCollection',
};
try {
const request = `https://nominatim.openstreetmap.org/search?format=json&q=${config.query}&limit=5&accept-language=${language}`;
const response = await fetch(request);
const geojson = await response.json();
results.features = geojson.map((result: any) => {
let geocoder = new MapboxGeocoder({
mapboxgl: mapboxgl,
enableEventLogging: false,
collapsed: true,
flyTo: fitBoundsOptions,
language,
localGeocoder: () => [],
localGeocoderOnly: true,
externalGeocoder: (query: string) =>
fetch(
`https://nominatim.openstreetmap.org/search?format=json&q=${query}&limit=5&accept-language=${language}`
)
.then((response) => response.json())
.then((data) => {
return data.map((result: any) => {
return {
type: 'Feature',
geometry: {
@@ -84,43 +114,61 @@ export class MapLibreGLMap {
place_name: result.display_name,
};
});
} catch (e) {}
return results;
},
},
{
maplibregl: maplibregl,
enableEventLogging: false,
collapsed: true,
flyTo: fitBoundsOptions,
language,
}),
});
let onKeyDown = geocoder._onKeyDown;
geocoder._onKeyDown = (e: KeyboardEvent) => {
// Trigger search on Enter key only
if (e.key === 'Enter') {
onKeyDown.apply(geocoder, [{ target: geocoder._inputEl }]);
} else if (geocoder._typeahead.data.length > 0) {
geocoder._typeahead.clear();
}
);
};
map.addControl(geocoder);
}
if (geolocate) {
map.addControl(
new maplibregl.GeolocateControl({
new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true,
},
fitBoundsOptions,
trackUserLocation: true,
showUserHeading: true,
})
);
}
const scaleControl = new maplibregl.ScaleControl({
const scaleControl = new mapboxgl.ScaleControl({
unit: get(distanceUnits),
});
map.addControl(scaleControl);
map.on('style.load', () => {
map.setFog({
color: 'rgb(186, 210, 235)',
'high-color': 'rgb(36, 92, 223)',
'horizon-blend': 0.1,
'space-color': 'rgb(156, 240, 255)',
});
map.on('pitch', this.setTerrain.bind(this));
this.setTerrain();
});
map.on('style.import.load', () => {
const basemap = map.getStyle().imports?.find((imprt) => imprt.id === 'basemap');
if (basemap && basemap.data && basemap.data.glyphs) {
map.setGlyphsUrl(basemap.data.glyphs);
}
});
map.on('load', () => {
this._map = map;
this._mapStore.set(map); // only set the store after the map has loaded
this._map.set(map); // only set the store after the map has loaded
window._map = map; // entry point for extensions
this.resize();
this.setTerrain();
scaleControl.setUnit(get(distanceUnits));
this._onLoadCallbacks.forEach((callback) => callback(map));
this._onLoadCallbacks = [];
});
map.on('style.load', this.callOnLoadBinded);
this._unsubscribes.push(treeFileView.subscribe(() => this.resize()));
this._unsubscribes.push(elevationProfile.subscribe(() => this.resize()));
@@ -131,50 +179,70 @@ export class MapLibreGLMap {
scaleControl.setUnit(units);
})
);
this._unsubscribes.push(terrainSource.subscribe(() => this.setTerrain()));
}
onLoad(callback: (map: mapboxgl.Map) => void) {
const map = get(this._map);
if (map) {
callback(map);
} else {
this._onLoadCallbacks.push(callback);
}
}
destroy() {
if (this._map) {
this._map.remove();
this._mapStore.set(null);
const map = get(this._map);
if (map) {
map.remove();
this._map.set(null);
}
this._unsubscribes.forEach((unsubscribe) => unsubscribe());
this._unsubscribes = [];
}
resize() {
if (this._map) {
const map = get(this._map);
if (map) {
tick().then(() => {
this._map?.resize();
map.resize();
});
}
}
toggle3D() {
if (this._map) {
if (this._map.getPitch() === 0) {
this._map.easeTo({ pitch: 70 });
const map = get(this._map);
if (map) {
if (map.getPitch() === 0) {
map.easeTo({ pitch: 70 });
} else {
this._map.easeTo({ pitch: 0 });
map.easeTo({ pitch: 0 });
}
}
}
onLoad(callback: (map: maplibregl.Map) => void) {
if (this._map) {
callback(this._map);
setTerrain() {
const map = get(this._map);
if (map) {
const source = get(terrainSource);
try {
if (!map.getSource(source)) {
map.addSource(source, terrainSources[source]);
}
if (map.getPitch() > 0) {
map.setTerrain({
source: source,
exaggeration: 1,
});
} else {
this._onLoadCallbacks.push(callback);
map.setTerrain(null);
}
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
return;
}
callOnLoad() {
if (this._map && this._map.getLayer(ANCHOR_LAYER_KEY.overlays)) {
this._onLoadCallbacks.forEach((callback) => callback(this._map!));
this._onLoadCallbacks = [];
this._map.off('style.load', this.callOnLoadBinded);
}
}
}
export const map = new MapLibreGLMap();
export const map = new MapboxGLMap();

View File

@@ -20,14 +20,9 @@
let container: HTMLElement;
onMount(() => {
map.onLoad((map_: maplibregl.Map) => {
googleRedirect = new GoogleRedirect(map_);
mapillaryLayer = new MapillaryLayer(
map_,
map.layerEventManager!,
container,
mapillaryOpen
);
map.onLoad((map: mapboxgl.Map) => {
googleRedirect = new GoogleRedirect(map);
mapillaryLayer = new MapillaryLayer(map, container, mapillaryOpen);
});
});

View File

@@ -1,10 +1,11 @@
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import type mapboxgl from 'mapbox-gl';
export class GoogleRedirect {
map: maplibregl.Map;
map: mapboxgl.Map;
enabled = false;
constructor(map: maplibregl.Map) {
constructor(map: mapboxgl.Map) {
this.map = map;
}
@@ -24,7 +25,7 @@ export class GoogleRedirect {
this.map.off('click', this.openStreetView);
}
openStreetView(e: maplibregl.MapMouseEvent) {
openStreetView(e: mapboxgl.MapMouseEvent) {
window.open(
`https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${e.lngLat.lat},${e.lngLat.lng}`
);

View File

@@ -1,9 +1,8 @@
import maplibregl, { type LayerSpecification, type VectorSourceSpecification } from 'maplibre-gl';
import mapboxgl, { type LayerSpecification, type VectorSourceSpecification } from 'mapbox-gl';
import { Viewer, type ViewerBearingEvent } from 'mapillary-js/dist/mapillary.module';
import 'mapillary-js/dist/mapillary.css';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '../style';
import type { MapLayerEventManager } from '$lib/components/map/map-layer-event-manager';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
const mapillarySource: VectorSourceSpecification = {
type: 'vector',
@@ -43,9 +42,8 @@ const mapillaryImageLayer: LayerSpecification = {
};
export class MapillaryLayer {
map: maplibregl.Map;
layerEventManager: MapLayerEventManager;
marker: maplibregl.Marker;
map: mapboxgl.Map;
marker: mapboxgl.Marker;
viewer: Viewer;
active = false;
@@ -55,14 +53,8 @@ export class MapillaryLayer {
onMouseEnterBinded = this.onMouseEnter.bind(this);
onMouseLeaveBinded = this.onMouseLeave.bind(this);
constructor(
map: maplibregl.Map,
layerEventManager: MapLayerEventManager,
container: HTMLElement,
popupOpen: { value: boolean }
) {
constructor(map: mapboxgl.Map, container: HTMLElement, popupOpen: { value: boolean }) {
this.map = map;
this.layerEventManager = layerEventManager;
this.viewer = new Viewer({
accessToken: 'MLY|4381405525255083|3204871ec181638c3c31320490f03011',
@@ -70,12 +62,15 @@ export class MapillaryLayer {
});
const element = document.createElement('div');
element.className = 'maplibregl-user-location maplibregl-user-location-show-heading';
element.className = 'mapboxgl-user-location mapboxgl-user-location-show-heading';
const dot = document.createElement('div');
dot.className = 'maplibregl-user-location-dot';
dot.className = 'mapboxgl-user-location-dot';
const heading = document.createElement('div');
heading.className = 'mapboxgl-user-location-heading';
element.appendChild(dot);
element.appendChild(heading);
this.marker = new maplibregl.Marker({
this.marker = new mapboxgl.Marker({
rotationAlignment: 'map',
element,
});
@@ -111,14 +106,14 @@ export class MapillaryLayer {
this.map.addLayer(mapillaryImageLayer, ANCHOR_LAYER_KEY.mapillary);
}
this.map.on('style.load', this.addBinded);
this.layerEventManager.on('mouseenter', 'mapillary-image', this.onMouseEnterBinded);
this.layerEventManager.on('mouseleave', 'mapillary-image', this.onMouseLeaveBinded);
this.map.on('mouseenter', 'mapillary-image', this.onMouseEnterBinded);
this.map.on('mouseleave', 'mapillary-image', this.onMouseLeaveBinded);
}
remove() {
this.map.off('style.load', this.addBinded);
this.layerEventManager.off('mouseenter', 'mapillary-image', this.onMouseEnterBinded);
this.layerEventManager.off('mouseleave', 'mapillary-image', this.onMouseLeaveBinded);
this.map.off('mouseenter', 'mapillary-image', this.onMouseEnterBinded);
this.map.off('mouseleave', 'mapillary-image', this.onMouseLeaveBinded);
if (this.map.getLayer('mapillary-image')) {
this.map.removeLayer('mapillary-image');
@@ -140,7 +135,7 @@ export class MapillaryLayer {
this.popupOpen.value = false;
}
onMouseEnter(e: maplibregl.MapLayerMouseEvent) {
onMouseEnter(e: mapboxgl.MapMouseEvent) {
if (
e.features &&
e.features.length > 0 &&

View File

@@ -1,231 +0,0 @@
import { settings } from '$lib/logic/settings';
import { get, type Writable } from 'svelte/store';
import {
basemaps,
defaultBasemap,
maptilerKeyPlaceHolder,
overlays,
terrainSources,
} from '$lib/assets/layers';
import { getLayers } from '$lib/components/map/layer-control/utils';
import { i18n } from '$lib/i18n.svelte';
const { currentBasemap, currentOverlays, customLayers, opacities, terrainSource } = settings;
const emptySource: maplibregl.GeoJSONSourceSpecification = {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
};
export const ANCHOR_LAYER_KEY = {
overlays: 'overlays-end',
mapillary: 'mapillary-end',
tracks: 'tracks-end',
directionMarkers: 'direction-markers-end',
distanceMarkers: 'distance-markers-end',
startEndMarkers: 'start-end-markers-end',
interactions: 'interactions-end',
overpass: 'overpass-end',
waypoints: 'waypoints-end',
routingControls: 'routing-controls-end',
};
const anchorLayers: maplibregl.LayerSpecification[] = Object.values(ANCHOR_LAYER_KEY).map((id) => ({
id: id,
type: 'symbol',
source: 'empty-source',
}));
export class StyleManager {
private _map: Writable<maplibregl.Map | null>;
private _maptilerKey: string;
private _pastOverlays: Set<string> = new Set();
constructor(map: Writable<maplibregl.Map | null>, maptilerKey: string) {
this._map = map;
this._maptilerKey = maptilerKey;
this._map.subscribe((map_) => {
if (map_) {
this.updateBasemap();
map_.on('style.load', () => this.updateOverlays());
map_.on('pitch', () => this.updateTerrain());
}
});
currentBasemap.subscribe(() => this.updateBasemap());
currentOverlays.subscribe(() => this.updateOverlays());
opacities.subscribe(() => this.updateOverlays());
terrainSource.subscribe(() => this.updateTerrain());
customLayers.subscribe(() => this.updateBasemap());
}
updateBasemap() {
const map_ = get(this._map);
if (!map_) return;
this.buildStyle().then((style) => map_.setStyle(style));
}
async buildStyle(): Promise<maplibregl.StyleSpecification> {
const custom = get(customLayers);
const style: maplibregl.StyleSpecification = {
version: 8,
projection: {
type: 'globe',
},
sources: {
'empty-source': emptySource,
},
layers: [],
};
let basemap = get(currentBasemap);
const basemapInfo = basemaps[basemap] ?? custom[basemap]?.value ?? basemaps[defaultBasemap];
const basemapStyle = await this.get(basemapInfo);
this.merge(style, basemapStyle);
const terrain = this.getCurrentTerrain();
style.sources[terrain.source] = terrainSources[terrain.source];
style.terrain = terrain.exaggeration > 0 ? terrain : undefined;
style.layers.push(...anchorLayers);
return style;
}
async updateOverlays() {
const map_ = get(this._map);
if (!map_) return;
if (!map_.getSource('empty-source')) return;
const custom = get(customLayers);
const overlayOpacities = get(opacities);
try {
const layers = getLayers(get(currentOverlays) ?? {});
for (let overlay in layers) {
if (!layers[overlay]) {
if (this._pastOverlays.has(overlay)) {
const overlayInfo = custom[overlay]?.value ?? overlays[overlay];
const overlayStyle = await this.get(overlayInfo);
for (let layer of overlayStyle.layers ?? []) {
if (map_.getLayer(layer.id)) {
map_.removeLayer(layer.id);
}
}
this._pastOverlays.delete(overlay);
}
} else {
const overlayInfo = custom[overlay]?.value ?? overlays[overlay];
const overlayStyle = await this.get(overlayInfo);
const opacity = overlayOpacities[overlay];
for (let sourceId in overlayStyle.sources) {
if (!map_.getSource(sourceId)) {
map_.addSource(sourceId, overlayStyle.sources[sourceId]);
}
}
for (let layer of overlayStyle.layers ?? []) {
if (!map_.getLayer(layer.id)) {
if (opacity !== undefined) {
if (layer.type === 'raster') {
if (!layer.paint) {
layer.paint = {};
}
layer.paint['raster-opacity'] = opacity;
} else if (layer.type === 'hillshade') {
if (!layer.paint) {
layer.paint = {};
}
layer.paint['hillshade-exaggeration'] = opacity / 2;
}
}
map_.addLayer(layer, ANCHOR_LAYER_KEY.overlays);
}
}
this._pastOverlays.add(overlay);
}
}
} catch (e) {}
}
updateTerrain() {
const map_ = get(this._map);
if (!map_) return;
const mapTerrain = map_.getTerrain();
const terrain = this.getCurrentTerrain();
if (JSON.stringify(mapTerrain) !== JSON.stringify(terrain)) {
if (terrain.exaggeration > 0) {
if (!map_.getSource(terrain.source)) {
map_.addSource(terrain.source, terrainSources[terrain.source]);
}
map_.setTerrain(terrain);
} else {
map_.setTerrain(null);
}
}
}
async get(
styleInfo: maplibregl.StyleSpecification | string
): Promise<maplibregl.StyleSpecification> {
if (typeof styleInfo === 'string') {
let styleUrl = styleInfo as string;
if (styleUrl.includes(maptilerKeyPlaceHolder)) {
styleUrl = styleUrl.replace(maptilerKeyPlaceHolder, this._maptilerKey);
}
const response = await fetch(styleUrl, { cache: 'force-cache' });
const style = await response.json();
return style;
} else {
return styleInfo;
}
}
merge(style: maplibregl.StyleSpecification, other: maplibregl.StyleSpecification) {
style.sources = { ...style.sources, ...other.sources };
for (let layer of other.layers ?? []) {
if (layer.type === 'symbol' && layer.layout && layer.layout['text-field']) {
const textField = layer.layout['text-field'];
if (
Array.isArray(textField) &&
textField.length >= 2 &&
textField[0] === 'coalesce' &&
Array.isArray(textField[1]) &&
textField[1][0] === 'get' &&
typeof textField[1][1] === 'string' &&
textField[1][1].startsWith('name')
) {
layer.layout['text-field'] = [
'coalesce',
['get', `name:${i18n.lang}`],
['get', 'name'],
];
}
}
style.layers.push(layer);
}
if (other.sprite && !style.sprite) {
style.sprite = other.sprite;
}
if (other.glyphs && !style.glyphs) {
style.glyphs = other.glyphs;
}
}
getCurrentTerrain() {
const terrain = get(terrainSource);
const source = terrainSources[terrain];
if (source.url && source.url.includes(maptilerKeyPlaceHolder)) {
source.url = source.url.replace(maptilerKeyPlaceHolder, this._maptilerKey);
}
const map_ = get(this._map);
return {
source: terrain,
exaggeration: !map_ || map_.getPitch() === 0 ? 0 : 1,
};
}
}

View File

@@ -11,7 +11,7 @@
import Clean from '$lib/components/toolbar/tools/Clean.svelte';
import Reduce from '$lib/components/toolbar/tools/reduce/Reduce.svelte';
import RoutingControlPopup from '$lib/components/toolbar/tools/routing/RoutingControlPopup.svelte';
import maplibregl from 'maplibre-gl';
import mapboxgl from 'mapbox-gl';
import { settings } from '$lib/logic/settings';
let {
@@ -23,11 +23,11 @@
const { minimizeRoutingMenu } = settings;
let popupElement: HTMLDivElement | undefined = $state(undefined);
let popup: maplibregl.Popup | undefined = $derived.by(() => {
let popup: mapboxgl.Popup | undefined = $derived.by(() => {
if (!popupElement) {
return undefined;
}
let popup = new maplibregl.Popup({
let popup = new mapboxgl.Popup({
closeButton: false,
maxWidth: undefined,
});

View File

@@ -15,12 +15,11 @@
import { onDestroy, onMount } from 'svelte';
import { getURLForLanguage } from '$lib/utils';
import { Trash2 } from '@lucide/svelte';
import { map } from '$lib/components/map/map';
import type { GeoJSONSource } from 'maplibre-gl';
import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import type { GeoJSONSource } from 'mapbox-gl';
import { selection } from '$lib/logic/selection';
import { fileActions } from '$lib/logic/file-actions';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
let props: {
class?: string;
@@ -29,7 +28,7 @@
let cleanType = $state(CleanType.INSIDE);
let deleteTrackpoints = $state(true);
let deleteWaypoints = $state(true);
let rectangleCoordinates: maplibregl.LngLat[] = $state([]);
let rectangleCoordinates: mapboxgl.LngLat[] = $state([]);
$effect(() => {
if ($map) {

View File

@@ -1,11 +1,10 @@
import { ListItem, ListTrackSegmentItem } from '$lib/components/file-list/file-list';
import { map } from '$lib/components/map/map';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { fileActions } from '$lib/logic/file-actions';
import { GPXFileStateCollectionObserver, type GPXFileState } from '$lib/logic/file-state';
import { selection } from '$lib/logic/selection';
import { ramerDouglasPeucker, TrackPoint, type SimplifiedTrackPoint } from 'gpx';
import type { GeoJSONSource } from 'maplibre-gl';
import type { GeoJSONSource } from 'mapbox-gl';
import { get, writable } from 'svelte/store';
export const minTolerance = 0.1;

View File

@@ -21,7 +21,7 @@
SquareArrowUpLeft,
SquareArrowOutDownRight,
} from '@lucide/svelte';
import { routingProfiles } from '$lib/components/toolbar/tools/routing/routing';
import { brouterProfiles } from '$lib/components/toolbar/tools/routing/routing';
import { i18n } from '$lib/i18n.svelte';
import { slide } from 'svelte/transition';
import {
@@ -51,7 +51,7 @@
}: {
minimized?: boolean;
minimizable?: boolean;
popup?: maplibregl.Popup;
popup?: mapboxgl.Popup;
popupElement?: HTMLDivElement;
class?: string;
} = $props();
@@ -167,7 +167,7 @@
{i18n._(`toolbar.routing.activities.${$routingProfile}`)}
</Select.Trigger>
<Select.Content>
{#each Object.keys(routingProfiles) as profile}
{#each Object.keys(brouterProfiles) as profile}
<Select.Item value={profile}
>{i18n._(
`toolbar.routing.activities.${profile}`

View File

@@ -6,7 +6,7 @@ import { get } from 'svelte/store';
const { routing, routingProfile, privateRoads } = settings;
export const routingProfiles: { [key: string]: string } = {
export const brouterProfiles: { [key: string]: string } = {
bike: 'Trekking-dry',
racing_bike: 'fastbike',
gravel_bike: 'gravel',
@@ -19,7 +19,7 @@ export const routingProfiles: { [key: string]: string } = {
export function route(points: Coordinates[]): Promise<TrackPoint[]> {
if (get(routing)) {
return getRoute(points, routingProfiles[get(routingProfile)], get(privateRoads));
return getRoute(points, brouterProfiles[get(routingProfile)], get(privateRoads));
} else {
return getIntermediatePoints(points);
}

View File

@@ -2,21 +2,15 @@ import { ramerDouglasPeucker, type GPXFile, type TrackSegment } from 'gpx';
const earthRadius = 6371008.8;
export const MIN_ANCHOR_ZOOM = 0;
export const MAX_ANCHOR_ZOOM = 22;
export function getZoomLevelForDistance(latitude: number, distance?: number): number {
if (distance === undefined) {
return MIN_ANCHOR_ZOOM;
return 0;
}
const rad = Math.PI / 180;
const lat = latitude * rad;
return Math.min(
MAX_ANCHOR_ZOOM,
Math.max(MIN_ANCHOR_ZOOM, Math.round(Math.log2((earthRadius * Math.cos(lat)) / distance)))
);
return Math.min(22, Math.max(0, Math.log2((earthRadius * Math.cos(lat)) / distance)));
}
export function updateAnchorPoints(file: GPXFile) {

View File

@@ -36,7 +36,7 @@
onMount(() => {
if ($map) {
splitControls = new SplitControls($map, map.layerEventManager!);
splitControls = new SplitControls($map);
}
});

View File

@@ -8,33 +8,40 @@ import { get } from 'svelte/store';
import { fileStateCollection } from '$lib/logic/file-state';
import { fileActions } from '$lib/logic/file-actions';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import type { GeoJSONSource } from 'maplibre-gl';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/style';
import type { MapLayerEventManager } from '$lib/components/map/map-layer-event-manager';
import { loadSVGIcon } from '$lib/utils';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
export class SplitControls {
map: maplibregl.Map;
layerEventManager: MapLayerEventManager;
map: mapboxgl.Map;
unsubscribes: Function[] = [];
layerOnMouseEnterBinded: (e: any) => void = this.layerOnMouseEnter.bind(this);
layerOnMouseLeaveBinded: () => void = this.layerOnMouseLeave.bind(this);
layerOnClickBinded: (e: any) => void = this.layerOnClick.bind(this);
constructor(map: maplibregl.Map, layerEventManager: MapLayerEventManager) {
constructor(map: mapboxgl.Map) {
this.map = map;
this.layerEventManager = layerEventManager;
loadSVGIcon(
this.map,
'split-control',
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
if (!this.map.hasImage('split-control')) {
let icon = new Image(100, 100);
icon.onload = () => {
if (!this.map.hasImage('split-control')) {
this.map.addImage('split-control', icon);
}
};
// Lucide icons are SVG files with a 24x24 viewBox
// Create a new SVG with a 32x32 viewBox and center the icon in a circle
icon.src =
'data:image/svg+xml,' +
encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
<circle cx="20" cy="20" r="20" fill="white" />
<g transform="translate(8 8)">
${Scissors.replace('stroke="currentColor"', 'stroke="black"')}
</g>
</svg>`
);
</svg>
`);
}
this.unsubscribes.push(gpxStatistics.subscribe(this.addIfNeeded.bind(this)));
this.unsubscribes.push(currentTool.subscribe(this.addIfNeeded.bind(this)));
@@ -91,7 +98,7 @@ export class SplitControls {
}, false);
try {
let source = this.map.getSource('split-controls') as GeoJSONSource | undefined;
let source = this.map.getSource('split-controls') as mapboxgl.GeoJSONSource | undefined;
if (source) {
source.setData(data);
} else {
@@ -117,17 +124,9 @@ export class SplitControls {
ANCHOR_LAYER_KEY.interactions
);
this.layerEventManager.on(
'mouseenter',
'split-controls',
this.layerOnMouseEnterBinded
);
this.layerEventManager.on(
'mouseleave',
'split-controls',
this.layerOnMouseLeaveBinded
);
this.layerEventManager.on('click', 'split-controls', this.layerOnClickBinded);
this.map.on('mouseenter', 'split-controls', this.layerOnMouseEnterBinded);
this.map.on('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded);
this.map.on('click', 'split-controls', this.layerOnClickBinded);
}
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
@@ -135,9 +134,9 @@ export class SplitControls {
}
remove() {
this.layerEventManager.off('mouseenter', 'split-controls', this.layerOnMouseEnterBinded);
this.layerEventManager.off('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded);
this.layerEventManager.off('click', 'split-controls', this.layerOnClickBinded);
this.map.off('mouseenter', 'split-controls', this.layerOnMouseEnterBinded);
this.map.off('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded);
this.map.off('click', 'split-controls', this.layerOnClickBinded);
try {
if (this.map.getLayer('split-controls')) {
@@ -160,7 +159,7 @@ export class SplitControls {
mapCursor.notify(MapCursorState.SPLIT_CONTROL, false);
}
layerOnClick(e: maplibregl.MapLayerMouseEvent) {
layerOnClick(e: mapboxgl.MapMouseEvent) {
let coordinates = (e.features![0].geometry as GeoJSON.Point).coordinates;
fileActions.split(
get(splitAs),

View File

@@ -16,7 +16,7 @@
import { fileActions } from '$lib/logic/file-actions';
import { map } from '$lib/components/map/map';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import maplibregl from 'maplibre-gl';
import mapboxgl from 'mapbox-gl';
import { getSvgForSymbol } from '$lib/components/map/gpx-layer/gpx-layer';
let props: {
@@ -41,7 +41,7 @@
})
);
let marker: maplibregl.Marker | null = null;
let marker: mapboxgl.Marker | null = null;
function reset() {
if ($selectedWaypoint) {
@@ -125,7 +125,7 @@
let element = document.createElement('div');
element.classList.add('w-8', 'h-8');
element.innerHTML = getSvgForSymbol(symbolKey);
marker = new maplibregl.Marker({
marker = new mapboxgl.Marker({
element,
anchor: 'bottom',
})

View File

@@ -12,6 +12,7 @@ title: Files and statistics
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -84,20 +85,18 @@ You can also use the mouse wheel to zoom in and out on the elevation profile, an
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>
</div>
<div class="flex flex-col items-center -mt-6">
<div class="h-10 w-fit">
<div class="flex flex-col items-center w-full">
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={120}
orientation={'horizontal'}
/>
</div>
</div>
### Additional data

View File

@@ -5,7 +5,7 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Help keep the website free (and ad-free)
Each time you add or move GPS points, our servers calculate the best route on the road network.
We also use APIs from <a href="https://mapbox.com" target="_blank">Mapbox</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
We also use APIs from <a href="https://maptiler.com" target="_blank">MapTiler</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
Unfortunately, this is expensive.
If you enjoy using this tool and find it valuable, please consider making a small donation to help keep the website free and ad-free.

View File

@@ -13,8 +13,8 @@ title: Інтэграцыя
Усё, што вам трэба, гэта:
1. <a href="https://account.mapbox.com/auth/signup" target="_blank">Ключ доступу Mapbox</a> для загрузкі карты і
2. Файлы GPX, размешчаныя на вашым серверы або на Google Drive, або даступныя праз публічны URL.
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
Затым вы можаце пагуляць з канфігуратарам ніжэй, каб наладзіць сваю карту і стварыць адпаведны HTML-код.

View File

@@ -58,7 +58,9 @@ These controls allow you to navigate the map, zoom in and out, and switch betwee
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Навядзіце курсор мышы на карту, каб паказаць накладанне <a href="https://hiking.waymarkedtrails.org" target="_blank">Пешаходных Сцежак</a> на базавай карце <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Outdoors</a>.
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.maptiler.com/maps/outdoor-topo/" target="_blank">MapTiler Topo</a> basemap.
</span>
</div>
@@ -67,4 +69,4 @@ These controls allow you to navigate the map, zoom in and out, and switch betwee
У гэтых наладах вы таксама можаце кіраваць непразрыстасцю накладанняў.
Для прасунутых карыстальнікаў можна дадаваць карыстальніцкія базавыя карты і накладкі, дадаўшы URL-адрасы <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a> або <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">JSON у стылі Mapbox</a>.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.

View File

@@ -18,7 +18,7 @@ This tool allows you to add elevation data to traces and [points of interest](..
<DocsNote>
Elevation data is provided by <a href="https://mapbox.com" target="_blank">Mapbox</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">documentation</a>.
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
</DocsNote>

View File

@@ -5,6 +5,7 @@ title: Merge
<script>
import { Group } from '@lucide/svelte';
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
import DocsNote from '$lib/components/docs/DocsNote.svelte';
</script>
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
@@ -15,6 +16,13 @@ To use this tool, you need to [select](../files-and-stats) multiple files, [trac
- The second option can be used to create or manage files with multiple [tracks or segments](../gpx).
Merging files (or tracks) will result in a single file (or track) containing all tracks (or segments) from the selection.
<DocsNote>
Selected items are merged in the order they appear in the files list.
Reorder items by drag-and-drop if needed.
</DocsNote>
<div class="flex flex-row justify-center">
<Merge class="text-foreground p-3 border rounded-md shadow-lg" />
</div>

View File

@@ -12,6 +12,7 @@ title: Fitxers i estadístiques
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -84,20 +85,18 @@ També pots utilitzar la rodeta del ratolí per apropar o allunyar el perfil d'e
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>
</div>
<div class="flex flex-col items-center -mt-6">
<div class="h-10 w-fit">
<div class="flex flex-col items-center w-full">
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={120}
orientation={'horizontal'}
/>
</div>
</div>
### Dades addicionals

View File

@@ -5,7 +5,7 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Ajuda a mantenir aquesta pàgina web gratuïta (i sense anuncis)
Cada cop que afegeixes o mous un punt GPS, els nostres servidors calculen la millor ruta possible.
També utilitzen l'API de <a href="https://mapbox.com" target="_blank">Mapbox</a> per ensenyar mapes bonics, donar informació sobre l'altitud i permetre la cerca de llocs d'interès.
We also use APIs from <a href="https://maptiler.com" target="_blank">MapTiler</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
Desafortunadament, això és car.
Si gaudeixes aquesta eina i la trobes valuosa, si us plau, considera fer una petita donació per ajudar a mantenir la pàgina web gratuïta i sense anuncis.

View File

@@ -13,8 +13,8 @@ Pots utilitzar **gpx.studio** per crear mapes que mostrin els teus arxius GPX i
Tot el que necessites és:
1. Un <a href="https://account.mapbox.com/auth/signup" target="_blank"> token d'accés a Mapbox</a> per carregar el mapa i
2. Arxius GPX allotjats en el teu servidor, a Google Drive o accessibles a través d'una URL pública.
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
Aleshores pots jugar amb el configurador de sota per personalitzar el teu mapa i generar el corresponent codi HTML.

View File

@@ -55,7 +55,12 @@ El botó de capa de mapa permet canviar entre diferents mapes base i alternar ca
- Les **Capes sobreposades** són capes addicionals que es poden mostrar sobre el mapa base per proporcionar informació complementària.
- Els **Punts d'interès** es poden afegir al mapa per mostrar diferents categories de llocs, com botigues, restaurants o allotjaments.
<div class="flex flex-col items-center"><DocsLayers /><span class="text-sm text-center mt-2">Situa el cursor sobre el mapa per mostrar la capa <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> sobreposada sobre del <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Outdoors</a> mapa base.
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.maptiler.com/maps/outdoor-topo/" target="_blank">MapTiler Topo</a> basemap.
</span>
</div>
@@ -64,4 +69,4 @@ Poden activar-se en el [configuració de capes del mapa](./menu/settings).
En aquests ajustaments pots gestionar l'opacitat de les capes sobreposades.
Per a usuaris avançats és possible afegir mapes base i sobreposicions personalitzades proporcionant <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, o <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">JSON tipus Mapbox</a> URLs.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.

View File

@@ -29,13 +29,13 @@ Pots arrossegar y deixar arxius directament des del seu sistema d'arxius cap a l
Crear una còpia dels arxius seleccionats.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Esborra
Delete the currently selected files.
Esborra l'arxiu seleccinat.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Esborra-ho tot
Delete all files.
Esborra tots els fitxers.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Exportar...

View File

@@ -18,7 +18,7 @@ Aquesta eina permet afegir dades d'elevació a traces i [punts d'interès](../gp
<DocsNote>
Dades d'elevació subministrades per <a href="https://mapbox.com" target="_blank">Mapbox</a>.
Pots aprendre més sobre els seus orígens i precisió en la <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">documentació</a>.
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
</DocsNote>

View File

@@ -5,6 +5,7 @@ title: Fusionar
<script>
import { Group } from '@lucide/svelte';
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
import DocsNote from '$lib/components/docs/DocsNote.svelte';
</script>
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
@@ -15,6 +16,13 @@ Per utilitzar aquesta eina, s'ha de [seleccionar](../files-and-stats) múltiples
- La segona opció es pot utilitzar per a crear o gestionar arxius amb múltiples [tracs o segments](../gpx).
Fusionar arxius (o tracs) crearà un sol arxiu (o trac) que contindrà tots els tracs (o segments) seleccionats.
<DocsNote>
Selected items are merged in the order they appear in the files list.
Reorder items by drag-and-drop if needed.
</DocsNote>
<div class="flex flex-row justify-center">
<Merge class="text-foreground p-3 border rounded-md shadow-lg" />
</div>

View File

@@ -12,6 +12,7 @@ title: Soubory a statistiky
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -84,20 +85,18 @@ Pomocí kolečka myši můžete také výškový profil přiblížit a oddálit
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>
</div>
<div class="flex flex-col items-center -mt-6">
<div class="h-10 w-fit">
<div class="flex flex-col items-center w-full">
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={120}
orientation={'horizontal'}
/>
</div>
</div>
### Doplňující údaje

View File

@@ -5,7 +5,7 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Pomozte udržet web zdarma (a bez reklam)
Vždy, když přidáte nebo přesunete GPS body, naše servery vypočítají nejlepší cestu po silniční síti.
Používáme také API z <a href="https://mapbox.com" target="_blank">Mapboxu</a> pro zobrazení krásných map, získání dat o nadmořské výšce a vyhledávání míst.
We also use APIs from <a href="https://maptiler.com" target="_blank">MapTiler</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
Bohužel, to vše je nákladné.
Pokud rádi používáte tento nástroj a zdá se vám hodnotný, zvažte prosím malý příspěvek k udržení webu zdarma a bez reklam.

View File

@@ -13,8 +13,8 @@ Pomocí **gpx.studio** můžete vytvářet mapy se zobrazením souborů GPX a vk
Vše, co potřebujete, je:
1. <a href="https://account.mapbox.com/auth/signup" target="_blank">Přístupový token Mapboxu</a> k načtení mapy,
2. Soubory GPX umístěné na vašem serveru nebo na Disku Google, nebo přístupné prostřednictvím veřejné adresy URL.
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
V níže zobrazeném konfigurátoru si pak můžete mapu přizpůsobit a vygenerovat odpovídající kód HTML.

View File

@@ -58,7 +58,9 @@ Tlačítko mapové vrstvy umožňuje přepínat mezi různými podkladovými map
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Po najetí myší nad mapu se zobrazí překryv<a href="https://hiking.waymarkedtrails.org" target="_blank">značených stezek pro pěší turistiku</a> na podkladové mapě <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Outdoors</a>.
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.maptiler.com/maps/outdoor-topo/" target="_blank">MapTiler Topo</a> basemap.
</span>
</div>
@@ -67,4 +69,4 @@ Lze je povolit v nabídce [nastavení mapových vrstev](./menu/settings).
V tomto nastavení můžete také spravovat neprůhlednost překryvů.
Pokročilí uživatelé mohou přidávat vlastní podkladové mapy a překryvy pomocí <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a> nebo URL <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">Mapbox stylu JSON</a>.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.

View File

@@ -18,7 +18,7 @@ Tento nástroj umožňuje přidat údaje o nadmořské výšce ke stopám a [bod
<DocsNote>
Údaje o nadmořské výšce poskytuje <a href="https://mapbox.com" target="_blank">Mapbox</a>.
Více informací o jejich původu a přesnosti najdete v <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">dokumentaci</a>.
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
</DocsNote>

View File

@@ -5,6 +5,7 @@ title: Sloučit
<script>
import { Group } from '@lucide/svelte';
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
import DocsNote from '$lib/components/docs/DocsNote.svelte';
</script>
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
@@ -15,6 +16,13 @@ Chcete-li použít tento nástroj, musíte [vybrat](../files-and-stats) více so
- Druhá možnost může být použita k vytvoření nebo správě souborů s více [trasami nebo segmenty](../gpx).
Sloučením souborů (nebo tras) vznikne jeden soubor (nebo trasa) obsahující všechny trasy (nebo úseky) z výběru.
<DocsNote>
Selected items are merged in the order they appear in the files list.
Reorder items by drag-and-drop if needed.
</DocsNote>
<div class="flex flex-row justify-center">
<Merge class="text-foreground p-3 border rounded-md shadow-lg" />
</div>

View File

@@ -12,6 +12,7 @@ title: Files and statistics
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -84,20 +85,18 @@ You can also use the mouse wheel to zoom in and out on the elevation profile, an
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>
</div>
<div class="flex flex-col items-center -mt-6">
<div class="h-10 w-fit">
<div class="flex flex-col items-center w-full">
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={120}
orientation={'horizontal'}
/>
</div>
</div>
### Additional data

View File

@@ -5,7 +5,7 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Help keep the website free (and ad-free)
Each time you add or move GPS points, our servers calculate the best route on the road network.
We also use APIs from <a href="https://mapbox.com" target="_blank">Mapbox</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
We also use APIs from <a href="https://maptiler.com" target="_blank">MapTiler</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
Unfortunately, this is expensive.
If you enjoy using this tool and find it valuable, please consider making a small donation to help keep the website free and ad-free.

View File

@@ -13,8 +13,8 @@ You can use **gpx.studio** to create maps showing your GPX files and embed them
All you need is:
1. A <a href="https://account.mapbox.com/auth/signup" target="_blank">Mapbox access token</a> to load the map, and
2. GPX files hosted on your server or on Google Drive, or accessible via a public URL.
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
You can then play with the configurator below to customize your map and generate the corresponding HTML code.

View File

@@ -58,7 +58,9 @@ The map layers button allows you to switch between different basemaps, and toggl
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Outdoors</a> basemap.
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.maptiler.com/maps/outdoor-topo/" target="_blank">MapTiler Topo</a> basemap.
</span>
</div>
@@ -67,4 +69,4 @@ They can be enabled in the [map layer settings dialog](./menu/settings).
In these settings, you can also manage the opacity of the overlays.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">Mapbox style JSON</a> URLs.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.

View File

@@ -18,7 +18,7 @@ This tool allows you to add elevation data to traces and [points of interest](..
<DocsNote>
Elevation data is provided by <a href="https://mapbox.com" target="_blank">Mapbox</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">documentation</a>.
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
</DocsNote>

View File

@@ -5,6 +5,7 @@ title: Merge
<script>
import { Group } from '@lucide/svelte';
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
import DocsNote from '$lib/components/docs/DocsNote.svelte';
</script>
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
@@ -15,6 +16,13 @@ To use this tool, you need to [select](../files-and-stats) multiple files, [trac
- The second option can be used to create or manage files with multiple [tracks or segments](../gpx).
Merging files (or tracks) will result in a single file (or track) containing all tracks (or segments) from the selection.
<DocsNote>
Selected items are merged in the order they appear in the files list.
Reorder items by drag-and-drop if needed.
</DocsNote>
<div class="flex flex-row justify-center">
<Merge class="text-foreground p-3 border rounded-md shadow-lg" />
</div>

View File

@@ -12,6 +12,7 @@ title: Dateien und Statistiken
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -84,20 +85,18 @@ Sie können auch das Mausrad verwenden, um auf dem Höhenprofil heranzuzoomen un
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>
</div>
<div class="flex flex-col items-center -mt-6">
<div class="h-10 w-fit">
<div class="flex flex-col items-center w-full">
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={120}
orientation={'horizontal'}
/>
</div>
</div>
### Zusätzliche Daten

View File

@@ -5,7 +5,7 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Helfen Sie, die Website kostenlos (und werbefrei) zu erhalten
Jedes Mal, wenn Sie GPS-Punkte hinzufügen oder verschieben, berechnen unsere Server die beste Route im Straßennetz.
Wir verwenden auch APIs von <a href="https://mapbox.com" target="_blank">Mapbox</a>, um schöne Karten anzuzeigen, Höhendaten abzurufen und Ihnen die Suche nach Orten zu ermöglichen.
We also use APIs from <a href="https://maptiler.com" target="_blank">MapTiler</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
Leider ist dies mit hohen Kosten verbunden.
Wenn Sie dieses Tool gerne verwenden und es wertvoll finden, erwägen Sie bitte eine kleine Spende, um die Website kostenlos und werbefrei zu halten.

View File

@@ -1,5 +1,5 @@
Mapbox ist das Unternehmen, das einige der schönen Karten auf dieser Website zur Verfügung stellt.
Sie entwickeln auch die <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">Karten-Engine</a> welche **gpx.studio** unterstützt.
Mapbox stellt einige der auf dieser Website verwendeten Karten bereit.
Sie entwickeln auch die <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">Karten-Engine</a>, die **gpx.studio** unterstützt.
Wir sind äußerst glücklich und dankbar, Teil ihres <a href="https://mapbox.com/community" target="_blank">Community</a> Programms zu sein, das gemeinnützige Organisationen, Bildungseinrichtungen und Organisationen mit positivem Einfluss unterstützt.
Wir sind froh und dankbar, Teil ihres <a href="https://mapbox.com/community" target="_blank">Community</a> Programms zu sein, das gemeinnützige Organisationen, Bildungseinrichtungen und Organisationen unterstützt.
Diese Partnerschaft ermöglicht es **gpx.studio**, von den Mapbox-Tools zu ermäßigten Preisen zu profitieren, was erheblich zur finanziellen Tragfähigkeit des Projekts beiträgt und es uns ermöglicht, die bestmögliche Benutzererfahrung zu bieten.

View File

@@ -13,8 +13,8 @@ Du kannst **gpx.studio** verwenden, um Karten zu erstellen, die deine GPX-Dateie
Alles was Sie brauchen:
1. Eine <a href="https://account.mapbox.com/auth/signup" target="_blank">Mapbox Zugriffstoken</a> zum Laden der Karte, und
2. GPX-Dateien, die auf Ihrem Server oder Google Drive gehostet werden oder über eine öffentliche URL erreichbar sind.
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
Sie können dann mit dem Konfigurator unten spielen, um Ihre Karte anzupassen und den entsprechenden HTML-Code zu generieren.

View File

@@ -58,7 +58,9 @@ Mit der Schaltfläche Karten-Ebenen können Sie zwischen verschiedenen Basemaps
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Fahren Sie über der Karte, um die <a href="https://hiking.waymarkedtrails.org" target="_blank">Wegmarkierte Wanderwege</a> Overlay oben auf die <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Outdoors</a> Basemap anzuzeigen.
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.maptiler.com/maps/outdoor-topo/" target="_blank">MapTiler Topo</a> basemap.
</span>
</div>
@@ -67,4 +69,4 @@ Sie können im [Einstellungsdialog für die Kartenlayer Einstellungen](./menu/se
In diesen Einstellungen können Sie auch die Deckkraft der Overlays verwalten.
Für fortgeschrittene Benutzer ist es möglich, benutzerdefinierte Basemaps und Overlays durch die Bereitstellung von <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>hinzuzufügen, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>oder <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">Mapbox Stil JSON</a> URLs.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.

View File

@@ -33,7 +33,7 @@ Erstelle eine Kopie der aktuell ausgewählten Dateien.
Delete the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Lösche alles
Delete all files.

View File

@@ -18,7 +18,7 @@ Mit diesem Tool kannst du Höhendaten zu Routen und [Points of Interest] (../gpx
<DocsNote>
Höhendaten werden von <a href="https://mapbox.com" target="_blank">Mapbox</a> bereitgestellt.
Du kannst mehr über den Ursprung und die Genauigkeit des Tools in der <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">Dokumentation</a> erfahren.
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
</DocsNote>

View File

@@ -5,6 +5,7 @@ title: Zusammenfügen
<script>
import { Group } from '@lucide/svelte';
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
import DocsNote from '$lib/components/docs/DocsNote.svelte';
</script>
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
@@ -15,6 +16,13 @@ Um dieses Tool nutzen zu können, musst du [select](../files-and-stats) mehrere
- Die zweite Option kann verwendet werden, um Dateien mit mehreren [Tracks oder Segments](../gpx) zu erstellen oder zu verwalten.
Das Zusammenführen von Dateien (oder Tracks) führt zu einer einzigen Datei (oder Track), die alle Tracks (oder Segmente) aus der Auswahl enthält.
<DocsNote>
Selected items are merged in the order they appear in the files list.
Reorder items by drag-and-drop if needed.
</DocsNote>
<div class="flex flex-row justify-center">
<Merge class="text-foreground p-3 border rounded-md shadow-lg" />
</div>

View File

@@ -12,6 +12,7 @@ title: Files and statistics
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -84,20 +85,18 @@ You can also use the mouse wheel to zoom in and out on the elevation profile, an
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>
</div>
<div class="flex flex-col items-center -mt-6">
<div class="h-10 w-fit">
<div class="flex flex-col items-center w-full">
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={120}
orientation={'horizontal'}
/>
</div>
</div>
### Additional data

View File

@@ -5,7 +5,7 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Help keep the website free (and ad-free)
Each time you add or move GPS points, our servers calculate the best route on the road network.
We also use APIs from <a href="https://mapbox.com" target="_blank">Mapbox</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
We also use APIs from <a href="https://maptiler.com" target="_blank">MapTiler</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
Unfortunately, this is expensive.
If you enjoy using this tool and find it valuable, please consider making a small donation to help keep the website free and ad-free.

View File

@@ -2,11 +2,11 @@
import { Languages } from '@lucide/svelte';
</script>
## <Languages size="18" class="inline-block align-baseline" /> Translation
## <Languages size="18" class="inline-block align-baseline" />Μετάφραση
The website is translated by volunteers using a collaborative translation platform.
You can contribute by adding or improving translations on our <a href="https://crowdin.com/project/gpxstudio" target="_blank">Crowdin project</a>.
Αυτός ο ιστότοπος μεταφράζεται από εθελοντές μέσω μια πλατφόρμας συνεργατικής μετάφρασης.
Μπορείτε να συνεισφέρετε προσθέτοντας ή βελτιώνοντας μεταφράσεις στο <a href="https://crowdin.com/project/gpxstudio" target="_blank"> Crowdin έργο</a>.
If you would like to start translating into a new language, please <a href="#contact">get in touch</a>.
Αν θέλετε να ξεκινήσετε μετάφραση μιας νέας γλώσσας, παρακαλώ <a href="#contact">επικοινωνήστε μαζί μας<a href="#contact">.
Any help is greatly appreciated!
Οποιαδήποτε βοήθεια εκτιμάται ιδιαίτερα!

View File

@@ -13,8 +13,8 @@ You can use **gpx.studio** to create maps showing your GPX files and embed them
All you need is:
1. A <a href="https://account.mapbox.com/auth/signup" target="_blank">Mapbox access token</a> to load the map, and
2. GPX files hosted on your server or on Google Drive, or accessible via a public URL.
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
You can then play with the configurator below to customize your map and generate the corresponding HTML code.

View File

@@ -58,7 +58,9 @@ The map layers button allows you to switch between different basemaps, and toggl
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Outdoors</a> basemap.
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.maptiler.com/maps/outdoor-topo/" target="_blank">MapTiler Topo</a> basemap.
</span>
</div>
@@ -67,4 +69,4 @@ They can be enabled in the [map layer settings dialog](./menu/settings).
In these settings, you can also manage the opacity of the overlays.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">Mapbox style JSON</a> URLs.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.

View File

@@ -28,9 +28,9 @@ Change the language used in the interface.
<DocsNote>
You can contribute by adding or improving translations on our <a href="https://crowdin.com/project/gpxstudio" target="_blank">Crowdin project</a>.
If you would like to start translating into a new language, please <a href="#contact">get in touch</a>.
Any help is greatly appreciated!
Μπορείτε να συνεισφέρετε προσθέτοντας ή βελτιώνοντας μεταφράσεις στο <a href="https://crowdin.com/project/gpxstudio" target="_blank"> Crowdin έργο</a>.
Αν θέλετε να ξεκινήσετε μετάφραση μιας νέας γλώσσας, παρακαλώ <a href="#contact">επικοινωνήστε μαζί μας<a href="#contact">.
Οποιαδήποτε βοήθεια εκτιμάται ιδιαίτερα!
</DocsNote>

View File

@@ -1,5 +1,5 @@
---
title: Clean
title: Καθαρισμός
---
<script>
@@ -9,9 +9,9 @@ title: Clean
# <SquareDashedMousePointer size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
When the clean tool is selected, dragging the map will create a rectangular selection.
Όταν επιλεχθεί το εργαλείο καθαρισμού, σέρνοντας το χάρτη δημιουργείται μια ορθογώνια επιλογή.
Depending on the options selected in the dialog shown below, clicking the delete button will remove GPS points and/or [points of interest](../gpx) located either inside or outside the selection.
Ανάλογα με τις επιλεγμένες ρυθμίσεις στο παράθυρο παρακάτω, πατώντας το κουμπί διαγραφής θα αφαιρεθούν σημεία GPS και/ή [σημεία ενδιαφέροντος](../gpx) που βρίσκονται είτε μέσα είτε έξω από την επιλογή.
<div class="flex flex-row justify-center">
<Clean class="text-foreground p-3 border rounded-md shadow-lg" />

View File

@@ -18,7 +18,7 @@ This tool allows you to add elevation data to traces and [points of interest](..
<DocsNote>
Elevation data is provided by <a href="https://mapbox.com" target="_blank">Mapbox</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">documentation</a>.
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
</DocsNote>

View File

@@ -5,6 +5,7 @@ title: Merge
<script>
import { Group } from '@lucide/svelte';
import Merge from '$lib/components/toolbar/tools/Merge.svelte';
import DocsNote from '$lib/components/docs/DocsNote.svelte';
</script>
# <Group size="24" class="inline-block" style="margin-bottom: 5px" /> { title }
@@ -15,6 +16,13 @@ To use this tool, you need to [select](../files-and-stats) multiple files, [trac
- The second option can be used to create or manage files with multiple [tracks or segments](../gpx).
Merging files (or tracks) will result in a single file (or track) containing all tracks (or segments) from the selection.
<DocsNote>
Selected items are merged in the order they appear in the files list.
Reorder items by drag-and-drop if needed.
</DocsNote>
<div class="flex flex-row justify-center">
<Merge class="text-foreground p-3 border rounded-md shadow-lg" />
</div>

View File

@@ -12,7 +12,6 @@ title: Files and statistics
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -85,7 +84,6 @@ You can also use the mouse wheel to zoom in and out on the elevation profile, an
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>

View File

@@ -5,7 +5,7 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Help keep the website free (and ad-free)
Each time you add or move GPS points, our servers calculate the best route on the road network.
We also use APIs from <a href="https://maptiler.com" target="_blank">MapTiler</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
We also use APIs from <a href="https://mapbox.com" target="_blank">Mapbox</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
Unfortunately, this is expensive.
If you enjoy using this tool and find it valuable, please consider making a small donation to help keep the website free and ad-free.

View File

@@ -0,0 +1,5 @@
Mapbox is the company that provides some of the beautiful maps on this website.
They also develop the <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">map engine</a> which powers **gpx.studio**.
We are incredibly fortunate and grateful to be part of their <a href="https://mapbox.com/community" target="_blank">Community</a> program, which supports nonprofits, educational institutions, and positive impact organizations.
This partnership allows **gpx.studio** to benefit from Mapbox tools at discounted prices, greatly contributing to the financial viability of the project and enabling us to offer the best possible user experience.

View File

@@ -12,7 +12,7 @@ title: Integration
You can use **gpx.studio** to create maps showing your GPX files and embed them in your website.
All you need is:
1. A <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load the map, and
1. A <a href="https://account.mapbox.com/auth/signup" target="_blank">Mapbox access token</a> to load the map, and
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL.
You can then play with the configurator below to customize your map and generate the corresponding HTML code.

View File

@@ -58,7 +58,7 @@ Only one basemap can be displayed at a time.
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.maptiler.com/maps/outdoor-topo/" target="_blank">MapTiler Topo</a> basemap.
Hover over the map to show the <a href="https://hiking.waymarkedtrails.org" target="_blank">Waymarked Trails hiking</a> overlay on top of the <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Outdoors</a> basemap.
</span>
</div>
@@ -67,4 +67,4 @@ They can be enabled in the [map layer settings dialog](./menu/settings).
In these settings, you can also manage the opacity of the overlays.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">Mapbox style JSON</a> URLs.

View File

@@ -18,7 +18,7 @@ This tool allows you to add elevation data to traces and [points of interest](..
<DocsNote>
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
Elevation data is provided by <a href="https://mapbox.com" target="_blank">Mapbox</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">documentation</a>.
</DocsNote>

View File

@@ -12,6 +12,7 @@ title: Archivos y estadísticas
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -84,20 +85,18 @@ Puede usar el ratón para acercar o alejar el perfil de elevación y moverse hac
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>
</div>
<div class="flex flex-col items-center -mt-6">
<div class="h-10 w-fit">
<div class="flex flex-col items-center w-full">
<GPXStatistics
{gpxStatistics}
{slicedGPXStatistics}
panelSize={120}
orientation={'horizontal'}
/>
</div>
</div>
### Datos adicionales

View File

@@ -5,7 +5,7 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Ayude a mantener este sitio gratis (y libre de anuncios)
Cada vez que añade o mueve puntos GPS, nuestros servidores calculan la mejor ruta en la red de carreteras.
También usamos APIs de <a href="https://mapbox.com" target="_blank">Mapbox</a> para mostrar hermosos mapas, obtener datos de elevación y permitirle buscar lugares.
We also use APIs from <a href="https://maptiler.com" target="_blank">MapTiler</a> to display beautiful maps, retrieve elevation data and allow you to search for places.
Por desgracia, esto tiene un coste económico.
Si disfruta usando esta herramienta y la encuentra valiosa, por favor considere hacer una pequeña donación para ayudar a mantener este sitio gratis y libre de anuncios.

View File

@@ -13,8 +13,8 @@ Puede usar **gpx.studio** para crear mapas que muestren sus archivos GPX e integ
Todo lo que necesita es:
1. Un <a href="https://account.mapbox.com/auth/signup" target="_blank">token de acceso a Mapbox</a> para cargar el mapa, y
2. Archivos GPX alojados en su servidor, en Google Drive o accesibles a través de una URL pública.
1. GPX files hosted on your server or on Google Drive, or accessible via a public URL;
2. _Optional:_ a <a href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/maps/" target="_blank">MapTiler key</a> to load MapTiler maps.
Luego puede jugar con el configurador de abajo para personalizar su mapa y generar el código HTML correspondiente.

View File

@@ -58,7 +58,9 @@ El botón de capas de mapa le permite cambiar entre diferentes mapas bases y alt
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Sitúe el cursor sobre el mapa para mostrar la capa <a href="https://hiking.waymarkedtrails.org" target="_blank">Rutas Señalizadas de senderismo</a> sobre el mapa base <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Exteriores</a>.
Pasa el ratón sobre el mapa para que la capa de <a href="https://hiking.waymarkedtrails.org" target="_blank">Caminatas por Sendas Marcadas</a> aparezca superpuesta al mapa base <a href="https://www.maptiler.com/maps/outdoor-topo/" target="_blank">MapTiler</a>.
</span>
</div>
@@ -67,4 +69,4 @@ Pueden activarse en la [configuración de capas del mapa](./menu/settings).
En estos ajustes, también puede administrar la opacidad de las capas superpuestas.
Para los usuarios avanzados, es posible añadir mapas base y superposiciones personalizadas proporcionando <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a> o URLs <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">JSON estilo Mapbox</a>.
For advanced users, it is possible to add custom basemaps and overlays by providing <a href="https://en.wikipedia.org/wiki/Web_Map_Tile_Service" target="_blank">WMTS</a>, <a href="https://en.wikipedia.org/wiki/Web_Map_Service" target="_blank">WMS</a>, or <a href="https://maplibre.org/maplibre-style-spec/" target="_blank">MapLibre style JSON</a> URLs.

View File

@@ -18,7 +18,7 @@ Le permite añadir datos de desnivel a trazas y [puntos de interés](../gpx), o
<DocsNote>
Los datos de desnivel son proporcionados por <a href="https://mapbox.com" target="_blank">Mapbox</a>.
Puede aprender más sobre su origen y precisión en la <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">documentación</a>.
Elevation data is provided by <a href="https://maptiler.com" target="_blank">MapTiler</a>.
You can learn more about its origin and accuracy in the <a href="https://docs.maptiler.com/guides/map-tiling-hosting/data-hosting/rgb-terrain-by-maptiler/" target="_blank">documentation</a>.
</DocsNote>

Some files were not shown because too many files have changed in this diff Show More