406 Commits

Author SHA1 Message Date
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
277 changed files with 3192 additions and 3077 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,6 +85,7 @@ 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://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,7 +13,7 @@ title: Інтэграцыя
Усё, што вам трэба, гэта:
1. <a href="https://account.mapbox.com/auth/signup" target="_blank">Ключ доступу Mapbox</a> для загрузкі карты і
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
2. Файлы GPX, размешчаныя на вашым серверы або на Google Drive, або даступныя праз публічны URL.
Затым вы можаце пагуляць з канфігуратарам ніжэй, каб наладзіць сваю карту і стварыць адпаведны HTML-код.

View File

@@ -58,7 +58,7 @@ 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 +67,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

@@ -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,6 +85,7 @@ També pots utilitzar la rodeta del ratolí per apropar o allunyar el perfil d'e
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>

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,7 +13,7 @@ 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
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
2. Arxius GPX allotjats en el teu servidor, a Google Drive o accessibles a través d'una URL pública.
Aleshores pots jugar amb el configurador de sota per personalitzar el teu mapa i generar el corresponent codi HTML.

View File

@@ -55,7 +55,10 @@ 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 +67,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

@@ -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,6 +85,7 @@ Pomocí kolečka myši můžete také výškový profil přiblížit a oddálit
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>

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,7 +13,7 @@ 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,
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
2. Soubory GPX umístěné na vašem serveru nebo na Disku Google, nebo přístupné prostřednictvím veřejné adresy URL.
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,7 @@ 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 +67,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

@@ -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,6 +85,7 @@ 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://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,7 +13,7 @@ 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
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
2. 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 @@ 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 +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://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

@@ -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,6 +85,7 @@ Sie können auch das Mausrad verwenden, um auf dem Höhenprofil heranzuzoomen un
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>

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,7 +13,7 @@ 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
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
2. GPX-Dateien, die auf Ihrem Server oder Google Drive gehostet werden oder über eine öffentliche URL erreichbar sind.
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,7 @@ 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 +67,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

@@ -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,6 +85,7 @@ 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://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,7 +13,7 @@ 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
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
2. 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 @@ 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 +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://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

@@ -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,6 +85,7 @@ Puede usar el ratón para acercar o alejar el perfil de elevación y moverse hac
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>

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,7 +13,7 @@ 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
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
2. Archivos GPX alojados en su servidor, en Google Drive o accesibles a través de una URL pública.
Luego puede jugar con el configurador de abajo para personalizar su mapa y generar el código HTML correspondiente.

View File

@@ -58,7 +58,7 @@ 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>.
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 +67,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>

View File

@@ -12,6 +12,7 @@ title: Fitxategiak eta estatistikak
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
let hoveredPoint = writable(null);
let additionalDatasets = writable(['speed', 'atemp']);
let elevationFill = writable(undefined);
</script>
@@ -83,6 +84,7 @@ Saguaren gurpila ere erabil dezakezu altueren profila handitzeko eta mugitzeko e
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>

View File

@@ -4,7 +4,8 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Lagundu mantentzen webgunea doan (eta propagandarik gabe)
GPS puntuak gehitzen edo mugitzen dituzun bakoitzean, gure zerbitzariek bide sareko ibilbide onena kalkulatzen dute.<a href="https://mapbox.com" target="_blank">Mapbox</a>en APIak erabiltzen ditugu erakusteko mapa argiak, kota-datuak eskaintzeko eta ahalbidetzeko lekuen bilaketa.
GPS puntuak gehitzen edo mugitzen dituzun bakoitzean, gure zerbitzariek bide sareko ibilbide onena kalkulatzen dute.
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.
Tamalez, hau garestia da.
Tresna hau erabiltzen baduzu eta baliotsua suertatzen bazaizu, kontuan hartu dohaintza txiki bat egitea webgunea doakoa eta iragarkirik gabe mantentzen laguntzeko.

View File

@@ -13,7 +13,7 @@ title: Integrazioa
Behar duzun guztia hau da:
1. <a href="https://account.mapbox.com/auth/signup" target="_blank">Mapbox sarbiderako tokena</a> mapa kargatzeko, eta
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
2. GPX fitxategiak zure zerbitzarian, hodeian, Google Drive-n edo eskuragarri URL publiko baten bidez.
Ondoren, konfiguratzailearekin jolastu dezakezu zure mapa pertsonalizatzeko eta dagokion HTML kodea sortzeko.

View File

@@ -58,7 +58,7 @@ Maparen geruzen botoiak mapa-oinarri desberdinen artean aldatzeko aukera ematen
<div class="flex flex-col items-center">
<DocsLayers />
<span class="text-sm text-center mt-2">
Pasa maparen gainetik <a href="https://hiking.waymarkedtrails.org" target="_blank">mendiko bideak</a> ikusteko <a href="https://www.mapbox.com/maps/outdoors" target="_blank">Mapbox Outdoors</a> mapa oinarriaren gainean.
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 +67,4 @@ Global eta tokiko mapa oinarri bilduma handia dago **gpx.studio**-n eskuragarri,
Ezarpen horietan, gainjartzeen opakutasuna ere kudeatu dezakezu.
Erabiltzaile aurreratuetarako, mapa oinarri pertsonalizatuak gehitu daitezke <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>, edo <a href="https://docs.mapbox.com/help/glossary/style/" target="_blank">Mapbox Style Json</a> URLak gehituz.
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 @@ Tresna honen bidez elebazio datuak gehitzen ahal dira ibilbidetan eta [interesgu
<DocsNote>
Altuera datuen iturria <a href="https://mapbox.com" target="_blank">Mapbox</a> da.
Hauen jatorriaz edo xehetasunez informazio gehiago <a href="https://docs.mapbox.com/data/tilesets/reference/mapbox-terrain-dem-v1/#elevation-data" target="_blank">dokumentazioan</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

@@ -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,6 +85,7 @@ You can also use the mouse wheel to zoom in and out on the elevation profile, an
<ElevationProfile
{gpxStatistics}
{slicedGPXStatistics}
{hoveredPoint}
{additionalDatasets}
{elevationFill}
/>

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