kopia lustrzana https://github.com/FacilMap/facilmap
Porównaj commity
488 Commity
Autor | SHA1 | Data |
---|---|---|
Candid Dauth | 859f6e7371 | |
Candid Dauth | 6fbda7ff8a | |
gallegonovato | a2c6c985a5 | |
gallegonovato | ea778af1a7 | |
hugoalh | 6ad7d29c5b | |
Candid Dauth | 3718cdbe5b | |
Candid Dauth | 5df3fe99f6 | |
Candid Dauth | c715bc1db0 | |
Candid Dauth | 2386332134 | |
Adolfo Jayme Barrientos | 7a2415bc7c | |
Candid Dauth | e51e8e4e9e | |
Adolfo Jayme Barrientos | 51f7ad29b3 | |
Roman Deev | 48912107c8 | |
Candid Dauth | 78af1d76a0 | |
Adolfo Jayme Barrientos | 8965fafa04 | |
Adolfo Jayme Barrientos | 4306a58298 | |
Adolfo Jayme Barrientos | 846041999b | |
Adolfo Jayme Barrientos | ae8b78e336 | |
Candid Dauth | dee17aad75 | |
Candid Dauth | 8a12ce3984 | |
Candid Dauth | 19a8e9f770 | |
Candid Dauth | 4152c667bf | |
Candid Dauth | 1ae85c2232 | |
Candid Dauth | e9afb54806 | |
Candid Dauth | 469d2ee369 | |
Candid Dauth | 47ddfa9f21 | |
Candid Dauth | 301f0447e5 | |
Candid Dauth | 0661448444 | |
Adolfo Jayme Barrientos | 229a5dc551 | |
Adolfo Jayme Barrientos | 575a7dad25 | |
Adolfo Jayme Barrientos | f6d6dd5d5b | |
Adolfo Jayme Barrientos | abdb75fa5a | |
Adolfo Jayme Barrientos | 8838865974 | |
Adolfo Jayme Barrientos | 2632861157 | |
Adolfo Jayme Barrientos | 5291a6e81b | |
Adolfo Jayme Barrientos | 362da42466 | |
Adolfo Jayme Barrientos | 98798c2019 | |
Allan Nordhøy | b99668d234 | |
Candid Dauth | d3bb95048d | |
Candid Dauth | acf717f8e0 | |
Candid Dauth | e9f64750f5 | |
Candid Dauth | e585e5917e | |
Candid Dauth | 9bcb312ac7 | |
Candid Dauth | 54e41bbe72 | |
Candid Dauth | 635fa8885f | |
Candid Dauth | 27c089dd69 | |
Candid Dauth | a90d98bf3b | |
Candid Dauth | b2bbe1c2a2 | |
Candid Dauth | 7a5d74dc74 | |
Candid Dauth | 7fa2208079 | |
Candid Dauth | fe74eab306 | |
Candid Dauth | 24b4c3bf96 | |
Candid Dauth | 3f7d64a305 | |
Candid Dauth | 39f3df852f | |
Candid Dauth | daa1257a60 | |
Candid Dauth | 0592d0bca7 | |
Candid Dauth | db31dc8a2a | |
Candid Dauth | a547401e08 | |
Candid Dauth | 3589ec3336 | |
Candid Dauth | e080834696 | |
Candid Dauth | c9d20cea88 | |
Candid Dauth | 78d36ff2fa | |
Roman Deev | 15f2d91d23 | |
Roman Deev | 19eda4b29f | |
Roman Deev | d9be2030b7 | |
Candid Dauth | 347edfc861 | |
Candid Dauth | 79a2c08273 | |
Candid Dauth | b863c84603 | |
Candid Dauth | 3fba16ee00 | |
Candid Dauth | 85704f17a0 | |
Candid Dauth | f9c68cc1f5 | |
Candid Dauth | 2ceab20877 | |
Candid Dauth | 381d8ee071 | |
Candid Dauth | 1075b0730e | |
Candid Dauth | 43de05bec1 | |
Candid Dauth | 6718c776fe | |
Candid Dauth | 2aad260d8c | |
Candid Dauth | a00190e05e | |
Candid Dauth | 81cd918afc | |
Candid Dauth | e1130c0a78 | |
Candid Dauth | 8f1688ff8c | |
Candid Dauth | 32568b1682 | |
Candid Dauth | 5698d0b8cc | |
Candid Dauth | 5d952d3474 | |
Candid Dauth | 89071f96c5 | |
Candid Dauth | 39e02dce13 | |
Candid Dauth | 92b5bfdb83 | |
Candid Dauth | f2816f4269 | |
Candid Dauth | f53990bce5 | |
Candid Dauth | 82cfd0f9e4 | |
Candid Dauth | fdc541bc92 | |
Candid Dauth | b8d7d597df | |
Candid Dauth | 51a79c7a3b | |
Candid Dauth | b43d281134 | |
Candid Dauth | 77bdaa9a3a | |
Candid Dauth | 56ec7bf7dc | |
Candid Dauth | 56090599af | |
Candid Dauth | 51f072524f | |
Candid Dauth | 1ebe449286 | |
Candid Dauth | 2949910ce3 | |
Roman Deev | 7f9e7378e5 | |
Roman Deev | 9b51fb9f6d | |
Candid Dauth | f2a687e971 | |
Candid Dauth | 72210d9433 | |
Candid Dauth | 14c3ab1a4a | |
Candid Dauth | 4cb2f52928 | |
Candid Dauth | b9e4fffb0f | |
Candid Dauth | 7f103d4a95 | |
Candid Dauth | c6a89d111d | |
Candid Dauth | 1d82cfa7c3 | |
Candid Dauth | 726f49d91c | |
Candid Dauth | 1b9d8fa162 | |
Candid Dauth | 3aa5d91ce7 | |
Allan Nordhøy | 44fcb27382 | |
Allan Nordhøy | 42b181e597 | |
Candid Dauth | 667b7036bb | |
Candid Dauth | e5f806c21a | |
Allan Nordhøy | ef563d357b | |
Allan Nordhøy | c009cd4dcd | |
Allan Nordhøy | 4db6d44248 | |
Candid Dauth | cc4354877a | |
Candid Dauth | 1de9134b2a | |
Candid Dauth | 0e9398bc30 | |
Candid Dauth | bcdc4c1132 | |
Candid Dauth | b0426362f6 | |
Candid Dauth | d60455dd50 | |
Candid Dauth | 899ce296a9 | |
Candid Dauth | df2dbef085 | |
Candid Dauth | 935502b640 | |
Candid Dauth | e84a5d5663 | |
Candid Dauth | aea174eb5b | |
Anonymous | c1a3de6015 | |
Candid Dauth | 3ceb01015c | |
Candid Dauth | e51378496b | |
Candid Dauth | 0a94392fea | |
Candid Dauth | 8ec7e2b798 | |
Candid Dauth | c66a53386c | |
Candid Dauth | 45e4171544 | |
Candid Dauth | 6ec952ca5a | |
Candid Dauth | 8384cdd316 | |
Candid Dauth | 326258dbd1 | |
Candid Dauth | 0878fb3d4b | |
Candid Dauth | bbbfe83b77 | |
Candid Dauth | 383864bd08 | |
Candid Dauth | bfc1fb3028 | |
Candid Dauth | 99673bdbd7 | |
Candid Dauth | 09ebec0535 | |
Candid Dauth | 010425b2cb | |
Candid Dauth | fccb4eb2fa | |
Candid Dauth | f4c6a2f0cd | |
Candid Dauth | 0ca3fa9356 | |
Candid Dauth | 64be6aa18f | |
Candid Dauth | b842586f40 | |
Candid Dauth | f3a9f6e03b | |
Candid Dauth | 64c9a2fae6 | |
Candid Dauth | 9aa954c3c4 | |
Candid Dauth | 29ef631fc4 | |
Candid Dauth | 326332eedf | |
Candid Dauth | c43878c991 | |
Candid Dauth | 4941d77e98 | |
Candid Dauth | e2b0f11c92 | |
Candid Dauth | 6044bf117c | |
Candid Dauth | f56f860812 | |
Candid Dauth | 5a407e8395 | |
Candid Dauth | 86789a9af8 | |
Candid Dauth | 3c1b59c280 | |
Candid Dauth | 0e22ed24b2 | |
Candid Dauth | 02d4ab0c42 | |
Candid Dauth | 6fa3502f58 | |
Candid Dauth | 13dd7f6afc | |
Candid Dauth | d20356fa6e | |
Candid Dauth | b9a85a1185 | |
Candid Dauth | 3b2ff2dfc9 | |
Candid Dauth | 25c79e4a73 | |
Candid Dauth | 07c059237f | |
Candid Dauth | 9cdfa4cef9 | |
Candid Dauth | b1414d3428 | |
Candid Dauth | 7ef26a46ab | |
Candid Dauth | 028cd7216b | |
Candid Dauth | 2cc372f486 | |
Candid Dauth | 3562065aec | |
Candid Dauth | 73bb22b1be | |
Candid Dauth | 33642c546d | |
Candid Dauth | 4bfe3ff4c2 | |
Candid Dauth | e8798f3cde | |
Candid Dauth | b7eaa44324 | |
Candid Dauth | bc4d97d998 | |
Candid Dauth | db8d7d6080 | |
Candid Dauth | 3d3110607c | |
Candid Dauth | 21e7aa70e3 | |
Candid Dauth | e7c377105f | |
Candid Dauth | c97e76a6b4 | |
Candid Dauth | 1b5ad5838d | |
Candid Dauth | b42e54245d | |
Candid Dauth | 26753061a7 | |
Candid Dauth | 5071e5b417 | |
Candid Dauth | 16546e1585 | |
Candid Dauth | e3660ba870 | |
Candid Dauth | bc3cf1098b | |
Candid Dauth | de6b56de6e | |
Candid Dauth | 8d27a1a975 | |
Candid Dauth | aaf5d132b1 | |
Candid Dauth | 89cd0b4f20 | |
Candid Dauth | 4583476474 | |
Candid Dauth | d3e3c513f6 | |
Candid Dauth | 2999e4646c | |
Candid Dauth | d7f3524d91 | |
Candid Dauth | 082fa4379c | |
Candid Dauth | b97e366030 | |
Candid Dauth | f920856100 | |
Candid Dauth | 24c5b6d1db | |
Candid Dauth | e3030a6b55 | |
Candid Dauth | 7ab5594144 | |
Candid Dauth | 81b05e5bbc | |
Candid Dauth | 03072a29e9 | |
Candid Dauth | 97c5b7506e | |
Candid Dauth | 964d98cd1b | |
Candid Dauth | 6fdbd995ea | |
Candid Dauth | 441780beab | |
Candid Dauth | b211800973 | |
Candid Dauth | f713aceb76 | |
Candid Dauth | fac5648945 | |
Candid Dauth | 6fda31ad35 | |
Candid Dauth | fba5049c65 | |
Candid Dauth | a9e75cce5e | |
Candid Dauth | 7a63abc32f | |
Candid Dauth | b04a58eedd | |
Candid Dauth | 03dbf5d33e | |
Candid Dauth | fdd174b21a | |
Candid Dauth | b13025c777 | |
Candid Dauth | 8d857d4f4e | |
Candid Dauth | 0ef09d539d | |
Candid Dauth | 308dfe7dc9 | |
Candid Dauth | df37a4fb4a | |
Candid Dauth | a2d256e307 | |
Candid Dauth | 45256247cf | |
Candid Dauth | c27a776918 | |
Candid Dauth | 656f408cac | |
Candid Dauth | 69ef717c44 | |
Candid Dauth | cbf51d148a | |
Candid Dauth | 9550c91dae | |
Candid Dauth | b7b70b6cf0 | |
Candid Dauth | bdf6e186a8 | |
Candid Dauth | 29e0942019 | |
Candid Dauth | f1e82ab926 | |
Candid Dauth | 2c132c1ad6 | |
Candid Dauth | 38a642450c | |
Candid Dauth | df78811dd7 | |
Candid Dauth | d3f482f284 | |
Candid Dauth | 21e22fa91e | |
Candid Dauth | 52e78b0f2e | |
Candid Dauth | 8e97093eae | |
Candid Dauth | fa08ff153e | |
Candid Dauth | 94f3755857 | |
Candid Dauth | 6185c68d50 | |
Candid Dauth | 04ce90720c | |
Candid Dauth | 54e5e9f00f | |
Candid Dauth | 8365cf252d | |
Candid Dauth | 5471cb4d7d | |
Candid Dauth | 396457aff2 | |
Candid Dauth | c7d52cb1b6 | |
Candid Dauth | 85ccf30960 | |
Candid Dauth | fd7330c7a6 | |
Candid Dauth | 5d129c2072 | |
Candid Dauth | 40c4be2bba | |
Candid Dauth | 2f901114c2 | |
Candid Dauth | d7d93ae773 | |
Candid Dauth | 2418a5040c | |
Candid Dauth | b8eec23cf5 | |
Candid Dauth | d597d546ca | |
Candid Dauth | 3efcd06e67 | |
Candid Dauth | fe7c8f1a8d | |
Candid Dauth | 4fc3f51b6c | |
Candid Dauth | e1795d1297 | |
Candid Dauth | aa4b82a69f | |
Candid Dauth | 485aa98e2b | |
Candid Dauth | f842134d38 | |
Candid Dauth | fb2b631cb5 | |
Candid Dauth | f4b45cc197 | |
Candid Dauth | e67aceb3ea | |
Candid Dauth | 1a12e6abea | |
Candid Dauth | dc3973f0c0 | |
Candid Dauth | fad68137a1 | |
Candid Dauth | 2e2a363802 | |
Candid Dauth | a3c7cb3f5b | |
Candid Dauth | fe999df48c | |
Candid Dauth | 7c04ea3bdd | |
Candid Dauth | 8a3ff49f70 | |
Candid Dauth | 401c08c190 | |
Candid Dauth | 112b5ab047 | |
Candid Dauth | 9494e4c379 | |
Candid Dauth | b61de3e79f | |
Candid Dauth | 6dfbb0c130 | |
Candid Dauth | d7d6bcfed0 | |
Candid Dauth | 6fe73c2356 | |
Candid Dauth | ff94d09df0 | |
Candid Dauth | b177f5b043 | |
Candid Dauth | 73a9fb4d34 | |
Candid Dauth | e0bb064bc8 | |
Candid Dauth | f1d23def3a | |
Candid Dauth | 9635fbdd05 | |
Candid Dauth | 324dac6401 | |
Candid Dauth | 558067bb69 | |
Candid Dauth | 4cf3e2817d | |
Candid Dauth | ad1901f8a0 | |
Candid Dauth | 973878593c | |
Candid Dauth | 8e5f518fef | |
Candid Dauth | a012e1f0ec | |
Candid Dauth | bbd7c8ce05 | |
Candid Dauth | fb131f4a8c | |
Candid Dauth | f9c23366e3 | |
Candid Dauth | 3a8fb8bd4c | |
Candid Dauth | cdd17d6945 | |
Candid Dauth | 92b54d3b72 | |
Candid Dauth | 409eb86f4d | |
Candid Dauth | 5500153ef6 | |
Candid Dauth | b2169eb968 | |
Candid Dauth | 0afcbcfef9 | |
Candid Dauth | 757823fcb5 | |
Candid Dauth | 7977b051e5 | |
Candid Dauth | d8c79e950c | |
Candid Dauth | e6d612f772 | |
Candid Dauth | 9e2d259f07 | |
Candid Dauth | f456da2e7a | |
Candid Dauth | d46c49d88e | |
Candid Dauth | ca7ba85ea0 | |
Candid Dauth | 0efa96f0bd | |
Candid Dauth | 53accc1157 | |
Candid Dauth | 997509cda0 | |
Candid Dauth | 304b56d9c6 | |
Candid Dauth | e7d17f2469 | |
Candid Dauth | 8682f2754e | |
Candid Dauth | b9ac3b31a2 | |
Candid Dauth | fdfc443e31 | |
Candid Dauth | 8c1c36cb80 | |
Candid Dauth | 7838195e10 | |
Candid Dauth | 996ec4516f | |
Candid Dauth | e281c4ee26 | |
Candid Dauth | 11d58b3044 | |
Candid Dauth | f4555ee628 | |
Candid Dauth | 44c16a2f76 | |
Candid Dauth | a7b7693b43 | |
Candid Dauth | 0d4760bf4b | |
Candid Dauth | 1da9f21eac | |
Candid Dauth | 31ca895acc | |
Candid Dauth | b8583ce570 | |
Candid Dauth | 33ebfcdee1 | |
Candid Dauth | bc2c14dd25 | |
Candid Dauth | e12dd1920e | |
Candid Dauth | 6dc6585b24 | |
encrypt | cd748dda34 | |
encrypt | 5408c8059b | |
encrypt | 173e81b154 | |
Candid Dauth | 4871eea538 | |
Candid Dauth | 41c5b20455 | |
Candid Dauth | a2c7bbac1c | |
Candid Dauth | 869fb9741d | |
Candid Dauth | b96d64744c | |
Candid Dauth | 11c22e008f | |
Candid Dauth | 0243da52ed | |
Candid Dauth | 38df836d40 | |
Candid Dauth | 5a796db1c3 | |
Candid Dauth | 5065ef4df9 | |
Candid Dauth | 196528af3d | |
Candid Dauth | 69386ea1b5 | |
Candid Dauth | 78199d9351 | |
Candid Dauth | 650e2c8f02 | |
Candid Dauth | a87fa429f9 | |
Candid Dauth | aaa301c59d | |
Candid Dauth | effcde8bd7 | |
Candid Dauth | 99f23af11a | |
Candid Dauth | 06493182d4 | |
Candid Dauth | 3ca42f34e5 | |
Candid Dauth | 6b01a2093c | |
Candid Dauth | b83d3ba1dd | |
Candid Dauth | 43dd5b4c05 | |
Candid Dauth | d67e8d6c53 | |
Candid Dauth | f55e82ae6b | |
Candid Dauth | 5ff2d141e5 | |
Candid Dauth | 35dfb360a2 | |
Candid Dauth | d5ae145e92 | |
Candid Dauth | d50fda00c2 | |
Candid Dauth | 344ba3ec74 | |
Candid Dauth | 0bf164d917 | |
Candid Dauth | 110cb986ab | |
Candid Dauth | 964cdb40b2 | |
Candid Dauth | ccb51fa743 | |
Candid Dauth | 33068b7a32 | |
Candid Dauth | 177788658b | |
Candid Dauth | 5a1040d6fb | |
Candid Dauth | a37de7d2d3 | |
Candid Dauth | a7f319c5c7 | |
Candid Dauth | abf90b9192 | |
Candid Dauth | d822adf014 | |
Candid Dauth | 1726f4dfe5 | |
Candid Dauth | 68b43bec32 | |
Candid Dauth | 12b8e737bd | |
Candid Dauth | 2fac2bd04e | |
Candid Dauth | 681b564251 | |
Candid Dauth | 6c714a8455 | |
Candid Dauth | 227bd87599 | |
Candid Dauth | a91709a997 | |
Candid Dauth | be3f8ee952 | |
Candid Dauth | 84d01630ab | |
Candid Dauth | 358825a55e | |
Candid Dauth | f4938545ab | |
Candid Dauth | 488cc9bfc4 | |
Candid Dauth | 72b9f77bb5 | |
Candid Dauth | 69f15efc9e | |
Candid Dauth | c5efc2d391 | |
Candid Dauth | 3195085be1 | |
Candid Dauth | fca84d778a | |
Candid Dauth | deb2a29d1d | |
Candid Dauth | b29dc115df | |
Candid Dauth | 6454f11ac2 | |
Candid Dauth | 6f787064d6 | |
Candid Dauth | ee571b1ea7 | |
Candid Dauth | 473c602323 | |
Candid Dauth | edcfa17c06 | |
Candid Dauth | e4dddde03d | |
Candid Dauth | 2aace15aa0 | |
Candid Dauth | ce776a9b21 | |
Candid Dauth | bb8053c0a2 | |
Candid Dauth | 5aadfc04e0 | |
Candid Dauth | 0f5c0ca5e2 | |
Candid Dauth | 12a25cb486 | |
Candid Dauth | 59f6fd3763 | |
Candid Dauth | 68a97b1a5e | |
Candid Dauth | a9ff318aa5 | |
Candid Dauth | db6d3fcb76 | |
Candid Dauth | f3900ac083 | |
Candid Dauth | 10a3356609 | |
Candid Dauth | a047a55330 | |
Candid Dauth | cde789cca0 | |
Candid Dauth | 8a198e17ff | |
Candid Dauth | 827e6c7afa | |
Candid Dauth | d22931710c | |
Candid Dauth | f110cbe374 | |
Candid Dauth | 51e6069c7f | |
Candid Dauth | d9669e4641 | |
Candid Dauth | fbdea44c5b | |
Candid Dauth | 4903d3039e | |
Candid Dauth | fa27bd0445 | |
Candid Dauth | 9a239ae056 | |
Candid Dauth | 2d67b2cadf | |
Candid Dauth | ec6e4a0076 | |
Candid Dauth | 354bb2c502 | |
Candid Dauth | d20cf1f8a9 | |
Win Olario | 50eb331f6d | |
Win Olario | f8077991fa | |
Candid Dauth | 799c23e61d | |
Candid Dauth | 95e13e7491 | |
Candid Dauth | 89bc715fd6 | |
Candid Dauth | 0a42f6b502 | |
Candid Dauth | 8548fdf8a6 | |
Candid Dauth | 4c5a1230c1 | |
Candid Dauth | 6a426c0a07 | |
Candid Dauth | 3ecb6f2a6b | |
Candid Dauth | df38228aa2 | |
Candid Dauth | ba62fb59e3 | |
Candid Dauth | 0a780f45c3 | |
Candid Dauth | 5c739f66e4 | |
Candid Dauth | 73fc9cd22d | |
Candid Dauth | 3056b0d6d1 | |
Candid Dauth | bf6b04ddea | |
Candid Dauth | e5f92ed235 | |
Candid Dauth | ffac638528 | |
Candid Dauth | fc0d86c046 | |
Candid Dauth | ced3d8d379 | |
Candid Dauth | dfdd1b328b | |
Candid Dauth | dedbbd693e | |
Candid Dauth | 51fd8c4ef3 | |
Candid Dauth | 17a3b981ab | |
Candid Dauth | 986d62b87f | |
Candid Dauth | 1961a956b8 | |
Candid Dauth | 001979afb8 | |
Candid Dauth | c40f3174bc | |
Candid Dauth | f02a99bcc5 | |
Candid Dauth | 7cf01a9abe | |
Candid Dauth | 9c20ef6a0d | |
Candid Dauth | 02bfdb86a9 | |
Candid Dauth | 4a55d37f79 | |
Candid Dauth | 27717e3fc2 | |
Candid Dauth | 5930f5e72d | |
Candid Dauth | d97c875ab2 | |
Candid Dauth | 7d8a20c5ae | |
Candid Dauth | 894e51fe04 | |
Candid Dauth | 2f920ccc90 |
|
@ -1,4 +1,8 @@
|
|||
node_modules
|
||||
*/node_modules
|
||||
*/dist
|
||||
docs
|
||||
*/out
|
||||
*/out.node
|
||||
docs
|
||||
.github
|
||||
Dockerfile
|
|
@ -1,18 +1,32 @@
|
|||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint', 'import'],
|
||||
extends: ['plugin:import/typescript'],
|
||||
ignorePatterns: ["**/dist/*", "**/out/*", "**/out.*/*", "**/vite.config.ts.timestamp-*.mjs"],
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
project: ["*/tsconfig.json", "*/tsconfig.node.json"],
|
||||
extraFileExtensions: [".vue"]
|
||||
},
|
||||
plugins: ["@typescript-eslint", "import"],
|
||||
extends: [
|
||||
"plugin:import/typescript",
|
||||
"plugin:vue/vue3-essential"
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
extends: ["plugin:@typescript-eslint/disable-type-checked"],
|
||||
files: ["**/*.js", "**/*.cjs"]
|
||||
}
|
||||
],
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/explicit-module-boundary-types": ["warn", { "allowArgumentsExplicitlyTypedAsAny": true }],
|
||||
"import/no-unresolved": ["error", { "ignore": [ "geojson" ], "caseSensitive": true }],
|
||||
"import/no-unresolved": ["error", { "ignore": [ "geojson", "virtual:icons" ], "caseSensitive": true }],
|
||||
"import/no-extraneous-dependencies": ["error"],
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { "args": "none" }],
|
||||
"import/no-named-as-default": ["warn"],
|
||||
"import/no-named-as-default-member": ["warn"],
|
||||
"import/no-duplicates": ["warn"],
|
||||
"import/namespace": ["error"],
|
||||
"import/default": ["error"],
|
||||
|
@ -21,6 +35,11 @@ module.exports = {
|
|||
"@typescript-eslint/prefer-as-const": ["error"],
|
||||
"no-restricted-globals": ["error", "$"],
|
||||
"no-restricted-imports": ["error", "vue/types/umd"],
|
||||
"vue/multi-word-component-names": ["off"],
|
||||
"@typescript-eslint/no-base-to-string": ["error"],
|
||||
"@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: false }],
|
||||
"vue/return-in-computed-property": ["off"],
|
||||
"@typescript-eslint/no-floating-promises": ["error"],
|
||||
|
||||
"constructor-super": ["error"],
|
||||
"for-direction": ["error"],
|
||||
|
@ -57,7 +76,6 @@ module.exports = {
|
|||
"no-obj-calls": ["error"],
|
||||
"no-octal": ["error"],
|
||||
"no-prototype-builtins": ["error"],
|
||||
"no-redeclare": ["error"],
|
||||
"no-regex-spaces": ["error"],
|
||||
"no-self-assign": ["error"],
|
||||
"no-setter-return": ["error"],
|
||||
|
@ -75,5 +93,12 @@ module.exports = {
|
|||
"require-yield": ["error"],
|
||||
"use-isnan": ["error"],
|
||||
"valid-typeof": ["error"]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {
|
||||
"project": ["tsconfig.json", "*/tsconfig.json"],
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
github: [FacilMap]
|
||||
ko_fi: facilmap
|
||||
liberapay: facilmap
|
||||
patreon: facilmap
|
||||
custom: ["https://www.paypal.com/donate?hosted_button_id=FWR59UXY6HGGS"]
|
|
@ -0,0 +1,79 @@
|
|||
name: Publish Docker image (latest)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
env:
|
||||
TAG: facilmap/facilmap:latest
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Start integration test components in background
|
||||
run: docker compose -f ./integration-tests/docker-compose.yml up -d --quiet-pull mysql postgres &
|
||||
|
||||
-
|
||||
name: Build docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
load: true
|
||||
tags: |
|
||||
${{env.TAG}}
|
||||
facilmap-ci
|
||||
|
||||
-
|
||||
name: Start integration test components
|
||||
run: |
|
||||
docker compose -f ./integration-tests/docker-compose.yml up --wait
|
||||
status="$?"
|
||||
if (( status != 0 )); then
|
||||
docker compose -f ./integration-tests/docker-compose.yml logs
|
||||
exit "$status"
|
||||
fi
|
||||
|
||||
-
|
||||
name: Run integration tests
|
||||
run: >-
|
||||
docker run --rm -u root --add-host host.docker.internal:host-gateway facilmap-ci sh -c "
|
||||
yarn workspaces focus facilmap-integration-tests &&
|
||||
FACILMAP_URL=http://host.docker.internal:8080 yarn workspace facilmap-integration-tests run integration-tests &&
|
||||
FACILMAP_URL=http://host.docker.internal:8081 yarn workspace facilmap-integration-tests run integration-tests
|
||||
"
|
||||
|
||||
-
|
||||
name: Push docker image
|
||||
run: docker push "$TAG"
|
||||
|
||||
-
|
||||
name: Build and push (Docs)
|
||||
id: docker_build_docs
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
push: true
|
||||
context: ./docs
|
||||
tags: facilmap/facilmap-docs:latest
|
|
@ -0,0 +1,45 @@
|
|||
name: Publish Docker image (release)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: facilmap/facilmap
|
||||
tags: type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
|
@ -4,4 +4,14 @@ node_modules
|
|||
dist
|
||||
yarn-error.log
|
||||
.vscode
|
||||
config.env
|
||||
config.env
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
out
|
||||
out.*
|
||||
vite.config.ts.timestamp-*.mjs
|
|
@ -0,0 +1,11 @@
|
|||
diff --git a/dist/client.d.ts b/dist/client.d.ts
|
||||
index f441770687db57140a4c50b4a78a15b696f44177..3dbea34c932e79d6e3dbbf49b5dcafe2fb54cccd 100644
|
||||
--- a/dist/client.d.ts
|
||||
+++ b/dist/client.d.ts
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Socket as SocketIO } from "socket.io-client";
|
||||
-import { Bbox, BboxWithZoom, EventHandler, EventName, FindOnMapQuery, FindPadsQuery, FindPadsResult, FindQuery, GetPadQuery, HistoryEntry, ID, Line, LineCreate, LineExportRequest, LineTemplateRequest, LineToRouteCreate, LineUpdate, MapEvents, Marker, MarkerCreate, MarkerUpdate, MultipleEvents, ObjectWithId, PadData, PadDataCreate, PadDataUpdate, PadId, PagedResults, RequestData, RequestName, ResponseData, Route, RouteClear, RouteCreate, RouteExportRequest, RouteInfo, RouteRequest, SearchResult, TrackPoint, Type, TypeCreate, TypeUpdate, View, ViewCreate, ViewUpdate, Writable } from "facilmap-types";
|
||||
+import { Bbox, BboxWithZoom, EventHandler, EventName, FindOnMapQuery, FindPadsQuery, FindPadsResult, FindQuery, GetPadQuery, HistoryEntry, ID, Line, LineCreate, LineExportRequest, LineTemplateRequest, LineToRouteCreate, LineUpdate, MapEvents, Marker, MarkerCreate, MarkerUpdate, MultipleEvents, ObjectWithId, PadData, PadDataCreate, PadDataUpdate, PadId, PagedResults, RequestData, RequestName, ResponseData, Route, RouteClear, RouteCreate, RouteExportRequest, RouteInfo, RouteRequest, SearchResult, TrackPoint, Type, TypeCreate, TypeUpdate, View, ViewCreate, ViewUpdate, Writable } from "facilmap-types-v3";
|
||||
export interface ClientEvents<DataType = Record<string, string>> extends MapEvents<DataType> {
|
||||
connect: [];
|
||||
disconnect: [string];
|
|
@ -0,0 +1,90 @@
|
|||
diff --git a/dist/facilmap-client.d.ts b/dist/facilmap-client.d.ts
|
||||
index 80360fe16876fe53bcad94efe2ef607c0be793ee..442708ef08dd539c393924c86f510785697130da 100644
|
||||
--- a/dist/facilmap-client.d.ts
|
||||
+++ b/dist/facilmap-client.d.ts
|
||||
@@ -1,45 +1,45 @@
|
||||
-import { Bbox } from 'facilmap-types';
|
||||
-import { BboxWithZoom } from 'facilmap-types';
|
||||
-import { CRU } from 'facilmap-types';
|
||||
-import { EventHandler } from 'facilmap-types';
|
||||
-import { EventName } from 'facilmap-types';
|
||||
-import { FindOnMapQuery } from 'facilmap-types';
|
||||
-import { FindPadsQuery } from 'facilmap-types';
|
||||
-import { FindPadsResult } from 'facilmap-types';
|
||||
-import { FindQuery } from 'facilmap-types';
|
||||
-import { GetPadQuery } from 'facilmap-types';
|
||||
-import { HistoryEntry } from 'facilmap-types';
|
||||
-import { ID } from 'facilmap-types';
|
||||
-import { Line } from 'facilmap-types';
|
||||
-import { LineExportRequest } from 'facilmap-types';
|
||||
-import { LineTemplate } from 'facilmap-types';
|
||||
-import { LineTemplateRequest } from 'facilmap-types';
|
||||
-import { LineToRouteCreate } from 'facilmap-types';
|
||||
+import { Bbox } from 'facilmap-types-v4';
|
||||
+import { BboxWithZoom } from 'facilmap-types-v4';
|
||||
+import { CRU } from 'facilmap-types-v4';
|
||||
+import { EventHandler } from 'facilmap-types-v4';
|
||||
+import { EventName } from 'facilmap-types-v4';
|
||||
+import { FindOnMapQuery } from 'facilmap-types-v4';
|
||||
+import { FindPadsQuery } from 'facilmap-types-v4';
|
||||
+import { FindPadsResult } from 'facilmap-types-v4';
|
||||
+import { FindQuery } from 'facilmap-types-v4';
|
||||
+import { GetPadQuery } from 'facilmap-types-v4';
|
||||
+import { HistoryEntry } from 'facilmap-types-v4';
|
||||
+import { ID } from 'facilmap-types-v4';
|
||||
+import { Line } from 'facilmap-types-v4';
|
||||
+import { LineExportRequest } from 'facilmap-types-v4';
|
||||
+import { LineTemplate } from 'facilmap-types-v4';
|
||||
+import { LineTemplateRequest } from 'facilmap-types-v4';
|
||||
+import { LineToRouteCreate } from 'facilmap-types-v4';
|
||||
import { ManagerOptions } from 'socket.io-client';
|
||||
-import { Marker } from 'facilmap-types';
|
||||
-import { MultipleEvents } from 'facilmap-types';
|
||||
-import { ObjectWithId } from 'facilmap-types';
|
||||
-import { PadData } from 'facilmap-types';
|
||||
-import { PadId } from 'facilmap-types';
|
||||
-import { PagedResults } from 'facilmap-types';
|
||||
-import { Route } from 'facilmap-types';
|
||||
-import { RouteClear } from 'facilmap-types';
|
||||
-import { RouteCreate } from 'facilmap-types';
|
||||
-import { RouteExportRequest } from 'facilmap-types';
|
||||
-import { RouteInfo } from 'facilmap-types';
|
||||
-import { RouteRequest } from 'facilmap-types';
|
||||
-import { SearchResult } from 'facilmap-types';
|
||||
-import { SetLanguageRequest } from 'facilmap-types';
|
||||
-import { SocketEvents } from 'facilmap-types';
|
||||
+import { Marker } from 'facilmap-types-v4';
|
||||
+import { MultipleEvents } from 'facilmap-types-v4';
|
||||
+import { ObjectWithId } from 'facilmap-types-v4';
|
||||
+import { PadData } from 'facilmap-types-v4';
|
||||
+import { PadId } from 'facilmap-types-v4';
|
||||
+import { PagedResults } from 'facilmap-types-v4';
|
||||
+import { Route } from 'facilmap-types-v4';
|
||||
+import { RouteClear } from 'facilmap-types-v4';
|
||||
+import { RouteCreate } from 'facilmap-types-v4';
|
||||
+import { RouteExportRequest } from 'facilmap-types-v4';
|
||||
+import { RouteInfo } from 'facilmap-types-v4';
|
||||
+import { RouteRequest } from 'facilmap-types-v4';
|
||||
+import { SearchResult } from 'facilmap-types-v4';
|
||||
+import { SetLanguageRequest } from 'facilmap-types-v4';
|
||||
+import { SocketEvents } from 'facilmap-types-v4';
|
||||
import { SocketOptions } from 'socket.io-client';
|
||||
-import { SocketRequest } from 'facilmap-types';
|
||||
-import { SocketRequestName } from 'facilmap-types';
|
||||
-import { SocketResponse } from 'facilmap-types';
|
||||
-import { SocketVersion } from 'facilmap-types';
|
||||
-import { TrackPoint } from 'facilmap-types';
|
||||
-import { Type } from 'facilmap-types';
|
||||
-import { View } from 'facilmap-types';
|
||||
-import { Writable } from 'facilmap-types';
|
||||
+import { SocketRequest } from 'facilmap-types-v4';
|
||||
+import { SocketRequestName } from 'facilmap-types-v4';
|
||||
+import { SocketResponse } from 'facilmap-types-v4';
|
||||
+import { SocketVersion } from 'facilmap-types-v4';
|
||||
+import { TrackPoint } from 'facilmap-types-v4';
|
||||
+import { Type } from 'facilmap-types-v4';
|
||||
+import { View } from 'facilmap-types-v4';
|
||||
+import { Writable } from 'facilmap-types-v4';
|
||||
|
||||
declare class Client {
|
||||
private socket;
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,9 @@
|
|||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
32
Dockerfile
32
Dockerfile
|
@ -1,26 +1,28 @@
|
|||
FROM node:15.12-alpine
|
||||
FROM node:21-alpine
|
||||
MAINTAINER Candid Dauth <cdauth@cdauth.eu>
|
||||
|
||||
CMD yarn run server
|
||||
CMD yarn run prod-server
|
||||
EXPOSE 8080
|
||||
ENV CACHE_DIR=/opt/facilmap/cache
|
||||
HEALTHCHECK --start-period=60s --start-interval=3s --timeout=5s --retries=1 \
|
||||
CMD wget -O/dev/null 'http://127.0.0.1:8080/socket.io/?EIO=4&transport=polling' || exit 1
|
||||
|
||||
RUN apk add --no-cache yarn
|
||||
|
||||
RUN adduser -D -h /opt/facilmap -s /bin/sh facilmap
|
||||
RUN mkdir /opt/facilmap && adduser -D -H -h /opt/facilmap -s /bin/sh facilmap
|
||||
|
||||
WORKDIR /opt/facilmap/server
|
||||
WORKDIR /opt/facilmap
|
||||
|
||||
COPY ./ ../
|
||||
COPY ./ ./
|
||||
|
||||
RUN chown -R facilmap:facilmap /opt/facilmap
|
||||
|
||||
USER facilmap
|
||||
|
||||
RUN cd .. && yarn install
|
||||
|
||||
RUN cd .. && yarn run build
|
||||
|
||||
USER root
|
||||
RUN chown -R root:root /opt/facilmap && chown -R facilmap:facilmap /opt/facilmap/server/cache
|
||||
RUN yarn install && \
|
||||
yarn check-types && \
|
||||
yarn lint && \
|
||||
yarn test && \
|
||||
yarn run build:frontend:app && \
|
||||
yarn run build:server && \
|
||||
yarn workspaces focus -A --production
|
||||
|
||||
RUN mkdir -p "$CACHE_DIR" && chown -R facilmap:facilmap "$CACHE_DIR"
|
||||
|
||||
USER facilmap
|
||||
|
|
54
README.md
54
README.md
|
@ -1,15 +1,22 @@
|
|||
[FacilMap](https://facilmap.org/) is a privacy-friendly, open-source versatile online map that combines different services based on OpenStreetMap and makes it easy to find places, plan routes and create custom maps full of markers, lines and routes. Features include:
|
||||
# FacilMap
|
||||
|
||||
* Different map styles for roads, topography, cycling, hiking, public transportation, water navigation, ...
|
||||
* Search for places and show their information (website, opening hours, ...)
|
||||
* Calculate routes and adjust them by dragging. An elevation profile can be shown.
|
||||
* Smartphone-friendly interface.
|
||||
* Create and share custom maps with markers, lines and routes on them.
|
||||
* Open GPX/KML/OSM/GeoJSON files and save them on a custom map.
|
||||
* Define custom styles of markers, lines and routes and generate a legend automatically.
|
||||
* Define custom form fields for the details of markers, lines and routes.
|
||||
* API to embed custom maps into a website and to modify them programmatically.
|
||||
* Extensive [documentation](https://docs.facilmap.org/).
|
||||
[FacilMap](https://facilmap.org/) is a privacy-friendly, open-source versatile online map that combines different services based on OpenStreetMap and makes it easy to find places, plan trips and add markers, lines and routes to custom maps with live collaboration. Features include:
|
||||
|
||||
* Choose between different map styles for roads, topography, cycling, hiking, public transportation, water navigation, …
|
||||
* Search for places and show their information (website, opening hours, …)
|
||||
* Calculate routes and adjust them by dragging, optionally with an elevation profile and details about road quality
|
||||
* Show POIs on the map (amenities, attractions, businesses, …)
|
||||
* Smartphone-friendly interface and Progressive Web App
|
||||
* Create collaborative maps, add markers, lines and routes and collaborate live through a share link
|
||||
* View GPX/KML/OSM/GeoJSON files or import them to a collaborative map
|
||||
* Export collaborative maps to GPX or GeoJSON to import them into OsmAnd or other apps
|
||||
* Link or embed a read-only or editable version of a collaborative map on your website
|
||||
* Define different types of markers/lines with custom form fields to be filled out
|
||||
* Create custom views where markers/lines are shown/hidden based on their form field values
|
||||
* Define custom styles of markers/lines and routes and generate a legend automatically
|
||||
* Export the field values of markers/lines as HTML or CSV to import them into a spreadsheet
|
||||
* Programmatically read and modify collaborative maps through a Socket.io API
|
||||
* Extensive [user and developer documentation](https://docs.facilmap.org/).
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
@ -18,12 +25,21 @@ For more information, have a look at the [documentation](https://docs.facilmap.o
|
|||
|
||||
Quick links:
|
||||
* [User guide](https://docs.facilmap.org/users/)
|
||||
* [Embed FacilMap into a website](https://docs.facilmap.org/administrators/embed.html)
|
||||
* [Run the FacilMap server](https://docs.facilmap.org/administrators/server.html)
|
||||
* [Embed FacilMap into a website](https://docs.facilmap.org/developers/embed.html)
|
||||
* [Run the FacilMap server](https://docs.facilmap.org/developers/server/)
|
||||
* [Use the FacilMap client](https://docs.facilmap.org/developers/client/) to programmatically modify objects on a collaborative map.
|
||||
* [Dev setup](https://docs.facilmap.org/developers/development/dev-setup.html)
|
||||
|
||||
|
||||
Get help
|
||||
========
|
||||
|
||||
* Have a look at the [documentation](https://docs.facilmap.org/)
|
||||
* Ask questions [on GitHub](https://github.com/FacilMap/facilmap/discussions)
|
||||
* Raise bugs and feature requests [on GitHub](https://github.com/FacilMap/facilmap/issues)
|
||||
* Join the [Matrix chat room](https://matrix.to/#/#facilmap:rankenste.in)
|
||||
|
||||
|
||||
Quick start
|
||||
===========
|
||||
|
||||
|
@ -35,7 +51,17 @@ Quick start
|
|||
More details can be found in the [Administrator guide](https://docs.facilmap.org/administrators/server.html#standalone) and the [Developer guide](https://docs.facilmap.org/developers/development/dev-setup.html).
|
||||
|
||||
|
||||
Contribute
|
||||
==========
|
||||
|
||||
* Raise bugs, feature requests and ideas [as issues](https://github.com/FacilMap/facilmap/issues)
|
||||
* Help translate FacilMap into different languages on [Weblate](https://hosted.weblate.org/projects/facilmap/)
|
||||
* Create a local [dev setup](https://docs.facilmap.org/developers/development/dev-setup.html) and open pull requests
|
||||
* [Donate](https://docs.facilmap.org/users/contribute/)
|
||||
* Spread the word!
|
||||
|
||||
|
||||
Support FacilMap
|
||||
================
|
||||
|
||||
[Financial support](https://docs.facilmap.org/users/contribute/) is always welcome!
|
||||
I have been working on FacilMap as a hobby since 2009. While I have plenty of ideas and motivation to develop the app further, I have only limited time and resources to do so. By [financially supporting](https://docs.facilmap.org/users/contribute/) FacilMap, you allow me to spend more time implementing new features and to afford the infrastructure that is necessary to keep the app running for a growing number of users. Also, feel free to request the features that you would like to see!
|
|
@ -1,9 +1,8 @@
|
|||
{
|
||||
"name": "facilmap-client",
|
||||
"version": "3.0.1",
|
||||
"version": "5.0.0-alpha",
|
||||
"description": "A library that acts as a client to FacilMap and makes it possible to retrieve and modify objects on a collaborative map.",
|
||||
"keywords": [
|
||||
"webpack",
|
||||
"maps",
|
||||
"osm",
|
||||
"facilmap"
|
||||
|
@ -14,7 +13,9 @@
|
|||
},
|
||||
"license": "AGPL-3.0",
|
||||
"author": "Candid Dauth <cdauth@cdauth.eu>",
|
||||
"main": "./dist/client.js",
|
||||
"main": "./dist/facilmap-client.mjs",
|
||||
"type": "module",
|
||||
"types": "./dist/facilmap-client.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/FacilMap/facilmap.git"
|
||||
|
@ -22,26 +23,24 @@
|
|||
"files": [
|
||||
"dist",
|
||||
"src",
|
||||
"README.md",
|
||||
"tsconfig.json"
|
||||
"README.md"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"watch": "webpack --watch",
|
||||
"clean": "rimraf dist",
|
||||
"dev-server": "webpack-dev-server --mode development"
|
||||
"build": "vite build",
|
||||
"clean": "rimraf dist out out.node",
|
||||
"dev-server": "vite",
|
||||
"check-types": "tsc -b --emitDeclarationOnly"
|
||||
},
|
||||
"dependencies": {
|
||||
"facilmap-types": "3.0.1",
|
||||
"socket.io-client": "^4.0.0"
|
||||
"facilmap-types": "workspace:^",
|
||||
"serialize-error": "^11.0.3",
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/geojson": "^7946.0.7",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-loader": "^8.0.12",
|
||||
"typescript": "^4.1.3",
|
||||
"webpack": "^5.11.0",
|
||||
"webpack-bundle-analyzer": "^4.3.0",
|
||||
"webpack-cli": "^4.2.0"
|
||||
"@types/geojson": "^7946.0.14",
|
||||
"rimraf": "^5.0.5",
|
||||
"typescript": "^5.4.4",
|
||||
"vite": "^5.2.8",
|
||||
"vite-plugin-dts": "^3.8.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
**/*.test.ts
|
|
@ -1,16 +1,8 @@
|
|||
import { io, Socket as SocketIO } from "socket.io-client";
|
||||
import {
|
||||
Bbox,
|
||||
BboxWithZoom, EventHandler, EventName, FindOnMapQuery, FindQuery, HistoryEntry, ID, Line, LineCreate,
|
||||
LineExportRequest, LineTemplateRequest, LineToRouteCreate, LineUpdate, MapEvents, Marker, MarkerCreate, MarkerUpdate, MultipleEvents, ObjectWithId,
|
||||
PadData, PadDataCreate, PadDataUpdate, PadId, RequestData, RequestName, ResponseData, Route, RouteClear, RouteCreate, RouteExportRequest,
|
||||
RouteInfo,
|
||||
RouteRequest,
|
||||
SearchResult,
|
||||
TrackPoint, Type, TypeCreate, TypeUpdate, View, ViewCreate, ViewUpdate, Writable
|
||||
} from "facilmap-types";
|
||||
import { io, type ManagerOptions, type Socket as SocketIO, type SocketOptions } from "socket.io-client";
|
||||
import { type Bbox, type BboxWithZoom, type CRU, type EventHandler, type EventName, type FindOnMapQuery, type FindMapsQuery, type FindMapsResult, type FindQuery, type GetMapQuery, type HistoryEntry, type ID, type Line, type LineExportRequest, type LineTemplateRequest, type LineToRouteCreate, type SocketEvents, type Marker, type MultipleEvents, type ObjectWithId, type MapData, type MapId, type PagedResults, type SocketRequest, type SocketRequestName, type SocketResponse, type Route, type RouteClear, type RouteCreate, type RouteExportRequest, type RouteInfo, type RouteRequest, type SearchResult, type SocketVersion, type TrackPoint, type Type, type View, type Writable, type SocketClientToServerEvents, type SocketServerToClientEvents, type LineTemplate, type LinePointsEvent, MapNotFoundError, type SetLanguageRequest } from "facilmap-types";
|
||||
import { deserializeError, errorConstructors, serializeError } from "serialize-error";
|
||||
|
||||
export interface ClientEvents<DataType = Record<string, string>> extends MapEvents<DataType> {
|
||||
export interface ClientEventsInterface extends SocketEvents<SocketVersion.V3> {
|
||||
connect: [];
|
||||
disconnect: [string];
|
||||
connect_error: [Error];
|
||||
|
@ -29,11 +21,13 @@ export interface ClientEvents<DataType = Record<string, string>> extends MapEven
|
|||
route: [RouteWithTrackPoints];
|
||||
clearRoute: [RouteClear];
|
||||
|
||||
emit: { [eventName in RequestName]: [eventName, RequestData<eventName, DataType>] }[RequestName];
|
||||
emitResolve: { [eventName in RequestName]: [eventName, ResponseData<eventName, DataType>] }[RequestName];
|
||||
emitReject: [RequestName, Error];
|
||||
emit: { [eventName in SocketRequestName<SocketVersion.V3>]: [eventName, SocketRequest<SocketVersion.V3, eventName>] }[SocketRequestName<SocketVersion.V3>];
|
||||
emitResolve: { [eventName in SocketRequestName<SocketVersion.V3>]: [eventName, SocketResponse<SocketVersion.V3, eventName>] }[SocketRequestName<SocketVersion.V3>];
|
||||
emitReject: [SocketRequestName<SocketVersion.V3>, Error];
|
||||
}
|
||||
|
||||
export type ClientEvents = Pick<ClientEventsInterface, keyof ClientEventsInterface>; // Workaround for https://github.com/microsoft/TypeScript/issues/15300
|
||||
|
||||
const MANAGER_EVENTS: Array<EventName<ClientEvents>> = ['error', 'reconnect', 'reconnect_attempt', 'reconnect_error', 'reconnect_failed'];
|
||||
|
||||
export interface TrackPoints {
|
||||
|
@ -41,7 +35,7 @@ export interface TrackPoints {
|
|||
length: number;
|
||||
}
|
||||
|
||||
export interface LineWithTrackPoints<DataType = Record<string, string>> extends Line<DataType> {
|
||||
export interface LineWithTrackPoints extends Line {
|
||||
trackPoints: TrackPoints;
|
||||
}
|
||||
|
||||
|
@ -50,77 +44,119 @@ export interface RouteWithTrackPoints extends Omit<Route, "trackPoints"> {
|
|||
trackPoints: TrackPoints;
|
||||
}
|
||||
|
||||
export default class Client<DataType = Record<string, string>> {
|
||||
disconnected: boolean = true;
|
||||
server!: string;
|
||||
padId: string | undefined = undefined;
|
||||
bbox: BboxWithZoom | undefined = undefined;
|
||||
socket!: SocketIO;
|
||||
padData: PadData | undefined = undefined;
|
||||
readonly: boolean | undefined = undefined;
|
||||
writable: Writable | undefined = undefined;
|
||||
deleted: boolean = false;
|
||||
markers: Record<ID, Marker<DataType>> = { };
|
||||
lines: Record<ID, LineWithTrackPoints<DataType>> = { };
|
||||
views: Record<ID, View> = { };
|
||||
types: Record<ID, Type> = { };
|
||||
history: Record<ID, HistoryEntry> = { };
|
||||
route: RouteWithTrackPoints | undefined = undefined;
|
||||
routes: Record<string, RouteWithTrackPoints> = { };
|
||||
serverError: Error | undefined = undefined;
|
||||
loading: number = 0;
|
||||
interface ClientState {
|
||||
disconnected: boolean;
|
||||
server: string;
|
||||
mapId: string | undefined;
|
||||
bbox: BboxWithZoom | undefined;
|
||||
readonly: boolean | undefined;
|
||||
writable: Writable | undefined;
|
||||
deleted: boolean;
|
||||
serverError: Error | undefined;
|
||||
loading: number;
|
||||
listeningToHistory: boolean;
|
||||
}
|
||||
|
||||
_listeners: {
|
||||
interface ClientData {
|
||||
mapData: (MapData & { writable: Writable }) | undefined;
|
||||
markers: Record<ID, Marker>;
|
||||
lines: Record<ID, LineWithTrackPoints>;
|
||||
views: Record<ID, View>;
|
||||
types: Record<ID, Type>;
|
||||
history: Record<ID, HistoryEntry>;
|
||||
route: RouteWithTrackPoints | undefined;
|
||||
routes: Record<string, RouteWithTrackPoints>;
|
||||
}
|
||||
|
||||
errorConstructors.set("MapNotFoundError", MapNotFoundError as any);
|
||||
|
||||
class Client {
|
||||
private socket: SocketIO<SocketServerToClientEvents<SocketVersion.V3>, SocketClientToServerEvents<SocketVersion.V3>>;
|
||||
private state: ClientState;
|
||||
private data: ClientData;
|
||||
|
||||
private listeners: {
|
||||
[E in EventName<ClientEvents>]?: Array<EventHandler<ClientEvents, E>>
|
||||
} = { };
|
||||
_listeningToHistory: boolean = false;
|
||||
|
||||
constructor(server: string, padId?: string) {
|
||||
this._init(server, padId);
|
||||
constructor(server: string, mapId?: string, socketOptions?: Partial<ManagerOptions & SocketOptions>) {
|
||||
this.state = this._makeReactive({
|
||||
disconnected: true,
|
||||
server,
|
||||
mapId,
|
||||
bbox: undefined,
|
||||
readonly: undefined,
|
||||
writable: undefined,
|
||||
deleted: false,
|
||||
serverError: undefined,
|
||||
loading: 0,
|
||||
listeningToHistory: false
|
||||
});
|
||||
|
||||
this.data = this._makeReactive({
|
||||
mapData: undefined,
|
||||
markers: { },
|
||||
lines: { },
|
||||
views: { },
|
||||
types: { },
|
||||
history: { },
|
||||
route: undefined,
|
||||
routes: { }
|
||||
});
|
||||
|
||||
const serverUrl = typeof location != "undefined" ? new URL(this.state.server, location.href) : new URL(this.state.server);
|
||||
const socket = io(`${serverUrl.origin}/v3`, {
|
||||
forceNew: true,
|
||||
path: serverUrl.pathname.replace(/\/$/, "") + "/socket.io",
|
||||
...socketOptions
|
||||
});
|
||||
this.socket = socket;
|
||||
|
||||
for(const i of Object.keys(this._handlers) as EventName<ClientEvents>[]) {
|
||||
this.on(i, this._handlers[i] as EventHandler<ClientEvents, typeof i>);
|
||||
}
|
||||
|
||||
void Promise.resolve().then(() => {
|
||||
this._simulateEvent("loadStart");
|
||||
});
|
||||
|
||||
this.once("connect", () => {
|
||||
this._simulateEvent("loadEnd");
|
||||
});
|
||||
}
|
||||
|
||||
_set<O, K extends keyof O>(object: O, key: K, value: O[K]): void {
|
||||
protected _makeReactive<O extends object>(object: O): O {
|
||||
return object;
|
||||
}
|
||||
|
||||
protected _set<O, K extends keyof O>(object: O, key: K, value: O[K]): void {
|
||||
object[key] = value;
|
||||
}
|
||||
|
||||
_delete<O>(object: O, key: keyof O): void {
|
||||
protected _delete<O>(object: O, key: keyof O): void {
|
||||
delete object[key];
|
||||
}
|
||||
|
||||
_decodeData(data: Record<string, string>): DataType {
|
||||
protected _decodeData(data: Record<string, string>): Record<string, string> {
|
||||
const result = Object.create(null);
|
||||
Object.assign(result, data);
|
||||
return result;
|
||||
}
|
||||
|
||||
_encodeData(data: DataType): Record<string, string> {
|
||||
return data as any;
|
||||
}
|
||||
|
||||
_fixRequestObject<T>(requestName: RequestName, obj: T): T {
|
||||
if (typeof obj != "object" || !(obj as any)?.data || !["addMarker", "editMarker", "addLine", "editLine"].includes(requestName))
|
||||
return obj;
|
||||
|
||||
return {
|
||||
...obj,
|
||||
data: this._encodeData((obj as any).data)
|
||||
};
|
||||
}
|
||||
|
||||
_fixResponseObject<T>(requestName: RequestName, obj: T): T {
|
||||
private _fixResponseObject<T>(requestName: SocketRequestName<SocketVersion.V3>, obj: T): T {
|
||||
if (typeof obj != "object" || !(obj as any)?.data || !["getMarker", "addMarker", "editMarker", "deleteMarker", "getLineTemplate", "addLine", "editLine", "deleteLine"].includes(requestName))
|
||||
return obj;
|
||||
|
||||
|
||||
return {
|
||||
...obj,
|
||||
data: this._decodeData((obj as any).data)
|
||||
};
|
||||
}
|
||||
|
||||
_fixEventObject<T extends any[]>(eventName: EventName<ClientEvents>, obj: T): T {
|
||||
private _fixEventObject<T extends any[]>(eventName: EventName<ClientEvents>, obj: T): T {
|
||||
if (typeof obj?.[0] != "object" || !obj?.[0]?.data || !["marker", "line"].includes(eventName))
|
||||
return obj;
|
||||
|
||||
|
||||
return [
|
||||
{
|
||||
...obj[0],
|
||||
|
@ -130,65 +166,42 @@ export default class Client<DataType = Record<string, string>> {
|
|||
] as T;
|
||||
}
|
||||
|
||||
_init(server: string, padId: string | undefined): void {
|
||||
// Needs to be in a separate method so that we can merge this class with a scope object in the frontend.
|
||||
|
||||
this._set(this, 'server', server);
|
||||
this._set(this, 'padId', padId);
|
||||
|
||||
const serverUrl = typeof location != "undefined" ? new URL(server, location.href) : new URL(server);
|
||||
const socket = io(serverUrl.origin, {
|
||||
forceNew: true,
|
||||
path: serverUrl.pathname.replace(/\/$/, "") + "/socket.io"
|
||||
});
|
||||
this._set(this, 'socket', socket);
|
||||
|
||||
for(const i of Object.keys(this._handlers) as EventName<ClientEvents<DataType>>[]) {
|
||||
this.on(i, this._handlers[i] as EventHandler<ClientEvents<DataType>, typeof i>);
|
||||
on<E extends EventName<ClientEvents>>(eventName: E, fn: EventHandler<ClientEvents, E>): void {
|
||||
if(!this.listeners[eventName]) {
|
||||
(MANAGER_EVENTS.includes(eventName) ? this.socket.io as any : this.socket)
|
||||
.on(eventName, (...[data]: ClientEvents[E]) => { this._simulateEvent(eventName as any, data); });
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this._simulateEvent("loadStart");
|
||||
}, 0);
|
||||
this.once("connect", () => {
|
||||
this._simulateEvent("loadEnd");
|
||||
});
|
||||
this.listeners[eventName] = [...(this.listeners[eventName] || [] as any), fn];
|
||||
}
|
||||
|
||||
on<E extends EventName<ClientEvents>>(eventName: E, fn: EventHandler<ClientEvents<DataType>, E>): void {
|
||||
if(!this._listeners[eventName]) {
|
||||
(MANAGER_EVENTS.includes(eventName) ? this.socket.io as any : this.socket)
|
||||
.on(eventName, (...[data]: ClientEvents<DataType>[E]) => { this._simulateEvent(eventName as any, data); });
|
||||
}
|
||||
|
||||
this._set(this._listeners, eventName, [ ...(this._listeners[eventName] || [] as any), fn ]);
|
||||
}
|
||||
|
||||
once<E extends EventName<ClientEvents>>(eventName: E, fn: EventHandler<ClientEvents<DataType>, E>): void {
|
||||
once<E extends EventName<ClientEvents>>(eventName: E, fn: EventHandler<ClientEvents, E>): void {
|
||||
const handler = ((data: any) => {
|
||||
this.removeListener(eventName, handler);
|
||||
(fn as any)(data);
|
||||
}) as EventHandler<ClientEvents<DataType>, E>;
|
||||
}) as EventHandler<ClientEvents, E>;
|
||||
this.on(eventName, handler);
|
||||
}
|
||||
}
|
||||
|
||||
removeListener<E extends EventName<ClientEvents>>(eventName: E, fn: EventHandler<ClientEvents<DataType>, E>): void {
|
||||
const listeners = this._listeners[eventName] as Array<EventHandler<ClientEvents<DataType>, E>> | undefined;
|
||||
removeListener<E extends EventName<ClientEvents>>(eventName: E, fn: EventHandler<ClientEvents, E>): void {
|
||||
const listeners = this.listeners[eventName] as Array<EventHandler<ClientEvents, E>> | undefined;
|
||||
if(listeners) {
|
||||
this._set(this._listeners, eventName, listeners.filter((listener) => (listener !== fn)) as any);
|
||||
this.listeners[eventName] = listeners.filter((listener) => (listener !== fn)) as any;
|
||||
}
|
||||
}
|
||||
|
||||
async _emit<R extends RequestName>(eventName: R, ...[data]: RequestData<R, DataType> extends void ? [ ] : [ RequestData<R, DataType> ]): Promise<ResponseData<R, DataType>> {
|
||||
private async _emit<R extends SocketRequestName<SocketVersion.V3>>(eventName: R, ...[data]: SocketRequest<SocketVersion.V3, R> extends undefined | null ? [ ] : [ SocketRequest<SocketVersion.V3, R> ]): Promise<SocketResponse<SocketVersion.V3, R>> {
|
||||
try {
|
||||
this._simulateEvent("loadStart");
|
||||
|
||||
this._simulateEvent("emit", eventName as any, data as any);
|
||||
|
||||
const outerError = new Error();
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.socket.emit(eventName, this._fixRequestObject(eventName, data), (err: Error, data: ResponseData<R, DataType>) => {
|
||||
this.socket.emit(eventName as any, data, (err: any, data: SocketResponse<SocketVersion.V3, R>) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
const cause = deserializeError(err);
|
||||
reject(deserializeError({ ...serializeError(outerError), message: cause.message, cause }));
|
||||
this._simulateEvent("emitReject", eventName as any, err);
|
||||
} else {
|
||||
const fixedData = this._fixResponseObject(eventName, data);
|
||||
|
@ -202,49 +215,49 @@ export default class Client<DataType = Record<string, string>> {
|
|||
}
|
||||
}
|
||||
|
||||
_handlers: {
|
||||
[E in EventName<ClientEvents>]?: EventHandler<ClientEvents<DataType>, E>
|
||||
private _handlers: {
|
||||
[E in EventName<ClientEvents>]?: EventHandler<ClientEvents, E>
|
||||
} = {
|
||||
padData: (data) => {
|
||||
this._set(this, 'padData', data);
|
||||
mapData: (data) => {
|
||||
this._set(this.data, 'mapData', data);
|
||||
|
||||
if(data.writable != null) {
|
||||
this._set(this, 'readonly', data.writable == 0);
|
||||
this._set(this, 'writable', data.writable);
|
||||
this._set(this.state, 'readonly', data.writable == 0);
|
||||
this._set(this.state, 'writable', data.writable);
|
||||
}
|
||||
|
||||
const id = this.writable == 2 ? data.adminId : this.writable == 1 ? data.writeId : data.id;
|
||||
const id = this.state.writable == 2 ? data.adminId : this.state.writable == 1 ? data.writeId : data.id;
|
||||
if(id != null)
|
||||
this._set(this, 'padId', id);
|
||||
this._set(this.state, 'mapId', id);
|
||||
},
|
||||
|
||||
deletePad: () => {
|
||||
this._set(this, 'readonly', true);
|
||||
this._set(this, 'writable', 0);
|
||||
this._set(this, 'deleted', true);
|
||||
deleteMap: () => {
|
||||
this._set(this.state, 'readonly', true);
|
||||
this._set(this.state, 'writable', 0);
|
||||
this._set(this.state, 'deleted', true);
|
||||
},
|
||||
|
||||
marker: (data) => {
|
||||
this._set(this.markers, data.id, data);
|
||||
this._set(this.data.markers, data.id, data);
|
||||
},
|
||||
|
||||
deleteMarker: (data) => {
|
||||
this._delete(this.markers, data.id);
|
||||
this._delete(this.data.markers, data.id);
|
||||
},
|
||||
|
||||
line: (data) => {
|
||||
this._set(this.lines, data.id, {
|
||||
this._set(this.data.lines, data.id, {
|
||||
...data,
|
||||
trackPoints: this.lines[data.id]?.trackPoints || { length: 0 }
|
||||
trackPoints: this.data.lines[data.id]?.trackPoints || { length: 0 }
|
||||
});
|
||||
},
|
||||
|
||||
deleteLine: (data) => {
|
||||
this._delete(this.lines, data.id);
|
||||
this._delete(this.data.lines, data.id);
|
||||
},
|
||||
|
||||
linePoints: (data) => {
|
||||
const line = this.lines[data.id];
|
||||
const line = this.data.lines[data.id];
|
||||
if(line == null)
|
||||
return console.error("Received line points for non-existing line "+data.id+".");
|
||||
|
||||
|
@ -252,16 +265,16 @@ export default class Client<DataType = Record<string, string>> {
|
|||
},
|
||||
|
||||
routePoints: (data) => {
|
||||
if(!this.route) {
|
||||
if(!this.data.route) {
|
||||
console.error("Received route points for non-existing route.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._set(this.route, 'trackPoints', this._mergeTrackPoints(this.route.trackPoints, data));
|
||||
this._set(this.data.route, 'trackPoints', this._mergeTrackPoints(this.data.route.trackPoints, data));
|
||||
},
|
||||
|
||||
routePointsWithId: (data) => {
|
||||
const route = this.routes[data.routeId];
|
||||
const route = this.data.routes[data.routeId];
|
||||
if(!route) {
|
||||
console.error("Received route points for non-existing route.");
|
||||
return;
|
||||
|
@ -271,168 +284,213 @@ export default class Client<DataType = Record<string, string>> {
|
|||
},
|
||||
|
||||
view: (data) => {
|
||||
this._set(this.views, data.id, data);
|
||||
this._set(this.data.views, data.id, data);
|
||||
},
|
||||
|
||||
deleteView: (data) => {
|
||||
this._delete(this.views, data.id);
|
||||
if (this.padData) {
|
||||
if(this.padData.defaultViewId == data.id)
|
||||
this._set(this.padData, 'defaultViewId', null);
|
||||
this._delete(this.data.views, data.id);
|
||||
if (this.data.mapData) {
|
||||
if(this.data.mapData.defaultViewId == data.id)
|
||||
this._set(this.data.mapData, 'defaultViewId', null);
|
||||
}
|
||||
},
|
||||
|
||||
type: (data) => {
|
||||
this._set(this.types, data.id, data);
|
||||
this._set(this.data.types, data.id, data);
|
||||
},
|
||||
|
||||
deleteType: (data) => {
|
||||
this._delete(this.types, data.id);
|
||||
this._delete(this.data.types, data.id);
|
||||
},
|
||||
|
||||
disconnect: () => {
|
||||
this._set(this, 'disconnected', true);
|
||||
this._set(this, 'markers', { });
|
||||
this._set(this, 'lines', { });
|
||||
this._set(this, 'views', { });
|
||||
this._set(this, 'history', { });
|
||||
this._set(this.state, 'disconnected', true);
|
||||
this._set(this.data, 'markers', { });
|
||||
this._set(this.data, 'lines', { });
|
||||
this._set(this.data, 'views', { });
|
||||
this._set(this.data, 'history', { });
|
||||
},
|
||||
|
||||
connect: () => {
|
||||
this._set(this, 'disconnected', false); // Otherwise it gets set when padData arrives
|
||||
this._set(this.state, 'disconnected', false); // Otherwise it gets set when mapData arrives
|
||||
|
||||
if(this.padId)
|
||||
this._setPadId(this.padId);
|
||||
if(this.state.mapId)
|
||||
this._setMapId(this.state.mapId).catch(() => undefined);
|
||||
|
||||
if(this.bbox)
|
||||
this.updateBbox(this.bbox);
|
||||
// TODO: Handle errors
|
||||
|
||||
if(this._listeningToHistory) // TODO: Execute after setPadId() returns
|
||||
if(this.state.bbox)
|
||||
this.updateBbox(this.state.bbox).catch((err) => { console.error("Error updating bbox.", err); });
|
||||
|
||||
if(this.state.listeningToHistory) // TODO: Execute after setMapId() returns
|
||||
this.listenToHistory().catch(function(err) { console.error("Error listening to history", err); });
|
||||
|
||||
if(this.route)
|
||||
this.setRoute(this.route);
|
||||
for (const route of Object.values(this.routes))
|
||||
this.setRoute(route);
|
||||
if(this.data.route)
|
||||
this.setRoute(this.data.route).catch((err) => { console.error("Error setting route.", err); });
|
||||
for (const route of Object.values(this.data.routes))
|
||||
this.setRoute(route).catch((err) => { console.error("Error setting route.", err); });
|
||||
},
|
||||
|
||||
connect_error: (err) => {
|
||||
if (!this.socket.active) { // Fatal error, client will not try to reconnect anymore
|
||||
this._set(this.state, 'serverError', err);
|
||||
this._simulateEvent("serverError", err);
|
||||
}
|
||||
},
|
||||
|
||||
history: (data) => {
|
||||
this._set(this.history, data.id, data);
|
||||
this._set(this.data.history, data.id, data);
|
||||
// TODO: Limit to 50 entries
|
||||
},
|
||||
|
||||
loadStart: () => {
|
||||
this._set(this, 'loading', this.loading + 1);
|
||||
this._set(this.state, 'loading', this.state.loading + 1);
|
||||
},
|
||||
|
||||
loadEnd: () => {
|
||||
this._set(this, 'loading', this.loading - 1);
|
||||
this._set(this.state, 'loading', this.state.loading - 1);
|
||||
}
|
||||
};
|
||||
|
||||
setPadId(padId: PadId): Promise<void> {
|
||||
if(this.padId != null)
|
||||
throw new Error("Pad ID already set.");
|
||||
async setMapId(mapId: MapId): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
|
||||
if(this.state.mapId != null)
|
||||
throw new Error("Map ID already set.");
|
||||
|
||||
return this._setPadId(padId);
|
||||
return await this._setMapId(mapId);
|
||||
}
|
||||
|
||||
updateBbox(bbox: BboxWithZoom): Promise<void> {
|
||||
this._set(this, 'bbox', bbox);
|
||||
return this._emit("updateBbox", bbox).then((obj) => {
|
||||
this._receiveMultiple(obj);
|
||||
});
|
||||
async setLanguage(language: SetLanguageRequest): Promise<void> {
|
||||
await this._emit("setLanguage", language);
|
||||
}
|
||||
|
||||
createPad(data: PadDataCreate): Promise<void> {
|
||||
return this._emit("createPad", data).then((obj) => {
|
||||
this._set(this, 'readonly', false);
|
||||
this._set(this, 'writable', 2);
|
||||
async updateBbox(bbox: BboxWithZoom): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
|
||||
const isZoomChange = this.bbox && bbox.zoom !== this.bbox.zoom;
|
||||
|
||||
this._receiveMultiple(obj);
|
||||
});
|
||||
this._set(this.state, 'bbox', bbox);
|
||||
const obj = await this._emit("updateBbox", bbox);
|
||||
|
||||
if (isZoomChange) {
|
||||
// Reset line points on zoom change to prevent us from accumulating too many unneeded line points.
|
||||
// On zoom change the line points are sent from the server without applying the "except" rule for the last bbox,
|
||||
// so we can be sure that we will receive all line points that are relevant for the new bbox.
|
||||
obj.linePoints = obj.linePoints || [];
|
||||
const linePointEventsById = new Map(obj.linePoints.map((e): [number, LinePointsEvent] => [e.id, e])); // Cannot use "as const" due to https://github.com/microsoft/rushstack/issues/3875
|
||||
for (const lineIdStr of Object.keys(this.data.lines)) {
|
||||
const lineId = Number(lineIdStr);
|
||||
const e = linePointEventsById.get(lineId);
|
||||
if (e) {
|
||||
e.reset = true;
|
||||
} else {
|
||||
obj.linePoints.push({
|
||||
id: lineId,
|
||||
trackPoints: [],
|
||||
reset: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._receiveMultiple(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
editPad(data: PadDataUpdate): Promise<PadData> {
|
||||
return this._emit("editPad", data);
|
||||
async getMap(data: GetMapQuery): Promise<FindMapsResult | null> {
|
||||
return await this._emit("getMap", data);
|
||||
}
|
||||
|
||||
deletePad(): Promise<void> {
|
||||
return this._emit("deletePad");
|
||||
async findMaps(data: FindMapsQuery): Promise<PagedResults<FindMapsResult>> {
|
||||
return await this._emit("findMaps", data);
|
||||
}
|
||||
|
||||
listenToHistory(): Promise<void> {
|
||||
return this._emit("listenToHistory").then((obj) => {
|
||||
this._set(this, '_listeningToHistory', true);
|
||||
this._receiveMultiple(obj);
|
||||
});
|
||||
async createMap(data: MapData<CRU.CREATE>): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
|
||||
const obj = await this._emit("createMap", data);
|
||||
this._set(this.state, 'serverError', undefined);
|
||||
this._set(this.state, 'readonly', false);
|
||||
this._set(this.state, 'writable', 2);
|
||||
this._receiveMultiple(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
stopListeningToHistory(): Promise<void> {
|
||||
this._set(this, '_listeningToHistory', false);
|
||||
return this._emit("stopListeningToHistory");
|
||||
async editMap(data: MapData<CRU.UPDATE>): Promise<MapData> {
|
||||
return await this._emit("editMap", data);
|
||||
}
|
||||
|
||||
revertHistoryEntry(data: ObjectWithId): Promise<void> {
|
||||
return this._emit("revertHistoryEntry", data).then((obj) => {
|
||||
this._set(this, 'history', { });
|
||||
this._receiveMultiple(obj);
|
||||
});
|
||||
async deleteMap(): Promise<void> {
|
||||
await this._emit("deleteMap");
|
||||
}
|
||||
|
||||
async getMarker(data: ObjectWithId): Promise<Marker<DataType>> {
|
||||
async listenToHistory(): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
|
||||
const obj = await this._emit("listenToHistory");
|
||||
this._set(this.state, 'listeningToHistory', true);
|
||||
this._receiveMultiple(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
async stopListeningToHistory(): Promise<void> {
|
||||
this._set(this.state, 'listeningToHistory', false);
|
||||
await this._emit("stopListeningToHistory");
|
||||
}
|
||||
|
||||
async revertHistoryEntry(data: ObjectWithId): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
|
||||
const obj = await this._emit("revertHistoryEntry", data);
|
||||
this._set(this.data, 'history', {});
|
||||
this._receiveMultiple(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
async getMarker(data: ObjectWithId): Promise<Marker> {
|
||||
const marker = await this._emit("getMarker", data);
|
||||
this._set(this.markers, marker.id, marker);
|
||||
this._set(this.data.markers, marker.id, marker);
|
||||
return marker;
|
||||
}
|
||||
|
||||
async addMarker(data: MarkerCreate<DataType>): Promise<Marker<DataType>> {
|
||||
async addMarker(data: Marker<CRU.CREATE>): Promise<Marker> {
|
||||
const marker = await this._emit("addMarker", data);
|
||||
// If the marker is out of view, we will not recieve it in an event. Add it here manually to make sure that we have it.
|
||||
this._set(this.markers, marker.id, marker);
|
||||
this._set(this.data.markers, marker.id, marker);
|
||||
return marker;
|
||||
}
|
||||
|
||||
editMarker(data: ObjectWithId & MarkerUpdate<DataType>): Promise<Marker<DataType>> {
|
||||
return this._emit("editMarker", data);
|
||||
async editMarker(data: Marker<CRU.UPDATE> & { id: ID }): Promise<Marker> {
|
||||
return await this._emit("editMarker", data);
|
||||
}
|
||||
|
||||
deleteMarker(data: ObjectWithId): Promise<Marker<DataType>> {
|
||||
return this._emit("deleteMarker", data);
|
||||
async deleteMarker(data: ObjectWithId): Promise<Marker> {
|
||||
return await this._emit("deleteMarker", data);
|
||||
}
|
||||
|
||||
getLineTemplate(data: LineTemplateRequest): Promise<Line<DataType>> {
|
||||
return this._emit("getLineTemplate", data);
|
||||
async getLineTemplate(data: LineTemplateRequest): Promise<LineTemplate> {
|
||||
return await this._emit("getLineTemplate", data);
|
||||
}
|
||||
|
||||
addLine(data: LineCreate<DataType>): Promise<Line<DataType>> {
|
||||
return this._emit("addLine", data);
|
||||
async addLine(data: Line<CRU.CREATE>): Promise<Line> {
|
||||
return await this._emit("addLine", data);
|
||||
}
|
||||
|
||||
editLine(data: ObjectWithId & LineUpdate<DataType>): Promise<Line<DataType>> {
|
||||
return this._emit("editLine", data);
|
||||
async editLine(data: Line<CRU.UPDATE> & { id: ID }): Promise<Line> {
|
||||
return await this._emit("editLine", data);
|
||||
}
|
||||
|
||||
deleteLine(data: ObjectWithId): Promise<Line<DataType>> {
|
||||
return this._emit("deleteLine", data);
|
||||
async deleteLine(data: ObjectWithId): Promise<Line> {
|
||||
return await this._emit("deleteLine", data);
|
||||
}
|
||||
|
||||
exportLine(data: LineExportRequest): Promise<string> {
|
||||
return this._emit("exportLine", data);
|
||||
async exportLine(data: LineExportRequest): Promise<string> {
|
||||
return await this._emit("exportLine", data);
|
||||
}
|
||||
|
||||
find(data: FindQuery & { loadUrls?: false }): Promise<SearchResult[]>;
|
||||
find(data: FindQuery & { loadUrls: true }): Promise<string | SearchResult[]>; // eslint-disable-line no-dupe-class-members
|
||||
find(data: FindQuery): Promise<string | SearchResult[]> { // eslint-disable-line no-dupe-class-members
|
||||
return this._emit("find", data);
|
||||
async find(data: FindQuery & { loadUrls?: false }): Promise<SearchResult[]>;
|
||||
async find(data: FindQuery & { loadUrls: true }): Promise<string | SearchResult[]>; // eslint-disable-line no-dupe-class-members
|
||||
async find(data: FindQuery): Promise<string | SearchResult[]> { // eslint-disable-line no-dupe-class-members
|
||||
return await this._emit("find", data);
|
||||
}
|
||||
|
||||
findOnMap(data: FindOnMapQuery): Promise<ResponseData<'findOnMap'>> {
|
||||
return this._emit("findOnMap", data);
|
||||
async findOnMap(data: FindOnMapQuery): Promise<SocketResponse<SocketVersion.V3, 'findOnMap'>> {
|
||||
return await this._emit("findOnMap", data);
|
||||
}
|
||||
|
||||
getRoute(data: RouteRequest): Promise<RouteInfo> {
|
||||
return this._emit("getRoute", data);
|
||||
async getRoute(data: RouteRequest): Promise<RouteInfo> {
|
||||
return await this._emit("getRoute", data);
|
||||
}
|
||||
|
||||
async setRoute(data: RouteCreate): Promise<RouteWithTrackPoints | undefined> {
|
||||
|
@ -447,9 +505,9 @@ export default class Client<DataType = Record<string, string>> {
|
|||
};
|
||||
|
||||
if (data.routeId)
|
||||
this._set(this.routes, data.routeId, result);
|
||||
this._set(this.data.routes, data.routeId, result);
|
||||
else
|
||||
this._set(this, "route", result);
|
||||
this._set(this.data, "route", result);
|
||||
|
||||
this._simulateEvent("route", result);
|
||||
return result;
|
||||
|
@ -457,13 +515,13 @@ export default class Client<DataType = Record<string, string>> {
|
|||
|
||||
async clearRoute(data?: RouteClear): Promise<void> {
|
||||
if (data?.routeId) {
|
||||
this._delete(this.routes, data.routeId);
|
||||
this._delete(this.data.routes, data.routeId);
|
||||
this._simulateEvent("clearRoute", { routeId: data.routeId });
|
||||
return this._emit("clearRoute", data);
|
||||
} else if (this.route) {
|
||||
this._set(this, 'route', undefined);
|
||||
await this._emit("clearRoute", data);
|
||||
} else if (this.data.route) {
|
||||
this._set(this.data, 'route', undefined);
|
||||
this._simulateEvent("clearRoute", { routeId: undefined });
|
||||
return this._emit("clearRoute", data);
|
||||
await this._emit("clearRoute", data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,44 +537,44 @@ export default class Client<DataType = Record<string, string>> {
|
|||
};
|
||||
|
||||
if (data.routeId)
|
||||
this._set(this.routes, data.routeId, result);
|
||||
this._set(this.data.routes, data.routeId, result);
|
||||
else
|
||||
this._set(this, "route", result);
|
||||
this._set(this.data, "route", result);
|
||||
|
||||
this._simulateEvent("route", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
exportRoute(data: RouteExportRequest): Promise<string> {
|
||||
return this._emit("exportRoute", data);
|
||||
async exportRoute(data: RouteExportRequest): Promise<string> {
|
||||
return await this._emit("exportRoute", data);
|
||||
}
|
||||
|
||||
addType(data: TypeCreate): Promise<Type> {
|
||||
return this._emit("addType", data);
|
||||
async addType(data: Type<CRU.CREATE>): Promise<Type> {
|
||||
return await this._emit("addType", data);
|
||||
}
|
||||
|
||||
editType(data: ObjectWithId & TypeUpdate): Promise<Type> {
|
||||
return this._emit("editType", data);
|
||||
async editType(data: Type<CRU.UPDATE> & { id: ID }): Promise<Type> {
|
||||
return await this._emit("editType", data);
|
||||
}
|
||||
|
||||
deleteType(data: ObjectWithId): Promise<Type> {
|
||||
return this._emit("deleteType", data);
|
||||
async deleteType(data: ObjectWithId): Promise<Type> {
|
||||
return await this._emit("deleteType", data);
|
||||
}
|
||||
|
||||
addView(data: ViewCreate): Promise<View> {
|
||||
return this._emit("addView", data);
|
||||
async addView(data: View<CRU.CREATE>): Promise<View> {
|
||||
return await this._emit("addView", data);
|
||||
}
|
||||
|
||||
editView(data: ObjectWithId & ViewUpdate): Promise<View> {
|
||||
return this._emit("editView", data);
|
||||
async editView(data: View<CRU.UPDATE> & { id: ID }): Promise<View> {
|
||||
return await this._emit("editView", data);
|
||||
}
|
||||
|
||||
deleteView(data: ObjectWithId): Promise<View> {
|
||||
return this._emit("deleteView", data);
|
||||
async deleteView(data: ObjectWithId): Promise<View> {
|
||||
return await this._emit("deleteView", data);
|
||||
}
|
||||
|
||||
geoip(): Promise<Bbox | null> {
|
||||
return this._emit("geoip");
|
||||
async geoip(): Promise<Bbox | null> {
|
||||
return await this._emit("geoip");
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
|
@ -524,38 +582,39 @@ export default class Client<DataType = Record<string, string>> {
|
|||
this.socket.disconnect();
|
||||
}
|
||||
|
||||
async _setPadId(padId: string): Promise<void> {
|
||||
this._set(this, 'serverError', undefined);
|
||||
this._set(this, 'padId', padId);
|
||||
private async _setMapId(mapId: string): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
|
||||
this._set(this.state, 'serverError', undefined);
|
||||
this._set(this.state, 'mapId', mapId);
|
||||
try {
|
||||
const obj = await this._emit("setPadId", padId);
|
||||
const obj = await this._emit("setMapId", mapId);
|
||||
this._receiveMultiple(obj);
|
||||
} catch(err) {
|
||||
this._set(this, 'serverError', err);
|
||||
return obj;
|
||||
} catch(err: any) {
|
||||
this._set(this.state, 'serverError', err);
|
||||
this._simulateEvent("serverError", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
_receiveMultiple(obj?: MultipleEvents<ClientEvents<DataType>>): void {
|
||||
private _receiveMultiple(obj?: MultipleEvents<ClientEvents>): void {
|
||||
if (obj) {
|
||||
for(const i of Object.keys(obj) as EventName<ClientEvents>[])
|
||||
(obj[i] as Array<ClientEvents<DataType>[typeof i][0]>).forEach((it) => { this._simulateEvent(i, it as any); });
|
||||
(obj[i] as Array<ClientEvents[typeof i][0]>).forEach((it) => { this._simulateEvent(i, it as any); });
|
||||
}
|
||||
}
|
||||
|
||||
_simulateEvent<E extends EventName<ClientEvents>>(eventName: E, ...data: ClientEvents<DataType>[E]): void {
|
||||
private _simulateEvent<E extends EventName<ClientEvents>>(eventName: E, ...data: ClientEvents[E]): void {
|
||||
const fixedData = this._fixEventObject(eventName, data);
|
||||
|
||||
const listeners = this._listeners[eventName] as Array<EventHandler<ClientEvents<DataType>, E>> | undefined;
|
||||
const listeners = this.listeners[eventName] as Array<EventHandler<ClientEvents, E>> | undefined;
|
||||
if(listeners) {
|
||||
listeners.forEach(function(listener: EventHandler<ClientEvents<DataType>, E>) {
|
||||
listeners.forEach(function(listener: EventHandler<ClientEvents, E>) {
|
||||
listener(...fixedData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_mergeTrackPoints(existingTrackPoints: Record<number, TrackPoint> | null, newTrackPoints: TrackPoint[]): TrackPoints {
|
||||
private _mergeTrackPoints(existingTrackPoints: Record<number, TrackPoint> | null, newTrackPoints: TrackPoint[]): TrackPoints {
|
||||
const ret = { ...(existingTrackPoints || { }) } as TrackPoints;
|
||||
|
||||
for(let i=0; i<newTrackPoints.length; i++) {
|
||||
|
@ -570,4 +629,78 @@ export default class Client<DataType = Record<string, string>> {
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
get disconnected(): boolean {
|
||||
return this.state.disconnected;
|
||||
}
|
||||
|
||||
get server(): string {
|
||||
return this.state.server;
|
||||
}
|
||||
|
||||
get mapId(): string | undefined {
|
||||
return this.state.mapId;
|
||||
}
|
||||
|
||||
get bbox(): BboxWithZoom | undefined {
|
||||
return this.state.bbox;
|
||||
}
|
||||
|
||||
get readonly(): boolean | undefined {
|
||||
return this.state.readonly;
|
||||
}
|
||||
|
||||
get writable(): Writable | undefined {
|
||||
return this.state.writable;
|
||||
}
|
||||
|
||||
get deleted(): boolean {
|
||||
return this.state.deleted;
|
||||
}
|
||||
|
||||
get serverError(): Error | undefined {
|
||||
return this.state.serverError;
|
||||
}
|
||||
|
||||
get loading(): number {
|
||||
return this.state.loading;
|
||||
}
|
||||
|
||||
get listeningToHistory(): boolean {
|
||||
return this.state.listeningToHistory;
|
||||
}
|
||||
|
||||
get mapData(): (MapData & { writable: Writable }) | undefined {
|
||||
return this.data.mapData;
|
||||
}
|
||||
|
||||
get markers(): Record<ID, Marker> {
|
||||
return this.data.markers;
|
||||
}
|
||||
|
||||
get lines(): Record<ID, LineWithTrackPoints> {
|
||||
return this.data.lines;
|
||||
}
|
||||
|
||||
get views(): Record<ID, View> {
|
||||
return this.data.views;
|
||||
}
|
||||
|
||||
get types(): Record<ID, Type> {
|
||||
return this.data.types;
|
||||
}
|
||||
|
||||
get history(): Record<ID, HistoryEntry> {
|
||||
return this.data.history;
|
||||
}
|
||||
|
||||
get route(): RouteWithTrackPoints | undefined {
|
||||
return this.data.route;
|
||||
}
|
||||
|
||||
get routes(): Record<string, RouteWithTrackPoints> {
|
||||
return this.data.routes;
|
||||
}
|
||||
}
|
||||
|
||||
export default Client;
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": ["src/**/*"]
|
||||
}
|
|
@ -1,14 +1,15 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "es3",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"moduleResolution": "node",
|
||||
"noErrorTruncation": true,
|
||||
"skipLibCheck": true
|
||||
"outDir": "./out",
|
||||
"composite": true,
|
||||
"paths": {
|
||||
"facilmap-types": ["../types/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{ "path": "./tsconfig.node.json" },
|
||||
{ "path": "../types/tsconfig.json" }
|
||||
],
|
||||
"include": ["src/**/*"]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"outDir": "out.node"
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { defineConfig } from "vite";
|
||||
import dtsPlugin from "vite-plugin-dts";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
dtsPlugin({ rollupTypes: true, tsconfigPath: "./tsconfig.build.json" }),
|
||||
],
|
||||
build: {
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
lib: {
|
||||
entry: './src/client.ts',
|
||||
fileName: () => 'facilmap-client.mjs',
|
||||
formats: ['es']
|
||||
},
|
||||
rollupOptions: {
|
||||
external: (id) => !id.startsWith("./") && !id.startsWith("../") && /* resolved internal modules */ !id.startsWith("/")
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,51 +0,0 @@
|
|||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
const isDev = argv.mode == "development";
|
||||
|
||||
return {
|
||||
entry: `${__dirname}/src/client.ts`,
|
||||
output: {
|
||||
filename: "client.js",
|
||||
path: __dirname + "/dist/",
|
||||
library: ["FacilMap", "Client"],
|
||||
libraryTarget: "umd",
|
||||
libraryExport: "default"
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ ".js", ".ts" ]
|
||||
},
|
||||
mode: isDev ? "development" : "production",
|
||||
devtool: isDev ? "cheap-eval-source-map" : "source-map",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
resource: { and: [ /\.ts/, [
|
||||
__dirname + "/src/"
|
||||
] ] },
|
||||
loader: 'ts-loader'
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [ 'style-loader', 'css-loader' ]
|
||||
}
|
||||
]
|
||||
},
|
||||
externals: {
|
||||
"socket.io-client": {
|
||||
commonjs: 'socket.io-client',
|
||||
commonjs2: 'socket.io-client',
|
||||
amd: 'socket.io-client',
|
||||
root: 'io'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
//new BundleAnalyzerPlugin()
|
||||
],
|
||||
devServer: {
|
||||
publicPath: "/dist",
|
||||
disableHostCheck: true,
|
||||
injectClient: false // https://github.com/webpack/webpack-dev-server/issues/2484
|
||||
}
|
||||
};
|
||||
};
|
|
@ -1,8 +1,9 @@
|
|||
# Copy this file to config.env when running FacilMap standalone.
|
||||
# Find all the available configuration options in the documentation: https://docs.facilmap.org/developers/server/config.html
|
||||
|
||||
# HTTP requests made by the backend will send this User-Agent header. Please adapt to your URL and e-mail address.
|
||||
#USER_AGENT=FacilMap (https://facilmap.org/, cdauth@cdauth.eu)
|
||||
|
||||
# On which IP the HTTP server will listen. Leave empty to listen to all IPs.
|
||||
#HOST=
|
||||
# On which port the HTTP server will listen.
|
||||
#PORT=8080
|
||||
|
||||
|
@ -17,18 +18,19 @@
|
|||
|
||||
# OpenRouteService is used for calculating routes with advanced settings
|
||||
# Get a token on https://go.openrouteservice.org/
|
||||
#OSR_TOKEN=
|
||||
#ORS_TOKEN=
|
||||
|
||||
# MapBox is used for calculating routes with simple settings
|
||||
# Get an API key on https://www.mapbox.com/signup/
|
||||
#MAPBOX_TOKEN=
|
||||
|
||||
# MapZen is used for getting elevation information
|
||||
# Get an API key on https://mapzen.com/developers/sign_up
|
||||
#MAPZEN_TOKEN=
|
||||
|
||||
# Maxmind configuration. If specified, the maxmind GeoLite2 database will be downloaded
|
||||
# for Geo IP lookup (to show the initial map state) and kept in memory.
|
||||
# Sign up here: https://www.maxmind.com/en/geolite2/signup
|
||||
#MAXMIND_USER_ID=
|
||||
#MAXMIND_LICENSE_KEY=
|
||||
#MAXMIND_LICENSE_KEY=
|
||||
|
||||
# Lima Labs provides nice double resolution layers.
|
||||
# If this is defined, they are used as the default layer instead of Mapnik.
|
||||
# Get an API key here: https://maps.lima-labs.com/
|
||||
#LIMA_LABS_TOKEN=
|
|
@ -1,3 +1,6 @@
|
|||
# This docker-compose file is for internal testing only.
|
||||
# Please refer to the documentation on https://docs.facilmap.org/developers/server/docker.html for how to start FacilMap with docker.
|
||||
|
||||
version: "2"
|
||||
services:
|
||||
facilmap:
|
||||
|
@ -6,14 +9,20 @@ services:
|
|||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
||||
environment:
|
||||
DB_TYPE: mariadb
|
||||
DB_HOST: mariadb
|
||||
DB_TYPE: mysql
|
||||
DB_HOST: mysql
|
||||
# DB_TYPE: postgres
|
||||
# DB_HOST: postgres
|
||||
DB_NAME: facilmap
|
||||
DB_USER: facilmap
|
||||
DB_PASSWORD: facilmap
|
||||
links:
|
||||
- mysql
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
|
||||
mysql:
|
||||
image: mysql:5.7
|
||||
environment:
|
||||
|
@ -21,9 +30,26 @@ services:
|
|||
MYSQL_USER: facilmap
|
||||
MYSQL_PASSWORD: facilmap
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "true"
|
||||
phpmyadmin:
|
||||
image: phpmyadmin
|
||||
links:
|
||||
- mysql:db
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
healthcheck:
|
||||
test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
|
||||
ports:
|
||||
- 127.0.0.1:8090:80
|
||||
- "127.0.0.1:40830:3306"
|
||||
|
||||
# postgres:
|
||||
# image: postgis/postgis:16-3.4
|
||||
# environment:
|
||||
# POSTGRES_USER: facilmap
|
||||
# POSTGRES_PASSWORD: facilmap
|
||||
# POSTGRES_DB: facilmap
|
||||
# healthcheck:
|
||||
# test: pg_isready -d $$POSTGRES_DB
|
||||
# ports:
|
||||
# - "127.0.0.1:40831:5432"
|
||||
|
||||
# phpmyadmin:
|
||||
# image: phpmyadmin
|
||||
# links:
|
||||
# - mysql:db
|
||||
# ports:
|
||||
# - 127.0.0.1:8090:80
|
|
@ -0,0 +1,9 @@
|
|||
src/.vuepress/.cache
|
||||
src/.vuepress/.temp
|
||||
src/.vuepress/*.js.*.mjs
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,7 @@
|
|||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.3.cjs
|
|
@ -3,6 +3,8 @@ MAINTAINER Candid Dauth <cdauth@cdauth.eu>
|
|||
|
||||
RUN apk add --no-cache nodejs yarn
|
||||
|
||||
RUN echo "ErrorDocument 404 /404.html" >> /usr/local/apache2/conf/httpd.conf
|
||||
|
||||
COPY ./ /tmp/facilmap-docs
|
||||
|
||||
RUN cd /tmp/facilmap-docs && yarn install && yarn build && mv src/.vuepress/dist/* /usr/local/apache2/htdocs/ && rm -rf /tmp/facilmap-docs
|
||||
RUN cd /tmp/facilmap-docs && yarn install && yarn build && mv dist/* /usr/local/apache2/htdocs/ && rm -rf /tmp/facilmap-docs
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "facilmap-docs",
|
||||
"version": "3.0.1",
|
||||
"version": "5.0.0-alpha",
|
||||
"description": "Documentation for FacilMap.",
|
||||
"author": "Candid Dauth <cdauth@cdauth.eu>",
|
||||
"repository": {
|
||||
|
@ -8,19 +8,22 @@
|
|||
"url": "https://github.com/FacilMap/facilmap.git"
|
||||
},
|
||||
"license": "AGPL-3.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev-server": "vuepress dev src",
|
||||
"build": "vuepress build src",
|
||||
"deploy": "./deploy.sh",
|
||||
"clean": "rimraf dist",
|
||||
"check": "vuepress check-md src"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"markdown-it-footnote": "^3.0.2",
|
||||
"vuepress": "^1.5.3",
|
||||
"vuepress-plugin-check-md": "^0.0.2"
|
||||
"@vuepress/plugin-search": "next",
|
||||
"rimraf": "^5.0.1",
|
||||
"vuepress": "next",
|
||||
"vuepress-plugin-check-md": "^0.0.3"
|
||||
},
|
||||
"packageManager": "yarn@3.6.3",
|
||||
"dependencies": {
|
||||
"qrcode": "^1.4.4",
|
||||
"vue-qrcode": "^0.3.5"
|
||||
"qrcode.vue": "^3.4.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { defineClientConfig } from "@vuepress/client";
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import Screencast from "./components/Screencast.vue";
|
||||
import Screenshot from "./components/Screenshot.vue";
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ app, router, siteData }) {
|
||||
app.component("qrcode", QrcodeVue);
|
||||
app.component("Screencast", Screencast);
|
||||
app.component("Screenshot", Screenshot);
|
||||
|
||||
app.config.globalProperties.$resolveLink
|
||||
|
||||
router.addRoute({ path: '/users/hash/', redirect: '/users/share/' });
|
||||
}
|
||||
});
|
|
@ -1,18 +1,32 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
desktop: string;
|
||||
mobile: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<video controls style="height: 340px">
|
||||
<source :src="desktop">
|
||||
<div class="fm-screencast">
|
||||
<video controls>
|
||||
<source :src="props.desktop">
|
||||
</video>
|
||||
<video controls style="height: 340px">
|
||||
<source :src="mobile">
|
||||
<video controls>
|
||||
<source :src="props.mobile">
|
||||
</video>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
desktop: String,
|
||||
mobile: String
|
||||
|
||||
<style lang="scss">
|
||||
.fm-screencast {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
||||
> :nth-child(1) {
|
||||
width: calc(66.5% - 3px);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
> :nth-child(2) {
|
||||
width: calc(33.5% - 3px);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,14 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
desktop: string;
|
||||
mobile: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<img :src="desktop" style="height: 340px">
|
||||
<img :src="mobile" style="height: 340px">
|
||||
<div class="fm-screenshot">
|
||||
<img :src="props.desktop">
|
||||
<img :src="props.mobile">
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
desktop: String,
|
||||
mobile: String
|
||||
|
||||
<style lang="scss">
|
||||
.fm-screenshot {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
||||
> :nth-child(1) {
|
||||
width: calc(68.5% - 3px);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
> :nth-child(2) {
|
||||
width: calc(31.5% - 3px);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,120 +0,0 @@
|
|||
const { description } = require('../../package')
|
||||
|
||||
module.exports = {
|
||||
title: 'FacilMap',
|
||||
description: description,
|
||||
head: [
|
||||
['meta', { name: 'theme-color', content: '#3eaf7c' }],
|
||||
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
|
||||
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }]
|
||||
],
|
||||
themeConfig: {
|
||||
repo: '',
|
||||
editLinks: false,
|
||||
docsDir: '',
|
||||
editLinkText: '',
|
||||
lastUpdated: false,
|
||||
nav: [
|
||||
{
|
||||
text: 'Users',
|
||||
link: '/users/',
|
||||
},
|
||||
{
|
||||
text: 'Administrators',
|
||||
link: '/administrators/'
|
||||
},
|
||||
{
|
||||
text: 'Developers',
|
||||
link: '/developers/'
|
||||
}
|
||||
],
|
||||
sidebar: {
|
||||
'/users/': [
|
||||
{
|
||||
title: "Overview",
|
||||
collapsable: false,
|
||||
children: [
|
||||
"",
|
||||
"help/",
|
||||
"contribute/"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'General functions',
|
||||
collapsable: false,
|
||||
children: [
|
||||
"ui/",
|
||||
"layers/",
|
||||
"search/",
|
||||
"route/",
|
||||
"click-marker/",
|
||||
"files/",
|
||||
"locate/",
|
||||
"hash/",
|
||||
"app/",
|
||||
"privacy/"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Collaborative maps',
|
||||
collapsable: false,
|
||||
children: [
|
||||
"collaborative/",
|
||||
"markers/",
|
||||
"lines/",
|
||||
"types/",
|
||||
"legend/",
|
||||
"views/",
|
||||
"filter/",
|
||||
"history/",
|
||||
"export/",
|
||||
"import/",
|
||||
"map-settings/"
|
||||
]
|
||||
},
|
||||
],
|
||||
'/administrators/': [
|
||||
{
|
||||
title: 'Administrator guide',
|
||||
collapsable: false,
|
||||
children: [
|
||||
"",
|
||||
"embed",
|
||||
"server"
|
||||
]
|
||||
}
|
||||
],
|
||||
'/developers/': [
|
||||
"",
|
||||
{
|
||||
title: 'Client',
|
||||
collapsable: false,
|
||||
children: [
|
||||
"client/",
|
||||
"client/properties",
|
||||
"client/events",
|
||||
"client/methods",
|
||||
"client/types"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Development',
|
||||
collapsable: false,
|
||||
children: [
|
||||
"development/dev-setup.md"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
'@vuepress/plugin-back-to-top',
|
||||
'@vuepress/plugin-medium-zoom',
|
||||
'check-md'
|
||||
],
|
||||
markdown: {
|
||||
extendMarkdown: (md) => {
|
||||
md.use(require("markdown-it-footnote"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
import { description } from "../../package.json";
|
||||
import { defaultTheme, defineUserConfig } from "vuepress";
|
||||
import backToTopPlugin from "@vuepress/plugin-back-to-top";
|
||||
import mediumZoomPlugin from "@vuepress/plugin-medium-zoom";
|
||||
//import checkMdPluin from "vuepress-plugin-check-md";
|
||||
import { searchPlugin } from "@vuepress/plugin-search";
|
||||
|
||||
export default defineUserConfig({
|
||||
title: 'FacilMap',
|
||||
description: description,
|
||||
head: [
|
||||
['meta', { name: 'theme-color', content: '#3eaf7c' }], // TODO: Update
|
||||
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
|
||||
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }]
|
||||
],
|
||||
dest: `${__dirname}/../../dist`,
|
||||
theme: defaultTheme({
|
||||
repo: '',
|
||||
docsDir: '',
|
||||
editLinkText: '',
|
||||
lastUpdated: false,
|
||||
contributors: false,
|
||||
navbar: [
|
||||
{
|
||||
text: 'Users',
|
||||
link: '/users/',
|
||||
},
|
||||
{
|
||||
text: 'Developers',
|
||||
link: '/developers/'
|
||||
}
|
||||
],
|
||||
sidebar: {
|
||||
'/users/': [
|
||||
{
|
||||
text: "Overview",
|
||||
children: [
|
||||
"/users/",
|
||||
"/users/help/",
|
||||
"/users/releases/",
|
||||
"/users/contribute/",
|
||||
"/users/privacy/"
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'General functions',
|
||||
children: [
|
||||
"/users/ui/",
|
||||
"/users/layers/",
|
||||
"/users/search/",
|
||||
"/users/pois/",
|
||||
"/users/route/",
|
||||
"/users/click-marker/",
|
||||
"/users/files/",
|
||||
"/users/locate/",
|
||||
"/users/share/",
|
||||
"/users/app/",
|
||||
"/users/user-preferences/"
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Collaborative maps',
|
||||
children: [
|
||||
"/users/collaborative/",
|
||||
"/users/markers/",
|
||||
"/users/lines/",
|
||||
"/users/multiple/",
|
||||
"/users/types/",
|
||||
"/users/legend/",
|
||||
"/users/views/",
|
||||
"/users/filter/",
|
||||
"/users/history/",
|
||||
"/users/export/",
|
||||
"/users/import/",
|
||||
"/users/map-settings/"
|
||||
]
|
||||
},
|
||||
],
|
||||
'/developers/': [
|
||||
{
|
||||
text: "Developer guide",
|
||||
children: [
|
||||
"/developers/",
|
||||
"/developers/embed",
|
||||
"/developers/i18n"
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "Server",
|
||||
children: [
|
||||
"/developers/server/",
|
||||
"/developers/server/docker",
|
||||
"/developers/server/standalone",
|
||||
"/developers/server/config"
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Client',
|
||||
children: [
|
||||
"/developers/client/",
|
||||
"/developers/client/properties",
|
||||
"/developers/client/events",
|
||||
"/developers/client/methods",
|
||||
"/developers/client/types",
|
||||
"/developers/client/advanced",
|
||||
"/developers/client/changelog"
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "Leaflet components",
|
||||
children: [
|
||||
"/developers/leaflet/",
|
||||
"/developers/leaflet/bbox",
|
||||
"/developers/leaflet/layers",
|
||||
"/developers/leaflet/markers",
|
||||
"/developers/leaflet/lines",
|
||||
"/developers/leaflet/route",
|
||||
"/developers/leaflet/search",
|
||||
"/developers/leaflet/overpass",
|
||||
"/developers/leaflet/icons",
|
||||
"/developers/leaflet/hash",
|
||||
"/developers/leaflet/views",
|
||||
"/developers/leaflet/filter",
|
||||
"/developers/leaflet/click-listener"
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "Frontend",
|
||||
children: [
|
||||
"/developers/frontend/",
|
||||
"/developers/frontend/facilmap"
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Development',
|
||||
children: [
|
||||
"/developers/development/dev-setup",
|
||||
"/developers/development/documentation"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
plugins: [
|
||||
//checkMdPlugin,
|
||||
searchPlugin,
|
||||
]
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
if (typeof window !== "undefined")
|
||||
window.global = window;
|
||||
|
||||
const qrcode = require("vue-qrcode").default;
|
||||
|
||||
export default ({ Vue, options, router, siteData }) => {
|
||||
Vue.component("qrcode", qrcode);
|
||||
};
|
Przed Szerokość: | Wysokość: | Rozmiar: 1.1 KiB Po Szerokość: | Wysokość: | Rozmiar: 1.1 KiB |
|
@ -0,0 +1,4 @@
|
|||
// :root {
|
||||
// --c-brand: hsl(184, 100%, 30%);
|
||||
// --c-brand-light: hsl(184, 100%, 36%);
|
||||
// }
|
|
@ -1,8 +0,0 @@
|
|||
/**
|
||||
* Custom Styles here.
|
||||
*
|
||||
* ref:https://v1.vuepress.vuejs.org/config/#index-styl
|
||||
*/
|
||||
|
||||
.home .hero img
|
||||
max-width 450px!important
|
|
@ -1,10 +0,0 @@
|
|||
/**
|
||||
* Custom palette here.
|
||||
*
|
||||
* ref:https://v1.vuepress.vuejs.org/zh/config/#palette-styl
|
||||
*/
|
||||
|
||||
$accentColor = #3eaf7c
|
||||
$textColor = #2c3e50
|
||||
$borderColor = #eaecef
|
||||
$codeBgColor = #282c34
|
|
@ -1,6 +0,0 @@
|
|||
# Overview
|
||||
|
||||
* [Embed FacilMap](./embed) into your website
|
||||
* [Run your own FacilMap server](./server)
|
||||
|
||||
If you are interested in programmatically receiving or modifying objects on a collaborative map or in reusing individual components of the FacilMap UI, check out the [developer guide](../developers/).
|
|
@ -1,51 +0,0 @@
|
|||
# Embed FacilMap
|
||||
|
||||
You can embed a map into any website using an iframe:
|
||||
|
||||
```html
|
||||
<iframe style="height: 500px; width: 100%; border: none;" src="https://facilmap.org/mymap"></iframe>
|
||||
```
|
||||
|
||||
If you use a map ID that does not exist yet, the “Create Collaborative Map” dialog will be opened when accessing the
|
||||
map (unless the `interactive` parameter is `false`).
|
||||
|
||||
## Options
|
||||
|
||||
You can control the display of different components by using the following query parameters:
|
||||
|
||||
* `toolbox`: Show the toolbox (default: `true`)
|
||||
* `search`: Show the search bar (default: `true`)
|
||||
* `autofocus`: Autofocus the search field (default: `false`)
|
||||
* `legend`: Show the legend if available (default: `true`)
|
||||
* `interactive`: Show certain items (“Create collaborative map”, “Open file”) in the toolbox (default: `false`)
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<iframe style="height: 500px; width: 100%; border: none;" src="https://facilmap.org/mymap?search=false&toolbox=false"></iframe>
|
||||
```
|
||||
|
||||
## Location hash
|
||||
|
||||
When a FacilMap is opened directly in the browser, the current view of the map is [added to the location hash](../users/hash/) (the part after the `#` in the URL). This means that users can easily share the current view by copying the URL straight from the address bar of their browser, and reloading the page will not cause the current view to be lost.
|
||||
|
||||
FacilMap emits a [cross-origin message](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) every time it updates the location map. You can listen to it to synchronize the location hash of your website with the one of FacilMap by using the following script:
|
||||
|
||||
```html
|
||||
<iframe id="facilmap" style="height: 500px; width: 100%; border: none;" src="https://facilmap.org/mymap"></iframe>
|
||||
<script>
|
||||
window.addEventListener("message", function(evt) {
|
||||
if(evt.data && evt.data.type == "facilmap-hash" && location.hash != "#" + evt.data.hash)
|
||||
location.replace("#" + evt.data.hash);
|
||||
});
|
||||
|
||||
function handleHashChange() {
|
||||
var iframe = document.getElementById("facilmap");
|
||||
iframe.src = iframe.src.replace(/(#.*)?$/, "") + location.hash;
|
||||
}
|
||||
|
||||
window.addEventListener("hashchange", handleHashChange);
|
||||
if (location.hash)
|
||||
handleHashChange();
|
||||
</script>
|
||||
```
|
|
@ -1,88 +0,0 @@
|
|||
# Run your own server
|
||||
|
||||
## Using docker
|
||||
|
||||
FacilMap is available as [`facilmap/facilmap`](https://hub.docker.com/r/facilmap/facilmap/) on Docker Hub. Here is
|
||||
an example `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
version: "2"
|
||||
services:
|
||||
facilmap:
|
||||
image: facilmap/facilmap
|
||||
ports:
|
||||
- 8080
|
||||
links:
|
||||
- db
|
||||
environment:
|
||||
USER_AGENT: My FacilMap (https://facilmap.example.org/, facilmap@example.org)
|
||||
DB_TYPE: mysql
|
||||
DB_HOST: db
|
||||
DB_NAME: facilmap
|
||||
DB_USER: facilmap
|
||||
DB_PASSWORD: password
|
||||
ORS_TOKEN: # Get an API key on https://go.openrouteservice.org/ (needed for routing)
|
||||
MAPBOX_TOKEN: # Get an API key on https://www.mapbox.com/signup/ (needed for routing)
|
||||
MAPZEN_TOKEN: # Get an API key on https://mapzen.com/developers/sign_up (needed for elevation info)
|
||||
MAXMIND_USER_ID: # Sign up here https://www.maxmind.com/en/geolite2/signup (needed for geoip lookup to show initial map state)
|
||||
MAXMIND_LICENSE_KEY:
|
||||
restart: on-failure
|
||||
db:
|
||||
image: mariadb
|
||||
environment:
|
||||
MYSQL_DATABASE: facilmap
|
||||
MYSQL_USER: facilmap
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "true"
|
||||
```
|
||||
|
||||
Or the same using `docker create`:
|
||||
|
||||
```bash
|
||||
docker create --link=mysql_mysql_1 -p 8080 --name=facilmap -e "USER_AGENT=My FacilMap (https://facilmap.example.org/, facilmap@example.org)" -e DB_TYPE=mysql -e DB_HOST=mysql_mysql_1 -e DB_NAME=facilmap -e DB_USER=facilmap -e DB_PASSWORD=facilmap -e ORS_TOKEN= -e MAPBOX_TOKEN= -e MAPZEN_TOKEN= --restart on-failure facilmap/facilmap
|
||||
```
|
||||
|
||||
See [below](#config) for the available config options.
|
||||
|
||||
Both the FacilMap server and the frontend will be available via HTTP on port `8080`. It is recommended to use a reverse
|
||||
proxy, such as [`jwilder/nginx-proxy`](https://hub.docker.com/r/jwilder/nginx-proxy), to make it available over HTTPS.
|
||||
|
||||
|
||||
## Standalone
|
||||
|
||||
To run the FacilMap server by hand, follow the following steps:
|
||||
|
||||
1. Make sure that you have a recent version of [Node.js](https://nodejs.org/), [yarn](https://yarnpkg.com/)
|
||||
and a database (MariaDB, MySQL, PostgreSQL, SQLite, Microsoft SQL Server) set up. (Note that only MySQL/MariaDB has been tested so far.)
|
||||
2. Clone the [FacilMap repository](https://github.com/FacilMap/facilmap).
|
||||
3. Run `yarn install` in the root folder of this repository to install the dependencies.
|
||||
4. Run `yarn build` to create the JS bundles.
|
||||
5. Copy `config.env.example` to `config.env` and adjust the configuration (see [below](#config) for the available options).
|
||||
6. Inside the `server` directory, run `yarn server`. This will automatically set up the database structure and start the server.
|
||||
|
||||
|
||||
## Config
|
||||
|
||||
The config can be set either by using environment variables (useful for docker) or by editing `config.env`.
|
||||
|
||||
| Variable | Required | Default | Meaning |
|
||||
|-----------------------|----------|-------------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `USER_AGENT` | * | | Will be used for all HTTP requests (search, routing, GPX/KML/OSM/GeoJSON files). You better provide your e-mail address in here. |
|
||||
| `HOST` | | | The ip address to listen on (leave empty to listen on all addresses) |
|
||||
| `PORT` | | `8080` | The port to listen on. |
|
||||
| `DB_TYPE` | | `mysql` | The type of database. Either `mysql`, `postgres`, `mariadb`, `sqlite`, or `mssql`. |
|
||||
| `DB_HOST` | | `localhost` | The host name of the database server. |
|
||||
| `DB_PORT` | | | The port of the database server (optional). |
|
||||
| `DB_NAME` | | `facilmap` | The name of the database. |
|
||||
| `DB_USER` | | `facilmap` | The username to connect to the database with. |
|
||||
| `DB_PASSWORD` | | `facilmap` | The password to connect to the database with. |
|
||||
| `ORS_TOKEN` | * | | [OpenRouteService API key](https://go.openrouteservice.org). |
|
||||
| `MAPBOX_TOKEN` | * | | [Mapbox API key](https://www.mapbox.com/signup/). |
|
||||
| `MAPZEN_TOKEN` | | | [Mapzen API key](https://mapzen.com/developers/sign_up). |
|
||||
| `MAXMIND_USER_ID` | | | [MaxMind user ID](https://www.maxmind.com/en/geolite2/signup). |
|
||||
| `MAXMIND_LICENSE_KEY` | | | MaxMind license key. |
|
||||
|
||||
FacilMap makes use of several third-party services that require you to register (for free) and generate an API key:
|
||||
* Mapbox and OpenRouteService are used for calculating routes. Mapbox is used for basic routes, OpenRouteService is used when custom route mode settings are made. If these API keys are not defined, calculating routes will fail.
|
||||
* Maxmind provides a free database that maps IP addresses to approximate locations. FacilMap downloads this database to decide the initial map view for users (IP addresses are looked up in FacilMap’s copy of the database, on IP addresses are sent to Maxmind). This API key is optional, if it is not set, the default view will be the whole world.
|
||||
* Mapzen is used to look up the elevation info for search results. The API key is optional, if it is not set, no elevation info will be available for search results.
|
|
@ -1,4 +1,27 @@
|
|||
# Overview
|
||||
|
||||
* Use the [FacilMap client](./client/) to programmatically access and modify a collaborative map.
|
||||
* Read about the [dev setup](./development/dev-setup) to start contributing to the FacilMap code.
|
||||
## Quick links
|
||||
|
||||
* [Embed FacilMap](./embed.md) into any website using an iframe.
|
||||
* Run your own [FacilMap server](./server/).
|
||||
* Use the [FacilMap client](./client/) to programmatically access and modify data on a collaborative map.
|
||||
* Use the [Leaflet components](./leaflet/) to embed certain feature of FacilMap into a Leaflet map.
|
||||
* Use the [FacilMap frontend](./frontend/) to embed an extended or modified version of FacilMap into a website.
|
||||
* Read about the [dev setup](./development/dev-setup.md) to start contributing to the FacilMap code.
|
||||
|
||||
## Structural overview
|
||||
|
||||
FacilMap consists of several layers:
|
||||
* The **Server** is a Node.js app that stores the data of collaborative maps in a database and runs a [socket.io](https://socket.io/) server to access and modify those maps. It also includes a HTTP server that serves the frontend and the map exports.
|
||||
* The **Client** is a JavaScript library that provides methods to access the data on collaborative maps by sending requests to the socket.io server.
|
||||
* The **Leaflet components** are a JavaScript library that provides classes to dynamically show the data received by the Client on a [Leaflet](https://leafletjs.com/) map.
|
||||
* The **Frontend** is a JavaScript app that provides a complete UI written in [Vue.js](https://vuejs.org/) to create, access and modify collaborative maps. It uses the Client to access those maps and the Leaflet components to render them on a map.
|
||||
|
||||
FacilMap is completely written in [TypeScript](https://www.typescriptlang.org/). The code base is split into several NPM modules, each of which can be used independently (although some depend on some others):
|
||||
|
||||
* [facilmap-types](https://www.npmjs.com/package/facilmap-types) provides common TypeScript types for map objects and the socket communication and is used by all other modules.
|
||||
* [facilmap-client](https://www.npmjs.com/package/facilmap-client) contains the [FacilMap client](./client/).
|
||||
* [facilmap-utils](https://www.npmjs.com/package/facilmap-utils) contains helper methods that are used by facilmap-leaflet, facilmap-frontend and facilmap-server, so they can run both in the browser and in Node.js.
|
||||
* [facilmap-leaflet](https://www.npmjs.com/package/facilmap-leaflet) contains the [Leaflet components](./leaflet/).
|
||||
* [facilmap-frontend](https://www.npmjs.com/package/facilmap-frontend) contains the [Frontend](./frontend/).
|
||||
* [facilmap-server](https://www.npmjs.com/package/facilmap-server) contains the [Server](./server/).
|
|
@ -1,18 +1,13 @@
|
|||
# Overview
|
||||
|
||||
The FacilMap client makes a connection to the FacilMap server using [socket.io](http://socket.io/) and
|
||||
automatically receives updates when markers, lines, the map settings, or any other part of a map is created,
|
||||
changed or removed. When connecting to the map using the writable ID, it it also makes it possible to make
|
||||
any modifications to the map that you could make through the web interface, such as creating/changing/removing
|
||||
markers, lines and views and changing the map settings.
|
||||
The FacilMap client makes a connection to the FacilMap server using [socket.io](http://socket.io/). The client serves multiple purposes:
|
||||
* Proxy to third-party services (find, route and geoip)
|
||||
* Open a specific collaborative map and receive the objects on it
|
||||
* Modify the objects on a collaborative map (only if opened through its writable or admin ID)
|
||||
* Be notified live about changes that other people are making to the collaborative map.
|
||||
|
||||
One instance of the Client class represents one connection to one specific collaborative map on one specific
|
||||
FacilMap server. To receive markers and lines, a bbox has to be set, and only the markers and line points within
|
||||
that bbox will be received. The bbox can be changed (for example, if the user drags the map), which causes objects
|
||||
from the new bbox to be received. The Client instance will store all objects that it receives in its [properties](./properties).
|
||||
The socket on the server side maintains different API versions in an attempt to stay backwards compatible with older versions of the client. Have a look at the [./changelog.md](changelog) to find out what has changed when upgrading to a new version of the client.
|
||||
|
||||
Note that in the methods of the client, a collaborative map will be referred to as __pad__. This is because the
|
||||
collaborative part of FacilMap used to be a separate software called FacilPad.
|
||||
|
||||
## Setting it up
|
||||
|
||||
|
@ -22,57 +17,105 @@ Install facilmap-client as a dependency using npm or yarn:
|
|||
npm install -S facilmap-client
|
||||
```
|
||||
|
||||
or load the client directly from facilmap.org (along with socket.io, which is needed by facilmap-client):
|
||||
or import the client from a CDN (only recommended for test purposes):
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/socket.io-client@4"></script>
|
||||
<script src="https://unpkg.com/facilmap-client@3"></script>
|
||||
<script type="module">
|
||||
import Client from "https://esm.sh/facilmap-client";
|
||||
</script>
|
||||
```
|
||||
|
||||
The client class will be available as the global `FacilMap.Client` variable.
|
||||
|
||||
## TypeScript
|
||||
|
||||
facilmap-client is fully typed using [TypeScript](https://www.typescriptlang.org/). While facilmap-client can be used in a plain JavaScript app without problems, it is strongly suggested to use TypeScript, as it greatly helps to understand the data types of events, methods and properties and to avoid errors.
|
||||
|
||||
|
||||
## Setting up a connection
|
||||
## Usage
|
||||
|
||||
One instance of the client class represents one connection to one specific collaborative map on one specific FacilMap server. The client instance knows different states:
|
||||
|
||||
* No map ID set: Only the find, route and geoip methods are available.
|
||||
* No map ID set and bbox set: Simplified versions of the track points of active routes are sent according to the bbox.
|
||||
* Map ID set: All methods are available. Events are received when the map settings, types, views and lines (only metadata, not track points) are created/updated/deleted.
|
||||
* Map ID and bbox set: All methods are available. In addition to the other events, events are received when markers and lines in the specified bounding box are created/updated/deleted.
|
||||
|
||||
It is possible to initialize a client without a map ID and later open a map using [`setMapId`](./methods.md#setmapid-mapid) or [`createMap`](./methods.md#createmap-data). Once a specific map is loaded, it is not possible to close it or switch to another map anymore. To do that, a new client instance has to be created.
|
||||
|
||||
The bbox can be updated continuously. In the official FacilMap UI, the bbox is updated every time the user pans the map, causing the server to send the markers within that bbox and a simplified version of the line track points and active routes fit to the bbox and zoom level.
|
||||
|
||||
### Open a map
|
||||
|
||||
```js
|
||||
let client = new FacilMap.Client("https://facilmap.org/");
|
||||
client.setPadId("myMapId").then((padData) => {
|
||||
console.log(padData);
|
||||
}).catch((err) => {
|
||||
console.error(err.stack);
|
||||
import Client from "facilmap-client";
|
||||
|
||||
const client = new Client("https://facilmap.org/");
|
||||
await client.setMapId("myMapId");
|
||||
console.log(client.mapData, client.types, client.lines);
|
||||
```
|
||||
|
||||
The client [constructor](./methods.md#constructor-server-mapid) takes the URL where the FacilMap server is running and opens a socket.io connection to the server.
|
||||
|
||||
When opening a collaborative map using [`setMapId`](./methods.md#setmapid-mapid), the server sends [events](./events.md) for the map settings, types, views and lines (without track points). The same types of events will be received later if the respective objects are changed while the connection is open. The client has some default listeners registered that will store the data received as events in its [properties](./properties.md). For example, a `mapData` event contains the map settings and is emitted the first time the map ID is set and every time the map settings are changed while the connection is open. The `client.mapData` property always contains the latest state of the map settings.
|
||||
|
||||
Note that most methods of the client are asynchronous. Events that the server fires in response to a method call are always fired before the method returns. This is why in the above example, `client.mapData` and the other properties are available right after the `setMapId` call.
|
||||
|
||||
### Set a bbox
|
||||
|
||||
```js
|
||||
await client.updateBbox({ top: 53.5566, left: 8.7506, right: 19.8468, bottom: 50.1980, zoom: 8 });
|
||||
console.log(client.markers, client.lines);
|
||||
```
|
||||
|
||||
Setting the bounding box of the client will cause the server to send events for all the markers within these bounds, and also for any line track points within the bounds, simplified to be appropriate for the specified zoom level. It will also subscribe to any updates to those objects within the bbox.
|
||||
|
||||
The bbox can be updated again later to receive the data and change the subscription to objects in that bounding box. (Note that when changing the bounding box, the server will not send events again for objects that were already sent as part of the previous bounding box.)
|
||||
|
||||
### Change the map
|
||||
|
||||
```js
|
||||
const newMarker = client.editMarker({ id: 123, title: "New title" });
|
||||
```
|
||||
|
||||
When creating/updating/deleting an object, the data is propagated in multiple ways:
|
||||
* An event representing the change is fired before the method returns (in the above example, a `marker` event)
|
||||
* The client property is updated in reaction to the event (in the above example, the updated marker is stored in `client.markers[123]`)
|
||||
* The method returns the created/updated object.
|
||||
|
||||
Note that creating/updating/deleting an object will fail if the operation is not permitted. The above example will fail if the map was opened using its read-only ID.
|
||||
|
||||
### Internationalization
|
||||
|
||||
Most of the data returned by the client is user-generated and thus not internationalized. There are a few exceptions though, in particular error messages in case someting unexpected happens.
|
||||
|
||||
By default, the FacilMap backend detects the user language based on the `Accept-Language` HTTP header. The detected language can be overridden by setting a `lang` cookie or query parameter. In addition, a `units` cookie or query parameter can be set to `metric` or `us_customary`.
|
||||
|
||||
For the websocket, the `Accept-Language` header, cookies and query parameters are sent during the socket.io handshake. If you want to force the socket to use a sepcific language, you can pass query parameters through the third parameter of the client constructor:
|
||||
```js
|
||||
import Client from "facilmap-client";
|
||||
|
||||
const client = new Client("https://facilmap.org/", undefined, {
|
||||
query: {
|
||||
lang: "en",
|
||||
units: "us_customary"
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
You can also update the internationalization settings for an existing socket connection at any point using [`setLanguage()`](./methods#setlanguage-settings).
|
||||
|
||||
## Using it
|
||||
### Deal with connection problems
|
||||
|
||||
A detailed description of all the [properties](./properties), [events](./events), [methods](./methods) and [Types](./types) can be found in the respective sections of the documentation.
|
||||
|
||||
|
||||
## Change detection
|
||||
|
||||
When the FacilMap server sends an event to the client that an object has been created, changed or deleted, the client emits the
|
||||
event and also persists it in its properties. So you have two ways to access the map data: By listening to the map events and
|
||||
persisting the data somewhere else, or by accessing the properties on the Client object.
|
||||
|
||||
If you are using a UI framework that relies on a change detection mechanism (such as Vue.js or Angular), you can override the methods
|
||||
`_set` and `_delete`. facilmap-client consistently uses these to update any data on its properties.
|
||||
|
||||
In Vue.js, it could look like this:
|
||||
|
||||
```javascript
|
||||
let client = new FacilMap.Client("https://facilmap.org/");
|
||||
client._set = Vue.set;
|
||||
client._delete = Vue.delete;
|
||||
```js
|
||||
const client = new Client("https://facilmap.org/");
|
||||
client.on("connect", () => {
|
||||
console.log("connected");
|
||||
});
|
||||
client.on("disconnect", () => {
|
||||
console.log("disconnected");
|
||||
});
|
||||
```
|
||||
|
||||
In Angular.js, it could look like this:
|
||||
Constructing the client will attempt to connect to the server. socket.io will retry this until it succeeds. Once the connection is made, a `connect` event is fired.
|
||||
|
||||
```javascript
|
||||
let client = new FacilMap.Client("https://facilmap.org/");
|
||||
client._set = (object, key, value) => { $rootScope.$apply(() => { object[key] = value; }); };
|
||||
client._delete = (object, key) => { $rootScope.$apply(() => { delete object[key]; }); };
|
||||
```
|
||||
|
||||
This way your UI framework will detect changes to any properties on the client, and you can reference values like `client.padData.name`,
|
||||
`client.disconnected` and `client.loading` in your UI components.
|
||||
If the connection is lost at some point, a `disconnect` event is fired and socket.io will keep trying to connect again. When it succeeds, a `connect` event is fired again. Since the session on the server is lost when disconnecting, the client will automatically set the last map ID, bbox and routes again on reconnection. This means that events for all the map objects are received again.
|
|
@ -0,0 +1,121 @@
|
|||
# Advanced configuration
|
||||
|
||||
## Reactivity
|
||||
|
||||
When the FacilMap server sends an event to the client that an object has been created, changed or deleted, the client emits the
|
||||
event and also persists it in its properties. So you have two ways to access the map data: By listening to the map events and
|
||||
persisting the data somewhere else, or by accessing the properties on the Client object.
|
||||
|
||||
If you are using a UI framework that relies on a change detection mechanism (such as Vue.js or Angular), facilmap-client provides
|
||||
a way to make its properties reactive. Internally, the client maintains a `state` object (for any properties related to the client
|
||||
itself) and a `data` object (for any received map objects). These two objects are stored as private properties on the client object.
|
||||
All public properties of the client object are just getters that return the data from the two private objects. The client modifies
|
||||
these two objects consistently in the following way:
|
||||
* When the client object is constructed, it constructs the private `state` and `data` objects by calling
|
||||
`this.object = makeReactive(object)`.
|
||||
* When the client sets a property inside the `state` and `data` objects, it does so by calling `this._set(this.object, key, value)`.
|
||||
This includes setting nested properties.
|
||||
* When the client deletes a property inside the `state` and `data` objects, it does so by calling `this._delete(this.object, key)`.
|
||||
This includes deleting nested properties.
|
||||
|
||||
You can override the `_makeReactive`, `_set` and `_delete` methods to make the private properties (and as a consequence the public
|
||||
getters) of facilmap-client reactive. This way your UI framework will detect changes to any properties on the client, and you can
|
||||
reference values like `client.mapData.name`, `client.disconnected` and `client.loading` in your UI components.
|
||||
|
||||
Note that client always replaces whole objects rather than updating individual properties. For example, when a new version of the map settings arrives, `client.mapData` is replaced with a new object, or when a new marker arrives, `client.markers[markerId]` is replaced with a new object. This makes deep watches unnecessary in most cases.
|
||||
|
||||
### Vue.js 3
|
||||
|
||||
```javascript
|
||||
class ReactiveClient extends Client {
|
||||
_makeReactive(object) {
|
||||
return Vue.reactive(object);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Vue.js 2
|
||||
|
||||
```javascript
|
||||
class ReactiveClient extends Client {
|
||||
_set(object, key, value) {
|
||||
Vue.set(object, key, value);
|
||||
}
|
||||
|
||||
_delete(object, key) {
|
||||
Vue.delete(object, key);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Angular.js
|
||||
|
||||
```javascript
|
||||
class ReactiveClient extends Client {
|
||||
_set(object, key, value) {
|
||||
$rootScope.$apply(() => {
|
||||
object[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
_delete(object, key) {
|
||||
$rootScope.$apply(() => {
|
||||
delete object[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### React
|
||||
|
||||
```javascript
|
||||
class ObservableClient extends Client {
|
||||
_observers = new Set();
|
||||
|
||||
subscribe(callback) {
|
||||
this._observers.add(callback);
|
||||
return () => {
|
||||
this._observers.delete(callback);
|
||||
};
|
||||
}
|
||||
|
||||
_triggerObservers() {
|
||||
for (const observer of this._observers) {
|
||||
observer();
|
||||
}
|
||||
}
|
||||
|
||||
_set(object, key, value) {
|
||||
object[key] = value;
|
||||
this._triggerObservers();
|
||||
}
|
||||
|
||||
_delete(object, key) {
|
||||
delete object[key];
|
||||
this._triggerObservers();
|
||||
}
|
||||
}
|
||||
|
||||
function useClientObserver(client, selector) {
|
||||
React.useSyncExternalStore(
|
||||
(callback) => client.subscribe(callback),
|
||||
() => selector(client)
|
||||
);
|
||||
}
|
||||
|
||||
const MarkerInfo = ({ client, markerId }) => {
|
||||
const marker = useClientObserver(client, (client) => client.markers[markerId]);
|
||||
return (
|
||||
<div>
|
||||
Marker name: {marker?.name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Keep in mind that React’s `useSyncExternalStore` will rerender the component if the resulting _object reference_ changes.
|
||||
This means one the one hand that you cannot use this example implementation on a higher up object of the client (such as
|
||||
`client` itself or `client.markers`), as their identity never changes, causing your component to never rerender. And on
|
||||
the other hand that you should avoid using it on objects created in the selector (such as returning
|
||||
`[client.mapData.id, client.mapData.name]` in order to get multiple values at once), as it will cause your component to
|
||||
rerender every time the selector is called.
|
|
@ -0,0 +1,16 @@
|
|||
# Changelog
|
||||
|
||||
The websocket on the FacilMap server provides different API versions (implemented as socket.io namespaces such as `/` for version 1, `/v2` for version 2, etc.) in order to stay backwards compatible with older clients. Each release of facilmap-client is adapted to a particular API version of the server. When upgrading to a new version of the client, have a look at this page to find out what has changed.
|
||||
|
||||
## v5.0.0 (API v3)
|
||||
|
||||
_Note: This socket version is still under development and will still change. Do not use it in production yet._
|
||||
|
||||
* “symbol” was renamed to “icon” everywhere. This applies to `Marker.symbol`, `Type.defaultSymbol`, `Type.symbolFixed`, `Type.fields[].controlSymbol` and `Type.fields[].options[].symbol`.
|
||||
* “pad” was renamed “map” everywhere. This applies to the `padData` and `deletePad` socket events and `getPad` (including its `padId` request property), the `findPads`, `createPad`, `editPad`, `deletePad`, `setPadId` client/socket methods, the `PadNotFoundError`, and the `Marker.padId`, `Line.padId`, `Type.padId`, `View.padId` and `HistoryEntry.padId` properties.
|
||||
|
||||
## v4.0.0 (API v2)
|
||||
|
||||
* Before, creating a map with an empty name resulted in `padData.name` set to `"Unnamed map"`. Now, an empty name will result in `""` and the UI is responsible for displaying that in an appropriate way.
|
||||
* Before, creating a marker with an empty name resulted in `marker.name` set to `"Untitled marker"`. Now an empty name will result in `""` and the UI is responsible for displaying that in an appropriate way.
|
||||
* Before, creating a line with an empty name resulted in `line.name` set to `"Untitled line"`. Now an empty name will result in `""` and the UI is responsible for displaying that in an appropriate way.
|
|
@ -1,26 +1,36 @@
|
|||
# Events
|
||||
|
||||
Subscribe to events using the [`on(eventName, function)`](./methods#on-eventname-function) method. Example:
|
||||
The FacilMap server uses events to send information about objects on a collaborative map to the client. The events are fired when the client opens a map or a particular section of a map for the first time, and whenever an object is changed on the map (including when the change is made by the same instance of the client). The client has some listeners already attached to most events and uses them to persist and update the received objects in its [properties](./properties.md).
|
||||
|
||||
Note that events are always fired _before_ the method causing them returns. For example, when updating a marker using the `editMarker()` method, a `marker` event with the updated marker is fired first (if the marker is within the current bbox), and only then the method returns the updated marker as well.
|
||||
|
||||
Subscribe to events using the [`on(eventName, function)`](./methods.md#on-eventname-function) method. Example:
|
||||
|
||||
```js
|
||||
let client = new FacilMap.Client("https://facilmap.org/", "testPad");
|
||||
client.on("padData", (padData) => {
|
||||
document.title = padData.name;
|
||||
const client = new FacilMap.Client("https://facilmap.org/", "testMap");
|
||||
client.on("mapData", (mapData) => {
|
||||
document.title = mapData.name;
|
||||
});
|
||||
```
|
||||
|
||||
## `connect`, `disconnect`, `connect_error`, `error`, `reconnect`, `reconnect_attempt`, `reconnect_error`, `reconnect_failed`
|
||||
|
||||
These events come from socket.io and are [documented there under the section “Events”](http://socket.io/docs/client-api/).
|
||||
These events come from socket.io and are [documented there](https://socket.io/docs/v4/client-api/#events).
|
||||
|
||||
## `padData`
|
||||
## `mapData`
|
||||
|
||||
The settings of the map have changed or are retrieved for the first time.
|
||||
|
||||
Note that when this event is fired, the read-only and/or the read-write ID of the map might have changed. The [`padId`](./properties#padid)
|
||||
Note that when this event is fired, the read-only and/or the read-write ID of the map might have changed. The [`mapId`](./properties.md#mapid)
|
||||
property is updated automatically.
|
||||
|
||||
_Type:_ [padData](./types#paddata)
|
||||
_Type:_ [MapData](./types.md#mapdata)
|
||||
|
||||
## `serverError`
|
||||
|
||||
[`setMapId()`](./methods.md#setmapid-mapid) failed and the map could not be opened.
|
||||
|
||||
_Type:_ Error
|
||||
|
||||
## `deletePad`
|
||||
|
||||
|
@ -30,14 +40,14 @@ The map has been deleted.
|
|||
|
||||
An existing marker is retrieved for the first time, has been modified, or a new marker has been created in the current bbox.
|
||||
|
||||
_Type:_ [marker](./types#marker)
|
||||
_Type:_ [Marker](./types.md#marker)
|
||||
|
||||
## `deleteMarker`
|
||||
|
||||
A marker has been removed. This event is emitted for all markers in the map, even if they are outside of the current bbox
|
||||
A marker has been removed. This event is emitted for all markers on the map, even if they are outside of the current bbox
|
||||
(in case that a marker outside of the current bbox is cached).
|
||||
|
||||
_Type:_ `{id: "<markerId>"}`
|
||||
_Type:_ `{ id: number }`
|
||||
|
||||
## `line`
|
||||
|
||||
|
@ -45,64 +55,82 @@ An existing line is retrieved for the first time, has been modified, or a new li
|
|||
objects only contain the line metadata, not its track points (those are handled separately as `linePoints`). This is why
|
||||
all line objects of the map are sent to the client, regardless of the current bbox.
|
||||
|
||||
_Type:_ [line without trackPoints](./types#line)
|
||||
_Type:_ [Line](./types.md#line) (without trackPoints)
|
||||
|
||||
## `deleteLine`
|
||||
|
||||
A line has been removed.
|
||||
|
||||
_Type:_ `{id: "<lineId>"}`
|
||||
_Type:_ `{ id: number }`
|
||||
|
||||
## `linePoints`
|
||||
|
||||
New track points for an existing line are retrieved after a change of bbox (`reset == false`), or the line has been
|
||||
modified, so the new track points are retrieved (`reset == true`).
|
||||
|
||||
_Type:_ {
|
||||
_Type:_ object with the following properties:
|
||||
* __id__ (number): The ID of the line that these track points belong to
|
||||
* __reset__ (boolean): Whether to remove all cached track points for this line (`true`) or to merge these track points
|
||||
with the cached ones (`false`).
|
||||
* __trackPoints__ (Array<[trackPoint](./types#trackpoint)>): The track points
|
||||
|
||||
}
|
||||
* __trackPoints__ (Array<[TrackPoint](./types.md#trackpoint)>): The track points
|
||||
|
||||
## `view`
|
||||
|
||||
A view is retrieved for the first time, has been modified, or a new view has been created.
|
||||
|
||||
_Type:_ [view](./types#view)
|
||||
_Type:_ [View](./types.md#view)
|
||||
|
||||
## `deleteView`
|
||||
|
||||
A view has been removed.
|
||||
|
||||
_Type:_ `{id: "<viewId>"}`
|
||||
_Type:_ `{ id: number }`
|
||||
|
||||
## `type`
|
||||
|
||||
A type is retrieved for the first time, has been modified, or a new type has been created.
|
||||
|
||||
_Type:_ [type](./types#type)
|
||||
_Type:_ [Type](./types.md#type)
|
||||
|
||||
## `deleteType`
|
||||
|
||||
A type has been removed.
|
||||
|
||||
_Type:_ `{id: "<typeId>"}`
|
||||
_Type:_ `{ id: number }`
|
||||
|
||||
## `history`
|
||||
|
||||
An entry of the modification history is retrieved for the first time, or a new entry has been created due to something
|
||||
being modified. Note that this event is only fired when the client has subscribed using [`listenToHistory()`](./methods#listentohistory).
|
||||
|
||||
_Type:_ [historyEntry](./types#historyentry)
|
||||
|
||||
being modified. Note that this event is only fired when the client has subscribed using [`listenToHistory()`](./methods.md#listentohistory).
|
||||
|
||||
_Type:_ [historyEntry](./types.md#historyentry)
|
||||
|
||||
## `route`
|
||||
|
||||
A new route has been set.
|
||||
|
||||
_Type:_ [Route](./types.md#route)> with trackpoints for the current bbox. The `routeId` property identifies the route (can be a string or undefined).
|
||||
|
||||
## `clearRoute`
|
||||
|
||||
A route has been cleared.
|
||||
|
||||
_Type:_ `{ routeId: string | undefined }`
|
||||
|
||||
## `routePoints`
|
||||
|
||||
New track points for the temporary route are retrieved after a change of bbox.
|
||||
New track points for the default route (route that has been set using [`setRoute()`](./methods.md#setroute-data) without a `routeId`) are retrieved after a change of bbox.
|
||||
|
||||
_Type:_ Array<[TrackPoint](./types.md#trackpoint)>
|
||||
|
||||
## `routePointsWithId`
|
||||
|
||||
New track points for a route with a `routeId` are retrieved after a change of bbox.
|
||||
|
||||
_Type:_ object with the following properties:
|
||||
* **routeId** (string): The `routeId` that was passed when setting the route using [`setRoute()`](./methods.md#setroute-data)
|
||||
* **trackPoints** (`Array<[trackPoint](./types.md#trackpoint)>`): The additional track points for the route
|
||||
|
||||
_Type:_ Array<[trackPoint](./types#trackpoint)>
|
||||
|
||||
## `loadStart`, `loadEnd`
|
||||
|
||||
This event is fired every time some request is sent to the server and when the response has arrived. It can be used to
|
||||
|
@ -118,4 +146,8 @@ client.on("loadEnd", () => {
|
|||
if(--loading == 0)
|
||||
hideLoadingIndicator();
|
||||
});
|
||||
```
|
||||
```
|
||||
|
||||
## `emit`, `emitResolve`, `emitReject`
|
||||
|
||||
`emit` is emitted by the client whenever any request is sent to the server, and `emitResolve` or `emitReject` is emitted when the request is answered. These can be used to hook into the communication between the client and the server. All 3 events are called with two arguments, the first one being the request name and the second one being the request data, response data or error.
|
|
@ -1,75 +1,114 @@
|
|||
# Methods
|
||||
|
||||
## `constructor(server, padId)`
|
||||
## `constructor(server, mapId, socketOptions)`
|
||||
|
||||
Connects to the FacilMap server `server` and optionally opens the collaborative map with the ID `padId`. If the pad ID
|
||||
is not set, it can be set later using [`setPadId(padId)`](#setpadid-padid) or using [`createPad(data)`](#createpad-data).
|
||||
Connects to the FacilMap server `server` and optionally opens the collaborative map with the ID `mapId`. If the map ID
|
||||
is not set, it can be set later using [`setMapId(mapId)`](#setmapid-mapid) or using [`createMap(data)`](#createmap-data).
|
||||
|
||||
Setting the padId causes the server to send several objects, such as the map settings, all views, all types, and all
|
||||
lines (just meta data, without line points).
|
||||
The connection is established in the background, and a `connect` event is fired when it is successful. If a `mapId` is specified, a [`mapData`](./events.md#mapdata) or [`serverError`](./events.md#servererror) event will indicate when the map has been opened successfully or unsuccessfully. Note that you can already call methods immediately after constructing the client, causing them to be delayed until the connection is established.
|
||||
|
||||
If the connection to the server breaks down, a `disconnect` event will be emitted and socket.io will attempt to reconnect.
|
||||
On successful reconnection, a `reconnect` and `connect` event will be fired.
|
||||
If the connection to the server breaks down, a `disconnect` event will be emitted and socket.io will attempt to reconnect. On successful reconnection, a `reconnect` and `connect` event will be fired. During the interruption, you can still call methods, causing them to be delayed until the connection is reestablished.
|
||||
|
||||
* `server` (string): The URL of the FacilMap server, for example `https://facilmap.org/`
|
||||
* `padId` (string, optional): The ID of the collaborative map to open
|
||||
* `server` (string): The URL of the FacilMap server, for example `https://facilmap.org/`.
|
||||
* `mapId` (string, optional): The ID of the collaborative map to open.
|
||||
* `socketOptions` (object, optional): Any additional [Socket.io client options](https://socket.io/docs/v4/client-options/).
|
||||
* **Events:** Causes a `connect` event to be fired when the connection is established. If `mapId` is defined, causes events to be fired with the map settings, all views, all types and all lines (without line points) of the map. If the map with `mapId` could not be opened, causes a [`serverError`](./events.md#servererror) event.
|
||||
|
||||
## `on(eventName, function)`
|
||||
|
||||
Registers a new [event](./events) handler.
|
||||
Registers a new [event](./events.md) handler.
|
||||
|
||||
* `eventName` (string): The name of the event
|
||||
* `function` (function): The function that should be executed when the event occurs. If the event emits an object,
|
||||
it will be passed to the function as the first parameter.
|
||||
* `eventName` (string): The name of the event.
|
||||
* `function` (function): The function that should be executed when the event occurs. If the event emits an object, it will be passed to the function as the first parameter.
|
||||
|
||||
## `removeListener(eventName, function)`
|
||||
|
||||
Unregisters an event handler previously assigned using `on(eventName, function)`.
|
||||
|
||||
* `eventName` (string): The name of the event
|
||||
* `function` (function): The function that was passed to `on(eventName, function)` when registering the handler
|
||||
* `eventName` (string): The name of the event.
|
||||
* `function` (function): The function that was passed to `on(eventName, function)` when registering the handler.
|
||||
|
||||
## `setPadId(padId)`
|
||||
## `setMapId(mapId)`
|
||||
|
||||
Opens the collaborative map with the ID `padId`.
|
||||
Opens the collaborative map with the ID `mapId`.
|
||||
|
||||
This method can only be called once, and only if no `padId` has been passed to the constructor. If you want to open
|
||||
a different map, you need to create a new instance of the client.
|
||||
This method can only be called once, and only if no `mapId` was passed to the constructor. If you want to open a different map, you need to create a new instance of the client.
|
||||
|
||||
Setting the padId causes the server to send several objects, such as the map settings, all views, and all lines (just
|
||||
meta data, without line points).
|
||||
Setting the mapId causes the server to send several objects, such as the map settings, all views, and all lines (just metadata, without line points). Each of these objects is sent as an individual [`event`](./events.md).
|
||||
|
||||
* `padId` (string): The ID of the collaborative map to open
|
||||
* _returns_ (Promise): A promise that resolves when all objects have been received.
|
||||
* `mapId` (string): The ID of the collaborative map to open. Can be a read-only ID, writable ID or admin ID of a map.
|
||||
* **Returns:** A promise that is resolved empty when all objects have been received.
|
||||
* **Events:** Causes events to be fired with the map settings, all views, all types and all lines (without line points) of the map. If the map could not be opened, causes a [`serverError`](./events.md#servererror) event.
|
||||
* **Availability:** Only available if no map is opened yet on this client instance.
|
||||
|
||||
## `setLanguage(settings)`
|
||||
|
||||
Updates the language settings for the current socket connection. Usually this only needs to be called if the user changes their internationalization settings and you want to apply the new settings live in the UI. See [Internationalization](./#internationalization) for the details and how to set the language settings when opening a client.
|
||||
|
||||
* `settings`: An object with the following properties:
|
||||
* `lang` (optional): The language, for example `en` or `de`.
|
||||
* `units` (optional): The units to use, either `metric` or `us_costomary`.
|
||||
* **Returns:** A promise tat is resolved empty when the settings have been applied.
|
||||
* **Events:** None.
|
||||
* **Availability:** Always.
|
||||
|
||||
## `updateBbox(bbox)`
|
||||
|
||||
Updates the bbox. This will cause all markers and line points within the bbox (except the ones that were already in the
|
||||
previous bbox, if there was one) to be received.
|
||||
Updates the bbox. This will cause all markers, line points and route points within the bbox (except the ones that were already in the previous bbox, if there was one) to be received as individual events.
|
||||
|
||||
* __bbox__ ([bbox](./types#bbox) with zoom): The bbox that objects should be received for
|
||||
* _returns_ (Promise): A promise that resolves when all objects have been received.
|
||||
* `bbox` ([Bbox](./types.md#bbox) with zoom): The bbox that objects should be received for.
|
||||
* **Returns:** A promise that is resolved empty when all objects have been received.
|
||||
* **Events:** Causes events to be fired with the markers, line points and route points within the bbox.
|
||||
* **Availability:** Always.
|
||||
|
||||
## `createPad(data)`
|
||||
## `getMap(data)`
|
||||
|
||||
Creates a new collaborative map.
|
||||
Finds a collaborative map by ID. This can be used to check if a map with a certain ID exists.
|
||||
|
||||
* `data` ([padData](./types#paddata)): The data of the new map, including the desired read-only and writable ID
|
||||
* _returns_ (Promise): A promise that resolves when the map has been created, returning the new padData.
|
||||
* `data`: An object with the following properties:
|
||||
* `mapId`: The read-only, writable or admin ID of the map.
|
||||
* **Returns:** A promise that is resolved with undefined (if no map with that ID exists) or with an object with an `id` (read-only ID), `name` and `description` property.
|
||||
* **Events:** None.
|
||||
* **Availability:** Always.
|
||||
|
||||
## `editPad(data)`
|
||||
## `findMaps(data)`
|
||||
|
||||
Update the metadata of the current map.
|
||||
Finds collaborative maps by a search term. Only finds maps that have been made public by setting [`searchEngines`](./types.md#mapdata) to `true`.
|
||||
|
||||
* `data` ([padData](./types#paddata)): The data of the map that should be modified. Fields that are not defined will not be
|
||||
modified. To change the default view, set the `defaultViewId` property. The `defaultView` property is ignored.
|
||||
* _returns_ (Promise): The new padData.
|
||||
* `data`: An object with the following properties:
|
||||
* `query` (string): A search term. `*` can be used as a wildcard and `?` as a single-character wildcard.
|
||||
* `start`, `limit` (number): If specified, can be used for paging.
|
||||
* **Returns:**: A promise that is resolved to an object with the following properties:
|
||||
* `results`: An array of objects with an `id`, `name` and `description` property.
|
||||
* `totalLength`: The total number of results. If paging is used, this number may be higher than the number of `results` returned.
|
||||
* **Events:** None.
|
||||
* **Availability:** Always.
|
||||
|
||||
## `deletePad()`
|
||||
## `createMap(data)`
|
||||
|
||||
Creates a new collaborative map and opens it.
|
||||
|
||||
* `data` ([mapData](./types.md#mapdata)): The data of the new map, including the desired read-only, writable and admin ID.
|
||||
* **Returns:** A promise that is resolved with the new mapData when the map has been created.
|
||||
* **Events:** Causes a [`mapData`](./events.md#mapdata) event and other events for objects that have been created on the map (such as the default Marker and Line types).
|
||||
* **Availability:** Only if no collaborative map is opened yet.
|
||||
|
||||
## `editMap(data)`
|
||||
|
||||
Update the map settings of the current map.
|
||||
|
||||
* `data` ([MapData](./types.md#mapdata)): The data of the map that should be modified. Fields that are not defined will not be modified. To change the default view, set the `defaultViewId` property. The `defaultView` property is ignored.
|
||||
* **Returns:** A promise that is resolved with the new mapData.
|
||||
* **Events:** Causes a [`mapData`](./events.md#mapdata) event.
|
||||
* **Availability:** Only if a collaborative map is opened through its admin ID.
|
||||
|
||||
## `deleteMap()`
|
||||
|
||||
Delete the current map irrevocably.
|
||||
|
||||
* _returns_ (Promise): An empty promise that resolves when the map has been deleted.
|
||||
* **Returns:** A promise that is resolved empty when the map has been deleted.
|
||||
* **Events:** Causes a [`deleteMap`](./events.md#deletemap) event.
|
||||
* **Availability:** Only if a collaborative map is opened through its admin ID.
|
||||
|
||||
## `listenToHistory()`
|
||||
|
||||
|
@ -77,13 +116,17 @@ Start listening to the modification history of the map. Calling this will cause
|
|||
received (that describe the modification history until now), and new `history` objects will be received every time
|
||||
something is modified (in addition to the modified object).
|
||||
|
||||
* _returns_ (Promise): A promise that resolves when all history objects have been received
|
||||
* **Returns:** A promise that is resolved empty when all history objects have been received.
|
||||
* **Events:** Causes multiple [`history`](./events.md#history) events.
|
||||
* **Availability:** Only if a collaborative map is opened through its admin ID.
|
||||
|
||||
## `stopListeningToHistory()`
|
||||
|
||||
Stop listening to the modification history of the map.
|
||||
|
||||
* _returns_ (Promise): A promise that resolves when the command has completed.
|
||||
* **Returns:** A promise that is resolved empty when the command has completed.
|
||||
* **Events:** None.
|
||||
* **Availability:** Only if a collaborative map is opened through its admin ID and [`listenToHistory()`](#listentohistory) has been called before.
|
||||
|
||||
## `revertHistoryEntry(data)`
|
||||
|
||||
|
@ -91,8 +134,10 @@ Undo a modification in the map. When a previously removed object is restored, it
|
|||
IDs of all other history entries connected to this object are updated as well. This is why reverting a history entry
|
||||
will cause the whole history to be received again (as if you were calling `listenToHistory()` again).
|
||||
|
||||
* `data` (`{id: "<historyEntryId>"}`)): The history object that should be reverted
|
||||
* _returns_ (Promise): A promise that resolves when the command has completed and all new history objects have been received
|
||||
* `data` (`{ id: number }`)): The history object that should be reverted.
|
||||
* **Returns:** A promise that is resolved empty when the command has completed and all new history objects have been received.
|
||||
* **Events:** Causes multiple [`history`](./events.md#history) events and an event that reverts the change.
|
||||
* **Availability:** Only if a collaborative map is opened through its admin ID.
|
||||
|
||||
## `disconnect()`
|
||||
|
||||
|
@ -100,14 +145,16 @@ Empties all cached objects and disconnects from the server.
|
|||
|
||||
## `find(data)`
|
||||
|
||||
Search for places.
|
||||
Search for places. Does not persist anything on the server, simply serves as a proxy to the search service.
|
||||
|
||||
* `data` (object): An object with the following properties:
|
||||
* `query` (string): The query string
|
||||
* `loadUrls` (boolean): Whether to return the file if `query` is a URL
|
||||
* `elevation` (boolean): Whether to find out the elevation of the result(s). Will make the search significantly slower.
|
||||
* _returns_ (Promise<string|Array<[searchResult](./types#searchresult)>>): If `data.query` is a URL to a GPX/KML/OSM/GeoJSON
|
||||
file, that file as a string, otherwise an array of search results.
|
||||
* `query` (string): The query string
|
||||
* `loadUrls` (boolean): Whether to return the file if `query` is a URL
|
||||
* **Returns:** A promise that is resolved with the following value:
|
||||
* If `data.query` is a URL to a GPX/KML/OSM/GeoJSON file and `loadUrls` is `true`, a string with the content of the file.
|
||||
* Otherwise an array of [SearchResults](./types.md#searchresult).
|
||||
* **Events:** None.
|
||||
* **Availability:** Always.
|
||||
|
||||
## `findOnMap(data)`
|
||||
|
||||
|
@ -115,46 +162,59 @@ Search for markers and lines inside the map.
|
|||
|
||||
* `data` (object): An object with the following properties:
|
||||
* `query` (string): The query string
|
||||
* _returns_ (Promise<Array<[Marker](./types#marker)|[Line](./types#line)>>) An array of (stripped down) marker and line objects.
|
||||
The objects only contain the `id`, `name`, `typeId`, `lat`/`lon` (for markers), `left`/`top`/`right`/`bottom` (for
|
||||
lines) properties, plus an additional `kind` property that is either `"marker"` or `"line"`.
|
||||
* **Returns:** A promise that is resolved with an array of (stripped down) [Marker](./types.md#marker) and [Line](./types.md#line) objects. The objects only contain the `id`, `name`, `typeId`, `lat`/`lon` (for markers), `left`/`top`/`right`/`bottom` (for lines) properties, plus an additional `kind` property that is either `"marker"` or `"line"`.
|
||||
* **Events:** None.
|
||||
* **Availability:** Only when a map is opened.
|
||||
|
||||
## `getRoute(data)`
|
||||
|
||||
Calculate a route between two or more points.
|
||||
Calculate a route between two or more points. Does not persist anything on the server, simply serves as a proxy to the routing service.
|
||||
|
||||
* `data` (object): An object with the following properties:
|
||||
* `destinations` (array): An array of at least two route points (objects with a `lat` and `lon` property)
|
||||
* `mode` (string): `"car"`, `"bicycle"` or `"pedestrian"`
|
||||
* _returns_ (Promise<[route](./types#route)>)
|
||||
* `destinations` (array): An array of at least two route points (objects with a `lat` and `lon` property)
|
||||
* `mode` ([RouteMode](./types.md#routemode)): the route mode
|
||||
* **Returns:** A promise that is resolved with a [Route](./types.md#route)>.
|
||||
* **Events:** None.
|
||||
* **Availability:** Always.
|
||||
|
||||
## `setRoute(data)`
|
||||
|
||||
Calculate a route between two or more points, but but do not return the track points of the route but cache them on the
|
||||
server side and send them according to the client bbox. The properties of the route are saved in the [`route`](./properties#route)
|
||||
property and the trackPoints in `route.trackPoints`. Only one temporary route like this can be set at a time, when
|
||||
calling `setRoute()` again, the existing route will be modified.
|
||||
Calculate a route between two or more points, but but do not return the track points of the route but cache them on the server side and send them according to the client bbox. The route is not persisted on a collaborative map, but is temporarily persisted on the server in the scope one particular client connection only. As long as the route is active, the server will send [`routePoints`](./events.md#routepoints) events in response to [`updateBbox()`](#updatebbox-bbox) with the track points of the route simplified according to the bbox. The route will stay active until it is cleared using [`clearRoute()`](#clearroute-data) or the connection is closed.
|
||||
|
||||
Multiple routes can be active at the same time. They can be distinguished by their `routeId` property, which is a custom string that you can specify when activating a route. A `routeId` needs to be unique in the scope of this client instance, other clients are not affected by it. For backwards compatibility reasons, `undefined` is an acceptable value for `routeId`, but is considered a unique identifier nonetheless.
|
||||
|
||||
Calling `setRoute()` with a `routeId` of a route that is already active will replace that route.
|
||||
|
||||
The metadata of a route whose `routeId` is `undefined` is persisted in the [`route`](./properties.md#route) property and its track points in `route.trackPoints`. The metadata of a route whose `routeId` is a string is persisted in the [`routes[routeId]`](./properties.md#routes) property and its track points in `routes[routeId].trackPoints`.
|
||||
|
||||
* `data` (object): An object with the following properties:
|
||||
* `routePoints` (array): An array of at least two route points (objects with a `lat` and `lon` property)
|
||||
* `mode` (string): `"car"`, `"bicycle"` or `"pedestrian"`
|
||||
* `elevation` (boolean): `true` to get elevation data for the route
|
||||
* _returns_ (Promise<[route](./types#route)>)
|
||||
* `routePoints` (array): An array of at least two route points (objects with a `lat` and `lon` property)
|
||||
* `mode` ([RouteMode](./types.md#routemode)): the route mode
|
||||
* `routeId` (string or undefined): the custom `routeId` to identify the route
|
||||
* **Returns:** A promise that is resolved with a [Route](./types.md#route)> object.
|
||||
* **Events:** Causes a [`route`](./events.md#route) and a [`routePoints`](./events.md#routepoints) event.
|
||||
* **Availability:** Always.
|
||||
|
||||
## `clearRoute()`
|
||||
## `clearRoute(data)`
|
||||
|
||||
Clear the temporary route set via [`setRoute(data)`](#setroute-data).
|
||||
Clear a temporary route set via [`setRoute(data)`](#setroute-data).
|
||||
|
||||
* _returns_ (Promise)
|
||||
* `data` (object): An object with the following properties:
|
||||
* `routeId` (string or undefined): the custom `routeId` to identify the route
|
||||
* **Returns:** A promise that is resolved empty when the route is cleared.
|
||||
* **Events:** Causes a [`clearRoute`](./events.md#clearroute) event.
|
||||
* **Availability:** If a route with the specified `routeId` is active.
|
||||
|
||||
## `lineToRoute(data)`
|
||||
|
||||
Call [`setRoute()`](#setroute-data) with the parameters of an existing line. Saves time, as the route does not need to be
|
||||
recalculated.
|
||||
Call [`setRoute()`](#setroute-data) with the parameters of an existing line. Saves time, as the route does not need to be recalculated. If a route with the same `routeId` is already active, it is replaced.
|
||||
|
||||
* `data` (object): An object with the following properties:
|
||||
* `id` (string): The ID of the line
|
||||
* _returns_ (Promise<[route](./types#route)>)
|
||||
* `routeId` (string or undefined): the custom `routeId` to identify the route
|
||||
* **Returns:** A promise that is resolved with a [Route](./types.md#route)> object.
|
||||
* **Events:** Causes a [`route`](./events.md#route) and a [`routePoints`](./events.md#routepoints) event.
|
||||
* **Availability:** Only if a collaborative map is opened.
|
||||
|
||||
## `exportRoute(data)`
|
||||
|
||||
|
@ -164,69 +224,84 @@ Export the current route.
|
|||
* `format` (string): One of the following:
|
||||
* `gpx-trk`: GPX track (contains the whole course of the route)
|
||||
* `gpx-rte`: GPX route (contains only the route points, and the navigation device will have to calculate the route)
|
||||
* _returns_ (Promise<string>)
|
||||
* `routeId` (string or undefined): the custom `routeId` to identify the route
|
||||
* **Returns:** A promise that is resolved with a string with the file contents.
|
||||
* **Events:** None.
|
||||
* **Availability:** if a route with the specified `routeId` is active.
|
||||
|
||||
## `getMarker(data)`
|
||||
|
||||
Get the marker with the given ID.
|
||||
Get the marker with the given ID. This is useful if you want to access a specific marker but it is not loaded as part of the current bbox.
|
||||
|
||||
* `data` (object): An object with the following properties:
|
||||
* `id` (number): The ID of the marker to load
|
||||
* _returns_ (Promise<[marker](./types#marker)>): The marker
|
||||
* `id` (number): The ID of the marker to load
|
||||
* **Returns:** A promise that is resolved with a [Marker](./types.md#marker)>. If the marker is not found, the promise rejects.
|
||||
* **Events:** None.
|
||||
* **Availability:** Only if a collaborative map is opened.
|
||||
|
||||
## `addMarker(data)`
|
||||
|
||||
Create a marker.
|
||||
|
||||
* `data` ([marker](./types#marker)): The data of the marker to create. An `id` will be assigned by the server
|
||||
* _returns_ (Promise<[marker](./types#marker)>): The marker as it is on the server, with an `id` assigned and possibly its
|
||||
styles modified by the settings of its type.
|
||||
* `data` ([Marker](./types.md#marker)): The data of the marker to create. An `id` will be assigned by the server.
|
||||
* **Returns:** A promise that is resolved with a [Marker](./types.md#marker)>, with an `id` assigned and possibly its styles modified by the settings of its type.
|
||||
* **Events:** May trigger a [`marker`](./events.md#marker) event if the created marker is in the current bbox.
|
||||
* **Availability:** Only if a collaborative map is opened using its writable or admin ID.
|
||||
|
||||
## `editMarker(data)`
|
||||
|
||||
Update an existing marker.
|
||||
|
||||
* `data` ([marker](./types#marker)). The new marker data. Fields that are not defined will not be unmodified. Only `id` needs
|
||||
to be defined.
|
||||
* _returns_ (Promise<[marker](./types#marker)>): The new marker. Might have some styles modified due to the settings of its type
|
||||
* `data` ([Marker](./types.md#marker)). The new marker data. Fields that are not defined will not be unmodified. Only `id` needs to be defined.
|
||||
* **Returns:** A promise that is resolved with the updated [Marker](./types.md#marker). Might have some styles modified due to the settings of its type.
|
||||
* **Events:** May trigger a [`marker`](./events.md#marker) event if the updated marker is in the current bbox.
|
||||
* **Availability:** Only if a collaborative map is opened using its writable or admin ID.
|
||||
|
||||
## `deleteMarker(data)`
|
||||
|
||||
Delete an existing marker
|
||||
|
||||
* `data` (`{id: <markerId>}`): An object that contains the ID of the marker to be removed
|
||||
* _returns_ (Promise): A promise that resolves when the operation has completed
|
||||
* `data` (`{ id: number }`): an object that contains the ID of the marker to be removed
|
||||
* **Returns:** An promise that is resolved with the deleted [Marker](./types.md#marker) when the operation has completed.
|
||||
* **Events:** Causes a [`deleteMarker`](./events.md#deletemarker) event.
|
||||
* **Availability:** Only if a collaborative map is opened using its writable or admin ID.
|
||||
|
||||
## `getLineTemplate(data)`
|
||||
|
||||
Get a fake line object for a line with the given type. This can be used so that while the user is drawing a new line,
|
||||
|
||||
Get a mock line object for a line with the given type. This can be used so that while the user is drawing a new line,
|
||||
that line already has the right style.
|
||||
|
||||
* `data` (`{typeId: <typeId>}`): An object containing the type ID
|
||||
* _returns_ (Promise<[line](./types#line)>): A fake line object with the styles of this type
|
||||
* `data` (`{ typeId: number }`): An object containing the type ID
|
||||
* **Returns:** A promise that is resolved with a mock [Line](./types.md#line) with the styles of this type.
|
||||
* **Events:** None.
|
||||
* **Availability:** Only if a collaborative map is opened.
|
||||
|
||||
## `addLine(data)`
|
||||
|
||||
Create a line.
|
||||
|
||||
* `data` ([line](./types#line)): The data of the line to create. An `id` will be assigned by the server
|
||||
* _returns_ (Promise<[line](./types#line)>): The line as it is on the server, with an `id` assigned and possibly its
|
||||
styles modified by the settings of its type.
|
||||
* `data` ([Line](./types.md#line)): The data of the line to create. An `id` will be assigned by the server.
|
||||
* **Returns:** A promise that is resolved with a [Line](./types.md#line), with an `id` assigned and possibly its styles modified by the settings of its type.
|
||||
* **Events:** Causes a [`line`](./events.md#line) event and a [`linePoints`](./events.md#linepoints) event (if the line is in the current bbox).
|
||||
* **Availability:** Only if a collaborative map is opened using its writable or admin ID.
|
||||
|
||||
## `editLine(data)`
|
||||
|
||||
Update an existing line.
|
||||
|
||||
* `data` ([line](./types#line)). The new line data. Fields that are not defined will not be unmodified. Only `id` needs
|
||||
to be defined.
|
||||
* _returns_ (Promise<[line](./types#line)>): The new line. Might have some styles modified due to the settings of its type
|
||||
* `data` ([line](./types.md#line)). The new line data. Fields that are not defined will not be unmodified. Only `id` needs to be defined.
|
||||
* **Returns:** A promise that is resolved with the update [Line](./types.md#line). Might have some styles modified due to the settings of its type.
|
||||
* **Events:** Causes a [`line`](./events.md#line) event and possibly a [`linePoints`](./events.md#linepoints) event (if the route mode was changed and the line is in the current bbox).
|
||||
* **Availability:** Only if a collaborative map is opened using its writable or admin ID.
|
||||
|
||||
## `deleteLine(data)`
|
||||
|
||||
Delete an existing line
|
||||
|
||||
* `data` (`{id: <lineId>}`): An object that contains the ID of the line to be removed
|
||||
* _returns_ (Promise): A promise that resolves when the operation has completed
|
||||
* **Returns:** A promise that is resolved with the deleted [Line](./types.md#line) when the operation has completed.
|
||||
* **Events:** Causes a [`deleteLine`](./events.md#deleteline) event.
|
||||
* **Availability:** Only if a collaborative map is opened using its writable or admin ID.
|
||||
|
||||
## `exportLine(data)`
|
||||
|
||||
|
@ -237,58 +312,68 @@ Export a line.
|
|||
* `format` (string): One of the following:
|
||||
* `gpx-trk`: GPX track (contains the whole course of the route)
|
||||
* `gpx-rte`: GPX route (contains only the route points, and the navigation device will have to calculate the route)
|
||||
* _returns_ (Promise<string>)
|
||||
* **Returns:** A promise that is resolved with a string that contains the file.
|
||||
* **Events:** None.
|
||||
* **Availability:** If a collaborative map is opened.
|
||||
|
||||
## `addType(data)`
|
||||
|
||||
Create a type.
|
||||
|
||||
* `data` ([type](./types#type)): The data of the type to create. An `id` will be assigned by the server
|
||||
* _returns_ (Promise<[type](./types#type)>): The type as it is on the server, with an `id` assigned.
|
||||
* `data` ([Type](./types.md#type)): The data of the type to create. An `id` will be assigned by the server.
|
||||
* **Returns:** A promise that is resolved with the created [Type](./types.md#type)>, with an `id` assigned.
|
||||
* **Events:** Causes a [`type`](./events.md#type) event.
|
||||
* **Availability:** Only if a collaborative map is opened using its admin link.
|
||||
|
||||
## `editType(data)`
|
||||
|
||||
Update an existing type.
|
||||
|
||||
* `data` ([type](./types#type)). The new type data. Fields that are not defined will not be unmodified. Only `id` needs
|
||||
to be defined.
|
||||
To rename a field, set the `oldName` property of the field object to the previous name and the `name` property to the
|
||||
new name. To rename a dropdown entry, set the `oldValue` property to the old value and the `value` property to the new
|
||||
value.
|
||||
* _returns_ (Promise<[type](./types#type)>): The new type.
|
||||
* `data` ([type](./types.md#type)). The new type data. Fields that are not defined will not be unmodified. Only `id` needs to be defined. To rename a field, set the `oldName` property of the field object to the previous name and the `name` property to the new name. To rename a dropdown entry, set the `oldValue` property to the old value and the `value` property to the new value.
|
||||
* **Returns:** A promise that is resolved with the updated <[Type](./types.md#type)>.
|
||||
* **Events:** Causes a [`type`](./events.md#type) event. If the update causes the styles of existing markers or lines to change, events for those are triggered as well.
|
||||
* **Availability:** Only if a collaborative map is opened using its admin link.
|
||||
|
||||
## `deleteType(data)`
|
||||
|
||||
Delete an existing type
|
||||
|
||||
* `data` (`{id: <typeId>}`): An object that contains the ID of the type to be removed
|
||||
* _returns_ (Promise): A promise that resolves when the operation has completed
|
||||
* **Returns:** A promise that is resolved with the deleted [Type](./types.md#type) when the operation has completed. If there are any objects on the map that still use this type, the promise rejects.
|
||||
* **Events:** Causes a [`deleteType`](./events.md#deletetype) event.
|
||||
* **Availability:** Only if a collaborative map is opened using its admin link.
|
||||
|
||||
## `addView(data)`
|
||||
|
||||
Create a view.
|
||||
|
||||
* `data` ([view](./types#view)): The data of the view to create. An `id` will be assigned by the server
|
||||
* _returns_ (Promise<[view](./types#view)>): The view as it is on the server, with an `id` assigned.
|
||||
* `data` ([view](./types.md#view)): The data of the view to create. An `id` will be assigned by the server
|
||||
* **Returns:** A promise that is resolved with the created <[View](./types.md#view)>), with an `id` assigned.
|
||||
* **Events:** Causes a [`view`](./events.md#view) event.
|
||||
* **Availability:** Only if a collaborative map is opened using its admin link.
|
||||
|
||||
## `editView(data)`
|
||||
|
||||
Update an existing view.
|
||||
|
||||
* `data` ([view](./types#view)). The new view data. Fields that are not defined will not be unmodified. Only `id` needs
|
||||
to be defined.
|
||||
* _returns_ (Promise<[view](./types#view)>): The new view.
|
||||
* `data` ([view](./types.md#view)). The new view data. Fields that are not defined will not be unmodified. Only `id` needs to be defined.
|
||||
* **Returns:** A promise that is resolved with the updated <[View](./types.md#view)>).
|
||||
* **Events:** Causes a [`view`](./events.md#view) event.
|
||||
* **Availability:** Only if a collaborative map is opened using its admin link.
|
||||
|
||||
## `deleteView(data)`
|
||||
|
||||
Delete an existing view
|
||||
|
||||
* `data` (`{id: <viewId>}`): An object that contains the ID of the view to be removed
|
||||
* _returns_ (Promise): A promise that resolves when the operation has completed
|
||||
* **Returns:** A promise that is resolved when the operation has completed.
|
||||
* **Events:** Causes a [`deleteView`](./events.md#deleteview) event.
|
||||
* **Availability:** Only if a collaborative map is opened using its admin link.
|
||||
|
||||
## `geoip()`
|
||||
|
||||
Returns an approximate location for the IP address of the client.
|
||||
|
||||
* _returns_ (Promise<{top,right,bottom,left}>) A promise that resolves to a bounding box that includes the location of
|
||||
the client
|
||||
* **Returns:** A promise that is resolved to a [bounding box](./types.md#bbox) (without zoom) that includes the location of the client. If no location can be determined, the promise is resolved with `undefined`.
|
||||
* **Events:** None.
|
||||
* **Availability:** Always.
|
|
@ -25,25 +25,27 @@ client.on("marker", (marker) => {
|
|||
});
|
||||
```
|
||||
|
||||
## `padId`
|
||||
## `mapId`
|
||||
|
||||
The ID under which the client is connected to the map. Can be the read-only or a read-write ID of an existing map.
|
||||
The ID of the collaborative map that the client is connected to. Can be the read-only, writable or admin ID of an existing map.
|
||||
|
||||
Note that the ID can be changed in the settings. If in case of a [`padData`](./events#paddata) event, the ID of the pad has
|
||||
changed, this property is updated automatically.
|
||||
Note that the ID can be changed in the settings. If in case of a [`mapData`](./events.md#mapdata) event, the ID of the map has changed, this property is updated automatically.
|
||||
|
||||
_Set:_ when calling [`setMapId`](./methods.md#setmapid-mapid) and in response to a [`mapData`](./events.md#mapdata) event.\
|
||||
_Type:_ string
|
||||
|
||||
## `readonly`
|
||||
|
||||
`true` if the map has been opened using its read-only ID. `false` if the map is writable.
|
||||
|
||||
_Set:_ during [`setMapId`](./methods.md#setmapid-mapid).\
|
||||
_Type:_ boolean
|
||||
|
||||
## `writable`
|
||||
|
||||
`2` if the map has been opened using its admin ID, `1` if if has been opened using the writable ID, `0` if the map is read-only.
|
||||
|
||||
_Set:_ during [`setMapId`](./methods.md#setmapid-mapid).\
|
||||
_Type:_ number
|
||||
|
||||
|
||||
|
@ -51,56 +53,82 @@ _Type:_ number
|
|||
|
||||
`true` if the map was deleted while this client was connected to it.
|
||||
|
||||
## `padData`
|
||||
_Set:_ in response to a [`deleteMap`](./events.md#deletemap) event.\
|
||||
_Type:_ boolean
|
||||
|
||||
## `mapData`
|
||||
|
||||
The current settings of the map. `writeId` and/or `adminId` is null if if has been opened using another ID than the admin ID.
|
||||
|
||||
_Type:_ [padData](./types#paddata)
|
||||
_Set:_ in response to a [`mapData`](./events.md#mapdata) event.\
|
||||
_Type:_ [MapData](./types.md#mapdata)
|
||||
|
||||
## `markers`
|
||||
|
||||
All markers that have been retrieved so far.
|
||||
|
||||
_Type:_ `{"<marker id>": `[`marker`](./types#marker)`}`
|
||||
_Set:_ in response to [`marker`](./events.md#marker) and [`deleteMarker`](./events.md#deletemarker) events.\
|
||||
_Type:_ [<code>{ [markerId: number]: Marker }</code>](./types.md#marker)
|
||||
|
||||
## `lines`
|
||||
|
||||
All lines and their track points that have been retrieved so far.
|
||||
All lines of the map along with the track points that have been retrieved so far.
|
||||
|
||||
_Type:_ `{"<line id>": `[`line with trackPoints`](./types#line)`}`
|
||||
_Set:_ in response to [`line`](./events.md#line), [`linePoints`](./events.md#linepoints) and [`deleteLine`](./events.md#deleteline) events.\
|
||||
_Type:_ [<code>{ [lineId: number]: Line }</code>](./types.md#line) (with track points)
|
||||
|
||||
## `views`
|
||||
|
||||
All views that have been retrieved so far.
|
||||
All views of the map.
|
||||
|
||||
_Type:_ `{"<view id>": `[`view`](./types#view)`}`
|
||||
_Set:_ in response to [`view`](./events.md#view) and [`deleteView`](./events.md#deleteview) events.\
|
||||
_Type:_ [<code>{ [viewId: number]: View }</code>](./types.md#view)
|
||||
|
||||
## `types`
|
||||
|
||||
All types that have been retrieved so far.
|
||||
All types of the map.
|
||||
|
||||
_Type:_ `{"<type id>": `[`type`](./types#type)`}`
|
||||
_Set:_ in response to [`type`](./events.md#type) and [`deleteType`](./events.md#deletetype) events.\
|
||||
_Type:_ [<code>{ [typeId: number]: Type }</code>](./types.md#type)
|
||||
|
||||
## `history`
|
||||
|
||||
All history entries that have been retrieved so far. Note that you have to subscribe to the history using
|
||||
[`listenToHistory()`](./methods#listentohistory).
|
||||
All history entries that have been retrieved so far. Note that you have to subscribe to the history using [`listenToHistory()`](./methods.md#listentohistory).
|
||||
|
||||
_Type:_ `{"<entry id>": `[`historyEntry`](./types#historyentry)`}`
|
||||
_Set:_ in response to [`history`](./events.md#history) events.\
|
||||
_Type:_ [<code>{ [entryId: number]: HistoryEntry }</code>](./types.md#historyentry)
|
||||
|
||||
## `route`
|
||||
|
||||
Information about the temporary route set using [`setRoute()`](./methods#setroute-data).
|
||||
Details and track points (simplified for the current bbox) for the active route set using [`setRoute()`](./methods.md#setroute-data) with `routeId` set to `undefined`, or `undefined` if no such route is active.
|
||||
|
||||
_Type:_ [`route`](./types#route)
|
||||
_Set:_ during [`setRoute()`](./methods.md#setroute-data) and in response to [`routePoints`](./events.md#routepoints) events.\
|
||||
_Type:_ [`Route`](./types.md#route)
|
||||
|
||||
## `routes`
|
||||
|
||||
Details and track points (simplified for the current bbox) for the active routes set using [`setRoute()`](./methods.md#setroute-data) with `routeId` set to a string.
|
||||
|
||||
_Set:_ during [`setRoute()`](./methods.md#setroute-data) and in response to [`routePoints`](./events.md#routepoints) events.\
|
||||
_Type:_ [<code>{ [routeId: string]: Route }</code>](./types.md#route)
|
||||
|
||||
## `serverError`
|
||||
|
||||
If the opening the pad failed ([`setPadId(padId)`](./methods#setpadid-padid) promise got rejected), the error message is stored
|
||||
in this property.
|
||||
If the opening the map failed ([`setMapId(mapId)`](./methods.md#setmapid-mapid) promise got rejected), the error message is stored in this property.
|
||||
|
||||
_Set:_ in response to a [`serverError`](./events.md#servererror) event (fired during [`setMapId`](./methods.md#setmapid-mapid)).\
|
||||
_Type:_ Error
|
||||
|
||||
## `loading`
|
||||
|
||||
A number that indicates how many requests are currently pending. You can use this to show a loading spinner or disable certain
|
||||
UI elements while the value is greater than 0.
|
||||
A number that indicates how many requests are currently pending (meaning how many async methods are currently running). You can use this to show a loading spinner or disable certain UI elements while the value is greater than 0.
|
||||
|
||||
_Set:_ increased when any method is called and decreased when the method returns.\
|
||||
_Type:_ `number`
|
||||
|
||||
## `disconnected`
|
||||
|
||||
`false` in the beginning, changed to `true` as soon as the socket.io connection is made. May be `false` temporarily if the connection is lost.
|
||||
|
||||
_Set:_ in reaction to `connect` and `disconnect` events.\
|
||||
_Type:_ `boolean`
|
|
@ -1,6 +1,8 @@
|
|||
# Types
|
||||
|
||||
## bbox
|
||||
The [facilmap-types](https://www.npmjs.com/package/facilmap-types) package contains TypeScript typings for all the basic types used by the different components of FacilMap. Using TypeScript can be helpful to get some assistance what kind of properties an object provides and expects.
|
||||
|
||||
## Bbox
|
||||
|
||||
A bounding box that describes which part of the map the user is currently viewing.
|
||||
|
||||
|
@ -8,10 +10,9 @@ A bounding box that describes which part of the map the user is currently viewin
|
|||
* `bottom` (number, min: -90, max: 90): The latitude of the south end of the box
|
||||
* `left` (number, min: -180, max: 180): The longitude of the west end of the box
|
||||
* `right` (number, min: -180, max: 180): The longitude of the east end of the box
|
||||
* `zoom` (number, min: 1, max: 20): The current zoom level. This is relevant for the density of track points that
|
||||
should be received.
|
||||
* `zoom` (number, min: 1, max: 20): The current zoom level. This is relevant for the density of track points that should be received.
|
||||
|
||||
## marker
|
||||
## Marker
|
||||
|
||||
* `id` (number): The ID of this marker
|
||||
* `lat` (number, min: -90, max: 90): The latitude of this marker
|
||||
|
@ -19,13 +20,13 @@ A bounding box that describes which part of the map the user is currently viewin
|
|||
* `name` (string): The name of this marker
|
||||
* `colour` (string): The colour of this marker as a 6-digit hex value, for example `ff0000`
|
||||
* `size` (number, min: 15): The height of the marker in pixels
|
||||
* `symbol` (string): The symbol code for the marker. Default is an empty string.
|
||||
* `shape` (string): The shape code for the marker. Default is an empty string (equals `"drop"`).
|
||||
* `elevation` (number): The elevation of this marker in metres (set by the server)
|
||||
* `icon` (string): The icon name for the marker. Default is an empty string.
|
||||
* `shape` (string): The shape name for the marker. Default is an empty string (equivalent to `"drop"`).
|
||||
* `ele` (number or null): The elevation of this marker in metres (set by the server)
|
||||
* `typeId` (number): The ID of the type of this marker
|
||||
* `data` (`{"key", "value"}`): The filled out form fields of the marker
|
||||
* `data` (<code>{ [fieldName: string]: string }</code>): The filled out form fields of the marker. This is a null-prototype object to avoid prototype pollution.
|
||||
|
||||
## line
|
||||
## Line
|
||||
|
||||
Each line has `routePoints` and `trackPoints`. The `routePoints` are the start, end and via points that the user created
|
||||
for that line, the `trackPoints` describe how the line should be drawn. If no routing is used, `routePoints` and
|
||||
|
@ -44,18 +45,20 @@ separately through `linePoints` events.
|
|||
* `mode` (string): The routing mode, an empty string for no routing, or `car`, `bicycle`, `pedestrian`, or `track`
|
||||
* `colour` (string): The colour of this marker as a 6-digit hex value, for example `0000ff`
|
||||
* `width` (number, min: 1): The width of the line
|
||||
* `stroke` (string): The stroke style of the line, an empty string for solid or `dashed` or `dotted`.
|
||||
* `name` (string): The name of the line
|
||||
* `distance` (number): The distance of the line in kilometers (set by the server)
|
||||
* `ascent`, `descent` (number): The total ascent/descent of the line in metres (set by the server)
|
||||
* `time` (number): The time it takes to travel the route in seconds (only if routing mode is `car`, `bicycle` or `pedestrian`) (set by the server)
|
||||
* `left`, `top`, `right`, `bottom` (number): The bounding box of the line (set by the server)
|
||||
* `extraInfo` (<code>{ [type: string]: Array<[startIdx: number, endIdx: number, type: number]>> }</code> or null): Extra details about the route (set by the server). `type` can be for example `steepness`, `surface` or `waytype`. `startIdx` and `endIdx` describe a segment on the trackpoints of the route, the meaning of `type` can be seen in the documentation of [Leaflet.Heightgraph](https://github.com/GIScience/Leaflet.Heightgraph/blob/master/example/mappings.js).
|
||||
* `typeId` (number): The ID of the type of this line
|
||||
* `data` (`{"key", "value"}`): The filled out form fields of the line
|
||||
* `data` (<code>{ [fieldName: string]: string }</code>): The filled out form fields of the line. This is a null-prototype object to avoid prototype pollution.
|
||||
* `trackPoints`:
|
||||
* In the `lines` property of the client, an object of the format `{"<idx>": trackPoint}`
|
||||
* When creating/updating a line with the routing mode `track`, an array of the format `[trackPoint]`
|
||||
* In the `lines` property of the client, an object of the format [<code>{ [idx: number]: TrackPoint }</code>](#trackpoint)
|
||||
* When creating/updating a line with the routing mode `track`, an array of the format [`TrackPoint[]`](#trackpoint)
|
||||
|
||||
## trackPoint
|
||||
## TrackPoint
|
||||
|
||||
All track points have a `zoom` level and are only received when the zoom level of the current bbox is at least that
|
||||
level. This makes sure that at a small zoom level, only a low resolution of the line has to be downloaded. When zooming
|
||||
|
@ -66,22 +69,23 @@ their `idx` property.
|
|||
* `lat` (number, min: -90, max: 90): The latitude of this point
|
||||
* `lon` (number, min: -180, max: 180): The longitude of this point
|
||||
* `zoom` (number, min: 1, max: 20): The miminum zoom level from which this track point makes sense to show
|
||||
* `ele` (number): The elevation of this track point in metres (set by the server). Not set for high zoom levels.
|
||||
* `ele` (number or null): The elevation of this track point in metres (set by the server). Not set for high zoom levels.
|
||||
|
||||
## padData
|
||||
## MapData
|
||||
|
||||
* `id` (string): The read-only ID of this map
|
||||
* `writeId` (string): The read-write ID of this map
|
||||
* `adminId` (string): The admin ID of this map
|
||||
* `writeId` (string): The read-write ID of this map (not set when opened through its read-only ID)
|
||||
* `adminId` (string): The admin ID of this map (not set when opened through its writable or read-only ID)
|
||||
* `name` (string): The name of this map
|
||||
* `searchEngines` (boolean): Whether search engines may index the read-only version of this map
|
||||
* `description` (string): The description for search engines
|
||||
* `clusterMarkers` (boolean): Whether many markers close to each other should be grouped together
|
||||
* `legend1`, `legend2` (string): Markdown free text to be shown above and below the legend
|
||||
* `defaultViewId` (number): The ID of the default view (if any)
|
||||
* `defaultView` ([view](#view)): A copy of the default view object
|
||||
* `defaultView` ([view](#view)): A copy of the default view object (set by the server)
|
||||
* `createDefaultTypes` (boolean): On creation of a map, set this to false to not create one marker and one line type.
|
||||
|
||||
## view
|
||||
## View
|
||||
|
||||
* `id` (number): The ID of this view
|
||||
* `name` (string): The name of this view
|
||||
|
@ -90,7 +94,7 @@ their `idx` property.
|
|||
* `top`, `bottom`, `left`, `right`: The [bbox](#bbox) of this view
|
||||
* `filter` (string): If set, filter the objects according to this filtrex expression
|
||||
|
||||
## historyEntry
|
||||
## HistoryEntry
|
||||
|
||||
* `id` (number): The ID of this history entry
|
||||
* `time` (Date): The time when the modification was done
|
||||
|
@ -100,29 +104,28 @@ their `idx` property.
|
|||
* `objectBefore` (object): The object before the modification (null if `action` is `create`)
|
||||
* `objectAfter` (object): The object after the modification (null if `action` is `delete`)
|
||||
|
||||
## type
|
||||
## Type
|
||||
|
||||
* `id` (number): The ID of this type
|
||||
* `name` (string): The name of this type
|
||||
* `name` (string): The name of this type. Note that the if the name is "Marker" or "Line", the FacilMap UI will translate the name to other languages even though the underlying name is in English.
|
||||
* `type` (string): `marker` or `line`
|
||||
* `defaultColour`, `defaultSize`, `defaultSymbol`, `defaultWidth`, `defaultMode` (string/number): Default values for the
|
||||
* `idx` (number): The sorting position of this type. When a list of types is shown to the user, it must be ordered by this value. If types were deleted or reordered, there may be gaps in the sequence of indexes, but no two types on the same map can ever have the same index. When setting this as part of a type creation/update, other types with a same/higher index will have their index increased to be moved down the list.
|
||||
* `defaultColour`, `defaultSize`, `defaultIcon`, `defaultShape`, `defaultWidth`, `defaultStroke`, `defaultMode` (string/number): Default values for the
|
||||
different object properties
|
||||
* `colourFixed`, `sizeFixed`, `symbolFixed`, `shapeFixed`, `widthFixed`, `modeFixed` (boolean): Whether those values are fixed and
|
||||
* `colourFixed`, `sizeFixed`, `iconFixed`, `shapeFixed`, `widthFixed`, `strokeFixed`, `modeFixed` (boolean): Whether those values are fixed and
|
||||
cannot be changed for an individual object
|
||||
* `fields` ([object]): The form fields for this type. Each field has the following properties:
|
||||
* `name` (string): The name of the field. This is at the same time the key in the `data` properties of markers and lines
|
||||
* `oldName` (string): When renaming a field (using [`editType(data)`](./methods#edittype-data)), specify the former name here
|
||||
* `type` (string): The type of field, one of `textarea`, `dropdown`, `checkbox`, `input`
|
||||
* `controlColour`, `controlSize`, `controlSymbol`, `controlShape`, `controlWidth` (boolean): If this field is a dropdown, whether
|
||||
the different options set a specific property on the object
|
||||
* `default` (string/boolean): The default value of this field
|
||||
* `options` ([object]): If this field is a dropdown, an array of objects with the following properties:
|
||||
* `value` (string): The value of this option.
|
||||
* `oldValue` (string): When renaming a dropdown option (using [`editType(data)`](./methods#edittype-data)), specify the
|
||||
former value here
|
||||
* `colour`, `size`, `shape`, `symbol`, `width` (string/number): The property value if this field controls that property
|
||||
* `name` (string): The name of the field. This is at the same time the key in the `data` properties of markers and lines. Note that the if the name is "Description", the FacilMap UI will translate the name to other languages even though the underlying name is in English.
|
||||
* `oldName` (string): When renaming a field (using [`editType(data)`](./methods.md#edittype-data)), specify the former name here
|
||||
* `type` (string): The type of field, one of `textarea`, `dropdown`, `checkbox`, `input`
|
||||
* `controlColour`, `controlSize`, `controlIcon`, `controlShape`, `controlWidth`, `controlStroke` (boolean): If this field is a dropdown, whether the different options set a specific property on the object
|
||||
* `default` (string/boolean): The default value of this field
|
||||
* `options` ([object]): If this field is a dropdown or a checkbox, an array of objects with the following properties. For a checkbox, the array has to have 2 items, the first representing the unchecked and the second the checked state.
|
||||
* `value` (string): The value of this option.
|
||||
* `oldValue` (string): When renaming a dropdown option (using [`editType(data)`](./methods.md#edittype-data)), specify the former value here
|
||||
* `colour`, `size`, `shape`, `icon`, `width`, `stroke` (string/number): The property value if this field controls that property
|
||||
|
||||
## searchResult
|
||||
## SearchResult
|
||||
|
||||
* `short_name` (string): Name of the result
|
||||
* `display_name` (string): Name with address
|
||||
|
@ -132,18 +135,40 @@ their `idx` property.
|
|||
* `zoom` (number): Zoom level at which there is a good view onto the result. Might be null if `boundingbox` is set.
|
||||
* `extratags` (object): Extra OSM tags that might be useful
|
||||
* `geojson` (object): GeoJSON if the result has a shape
|
||||
* `icon` (string): Symbol key of the result
|
||||
* `icon` (string): Icon key of the result
|
||||
* `type` (string): Type of the result
|
||||
* `id` (string): If the result is an OSM object, the ID of the OSM object, prefixed by `n` (node), `w` (way) or `r` (relation)
|
||||
* `ele` (number): Elevation in meters
|
||||
|
||||
## route
|
||||
## Route
|
||||
|
||||
* `routePoints` (array): Array of route points (objects with `lon` and `lat` properties)
|
||||
* `mode` (string): Route mode: `"car"`, `"bicycle"`, `"pedestrian"` or an empty string `""` for a direct line
|
||||
* `trackPoints` (array): An array of track points (objects with a `lon`, `lat`, `ele`, `idx` property and also a `zoom`
|
||||
* `trackPoints` (array): An array of [track points](#trackpoint) (objects with a `lon`, `lat`, `ele`, `idx` property and also a `zoom`
|
||||
property that indicates from which zoom level the track point should be shown (to avoid an unnecessarily high resolution))
|
||||
* `distance` (number): The distance of the route in kilometers
|
||||
* `time` (number): The time it takes to travel the route in seconds
|
||||
* `ascent` (number): The total meters of climb
|
||||
* `descent` (number) The total meters of drop
|
||||
* `descent` (number): The total meters of drop
|
||||
* `left`, `top`, `right`, `bottom` (number): The bounding box of the line (set by the server)
|
||||
* `routeId` (string): Some methods allow specifying a custom string here to identify the route. This allows having multiple active routes per connection.
|
||||
|
||||
## RouteMode
|
||||
|
||||
The route mode is a string that describes for what type of transportation a route should be calculated. The following route modes are available:
|
||||
* (empty string), aliases `helicopter`, `straight`: Go in a straight line
|
||||
* `pedestrian`, aliases `foot`, `walk`, `walking`: Go by foot
|
||||
* `bicycle`, alias `bike`: Go by bicycle
|
||||
* `car`: Go by car
|
||||
* `track`: Special route mode for lines, indicates that the line is a track imported from a file, not a calculated route.
|
||||
|
||||
To use advanced routing settings, additional keywords can be appended to the route mode, separated by a space:
|
||||
* `hgv`: HGV (truck) routing (in combination with `car`)
|
||||
* `road`, `mountain`, `electric`: type of bicycle (in combination with `bicycle`).
|
||||
* `hiking`, `wheelchair`: type of pedestrian (in combination with `pedestrian`).
|
||||
* `fastest`, `shortest`: Use this routing preference.
|
||||
* `details`: Load route details (elevation profile, extra info).
|
||||
* `highways`, `tollways`, `ferries`, `fords`, `steps`: Avoid these.
|
||||
* `avoid`: Has no effect, but can be inserted in front of the avoidance types to make the route mode more readable.
|
||||
|
||||
An example advanced route mode would be `pedestrian wheelchair details avoid steps fords`. Any unknown words inside the route mode should be ignored (they may be options that used to be available in the past).
|
|
@ -1,13 +1,12 @@
|
|||
# Dev setup
|
||||
|
||||
1. Run `yarn install` to install the dependencies
|
||||
2. Run `yarn build` to build the JS bundles
|
||||
3. Copy `config.env.example` to `config.env` and adjust the settings
|
||||
4. Run `yarn server` inside the `server` directory
|
||||
2. Copy `config.env.example` to `config.env` and adjust the settings
|
||||
3. Run `yarn dev-server` inside the `server` directory
|
||||
|
||||
For developing the frontend/client, FacilMap server can integrate webpack-dev-server. This server will automatically
|
||||
recompile the frontend files when a file changes, and even delay reloads until the compilation has finished. To run
|
||||
the dev server, run `yarn dev-server` instead of `yarn server` in the `server` directory. Note that changes in the `client`, `types` or `leaflet` directory still have to be built using `yarn build` in the respective directories for the dev-server to notice them.
|
||||
This will start the FacilMap server with an integrated Vite dev server that takes care of transpiling the frontend on the fly and also integrating hot module reloading, which can apply Vue component changes without a page reload.
|
||||
|
||||
While developing the server, you can also run `yarn server` instead, which will start the server straight from the TypeScript files (which makes it obsolete to run `yarn build` every time before restarting the server) but without transpiling the frontend each time, which makes restarts faster.
|
||||
|
||||
To enable debug output of various components, additionally prepend the command by `DEBUG=*`. See the documentation of
|
||||
[debug](https://github.com/visionmedia/debug). To only enable the debug logging of SQL queries, use `DEBUG=sql`.
|
|
@ -0,0 +1,37 @@
|
|||
# Documentation
|
||||
|
||||
The FacilMap documentation is built using [VuePress](https://vuepress.vuejs.org/) from the Markdown files in the [docs](https://github.com/FacilMap/facilmap/tree/main/docs) directory. The [facilmap/facilmap-docs](https://hub.docker.com/r/facilmap/facilmap-docs) Docker image is automatically generated with the latest docs.
|
||||
|
||||
## Dev setup
|
||||
|
||||
If you want to make some changes to the documentation, first install the necessary dependencies by running `yarn install` in the `docs` directory, and then run `yarn dev-server`. This will start a webserver on http://localhost:8080/ that will show the documentation. Whenever you make changes to the documentation files, the browser will automatically refresh the page and show the latest version.
|
||||
|
||||
## Build
|
||||
|
||||
Before building the documentation, run `yarn run check` to check that all links in the markdown point to a valid destination.
|
||||
|
||||
To build the documentation, run `yarn run build`. This will create static HTML files in `dist` that can be served by a simple HTTP server.
|
||||
|
||||
## Embed videos
|
||||
|
||||
In the documentation, more complex user interactions are illustrated by screencasts. Since the UI looks somewhat different on big screens than on small screens and works somewhat differently on touch devices than on mouse devices, two recordings of the same interaction are shown next to each other, one representing a desktop computer and one representing a smartphone.
|
||||
|
||||
The desktop video should have dimensions of 1024×768 pixels and the smartphone video 320×480 pixels. Both should be recorded with 15 fps and in MP4 format with a sensible framerate (the FFMPEG default bitrate has been used so far, using `ffmpeg -i recording_raw.mp4 recording.mp4`).
|
||||
|
||||
The existing recordings were created using [SimpleScreenRecorder](https://www.maartenbaert.be/simplescreenrecorder/). Chromium shows the dimensions of the viewport when the dev tools are open, which makes it easier to adjust it to the right size. For the mobile recordings, Chromium was configured to simulate an iPhone 4.
|
||||
|
||||
To embed a video into the documentation, the custom `Screencast` component can be used in Markdown:
|
||||
```jsx
|
||||
<Screencast :desktop="recordingMp4" :mobile="recordingMobileMp4"></Screencast>
|
||||
```
|
||||
|
||||
## Embed screenshots
|
||||
|
||||
Screenshots should be saved as PNG, with a size of 1024×768 pixels for desktop and 320×480 pixels for mobile. To show the desktop and mobile screenshots next to each other, use the custom `Screenshot` component in Markdown:
|
||||
```jsx
|
||||
<Screenshot :desktop="screenshot.png')" :mobile="screenshot-mobile.png')"></Screenshot>
|
||||
```
|
||||
|
||||
## Docker image
|
||||
|
||||
To build the docker image, run `docker build -t facilmap-docs .`. To run it, simply run `docker run -d facilmap-docs`. The docker container will expose a HTTP server on port 80.
|
|
@ -0,0 +1,66 @@
|
|||
# Embed FacilMap
|
||||
|
||||
You can embed a map into any website using an iframe:
|
||||
|
||||
```html
|
||||
<iframe style="height: 500px; width: 100%; border: none;" src="https://facilmap.org/mymap"></iframe>
|
||||
```
|
||||
|
||||
If you use a map ID that does not exist yet, the “Create Collaborative Map” dialog will be opened when accessing the
|
||||
map (unless the `interactive` parameter is `false`).
|
||||
|
||||
## Options
|
||||
|
||||
You can control the display of different components by using the following query parameters:
|
||||
|
||||
* `toolbox`: Show the toolbox (default: `true`)
|
||||
* `search`: Show the search box (default: `true`)
|
||||
* `route`: Show the route tab in the search box (default: `true`)
|
||||
* `pois`: Show the POIs tab in the search box (default: `true`)
|
||||
* `autofocus`: Autofocus the search field (default: `false`)
|
||||
* `legend`: Show the legend if available (default: `true`)
|
||||
* `locate`: Show the locate control to zoom to the user’s location (default: `true`)
|
||||
* `interactive`: Enable [interactive mode](#interactive-mode) (default: `false`)
|
||||
* `lang`: Use this display language (for example `en`) by default, instead of the language set by the user in the user preferences dialog or in their browser.
|
||||
* `units`: Use this type of units (either `metric` or `us_customary`) by default, instead of what the user has configured in the user preferences dialog.
|
||||
|
||||
Example:
|
||||
|
||||
```html
|
||||
<iframe style="height: 500px; width: 100%; border: none;" src="https://facilmap.org/mymap?search=false&toolbox=false"></iframe>
|
||||
```
|
||||
|
||||
## Interactive mode
|
||||
|
||||
When embedding FacilMap into a website, you may want to disable certain UI interactions that make more sense when FacilMap runs as a standalone app. For example, when you want to embed a specific collaborative map, you may want to disable any interactions that will navigate the user away from the map, such as closing the map or navigating to a bookmark. Disabling interactive mode will hide the following UI interactions:
|
||||
|
||||
* “Collaborative map” menu in the toolbox, including bookmarks, the “Open collaborative map” dialog and “Close current map”.
|
||||
* “Share” dialog.
|
||||
* “Open file” or dragging a geographic file onto the map.
|
||||
* “Create collaborative map” dialog when opening a map ID that does not exist.
|
||||
* “Close map” button when the open map is deleted.
|
||||
|
||||
## Location hash
|
||||
|
||||
When a FacilMap is opened directly in the browser, the current view of the map is [added to the location hash](../users/share/) (the part after the `#` in the URL). This means that users can easily share the current view by copying the URL straight from the address bar of their browser, and reloading the page will not cause the current view to be lost.
|
||||
|
||||
FacilMap emits a [cross-origin message](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) every time it updates the location map. You can listen to it to synchronize the location hash of your website with the one of FacilMap by using the following script:
|
||||
|
||||
```html
|
||||
<iframe id="facilmap" style="height: 500px; width: 100%; border: none;" src="https://facilmap.org/mymap"></iframe>
|
||||
<script>
|
||||
window.addEventListener("message", function(evt) {
|
||||
if(evt.data && evt.data.type == "facilmap-hash" && location.hash != "#" + evt.data.hash)
|
||||
location.replace("#" + evt.data.hash);
|
||||
});
|
||||
|
||||
function handleHashChange() {
|
||||
var iframe = document.getElementById("facilmap");
|
||||
iframe.src = iframe.src.replace(/(#.*)?$/, "") + location.hash;
|
||||
}
|
||||
|
||||
window.addEventListener("hashchange", handleHashChange);
|
||||
if (location.hash)
|
||||
handleHashChange();
|
||||
</script>
|
||||
```
|
|
@ -0,0 +1,59 @@
|
|||
# Overview
|
||||
|
||||
The FacilMap frontend is a [Vue.js](https://vuejs.org/) app that provides the main FacilMap UI. You can use it to integrate a modified or extended version of the FacilMap UI or its individual components into your app. If you just want to embed the whole FacilMap UI without any modifications, it is easier to [embed it as an iframe](../embed.md).
|
||||
|
||||
The FacilMap frontend is available as the [facilmap-frontend](https://www.npmjs.com/package/facilmap-frontend) package on NPM.
|
||||
|
||||
Right now there is no documentation of the individual UI components, nor are they designed to be reusable or have a stable interface. Use them at your own risk and have a look at the [source code](https://github.com/FacilMap/facilmap/tree/main/frontend/src/lib) to get an idea how to use them.
|
||||
|
||||
## Setup
|
||||
|
||||
The FacilMap frontend is published as an ES module. It is meant to be used as part of an app that is built using a bundler, rather than importing it directly into a HTML file.
|
||||
|
||||
The frontend heavily relies on Vue, Bootstrap and other large libraries. These are not part of the bundle, but are imported using `import` JavaScript statements. This avoids duplicating these dependencies if you also use them elsewhere in your app.
|
||||
|
||||
To get FacilMap into your app, install the NPM package using `npm install -S facilmap-frontend` or `yarn add facilmap-frontend` and then import the components that you need.
|
||||
|
||||
```javascript
|
||||
import { FacilMap } from "facilmap-frontend";
|
||||
```
|
||||
|
||||
The FacilMap UI uses a slightly adjusted version of [Bootstrap 5](https://getbootstrap.com/) for styling. To avoid duplication if you want to integrate FacilMap into an app that is already using Bootstrap, the Bootstrap CSS is not part of the main export. If you want to use FacilMap’s default Bootstrap styles, import them separately from `facilmap-frontend/bootstrap.css`:
|
||||
|
||||
```javascript
|
||||
import "facilmap-frontend/bootstrap.css";
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
The [`<FacilMap>`](./facilmap.md) component renders the whole frontend, including a component that provides a connection to the FacilMap server. If you want to render the whole FacilMap UI, simply render that component rather than rendering all the components of the UI individually.
|
||||
|
||||
```vue
|
||||
<FacilMap
|
||||
baseUrl="https://facilmap.org/"
|
||||
serverUrl="https://facilmap.org/"
|
||||
:mapId="undefined"
|
||||
></FacilMap>
|
||||
```
|
||||
|
||||
The `<FacilMapContextProvider>` component provides a reactive object that acts as the central hub for all components to provide their public state (for example the facilmap-client object, the current map ID, the current selection, the current map view) and their public API (for example to open a search box tab, to open a map, to set route destinations). All components that need to communicate with each other use this context for that. This means that if you want to render individual UI components, you need to make sure that they are rendered within a `<FacilMapContextProvider>` (or within a `<FacilMap>`, which renders the context provider for you). It also means that if you want to add your own custom UI components, they will benefit greatly from accessing the context.
|
||||
|
||||
The context is both injected and exposed by both the `<FacilMap>` and the `<FacilMapContextProvider>` component. To access the injected context, import the `injectContextRequired` function from `facilmap-frontend` and call it in the setup function of your component. For this, the component must be a child of `<FacilMap>` or `<FacilMapContextProvider>`. To access the exposed context, use a ref:
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { FacilMap } from "facilmap-frontend";
|
||||
|
||||
const facilMapRef = ref<InstanceType<typeof FacilMap>>();
|
||||
|
||||
// Access the context as facilMapRef.value.context
|
||||
</script>
|
||||
<template>
|
||||
<FacilMap ref="facilMapRef"></FacilMap>
|
||||
</template>
|
||||
```
|
||||
|
||||
For now there is no documentation of the context object. To get an idea of its API, have a look at its [source code](https://github.com/FacilMap/facilmap/blob/main/frontend/src/lib/components/facil-map-context-provider/facil-map-context.ts).
|
||||
|
||||
## Styling
|
||||
|
||||
All FacilMap components use static class names to make it possible to adjust the styling. FacilMap class names are prefixed with `fm-`.
|
|
@ -0,0 +1,66 @@
|
|||
# FacilMap component
|
||||
|
||||
The `FacilMap` component renders a complete FacilMap UI. It can be used like this in a Vue.js app:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { FacilMap } from "facilmap-frontend";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FacilMap
|
||||
baseUrl="https://facilmap.org/"
|
||||
serverUrl="https://facilmap.org/"
|
||||
mapId="my-map"
|
||||
></FacilMap>
|
||||
</template>
|
||||
```
|
||||
|
||||
In a non-Vue.js app, it can be embedded like this:
|
||||
|
||||
```javascript
|
||||
import { FacilMap } from "facilmap-frontend";
|
||||
import Vue, { createApp, defineComponent, h } from "vue";
|
||||
|
||||
createApp(defineComponent({
|
||||
setup() {
|
||||
return () => h(FacilMap, {
|
||||
baseUrl: "https://facilmap.org/",
|
||||
serverUrl: "https://facilmap.org/",
|
||||
mapId: "my-map"
|
||||
});
|
||||
}
|
||||
})).mount(document.getElementById("facilmap")!); // A DOM element that be replaced with the FacilMap component
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
Note that all of these props are reactive and can be changed while the map is open.
|
||||
|
||||
* `baseUrl` (string, required): Collaborative maps should be reachable under `${baseUrl}${mapId}`, while the general map should be available under `${baseUrl}`. For the default FacilMap installation, `baseUrl` would be `https://facilmap.org/`. It needs to end with a slash. It is used to create the map URL for example in the map settings or when switching between different maps (only in interactive mode).
|
||||
* `serverUrl` (string, required): The URL under which the FacilMap server is running, for example `https://facilmap.org/`. This is invisible to the user.
|
||||
* `mapId` (string or undefined, required): The ID of the collaborative map that should be opened. If this is undefined, no map is opened. This is reactive, when a new value is passed, a new map is opened. Note that the map ID may change as the map is open, either because the ID of the map is changed in the map settings, or because the user navigates to a different map (only in interactive mode). Use `v-model:mapId` to get a [two-way binding](https://vuejs.org/guide/essentials/forms.html) (or listen to the `update:mapId` event).
|
||||
* `settings` (object, optional): An object with the following properties:
|
||||
* `toolbox` (boolean, optional): Whether the toolbox should be shown. Default is `true`.
|
||||
* `search` (boolean, optional): Whether the search box should be shown. Default is `true`.
|
||||
* `autofocus` (boolean, optional): Whether the search field should be focused. Default is `false`.
|
||||
* `legend` (boolean, optional): Whether the legend should be shown (if it is available). Default is `true`.
|
||||
* `interactive` (boolean, optional): Whether [interactive mode](../embed.md#interactive-mode) should be enabled. Default is `true`.
|
||||
* `linkLogo` (boolean, optional): If `true`, the FacilMap logo will be a link that opens the map in a new window. Default is `false`.
|
||||
* `updateHash` (boolean, optional): Whether `location.hash` should be synchonised with the current map view. Default is `false`.
|
||||
|
||||
## Events
|
||||
|
||||
* `update:mapId`: When the ID of the currently open map is changed, either because the ID of the map was changed in the map settings or because the user navigated to another map. The parameter is a string or undefined (if no map is opened).
|
||||
* `update:mapName`: When the name of the currently open map is changed, either because it was changed in the map settings or because the user navigated to another map. The parameter is a string or undefined (if no map is opened).
|
||||
|
||||
## Slots
|
||||
|
||||
* `default`: Components in this slot are rendered directly on top of the map. It makes sense to position them absolutely and choose an appropriate `z-index`.
|
||||
* `before`, `after`: Components in these slots are rendered above and below the map. Note that on narrow screens (such as smartphones), the search box is also rendered in the `after` slot.
|
||||
|
||||
## Styling
|
||||
|
||||
By default, the `FacilMap` component renders a flex box with `flex-grow: 1`. To render it at a certain size, wrap it in a flex box of a certain size or override the styles of the `fm-facilmap` class.
|
||||
|
||||
The map itself will shrink to make space for the `before` and `after` slots.
|
|
@ -0,0 +1,93 @@
|
|||
# I18n
|
||||
|
||||
FacilMap uses [i18next](https://www.i18next.com/) for internationalization throughout the frontend, the server and its libraries. It detects the desired user language like this:
|
||||
* In the browser, [i18next-browser-languageDetector](https://github.com/i18next/i18next-browser-languageDetector) is used to detect the user’s language. It looks at the configured browser languages ([`navigator.languages`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/languages)) and checks for which one a translation exists. The configured language can be overridden by setting a `lang` cookie or appending a `?lang=` query parameter to the URL.
|
||||
* On the server, when a request is handled through HTTP (including the WebSocket), [i18next-http-middleware](https://www.npmjs.com/package/i18next-http-middleware) is used to detect the user’s language. It looks at the configured browser languages ([`Accept-Language`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language)) and checks for which one a translation exists. The configured language can be overridden by setting a `lang` cookie or by appending a `?lang=` query parameter to the URL. The server stores the selected language in the [Node.js domain](https://nodejs.org/api/domain.html) that is created for each incoming request, causing all functions triggered (sync or async) from the request to use the language setting of the request.
|
||||
* On the sever, when a function is called outside of an incoming HTTP request, messages are not internationalized and output in English.
|
||||
|
||||
In addition, certain values can be shown in metric units or in US customary units. By default, metric units are used. This can be changed by sending a `units` query parameter or cookie with the value `us_customary`.
|
||||
|
||||
Translations are managed on [Weblate](https://hosted.weblate.org/projects/facilmap/), changes there automatically trigger a pull request to the FacilMap repository.
|
||||
|
||||
## Use FacilMap in an app not using i18next
|
||||
|
||||
When you import any of the FacilMap modules into a JavaScript app that does not use i18next, they will automatically detect the user language and internationalize their output accordingly as described above.
|
||||
|
||||
The main instance of i18next is [initialized](https://www.i18next.com/overview/api#init) by FacilMap as soon as the first message is internationalized. This that before calling any of the functions exported by FacilMap, you can still change the i18next configuration.
|
||||
|
||||
### Change the language detector
|
||||
|
||||
To change the detected user language, you have two options. As mentioned above, both options need to be executed before calling any FacilMap functions that generate internationalized messages.
|
||||
|
||||
Option 1 is to set a custom [language detector](https://www.i18next.com/overview/plugins-and-utils#language-detector) using the `setLanguageDetector()` function exported by `facilmap-utils`. This language detector is applied to the `i18next` main instance when it is initialized instead of the default language detector used by FacilMap.
|
||||
|
||||
```typescript
|
||||
import { setLanguageDetector } from "facilmap-utils";
|
||||
|
||||
setLanguageDetector(myLanguageDetector);
|
||||
```
|
||||
|
||||
Option 2 is to set a custom [i18next instance](https://www.i18next.com/overview/api#instance-creation) that will be used by FacilMap. In this example, we are disabling the language detector and use a custom i18next instance that always uses German:
|
||||
|
||||
```typescript
|
||||
import { setLanguageDetector, setI18nGetter } from "facilmap-utils";
|
||||
import { createInstance } from "i18next";
|
||||
|
||||
setLanguageDetector(undefined);
|
||||
setI18nGetter(() => createInstance({ lng: "de" }));
|
||||
```
|
||||
|
||||
### Use the backend language detector
|
||||
|
||||
If you are including some of FacilMap’s server modules in your own Node.js express server and want messages to be internationalized according to the user language as described above, you need to add FacilMap’s `i18nMiddleware` to your express server:
|
||||
```typescript
|
||||
import express from "express";
|
||||
import domainMiddleware from "express-domain-middleware";
|
||||
import { i18nMiddleware } from "facilmap-server";
|
||||
|
||||
const app = express();
|
||||
app.use(domainMiddleware);
|
||||
app.use(i18nMiddleware);
|
||||
```
|
||||
|
||||
As seen in the example, `i18nMiddleware` requires [express-domain-middleware](https://www.npmjs.com/package/express-domain-middleware) to be initialized before it.
|
||||
|
||||
## Use FacilMap in an app already using i18next
|
||||
|
||||
When you use FacilMap’s modules in an app that is already using i18next, you may want FacilMap to reuse your existing i18n configuration (such as language detection), or you may want it to use its own configuration independently from yours.
|
||||
|
||||
When a FacilMap function internationalizes a message for the first time, it initializes i18next in the following way:
|
||||
1. It retrieves the i18next instance set through `setI18nGetter()` (exported by `facilmap-utils`). If no instance was set, it defaults to the i18next main instance (`import i18next from "i18next"`).
|
||||
2. If the instance is not initialized yet (`!i18next.isInitializing && !i18next.isInitialized`), it initializes it with its default configuration (language detector as described above).
|
||||
3. It adds its resources to the instance under namespaces prefixed by `facilmap-`.
|
||||
|
||||
### Reuse your i18next instance for FacilMap
|
||||
|
||||
You can make FacilMap reuse your existing i18next instance and configuration in the following way:
|
||||
* Make sure your i18next instance is initialized before you call any FacilMap functions that need to internationalize messages.
|
||||
* Call `setI18nGetter()` with a callback that returns your i18next instance (can be skipped if you are using the main instance).
|
||||
|
||||
```typescript
|
||||
import { setLanguageDetector, setI18nGetter } from "facilmap-utils";
|
||||
import { createInstance } from "i18next";
|
||||
|
||||
const i18next = createInstance();
|
||||
await i18next.init({
|
||||
lng: "en"
|
||||
});
|
||||
setI18nGetter(() => i18next);
|
||||
```
|
||||
|
||||
### Use a separate i18next instance for FacilMap
|
||||
|
||||
If you want to make FacilMap use a separate i18next instance, for example because you want to keep your and FacilMap’s language detection independent, use the following example:
|
||||
|
||||
```typescript
|
||||
import { setI18nGetter } from "facilmap-utils";
|
||||
import { createInstance } from "i18next";
|
||||
|
||||
setLanguageDetector(undefined);
|
||||
setI18nGetter(() => createInstance());
|
||||
```
|
||||
|
||||
Because the instance returned by `createInstance()` is not initialized, FacilMap will initialize it using its default configuration.
|
|
@ -0,0 +1,54 @@
|
|||
# Overview
|
||||
|
||||
The Leaflet components of FacilMap are classes that you can use to show FacilMap data on a [Leaflet](https://leafletjs.com/) map.
|
||||
|
||||
The Leaflet components are only useful in combination with a [FacilMap client](../client/) instance. Most components either require an object created by the client as an argument, or require the client instance itself and render the available map data automatically.
|
||||
|
||||
## Components
|
||||
|
||||
* [BboxHandler](./bbox.md) automatically calls [updateBbox()](../client/methods.md#updatebbox-bbox) when the position of the map changes.
|
||||
* [Layers](./layers.md) provides the layers that FacilMap offers by default and helpers to show them.
|
||||
* [Markers](./markers.md) shows the markers of a collaborative map.
|
||||
* [Lines](./lines.md) shows the lines of a collaborative map.
|
||||
* [Route](./route.md) allows showing a calculated route on the map.
|
||||
* [Search](./search.md) renders search results.
|
||||
* [Overpass](./overpass.md) shows amenities and POIs in the current map bounds using the Overpass API.
|
||||
* [Icons](./icons.md) provides methods to draw marker icons and shapes.
|
||||
* [HashHandler](./hash.md) hooks up the location hash to the current map view.
|
||||
* [Views](./views.md) allow controlling the map view and handling saved views.
|
||||
* [Filter](./filter.md) expressions can be used to control which markers/lines are visible on the map.
|
||||
* [Click listener](./click-listener.md) is a helper to ask the user to click somewhere on the map.
|
||||
|
||||
## Setup
|
||||
|
||||
The Leaflet components are published on npm as [facilmap-leaflet](https://www.npmjs.com/package/facilmap-leaflet). The recommended way to use them is to install the package using npm or yarn:
|
||||
|
||||
```bash
|
||||
npm install -S facilmap-leaflet
|
||||
```
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { BboxHandler } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/");
|
||||
new BboxHandler(map, client).enable();
|
||||
```
|
||||
|
||||
However, a build to use the components directly in the browser is also available. The build contains all the dependencies except Leaflet, including the FacilMap client. The components are available in the global `L.FacilMap` object.
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/leaflet@1"></script>
|
||||
<script src="https://unpkg.com/facilmap-leaflet@3/dist/facilmap-leaflet.full.js"></script>
|
||||
<script>
|
||||
const map = L.map('map');
|
||||
const client = new FacilMap.Client("https://facilmap.org/");
|
||||
new L.FacilMap.BboxHandler(map, client).enable();
|
||||
</script>
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
An example that shows all the Leaflet components in actions can be found in [example.html](https://github.com/FacilMap/facilmap/blob/main/leaflet/example.html) ([demo](https://unpkg.com/facilmap-leaflet/example.html)).
|
|
@ -0,0 +1,32 @@
|
|||
# BboxHandler
|
||||
|
||||
The FacilMap client needs to tell the current bounding box and zoom level of the map to the server in order to receive the following information:
|
||||
* If a collaborative map is open, the markers in the specified bounding box
|
||||
* If a collaborative map is open, the line points in the specified bounding box (simplified for the specified zoom level)
|
||||
* If a calculated route is active, the route points in the specified bounding box (simplified for the specified zoom level).
|
||||
|
||||
BboxHandler automatically calls [updateBbox()](../client/methods.md#updatebbox-bbox) whenever the position of the map changes, either because the user panned the map or because it was changed programmatically.
|
||||
|
||||
## Usage
|
||||
|
||||
BboxHandler extends [L.Handler](https://leafletjs.com/reference.html#handler). To use it, pass the map and client objects to the constructor and enable the handler.
|
||||
|
||||
```javascript
|
||||
import { map } from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { BboxHandler } from "facilmap-leaflet";
|
||||
|
||||
const map = map('map');
|
||||
const client = new Client("https://facilmap.org/");
|
||||
new BboxHandler(map, client).enable();
|
||||
```
|
||||
|
||||
## Technical details
|
||||
|
||||
The bbox is only sent to the server if one of the following conditions are met (because only in these conditions the bbox is relevant for the server):
|
||||
* A collaborative map is opened
|
||||
* A route is active
|
||||
|
||||
The bbox will be sent as soon as one of these conditions is fulfilled, and each time the position of the map is changed. The bbox is updated when Leaflet fires a [`moveend`](https://leafletjs.com/reference.html#map-moveend) event, so if the user is panning the map around, the bbox is only updated when they are done, not in the process.
|
||||
|
||||
If the position of the map is updated programmatically using [`flyTo()`](https://leafletjs.com/reference.html#map-flyto) (or `flyToBounds()`), the destination bbox is already set when the animation starts, so that the map data can already be loaded while the animation is in progress.
|
|
@ -0,0 +1,55 @@
|
|||
# Click listener
|
||||
|
||||
The click listener is a helper that requires the user to click one point on the map. The frontend uses this in the “Add marker” function for example. While the click listener is active, the mouse cursor changes into a crosshair and it is not possible to click any objects on the map anymore, but it is still possible to move the map around.
|
||||
|
||||
Calling `addClickListener(map, listener, moveListener)` will enable the click listener. The listener waits for one click and then disables itself again. If you need to listen for multiple clicks in a row, simply enable another click listener when the user clicks. The method returns an object that contains a `cancel()` method, which you can use to manually cancel the handler. `addClickListener` accepts the following arguments:
|
||||
* `map` ([Map](https://leafletjs.com/reference.html#map)): The Leaflet map
|
||||
* `listener` (Function): A function that will be called once with the coordinates of the click (an object with a `lat` and `lon` property). Note that if the listener is canceled using the `cancel()` method, the listener is not called.
|
||||
* `moveListener` (Function, optional): A function that will be called continuously with the mouse coordinates (an object with a `lat` and `lon` property) as the user moves the mouse across the map. Note that on touch devices, `listener` will be called without `moveListener` ever being invoked.
|
||||
|
||||
## Interaction event
|
||||
|
||||
When a click listener is enabled, the map will fire an `fmInteractionStart` event. When it is finished, the map will fire an `fmInteractionEnd` event. You can listen to these events to disable certain UI actions that themselves need to add a click listener or that otherwise don't make sense while a click listener is active. For example, the FacilMap frontend disables the “Add marker” and “Move marker” actions while a click listener is active, and does fire its own `fmInteractionStart` event when a marker is made draggable as part of a “Move marker” action.
|
||||
|
||||
## Example
|
||||
|
||||
Here is an example how a click listener could be used to add a marker to the map:
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { addClickListener } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/", "myMapId");
|
||||
|
||||
map.on("fmInteractionStart", () => {
|
||||
document.getElementById("add-marker-button").disabled = true;
|
||||
});
|
||||
map.on("fmInteractionEnd", () => {
|
||||
document.getElementById("add-marker-button").disabled = false;
|
||||
});
|
||||
|
||||
document.getElementById("add-marker-button").addEventListener("click", () => {
|
||||
const message = showMessage({
|
||||
body: "Please click on the map to add a marker.",
|
||||
actions: [
|
||||
{ label: "Cancel", onClick: () => { clickListener.cancel(); } }
|
||||
]
|
||||
});
|
||||
|
||||
const clickListener = addClickListener(map, async (point) => {
|
||||
message.hide();
|
||||
|
||||
try {
|
||||
const marker = await client.addMarker({
|
||||
lat: point.lat,
|
||||
lon: point.lon,
|
||||
typeId: 1
|
||||
});
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
# Filter
|
||||
|
||||
Filter expressions in FacilMap can be used to show/hide markers and lines on collaborative maps based on certain criteria. More information can be found in the [User guide](../../users/filter/).
|
||||
|
||||
facilmap-leaflet injects the following properties and methods into [Leaflet map](https://leafletjs.com/reference.html#map) objects to use filters:
|
||||
* `setFmFilter(filter)`: Set the current filter expression. `filter` is a string with the filter expression or `undefined`. An exception is thrown if the filter expression is invalid.
|
||||
* `fmFilter`: The current filter expression (string or undefined).
|
||||
* `fmFilterFunc(object, type)`: Returns a boolean that indicates whether the specified object matches the current filter. `object` can be a [Marker](../client/types.md#marker) or [Line](../client/types.md#line), and `type` is its [Type](../client/types.md#type).
|
||||
|
||||
When the filter is updated using `setFmFilter()`, the map fires an `fmFilter` event. Other FacilMap Leaflet components ([`MarkersLayer`](./markers.md), [`LinesLayer`](./lines.md), [`HashHandler`](./hash.md)) react to this event and update their state accordingly.
|
|
@ -0,0 +1,129 @@
|
|||
# HashHandler
|
||||
|
||||
FacilMap can store the current map view in the location hash. The details can be found in the [User guide](../../users/share/).
|
||||
|
||||
`HashHandler` creates a two-way binding between the location hash and the current map view. This means that when the map is loaded and a location hash is set, the map view encoded in the hash is opened. If the location hash changes while the map is open, the new map view is opened. When the map view changes, the location hash is updated to reflect the current map view.
|
||||
|
||||
`HashHandler` handles the different details encoded in the location hash in the following way:
|
||||
* Longitude, latitude, zoom level: automatically synchronised.
|
||||
* Active layers: automatically synchronised (using the [FacilMap layer helpers](./layers.md)).
|
||||
* Search term: needs to be synchronised manually (see [below](#search-term)).
|
||||
* Active filter: automatically synchronised (using the [filter map extension](./filter.md)).
|
||||
|
||||
`HashHandler` also has support for saved views. If the position of the map is exactly that of a saved view, the location hash will be set to something like `#q=v123`.
|
||||
|
||||
## Usage
|
||||
|
||||
`HashHandler` is a [Leaflet handler](https://leafletjs.com/reference.html#handler) whose constructor accepts two arguments, the Leaflet map and a FacilMap client instance. Here is an example how to use it:
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { HashHandler } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/", "myMapId");
|
||||
const hashHandler = new HashHandler(map, client).enable();
|
||||
```
|
||||
|
||||
## HashHandler and initial view
|
||||
|
||||
facilmap-leaflet provides helper methods to set the [initial view](./views.md#initial-view) of a map. However, when opening the map with a specific map view already present in the location hash, loading the initial view would cause unnecessary network requests and jumping of the map if the hash handler would move the map position immediately afterwards.
|
||||
|
||||
When the hash handler is enabled for the first time, it checks whether a map view is present in the location hash. If there is, it zooms to that view, otherwise it leaves the map position untouched. This means that if the hash handler is enabled on a map whose position is not initialised yet, the position remains uninitialised if no position is present in the location hash. This can be checked by checking whether `map._loaded` is defined or not, and loading the initial view only if it is not.
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { displayView, getInitialView, HashHandler } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/", "myMapId");
|
||||
const hashHandler = new HashHandler(map, client).enable();
|
||||
if (!map._loaded)
|
||||
displayView(map, await getInitialView(this.client));
|
||||
```
|
||||
|
||||
## Search term
|
||||
|
||||
The FacilMap frontend uses the 5th part (search term) of the location hash to represent the selected map object, the active search term or the active route. If you want to make use of the search term part, you have to set up a two-way binding yourself.
|
||||
|
||||
Calling `hashHandler.setQuery(queryObject)` will notify the hash handler about the active search term and cause it to update the location hash. `queryObject` can be `undefined` or an object with the following properties:
|
||||
* `query` (string): The search term.
|
||||
* `center` ([LatLng](https://leafletjs.com/reference.html#latlng), optional): The map center if the map were zoomed exactly to the active query.
|
||||
* `zoom` (number, optional): The zoom level if the map were zoomed exactly to the active query.
|
||||
|
||||
If `center` and `zoom` are specified and the map is exactly at that position, the location hash is shortened to `#q=<search term>` (unless a filter or non-default layers are active).
|
||||
|
||||
When the hash handler is enabled or the location hash is changed, an `fmQueryChange` event is fired. The event contains the following properties:
|
||||
* `query`: The search term (may be an empty string or undefined).
|
||||
* `zoom`: A boolean that indicates whether the map should zoom to the result or not. This is `true` if the location hash is the shortened form `#q=<search term>` and `false` if it is the long form (which has a fixed map position encoded).
|
||||
|
||||
Here is an example how the search term could be hooked up to a search form:
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { HashHandler, SearchResultsLayer } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/");
|
||||
const hashHandler = new HashHandler(map, client).enable();
|
||||
const resultLayer = new SearchResultsLayer().addTo(map);
|
||||
|
||||
async function submitSearch(shouldZoom) {
|
||||
const query = document.getElementById("search-input").value;
|
||||
if (query.trim() != "") {
|
||||
try {
|
||||
resultLayer.setResults(await client.find({ query }));
|
||||
|
||||
const bounds = resultLayer.getBounds();
|
||||
const center = bounds.getCenter();
|
||||
const zoom = Math.min(15, map.getBoundsZoom(resultLayer.getBounds()));
|
||||
if (shouldZoom)
|
||||
map.flyTo(center, zoom);
|
||||
hashHandler.setQuery({ query, center, zoom })
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
}
|
||||
} else {
|
||||
resultsLayer.setResults([]);
|
||||
hashHandler.setQuery(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("search-form").addEventListener("submit", () => {
|
||||
submitSearch(true);
|
||||
});
|
||||
|
||||
hashHandler.on("fmQueryChange", (event) => {
|
||||
document.getElementById("search-input").value = event.query || "";
|
||||
submitSearch(event.zoom);
|
||||
});
|
||||
```
|
||||
|
||||
## Manual binding
|
||||
|
||||
Setting the `simulate` option to `true` will disable the two-way binding between the location hash and the map view. You can use this to set up your own binding using the following features:
|
||||
* `hashHandler.getHash()` will return a hash string (without leading `#`) representing the current map view.
|
||||
* `hashHandler.applyHash(hash)` will set the map view to the one represented by the given hash string (without leading `#`).
|
||||
* The hash handler will emit an `fmHash` event whenever the map view changes. The event object contains a `hash` property containing a hash string (without leading `#`) representing the current map view.
|
||||
|
||||
Example setting up a two-way binding between a text field and the map view:
|
||||
```javascript
|
||||
const hashHandler = new HashHandler(map, client, { simulate: true }).enable();
|
||||
hashHandler.on("fmHash", (e) => {
|
||||
hashInput.value = e.hash;
|
||||
});
|
||||
|
||||
const hashInput = document.getElementById("#hash-input");
|
||||
hashInput.value = hashHandler.getHash();
|
||||
hashInput.addEventListener("change", () => {
|
||||
hashHandler.applyHash(hashInput.value);
|
||||
});
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
The third argument to the `HashHandler` constructor can be an object with some of the following properties:
|
||||
* `simulate`: If this is `true`, the actual location hash will neither be read nor updated. The hash handler will only emit an `fmHash` event with the `hash` property of the event object being the location hash representing the current map view.
|
||||
* `overpassLayer`: This can be an instance of [OverpassLayer](./overpass.md), causing the overpass query to be persisted in the location hash as well.
|
|
@ -0,0 +1,54 @@
|
|||
# Icons
|
||||
|
||||
FacilMap comes with a large selection of icons and marker shapes. The icons come from the following sources:
|
||||
* All the [Open SVG Map Icons](https://github.com/twain47/Open-SVG-Map-Icons/) (these are the ones used by Nominatim for search results)
|
||||
* A selection of [Glyphicons](https://getbootstrap.com/docs/3.4/components/#glyphicons-glyphs) from Bootstrap 3.
|
||||
* A few icons from [Material Design Iconic Font](https://zavoloklom.github.io/material-design-iconic-font/).
|
||||
* A selection of icons from [Font Awesome](https://fontawesome.com/).
|
||||
|
||||
FacilMap uses these icons as part of markers on the map and in regular UI elements such as buttons.
|
||||
|
||||
facilmap-leaflet includes all the icons and marker shapes and provides some helper methods to access them in different sizes and styles.
|
||||
|
||||
To make the bundle size smaller, the icons are separated into two sets:
|
||||
* The *core* icons are included in the main facilmap-leaflet bundle. This includes all all icons that are used by FacilMap as UI elements.
|
||||
* The *extra* icons are included in a separate file. When you call any of the methods below for the first time for an extra icon, this separate file is loaded using an async import. You can also explicitly load the extra icons at any point of time by calling `preloadExtraIcons()`.
|
||||
|
||||
## Available icons and shapes
|
||||
|
||||
`iconList` and `shapeList` are arrays of strings that contain the names of all the available icons (core and extra) and marker shapes. The `coreIconList` array contains only the names of the core icons.
|
||||
|
||||
In addition to the icons listed in `iconList`, any single character can be used as an icon. Single-character icons are rendered in the browser, they don’t require loading the extra icons.
|
||||
|
||||
## Get an icon
|
||||
|
||||
The following methods returns a simple monochrome icon.
|
||||
|
||||
* `async getIconCode(colour, size, icon)`: Returns a raw SVG object with the code of the icon as a string.
|
||||
* `async getIconUrl(colour, size, icon)`: Returns the icon as a `data:` URL (that can be used as the `src` of an `img` for example)
|
||||
* `async getIconHtml(colour, size, icon)`: Returns the icon as an SVG element source code (as a string) that can be embedded straight into a HTML page.
|
||||
|
||||
The following arguments are expected:
|
||||
* `colour`: Any colour that would be acceptable in SVG, for example `#000000` or `currentColor`.
|
||||
* `size`: The height/width in pixels that the icon should have (icons are square). For `getIconHtml()`, the size can also be a string (for example `1em`).
|
||||
* `icon`: Either one of the icon name that is listed in `iconList`, or a single letter, or an empty string or undefined to render the default icon (a dot).
|
||||
|
||||
## Get a marker icon
|
||||
|
||||
The following methods returns a marker icon with the specified shape and the specified icon inside.
|
||||
|
||||
* `async getMarkerCode(colour, height, icon, shape, highlight)`: Returns a raw SVG object with the code of the marker as a string.
|
||||
* `async getMarkerUrl(colour, height, icon, shape, highlight)`: Returns the marker as a `data:` URL (that can be used as the `src` of an `img` for example)
|
||||
* `async getMarkerHtml(colour, height, icon, shape, highlight)`: Returns the marker as an SVG element source code (as a string) that can be embedded straight into a HTML page.
|
||||
* `getMarkerIcon(colour, height, icon, shape, highlight)`: Returns the marker as a [Leaflet Icon](https://leafletjs.com/reference.html#icon) that can be used for Leaflet markers. The anchor point is set correctly. The Icon object is returned synchronously and updates its `src` automatically as soon as it is loaded.
|
||||
|
||||
The following arguments are expected:
|
||||
* `colour`: A colour in hex format, for example `#000000`.
|
||||
* `height`: The height of the marker in pixels. Different marker shapes have different aspect ratios, so the width will differ depending on which shape is used. Note that the height is approximate, it is scaled down for some shapes with the aim that two markers with different shapes but the same `height` should visually appear roughly the same size.
|
||||
* `icon`: Either one of the icon name that is listed in `iconList`, or a single letter, or an empty string or undefined to render the default icon (a dot).
|
||||
* `shape`: A shape name that is listed in `shapeList`, or an empty string or undefined to render the default shape (`drop`).
|
||||
* `highlight`: If this is set to true, the marker is rendered as highlighted (with an increased border width).
|
||||
|
||||
## Get an icon for an OSM element
|
||||
|
||||
Calling `getIconForTags(tags)` will make a best attempt to return an icon that is appropriate for a given OSM element. `tags` should be an object that maps OSM tag keys to values. The function returns a string representing the name of an icon. If no fitting icon can be found, an empty string is returned.
|
|
@ -0,0 +1,126 @@
|
|||
# Layers
|
||||
|
||||
In Leaflet, a lot of different types of objects are layers internally, for example tile layers, polylines, markers and even tooltips. In the context of FacilMap, there are base layers (tile layers that make up the main map style, only one can be active at a time) and overlays (layers that are shown on top of the base layer). These layers are used in the following way:
|
||||
* The frontend offers the user to change which base layer and which overlays are visible
|
||||
* [Saved views](./views.md) contain information about which base layer and which overlays should be visible
|
||||
* The [location hash](./hash.md) stores which base layer and which overlays are visible.
|
||||
|
||||
facilmap-leaflet maintains a list of available base layers and overlays. This list is used by the layer picker in the frontend to show the available layers to the user, but it is also used when for views and the location hash to distinguish which layers on a map are FacilMap layers and which are other types of Leaflet layers.
|
||||
|
||||
The methods on this page make it possible to add FacilMap’s default selection of layers to a map and to modify that selection.
|
||||
|
||||
## Get the available layers
|
||||
|
||||
To get the available layers, call `getLayers(map)`. It returns an Object whose `baseLayers` and `overlays` properties contain an object that maps a key to a Leaflet layer. The key is used to identify the map in the location hash or in a saved view. The reason why the map has to be passed as an argument is that a Leaflet layer object can only be used on one map at a time. Internally, `getLayers(map)` persists the list of maps in the `_fmLayers` property of the map.
|
||||
|
||||
The following example shows how to add a [Leaflet Layers control](https://leafletjs.com/reference.html#control-layers) to the map that shows all the available layers. Note that the control expects objects mapping the layer name to the layer, while the objects returned by `getLayers(map)` map the layer key to the layer, so a mapping has to be done. FacilMap layers use the `fmName` option to store their display name.
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import { getLayers } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const layers = getLayers(map);
|
||||
const byName = (layerMap) => Object.fromEntries(Object.entries(layerMap).map(([key, layer]) => [layer.options.fmName || key, layer]));
|
||||
L.control.layers(byName(layers.baseLayers), byName(layers.overlays)).addTo(map);
|
||||
```
|
||||
|
||||
## Set the layer options
|
||||
|
||||
There are some global layer options that change the behaviour of the available layers:
|
||||
* `limaLabsToken`: A [Lima Labs](https://maps.lima-labs.com/) API token. If defined, the Lima Labs layer will be available and used as the default layer instead of Mapnik. Lima Labs layers are very similar to Mapnik in style, but they are double resolution (so they don’t look pixely on high-resolution screens) and have English place names in addition to the local language.
|
||||
|
||||
To set the global layer options, use the `setLayerOptions()` function:
|
||||
```javascript
|
||||
import { setLayerOptions } from "facilmap-leaflet";
|
||||
|
||||
setLayerOptions({
|
||||
limaLabsToken: "..."
|
||||
});
|
||||
```
|
||||
|
||||
Note that to avoid unexpected inconsistencies, this should be called before `getLayers()` or any other of functions documented on this page are called.
|
||||
|
||||
## Change the available layers
|
||||
|
||||
To change the available layers for a particular Leaflet map, set the `_fmLayers` properties of that map.
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
map._fmLayers = {
|
||||
baseLayers: {
|
||||
test: L.tileLayer("...", { fmName: "Test" })
|
||||
},
|
||||
overlays: {
|
||||
ovr1: L.tileLayer("...", { fmName: "Overlay 1" })
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
To change the available layers for all maps, use `setLayers()`:
|
||||
|
||||
```javascript
|
||||
import { setLayers } from "facilmap-leaflet";
|
||||
|
||||
setLayers(() => ({
|
||||
baseLayers: {
|
||||
test: L.tileLayer("...", { fmName: "Test" })
|
||||
},
|
||||
overlays: {
|
||||
ovr1: L.tileLayer("...", { fmName: "Overlay 1" })
|
||||
},
|
||||
fallbackLayer: "test"
|
||||
}));
|
||||
```
|
||||
|
||||
Note that `setLayers()` only affects how the `_fmLayers` property is created, it does not affect the `_fmLayers` properties of existing maps. This means that it has to be called before any part of the code accesses the layers of the map.
|
||||
|
||||
The FacilMap frontend uses the `fmName` option to decide under which name to show a layer. The layer key needs to be unique and should by convention be 4 characters long.
|
||||
|
||||
If you want to extend the default selection of layers, you can get it using `createDefaultLayers()`:
|
||||
|
||||
```javascript
|
||||
import { createDefaultLayers, setLayers } from "facilmap-leaflet";
|
||||
|
||||
setLayers(() => {
|
||||
const layers = createDefaultLayers();
|
||||
layers.baseLayers.test = L.tileLayer("...", { fmName: "Test" });
|
||||
layers.overlays.ovr1: L.tileLayer("...", { fmName: "Overlay 1" });
|
||||
return layers;
|
||||
}));
|
||||
```
|
||||
|
||||
## Get the visible layers
|
||||
|
||||
To find out which of the FacilMap layers are currently visible, use the `getVisibleLayers(map)` function. It returns an object with the following properties:
|
||||
* `baseLayer`: A string that contains the layer key of the currently visible base layer
|
||||
* `overlays`: An array of strings with the layer keys of the currently visible overlays
|
||||
|
||||
## Set the visible layers
|
||||
|
||||
`setVisibleLayers(map, visibleLayers)` can be used with an object of the same shape as returned by `getVisibleLayers(map)`:
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import { setVisibleLayers } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
setVisibleLayers(map, { baseLayer: "Mpnk", overlays: ["Hike", "Rlie"] });
|
||||
```
|
||||
|
||||
If `visibleLayers` is not specified, it defaults to `{ baseLayer: "Mpnk", overlays: [] }`.
|
||||
|
||||
The helper functions `setBaseLayer(map, baseLayer)` and `toggleOverlay(map, overlay)` can be used to change the visibility of individual layers:
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import { setBaseLayer, toggleOverlay } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
setBaseLayer(map, "Mpnk");
|
||||
toggleOverlay(map, "Hike");
|
||||
```
|
||||
|
||||
To find out the key of an existing layer, open [https://facilmap.org/](https://facilmap.org/), enable the desired layer and find the layer key in the location hash in the address bar of your browser. For example, when Mapnik and Hiking paths are enabled, the URL will look like this: <code>https://facilmap.org/#9/52.5196/13.4069/<strong>Mpnk-Hike</strong></code>.
|
|
@ -0,0 +1,90 @@
|
|||
# Lines
|
||||
|
||||
## Show map lines
|
||||
|
||||
`LinesLayer` is a Leaflet layer that will render all the lines on a collaborative map with their appropriate styles (colour and width). To use it, open a connection to a collaborative map using the client and add the layer to the map:
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { LinesLayer } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/", "myMapId");
|
||||
const linesLayer = new LinesLayer(client).addTo(map);
|
||||
```
|
||||
|
||||
`LinesLayer` also has the following features:
|
||||
* Lines get a tooltip with their name.
|
||||
* It automatically reacts to changes. When lines are created, changed or deleted, these changes are reflected on the map. `LinesLayer` can also be added before a collaborative map is opened, and will draw the lines as soon as a map is opened.
|
||||
* It shows/hides the appropriate lines if a [filter](./filter.md) is set.
|
||||
|
||||
## Handle line clicks
|
||||
|
||||
`MarkersLayer` emits a `click` event when a line is clicked. To find out which line was clicked, use `event.layer.line`.
|
||||
|
||||
```javascript
|
||||
linesLayer.on("click", (event) => {
|
||||
console.log(event.layer.line);
|
||||
});
|
||||
```
|
||||
|
||||
You could for example show the line data or highlight the line in reaction to a click.
|
||||
|
||||
## Highlight lines
|
||||
|
||||
Lines can be highlighted, which will remove their opacity and raise them above other elements on the map. The frontend uses this to present a line as selected.
|
||||
|
||||
To highlight a line, use `linesLayer.highlightLine(id)`, `linesLayer.unhighlightLine(id)` or `linesLayer.setHighlightedLines(ids)`. Lines are identified by their ID, which makes it possible to highlight them even before they are actually loaded (they will be highlighted as soon as they are rendered).
|
||||
|
||||
This example will highlight a line on click:
|
||||
|
||||
```javascript
|
||||
linesLayer.on("click", (event) => {
|
||||
linesLayer.setHighlightedLines(new Set([event.layer.line.id]));
|
||||
});
|
||||
```
|
||||
|
||||
## Draw a line
|
||||
|
||||
Calling `linesLayer.drawLine(lineTemplate)` starts an interaction where the user can draw a line by adding line points through clicks on the map. The user can finish drawing the line by clicking twice on the map, or you can finish the drawing by calling `linesLayer.endDrawLine(true)`, for example in response to clicking a “Finish” button that you show somewhere.
|
||||
|
||||
`lineTemplate` should be a line object with basic styling (`colour` and `width` are defined). Calling `linesLayer.endDrawLine(true)` will finish the operation, while `linesLayer.endDrawLine(false)` will cancel the operation. `linesLayer.drawLine(lineTemplate)` returns a promise that is resolved with the line points (array of objects with a `lat` and `lon` property) when the operation is finished, or with `undefined` when the operation is canceled.
|
||||
|
||||
Here is an example how drawing a line could look:
|
||||
```javascript
|
||||
document.getElementById("draw-line-button").addEventListener("click", async () => {
|
||||
const message = showMessage({
|
||||
body: "Click on the map to add line points.",
|
||||
actions: [
|
||||
{ text: "Finish", onClick: () => { linesLayer.endDrawLine(true); } },
|
||||
{ text: "Cancel", onClick: () => { linesLayer.endDrawLine(false); } }
|
||||
]
|
||||
});
|
||||
|
||||
let routePoints;
|
||||
try {
|
||||
routePoints = await linesLayer.drawLine({ colour: "0000ff", width: 7, stroke: "" });
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
} finally {
|
||||
message.hide();
|
||||
}
|
||||
|
||||
if (routePoints) {
|
||||
try {
|
||||
await client.addLine({ typeId: 1, routePoints });
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Render a single line
|
||||
|
||||
The code that styles lines on FacilMap has been moved into an external module called [Leaflet.HighlightableLayers](https://github.com/FacilMap/Leaflet.HighlightableLayers). To draw lines that look like FacilMap lines, use that module.
|
||||
|
||||
## Hide a line
|
||||
|
||||
Calling `linesLayer.hideLine(lineId)` hides a line until `linesLayer.unhideLine(lineId)` is called. The frontend uses this function to temporarily make a line draggable by calling [client.lineToRoute()](../client/methods.md#linetoroute-data) to convert the line to a route and hiding the line until the dragging has finished.
|
|
@ -0,0 +1,76 @@
|
|||
# Markers
|
||||
|
||||
## Show map markers
|
||||
|
||||
`MarkersLayer` is a Leaflet layer that will render all the markers on a collaborative map with their appropriate styles (colour, size, icon and shape). To use it, open a connection to a collaborative map using the client and add the layer to the map:
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { MarkersLayer } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/", "myMapId");
|
||||
const markersLayer = new MarkersLayer(client).addTo(map);
|
||||
```
|
||||
|
||||
`MarkersLayer` also has the following features:
|
||||
* It clusters markers if this has been enabled in the map settings.
|
||||
* Markers get a tooltip with their name.
|
||||
* It automatically reacts to changes. When markers are created, changed or deleted, these changes are reflected on the map. `MarkersLayer` can also be added before a collaborative map is opened, and will draw the markers as soon as a map is opened.
|
||||
* It shows/hides the appropriate markers if a [filter](./filter.md) is set.
|
||||
|
||||
## Handle marker clicks
|
||||
|
||||
`MarkersLayer` emits a `click` event when a marker is clicked. To find out which marker was clicked, use `event.layer.marker`.
|
||||
|
||||
```javascript
|
||||
markersLayer.on("click", (event) => {
|
||||
console.log(event.layer.marker);
|
||||
});
|
||||
```
|
||||
|
||||
You could for example show the marker data or highlight the marker in reaction to a click.
|
||||
|
||||
## Highlight markers
|
||||
|
||||
Markers can be highlighted, which will increase their border width, remove their opacity and raise them above other elements on the map. The frontend uses this to present a marker as selected.
|
||||
|
||||
To highlight a marker, use `markersLayer.highlightMarker(id)`, `markersLayer.unhighlightMarker(id)` or `markersLayer.setHighlightedMarkers(ids)`. Markers are identified by their ID, which makes it possible to highlight them even before they are actually loaded (they will be highlighted as soon as they are rendered).
|
||||
|
||||
This example will highlight a marker on click:
|
||||
|
||||
```javascript
|
||||
markersLayer.on("click", (event) => {
|
||||
markersLayer.setHighlightedMarkers(new Set([event.layer.marker.id]));
|
||||
});
|
||||
```
|
||||
|
||||
## Show a particular marker
|
||||
|
||||
If you want to make sure that a marker with a particular ID is shown (regardless of whether it is in the current bbox or not), call `markersLayer.showMarker(id)`. This will load the marker from the server if it is not loaded yet. Calling `markersLayer.showMarker(id, true)` will load the marker and fly to it.
|
||||
|
||||
## Render a single marker
|
||||
|
||||
`MarkerLayer` (in singular, as opposed to `MarkersLayer`) makes it possible to render an individual marker object with its appropriate style. It does not automatically update when the marker changes and can also be used for markers that are not saved on the map.
|
||||
|
||||
`MarkerLayer` is based on regular [Leaflet markers](https://leafletjs.com/reference.html#marker), but accepts the following additional options:
|
||||
* `marker`: A marker object that the marker style will be based on. Only the properties relevant for the style (`colour`, `size`, `icon` and `shape`) need to be set.
|
||||
* `highlight`: If this is `true`, the marker will be shown with a thicker border.
|
||||
* `raised`: If this is `true`, the marker will be rendered above other map objects.
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import { MarkerLayer } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
new MarkerLayer([52.5295, 13.3840], {
|
||||
marker: { colour: "00ff00", size: 40, icon: "alert", shape: "pentagon" }
|
||||
}).addTo(map);
|
||||
```
|
||||
|
||||
## Lock a marker
|
||||
|
||||
Locking a marker will disable any position and filter updates for it. It will be shown regardless of whether a filter expression applies to it or not, and updates to its position on the collaborative map will be ignored, although style updates are still applied. The frontend uses this function for its “Move marker” feature. Users wouldn’t want the marker to jump to another place or to disappear while they are dragging it.
|
||||
|
||||
To lock a marker, call `markersLayer.lockMarker(markerId)`. To unlock it again, call `markersLayer.unlockMarker(markerId)`.
|
|
@ -0,0 +1,162 @@
|
|||
# Overpass
|
||||
|
||||
`OverpassLayer` can be used to show amenities or POIs using the [Overpass API](https://overpass-api.de/). In the code, individual amenities/POIs are referred to as “elements”. Elements are rendered on the map as markers, for elements that represent a way or relation in the OpenStreetMap database, the marker is positioned at their geographical centre.
|
||||
|
||||
`OverpassLayer` can be used like this:
|
||||
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { OverpassLayer, getOverpassPresets } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const overpassLayer = new OverpassLayer(getOverpassPresets(["parking"])).addTo(map);
|
||||
```
|
||||
|
||||
Elements are loaded through the Overpass API whenever the bounds of the map change (in reaction to the [`moveend`](https://leafletjs.com/reference.html#map-moveend) event). Some tight limits are applies to that the query to avoid overloading the API and the browser. The result of the load operation and any exceeding of the limits is reported as part of the `loadend` event ([see below](#loading-state)). You might want to listen to those events to inform the user about the load status and ask them to zoom in further.
|
||||
|
||||
## Use a preset
|
||||
|
||||
Pass an overpass query either as the first argument to the constructor, or set it later by using `overpassLayer.setQuery(query)`. A query can be an array of presets or a string to use a custom query ([see below](#set-a-query)). Passing an empty array, empty string or `undefined` will clear all elements.
|
||||
|
||||
facilmap-leaflet provides a list of POI types along with their Overpass queries. The list is taken from [OpenPoiMap](http://openpoimap.org/). Items from this list are referred to as presets. To find out which presets are defined, inspect the `overpassPresets` export or check [its source code](https://github.com/FacilMap/facilmap/blob/master/leaflet/src/overpass/overpass-presets.ts).
|
||||
|
||||
`overpassPresets` is an array of categories, each of which contains a list of presets. Each category is an object with the following properties:
|
||||
* `label`: The name of the category that can be shown to the user.
|
||||
* `presets`: An array of arrays of presets. Each array of of presets represents a logical group, the UI may render a separator between them. A preset is an object with the following properties:
|
||||
* `key`: A unique identifier for the preset.
|
||||
* `query`: The overpass query for the preset.
|
||||
* `label`: The name of the preset that can be shown to the user.
|
||||
|
||||
A preset object can be retrieved directly from `overpassPresets`, or by using the `getOverpassPreset(key)` or `getOverpassPresets(keys)` methods, which resolve preset keys to preset objects. For example, to show parking places and recycling stations, set the query as such:
|
||||
```javascript
|
||||
overpassLayer.setQuery(getOverpassPresets(["parking", "recycling"]));
|
||||
```
|
||||
|
||||
To enable the first preset of the first category, use this:
|
||||
```javascript
|
||||
overpassLayer.setQuery([overpassPresets[0].presets[0]]);
|
||||
```
|
||||
|
||||
## Set a query
|
||||
|
||||
If the Overpass presets don’t fit your use case, you can pass a custom overpass query as the first argument to the constructor to `overpassLayer.setQuery(query)`.
|
||||
|
||||
`OverpassLayer` expects only the raw query and will add other statements to it as needed. For example, the query `nwr[amenity=parking]` is sent to the Overpass API as something like `[out:json][timeout:1][bbox:52.4323,13.3269,52.4406,13.3681];nwr[amenity=parking];out center 50;`. Note that `OverpassLayer` only renders a marker at the centre of the Overpass element, it does not render any lines or polygons.
|
||||
|
||||
See the OpenStreetMap wiki for a reference of [query statements](https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#The_Query_Statement) and for an [overview of tags](https://wiki.openstreetmap.org/wiki/Map_features).
|
||||
|
||||
Here are some example queries:
|
||||
* `nwr[amenity=parking]`: Show all parking spaces (nodes, ways and relations).
|
||||
* `(nwr[amenity=atm];nwr[amenity=bank][atm][atm!=no];)`: Show ATMs.
|
||||
|
||||
To create queries based on user input, you may find the `quoteOverpassString(string)` function useful. It returns the string in quotes and with certain characters escaped. You can use `validateQuery(query)` to validate the syntax of a query. It returns a promise that resolves to `undefined` if the query is fine or otherwise a string with an error message.
|
||||
|
||||
## Handle errors
|
||||
|
||||
The Overpass API will throw an error when the resource limits are exceeded. `OverpassLayer` will emit an `error` event in that case, with the error message in `event.error.message`. Once the map is moved to a position where element can be loaded again, a `clearerror` event is fired.
|
||||
|
||||
```javascript
|
||||
let flag = undefined;
|
||||
overpassLayer.on("error", (event) => {
|
||||
if (flag)
|
||||
flag.close();
|
||||
if (event.error.message.includes("timed out") || event.error.message.includes("out of memory"))
|
||||
flag = showFlag("Too many results, please zoom in.");
|
||||
else
|
||||
flag = showFlag(event.error.message);
|
||||
});
|
||||
overpassLayer.on("clearerror", () => {
|
||||
flag.close();
|
||||
});
|
||||
```
|
||||
|
||||
## Handle element click
|
||||
|
||||
When an element marker is clicked, `OverpassLayer` emits a `click` event. The element can be accessed as `event.layer._fmOverpassElement`. It is an object with the following properties:
|
||||
* `type`: The type of the OpenStreetMap object, `node`, `way` or `relation`.
|
||||
* `id`: The ID of the OpenStreetMap object.
|
||||
* `lat`, `lon`: The coordinates of the object. For ways and relations, this is the geometric centre of the object.
|
||||
* `tags`: The tags of the OpenStreetMap object. An object that maps keys to values. May be undefined if the object does not have any tags.
|
||||
|
||||
```javascript
|
||||
overpassLayer.on("click", (event) => {
|
||||
alert(JSON.stringify(event.layer._fmOverpassElement.tags));
|
||||
});
|
||||
```
|
||||
|
||||
## Highlight elements
|
||||
|
||||
Using `overpassLayer.highlightElement(element)`, `overpassLayer.unhighlightElement(element)` and `overpassLayer.setHighlightedElements(elements)`, you can highlight some elements, causing them to be rendered with an increased outline, no opacity and above other map objects. The frontend uses this to present them as selected.
|
||||
|
||||
In this example, an element is highlighted when clicked:
|
||||
```javascript
|
||||
overpassLayer.on("click", (event) => {
|
||||
overpassLayer.setHighlightedElements(new Set([event.layer._fmOverpassElement]));
|
||||
});
|
||||
```
|
||||
|
||||
## Loading state
|
||||
|
||||
When `OverpassLayer` makes a request to the Overpass API, it fires a `loadstart` event. When the request is finished, a `loadend` event is fired. You might want to listen to these events to show a loading indicator to the user. Note that multiple requests might be going on simultaneously if the user moves the map before a pending request has finished, so you need to keep track of the number of times the events are fired.
|
||||
|
||||
```javascript
|
||||
let pending = 0;
|
||||
overpassLayer.on("loadstart", () => {
|
||||
if (++pending == 1)
|
||||
showLoadingSpinner();
|
||||
});
|
||||
overpassLayer.on("loadend", () => {
|
||||
if (--pending == 0)
|
||||
hideLoadingSpinner();
|
||||
});
|
||||
```
|
||||
|
||||
The `loadend` event object contains a `status` property that gives details about how the request went. It can have one of the following values:
|
||||
* `OverpassLoadStatus.COMPLETE`: The elements were successfully loaded and shown.
|
||||
* `OverpassLoadStatus.INCOMPLETE`: Some elements were loaded and shown, but the number of found elements exceeded the limit specified in the [options](#options). This means that the user needs to zoom in further to see the complete set of elements.
|
||||
* `OverpassLoadStatus.TIMEOUT`: The request exceeded the timeout specified in the [options](#options), so no elements were retrieved. This usually happens when there are way too many results (because the zoom level is very low). The user needs to zoom in to see any results.
|
||||
* `OverpassLoadStatus.ERROR`: The overpass API has reported an error and no elements were retrieved. The exact error object is available in the `error` property of the event object.
|
||||
* `OverpassLoadStatus.ABORTED`: Another request has been started in the meantime (because the user moved the map again while the request was still loading). This result can be ignored (apart from updating the number of pending requests), as another request is in progress (or has already succeeded) that supersedes the current request.
|
||||
|
||||
A `clear` event is fired whenever the POIs are cleared (for example if an empty query is applied). If you are persisting the latest load status or present it to the user in some way, you might want to reset that in response to the `clear` event.
|
||||
|
||||
Here is an example how the status can be presented to the user:
|
||||
```javascript
|
||||
import { OverpassLoadStatus } from "facilmap-leaflet";
|
||||
|
||||
// This is just an example, in reality you would use a styled element
|
||||
const overpassStatus = document.createElement("div");
|
||||
document.body.appendChild(overpassStatus);
|
||||
|
||||
overpassLayer.on("loadend", (event) => {
|
||||
if (event.status == OverpassLoadStatus.COMPLETE)
|
||||
overpassStatus.innerText = "";
|
||||
else if (event.status == OverpassLoadStatus.INCOMPLETE)
|
||||
overpassStatus.innerText = "Not all POIs are shown because there are too many results. Zoom in to show all results.";
|
||||
else if (event.status == OverpassLoadStatus.TIMEOUT)
|
||||
overpassStatus.innerText = "Zoom in to show POIs.";
|
||||
else if (event.status == OverpassLoadStatus.ERROR)
|
||||
overpassStatus.innerText = "Error loading POIs: " + event.error.message;
|
||||
}).on("clear", () => {
|
||||
overpassStatus.innerText = "";
|
||||
});
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
An object with options can be specified as the second parameter to the constructor:
|
||||
* `markerColour`: The colour to use for the element markers. A 6 character hex code, defaults to `000000`.
|
||||
* `markerSize`: The size to use for the element markers. A number that specifies the height in pixels, defaults to `35`.
|
||||
* `markerShape`: The shape to use for the element markers. Defaults to the default shape (drop).
|
||||
* `timeout`: The number in seconds to use as a timeout for Overpass API requests. Defaults to `1`. If this is exceeded, no elements are shown and the `loadend` event will have a `status` of `OverpassLoadStatus.TIMEOUT`.
|
||||
* `limit`: The maximum number of elements that should be returned by an Overpass request. Defaults to `50`. If this is reached, the loaded elements are shown, but the `loadend` event will have a `status` of `OverpassLoadStatus.INCOMPLETE`.
|
||||
|
||||
## Hash
|
||||
|
||||
The current query of an Overpass layer can be persisted in the location hash. To enable this, pass `overpassLayer` in the [options of the hash handler](./hash.md#options):
|
||||
```javascript
|
||||
const hashHandler = new HashHandler(map, client, { overpassLayer }).enable();
|
||||
```
|
||||
|
||||
The Overpass layer will be represented in the hash by a dynamic overlay name starting with `o_` for presets and `O_` for a custom query.
|
|
@ -0,0 +1,37 @@
|
|||
# Route
|
||||
|
||||
Routes in FacilMap are temporarily stored on the server in the scope of one particular client connection, unrelated to any collaborative map that is opened. This makes it possible that only the track points appropriate for the current bounding box and zoom level have to be received by the client, rather than the whole route in all detail. Multiple routes can be active at the same time by specifying a custom `routeId` when calling [setRoute()](../client/methods.md#setroute-data).
|
||||
|
||||
## Show a route
|
||||
|
||||
`RouteLayer` renders a route with one particular `routeId` on the map. As long as no route with that `routeId` is active, no route is shown, and as soon as a route is received, it is automatically rendered. An example usage pattern would be that you have one routing form that uses a fixed `routeId`, which would control the active route using [setRoute()](../client/methods.md#setroute-data) and [clearRoute()](../client/methods.md#clearroute-data), and there would be one `RouteLayer` for that `routeId` constantly present on the map which would always show the current state of the route.
|
||||
|
||||
The `RouteLayer` constructor accepts the following arguments:
|
||||
* A client instance
|
||||
* A `routeId` (string or undefined)
|
||||
* Layer options (any [Leaflet.HighlightableLayers](https://github.com/FacilMap/Leaflet.HighlightableLayers) can be specified)
|
||||
|
||||
Example usage:
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { RouteLayer } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/", "myMapId");
|
||||
const routeLayer = new RouteLayer(client, "routeForm", { raised: true }).addTo(map);
|
||||
```
|
||||
|
||||
## Make a route draggable
|
||||
|
||||
Most of the logic to make routes draggable has been moved into the external module [Leaflet.DraggableLines](https://github.com/FacilMap/Leaflet.DraggableLines).
|
||||
|
||||
`RouteDragHandler` is an extended version of `DraggableLines` that automatically updates the route points when the route is dragged, causing it to be recalculated.
|
||||
|
||||
Example usage:
|
||||
```javascript
|
||||
import { RouteDragHandler } from "facilmap-leaflet";
|
||||
|
||||
const routeDragHandler = new RouteDragHandler(map, client).enable();
|
||||
routeDragHandler.enableForLayer(routeLayer);
|
||||
```
|
|
@ -0,0 +1,61 @@
|
|||
# Search
|
||||
|
||||
## Show search results
|
||||
|
||||
`SearchResultsLayer` renders search results as markers, lines and polygons on the map. The constructor accepts the following arguments:
|
||||
* `results`: An array of search results as returned by [client.find()](../client/methods.md#find-data). You can also leave this `undefined` and add results later using `setResults()`.
|
||||
* `options`: An optional object containing the following properties:
|
||||
* `markerColour`: A 6-digit colour to use for search result markers, defaults to `000000`
|
||||
* `markerSize`: The height of search result markers in pixels, defaults to `35`
|
||||
* `markerShape`: The shape of search result markers, defaults to `drop`
|
||||
* `pathOptions`: [Path options](https://leafletjs.com/reference.html#path-option) for lines and polygons.
|
||||
|
||||
Note that the marker icon is determined by the type of result.
|
||||
|
||||
Example usage:
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { SearchResultsLayer } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/");
|
||||
const resultLayer = new SearchResultsLayer().addTo(map);
|
||||
|
||||
document.getElementById("search-form").addEventListener("submit", async () => {
|
||||
const query = document.getElementById("search-input").value;
|
||||
if (query.trim() != "") {
|
||||
try {
|
||||
resultLayer.setResults(await client.find({ query }));
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
}
|
||||
} else {
|
||||
resultsLayer.setResults([]);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Handle result click
|
||||
|
||||
`SearchResultsLayer` fires a `click` event when a result is clicked. To determine which result was clicked, use `event.layer._fmSearchResult`.
|
||||
|
||||
Example:
|
||||
```javascript
|
||||
resultsLayer.on("click", (event) => {
|
||||
alert(event.layer._fmSearchResult.display_name);
|
||||
});
|
||||
```
|
||||
|
||||
## Highlight results
|
||||
|
||||
Using `resultsLayer.highlightResult(result)`, `resultsLayer.unhighlightResult(result)` and `resultsLayer.setHighlightedResults(results)`, you can highlight some search results, causing them to be rendered with an increased outline, no opacity and above other map objects. The frontend uses this to present them as selected.
|
||||
|
||||
A single search result might consist of multiple geometries, for example a marker and a polygon. Highlighting a search result will highlight all those geometries.
|
||||
|
||||
In this example, a search result is highlighted when clicked:
|
||||
```javascript
|
||||
resultsLayer.on("click", (event) => {
|
||||
resultsLayer.setHighlightedResults(new Set([event.layer._fmSearchResult]));
|
||||
});
|
||||
```
|
|
@ -0,0 +1,45 @@
|
|||
# Views
|
||||
|
||||
Views in FacilMap are a particular bounding box on the map, with a particular set of layers visible and possibly an active filter. Views can be saved as part of collaborative maps. More details can be found in the [User guide](../../users/views/).
|
||||
|
||||
A view object represents a view. Its shape is documented in the [Client API](../client/types.md#view). Note that only objects that represent saved views will have an `id`.
|
||||
|
||||
|
||||
## Current view
|
||||
|
||||
`getCurrentView(map, { includeFilter })` returns a view object that represents the current view of the map. If `includeFilter` is `true`, the current [filter](./filter.md) is included in the object (if there is one), otherwise it is omitted.
|
||||
|
||||
`isAtView(map, view)` returns a boolean that indicates whether the current map view is equal to the view represented by the specified view object. If the `view` object is omitted, the method will indicate whether the current map shows the fallback view (the whole world).
|
||||
|
||||
|
||||
## Show a view
|
||||
|
||||
`displayView(map, view)` will animate the map to fly to the specified view and activate the layers and filter specified in the view object. If the `view` object is omitted, the map will show the fallback view (the whole world).
|
||||
|
||||
|
||||
## Initial view
|
||||
|
||||
`getInitialView(client)` returns a promise that is resolved with one of the following, in order:
|
||||
* If a collaborative map is open and a default view is configured, that view is returned.
|
||||
* Otherwise, an attempt is made to guess the position of the user using [geoip](../client/methods.md#geoip). If a guess could be made, a view object representing the rough area is returned.
|
||||
* Otherwise, `undefined` is returned.
|
||||
|
||||
Here is an example how the initial view could be set:
|
||||
```javascript
|
||||
import L from "leaflet";
|
||||
import Client from "facilmap-client";
|
||||
import { displayView, getInitialView } from "facilmap-leaflet";
|
||||
|
||||
const map = L.map('map');
|
||||
const client = new Client("https://facilmap.org/", "myMapId");
|
||||
displayView(map, await getInitialView(this.client));
|
||||
```
|
||||
|
||||
|
||||
## Overpass
|
||||
|
||||
To persist the active query of an [Overpass layer](./overpass.md) as part of a saved view, pass the `overpassLayer` object to the various methods:
|
||||
* `getCurrentView(map, { overpassLayer })`
|
||||
* `isAtView(map, view, { overpassLayer })`
|
||||
* `displayView(map, view, { overpassLayer })`
|
||||
* `getInitialView(map, view, { overpassLayer })`
|
|
@ -0,0 +1,11 @@
|
|||
# Overview
|
||||
|
||||
The FacilMap server is a HTTP server that fulfills the following tasks:
|
||||
* Serve the frontend under `/` and `/<map ID>`.
|
||||
* Serve exported collaborative maps under `/<map ID>/<type>`, where `<type>` can be `table`, `gpx` or `geojson`.
|
||||
* Run a socket.io server under `/socket.io`. The frontend connects to this using the [FacilMap client](../client/) and uses it to get calculated routes, get and update the data on a collaborative map, and receive live updates to a collaborative map, and as a proxy to perform searches.
|
||||
* Maintain a connection to a database where collaborative map data and calculated routes are stored.
|
||||
|
||||
The official FacilMap server is running on [https://facilmap.org/](https://facilmap.org/). If you want, you can run your own FacilMap server using one of these options:
|
||||
* [Docker](./docker.md) will run the server in an isolated container. It is easer to set up and more secure, but takes more resources.
|
||||
* Running the server [standalone](./standalone.md) takes less resources, but is less secure and takes more steps to set up.
|
|
@ -0,0 +1,48 @@
|
|||
# Configuration
|
||||
|
||||
The config of the FacilMap server can be set either by using environment variables (useful for docker) or by editing `config.env`.
|
||||
|
||||
| Variable | Required | Default | Meaning |
|
||||
|-----------------------|----------|-------------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `USER_AGENT` | * | | Will be used for all HTTP requests (search, routing, GPX/KML/OSM/GeoJSON files). You better provide your e-mail address in here. |
|
||||
| `APP_NAME` | | | If specified, will replace “FacilMap” as the name of the app throughout the UI. |
|
||||
| `TRUST_PROXY` | | | Whether to trust the X-Forwarded-* headers. Can be `true` or a comma-separated list of IP subnets (see the [express documentation](https://expressjs.com/en/guide/behind-proxies.html)). Currently only used to calculate the base URL for the `opensearch.xml` file. |
|
||||
| `BASE_URL` | | | If `TRUST_PROXY` does not work for your particular setup, you can manually specify the base URL where FacilMap can be publicly reached here. |
|
||||
| `HOST` | | | The ip address to listen on (leave empty to listen on all addresses) |
|
||||
| `PORT` | | `8080` | The port to listen on. |
|
||||
| `DB_TYPE` | | `mysql` | The type of database. Either `mysql`, `postgres`, `mariadb`, `sqlite`, or `mssql`. |
|
||||
| `DB_HOST` | | `localhost` | The host name of the database server. |
|
||||
| `DB_PORT` | | | The port of the database server (optional). |
|
||||
| `DB_NAME` | | `facilmap` | The name of the database. |
|
||||
| `DB_USER` | | `facilmap` | The username to connect to the database with. |
|
||||
| `DB_PASSWORD` | | `facilmap` | The password to connect to the database with. |
|
||||
| `ORS_TOKEN` | | | [OpenRouteService API key](https://openrouteservice.org/). If not specified, advanced routing settings will not be shown. |
|
||||
| `MAPBOX_TOKEN` | | | [Mapbox API key](https://www.mapbox.com/signup/). If neither this nor `ORS_TOKEN` are specified, the routing tab and any routing options will be hidden. |
|
||||
| `MAXMIND_USER_ID` | | | [MaxMind user ID](https://www.maxmind.com/en/geolite2/signup). |
|
||||
| `MAXMIND_LICENSE_KEY` | | | MaxMind license key. |
|
||||
| `LIMA_LABS_TOKEN` | | | [Lima Labs](https://maps.lima-labs.com/) API key |
|
||||
| `HIDE_COMMERCIAL_MAP_LINKS` | | | Set to `1` to hide the links to Google/Bing Maps in the “Map style” menu. |
|
||||
| `CUSTOM_CSS_FILE` | | | The path of a CSS file that should be included ([see more details below](#custom-css-file)). |
|
||||
| `NOMINATIM_URL` | | `https://nominatim.openstreetmap.org` | The URL to the Nominatim server (used to search for places). |
|
||||
| `OPEN_ELEVATION_URL` | | `https://api.open-elevation.com` | The URL to the Open Elevation server (used to look up the elevation for markers). |
|
||||
| `OPEN_ELEVATION_THROTTLE_MS` | | `1000` | The minimum time between two requests to the Open Elevation API. Set to `0` if you are using your own self-hosted instance of Open Elevation. |
|
||||
| `OPEN_ELEVATION_MAX_BATCH_SIZE` | | `200` | The maximum number of points to resolve in one request through the Open Elevation API. Set this to `1000` if you are using your own self-hosted Open Elevation instance. |
|
||||
|
||||
FacilMap makes use of several third-party services that require you to register (for free) and generate an API key:
|
||||
* Mapbox and OpenRouteService are used for calculating routes. Mapbox is used for basic routes, OpenRouteService is used when custom route mode settings are made. If these API keys are not defined, calculating routes will fail.
|
||||
* Maxmind provides a free database that maps IP addresses to approximate locations. FacilMap downloads this database to decide the initial map view for users (IP addresses are looked up in FacilMap’s copy of the database, on IP addresses are sent to Maxmind). This API key is optional, if it is not set, the default view will be the whole world.
|
||||
* Lima Labs is used for nicer and higher resolution map tiles than Mapnik. The API key is optional, if it is not set, Mapnik will be the default map style instead.
|
||||
|
||||
## Custom CSS file
|
||||
|
||||
To include a custom CSS file in the UI, set the `CUSTOM_CSS_FILE` environment variable to the file path.
|
||||
|
||||
When running FacilMap with docker, you can mount your CSS file as a volume into the container, for example with the following docker-compose configuration:
|
||||
```yaml
|
||||
environment:
|
||||
CUSTOM_CSS_FILE: /opt/facilmap/custom.css
|
||||
volumes:
|
||||
- ./custom.css:/opt/facilmap/custom.css
|
||||
```
|
||||
|
||||
Your custom CSS file will be included in the map UI and in the table export. You can distinguish between the two by using the `html.fm-facilmap-map` and `html.fm-facilmap-table` selectors.
|
|
@ -0,0 +1,93 @@
|
|||
# Docker
|
||||
|
||||
[Docker](https://www.docker.com/) is a container management system that is commonly used to run applications on servers. A docker image contains a full Linux system with one particular application installed, and docker runs this in a simulated virtual environment that is isolated from the rest of the server. The main advantages are security (if a hacker gains access to an application that is running inside a docker container is hacked, it is hard to impossible to gain access to the rest of the system from there) and simplicity (applications can be installed, updated and removed without leaving any traces behind using a single command).
|
||||
|
||||
This manual assumes that you have docker set up on your system.
|
||||
|
||||
The FacilMap server is available as [`facilmap/facilmap`](https://hub.docker.com/r/facilmap/facilmap/) on Docker Hub. The [configuration](./config.md) can be defined using environment variables. The container will expose a HTTP server on port 8080, which you should put behind a reverse proxy such as [nginx-proxy](https://hub.docker.com/r/jwilder/nginx-proxy) or [traefik](https://traefik.io/traefik/) for HTTPS support.
|
||||
|
||||
FacilMap needs a database supported by [Sequelize](https://sequelize.org/master/) to run, it is recommended to use MySQL/MariaDB. When creating a MySQL/MariaDB database for FacilMap, make sure to use the `utf8mb4` charset/collation to make sure that characters from all languages can be used on a map. By default, MySQL/MariaDB uses the `latin1` charset, which mostly supports only basic latin characters. When you start the FacilMap server for the first time, the necessary tables are created using the charset of the database. When using PostgreSQL, the PostGIS extensions must be enabled.
|
||||
|
||||
## docker-compose
|
||||
|
||||
To run FacilMap with MariaDB using [docker-compose](https://docs.docker.com/compose/), here is an example `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
facilmap:
|
||||
image: facilmap/facilmap
|
||||
ports:
|
||||
- 8080:8080
|
||||
links:
|
||||
- mariadb
|
||||
depends_on:
|
||||
mariadb:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
USER_AGENT: My FacilMap (https://facilmap.example.org/, facilmap@example.org)
|
||||
TRUST_PROXY: "true"
|
||||
DB_TYPE: mysql
|
||||
DB_HOST: mariadb
|
||||
DB_NAME: facilmap
|
||||
DB_USER: facilmap
|
||||
DB_PASSWORD: password
|
||||
ORS_TOKEN: # Get an API key on https://go.openrouteservice.org/ (needed for routing)
|
||||
MAPBOX_TOKEN: # Get an API key on https://www.mapbox.com/signup/ (needed for routing)
|
||||
MAXMIND_USER_ID: # Sign up here https://www.maxmind.com/en/geolite2/signup (needed for geoip lookup to show initial map state)
|
||||
MAXMIND_LICENSE_KEY:
|
||||
LIMA_LABS_TOKEN: # Get an API key on https://maps.lima-labs.com/ (optional, needed for double-resolution tiles)
|
||||
restart: unless-stopped
|
||||
mariadb:
|
||||
image: mariadb
|
||||
environment:
|
||||
MYSQL_DATABASE: facilmap
|
||||
MYSQL_USER: facilmap
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_RANDOM_ROOT_PASSWORD: "true"
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
healthcheck:
|
||||
test: healthcheck.sh --su-mysql --connect --innodb_initialized
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
Here is an example with Postgres:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
facilmap:
|
||||
image: facilmap/facilmap
|
||||
ports:
|
||||
- 8080:8080
|
||||
links:
|
||||
- postgres
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
USER_AGENT: My FacilMap (https://facilmap.example.org/, facilmap@example.org)
|
||||
TRUST_PROXY: "true"
|
||||
DB_TYPE: postgres
|
||||
DB_HOST: db
|
||||
DB_NAME: facilmap
|
||||
DB_USER: facilmap
|
||||
DB_PASSWORD: password
|
||||
ORS_TOKEN: # Get an API key on https://go.openrouteservice.org/ (needed for routing)
|
||||
MAPBOX_TOKEN: # Get an API key on https://www.mapbox.com/signup/ (needed for routing)
|
||||
MAXMIND_USER_ID: # Sign up here https://www.maxmind.com/en/geolite2/signup (needed for geoip lookup to show initial map state)
|
||||
MAXMIND_LICENSE_KEY:
|
||||
LIMA_LABS_TOKEN: # Get an API key on https://maps.lima-labs.com/ (optional, needed for double-resolution tiles)
|
||||
restart: unless-stopped
|
||||
postgres:
|
||||
image: postgis/postgis
|
||||
environment:
|
||||
POSTGRES_USER: facilmap
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_DB: facilmap
|
||||
healthcheck:
|
||||
test: pg_isready -d $$POSTGRES_DB
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
To start FacilMap, run `docker-compose up -d` in the directory of the `docker-compose.yml` file. To upgrade FacilMap, run `docker-compose pull` and then restart it by running `docker-compose up -d`.
|
||||
|
||||
Note that this exposes FacilMap through unencrypted HTTP on port 8080. In a production setup, FacilMap should be served by a reverse proxy that provides HTTPS. Usually, the `ports` directive can be removed then.
|
|
@ -0,0 +1,34 @@
|
|||
# Standalone
|
||||
|
||||
The FacilMap server runs on [Node.js](https://nodejs.org/en/). To run the FacilMap server, the following dependencies are needed:
|
||||
* You need to have a recent version of Node.js and npm installed.
|
||||
* You need to create a database on one of the systems supported by [Sequelize](https://sequelize.org/master/), it is recommended to use MySQL/MariaDB.
|
||||
* When creating a MySQL/MariaDB database for FacilMap, make sure to use the `utf8mb4` charset/collation to make sure that characters from all languages can be used on a map. By default, MySQL/MariaDB uses the `latin1` charset, which mostly supports only basic latin characters. When you start the FacilMap server for the first time, the necessary tables are created using the charset of the database.
|
||||
* When using PostgreSQL, the PostGIS extensions must be enabled.
|
||||
* It is recommended to run FacilMap as an unprivileged user.
|
||||
|
||||
## Run the latest release
|
||||
|
||||
A bundled version of the FacilMap server is published on NPM as [facilmap-server](https://www.npmjs.com/package/facilmap-server). To run it, run the following steps:
|
||||
|
||||
1. If you don’t have a global NPM prefix set up yet, run `npm config set prefix ~/.local`. This will install npm packages into `~/.local/bin`, rather than trying to install them into `/usr/local/bin`.
|
||||
2. Install facilmap-server by running `npm install -g facilmap-server`
|
||||
3. Create a `config.env` file based on [`config.env.example`](https://github.com/FacilMap/facilmap/blob/main/config.env.example) and to adjust the [configuration](./config.md).
|
||||
4. Start the FacilMap server by running `~/.local/bin/facilmap-server dotenv_config_path=config.env`.
|
||||
|
||||
FacilMap will need write access to the directory `~/.local/lib/node_modules/.cache/facilmap-server` (or specify another directory in the `CACHE_DIR` environment variable). All other files and directories can be read-only. To harden the FacilMap installation, make the whole installation folder owned by root, but create the cache directory and make it owned by the facilmap user.
|
||||
|
||||
|
||||
## Run the development version
|
||||
|
||||
To run the latest state from the [FacilMap repository](https://github.com/FacilMap/facilmap), run the following steps:
|
||||
|
||||
1. Make sure that you have a recent version of [Node.js](https://nodejs.org/), [yarn](https://yarnpkg.com/)
|
||||
and a database (MariaDB, MySQL, PostgreSQL, SQLite, Microsoft SQL Server) set up. (Note that only MySQL/MariaDB has been tested so far.)
|
||||
2. Clone the [FacilMap repository](https://github.com/FacilMap/facilmap).
|
||||
3. Run `yarn install` in the root folder of this repository to install the dependencies.
|
||||
4. Run `yarn build` to create the JS bundles.
|
||||
5. Copy `config.env.example` to `config.env` and adjust the [configuration](./config.md).
|
||||
6. Inside the `server` directory, run `yarn server`. This will automatically set up the database structure and start the server.
|
||||
|
||||
You can also run `yarn dev-server`, which will automatically rebuild the frontend bundle when the code is changed. See [dev setup](../development/dev-setup.md) for more information.
|
|
@ -1,15 +1,11 @@
|
|||
---
|
||||
home: true
|
||||
heroImage: https://v1.vuepress.vuejs.org/hero.png
|
||||
tagline:
|
||||
actionText: User guide →
|
||||
actionLink: /users/
|
||||
#features:
|
||||
#- title: Feature 1 Title
|
||||
# details: Feature 1 Description
|
||||
#- title: Feature 2 Title
|
||||
# details: Feature 2 Description
|
||||
#- title: Feature 3 Title
|
||||
# details: Feature 3 Description
|
||||
#footer: Made by with ❤️
|
||||
---
|
||||
heroImage: ./logo_f.svg
|
||||
tagline:
|
||||
actions:
|
||||
- text: User guide →
|
||||
link: /users/
|
||||
- text: Developer guide →
|
||||
link: /developers/
|
||||
type: secondary
|
||||
---
|
|
@ -5,18 +5,21 @@ FacilMap is a privacy-friendly, open-source versatile online map that combines d
|
|||
* ([Overview over the UI elements](./ui/))
|
||||
* [Show different map styles](./layers/), for example maps optimized for driving, cycling, hiking or showing the topography or public transportation networks.
|
||||
* [Search for places](./search/)
|
||||
* [Show amenities and POIs](./pois/)
|
||||
* [Calculate a route](./route/), optionally showing the elevation profile.
|
||||
* [Find out what is at a particular point on the map](./click-marker/)
|
||||
* [Open geographic files](./files/), for example GPX, KML or GeoJSON files
|
||||
* [Show your location on the map](./locate/)
|
||||
* [Share a link](./hash/) to a particular view of the map.
|
||||
* [Share a link](./share/) to a particular view of the map.
|
||||
* Add FacilMap as an [app](./app/) to your device.
|
||||
* Change the language settings in the [user preferences](./user-preferences/).
|
||||
* FacilMap is [privacy-friendly](./privacy/) and does not track you.
|
||||
|
||||
In addition, FacilMap allows you to create collaborative maps, where you and others can add markers, draw lines, save routes and import GPX/KML/GeoJSON files, which will be saved under a custom URL.
|
||||
* [Creating collaborative maps](./collaborative/)
|
||||
* [Add markers](./markers/)
|
||||
* [Add lines/routes](./lines/)
|
||||
* [Handle multiple objects](./multiple/), for example to delete multiple markers/lines at once.
|
||||
* By default, markers/lines/routes have a name and a description. You can [define additional fields](./types/) that contain more detailed information and can automatically influence the style of the marker/line/route.
|
||||
* [Add a legend](./legend/) that shows which colour/style belongs to which type of marker/line.
|
||||
* [Saved views](./views/) reference a particular area on the map with certain settings. You can change the default view of a collaborative map or save additional views as a shortcut for users to navigate to a certain area on the map.
|
||||
|
@ -26,4 +29,4 @@ In addition, FacilMap allows you to create collaborative maps, where you and oth
|
|||
* [Import](./import/) GPX, KML or GeoJSON files. Importing a GeoJSON file that was exported by FacilMap makes it possible to copy parts of one map onto another.
|
||||
* Adjust the [map settings](./map-settings/). This is also where your can [delete the map](./map-settings/#delete-the-map).
|
||||
|
||||
If you are interested in embedding FacilMap into your website or even running your own FacilMap server, check out the [Administrator guide](../administrators/).
|
||||
If you are interested in embedding FacilMap into your website or even running your own FacilMap server, check out the [Developer guide](../developers/).
|
|
@ -1,3 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import markerMp4 from "@source/users/click-marker/marker.mp4";
|
||||
import markerMobileMp4 from "@source/users/click-marker/marker-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# Map point information
|
||||
|
||||
By pressing a specific point on the map and holding the mouse/finger for a second, you can find out more information about that point. A temporary marker appears on the map and a [search box](../ui/#search-box) tab with information about the place is opened.
|
||||
|
@ -8,11 +13,11 @@ The marker and search box tab will disappear again when you click somewhere else
|
|||
|
||||
At the bottom of the search box tab, you will find a “Use as” button to [use the map point as a route destination](../route/#use-map-points-as-destinations). If a collaborative map is open, you can use the “Add to map” button to add the point as a marker to the map (which will automatically prefill some fields).
|
||||
|
||||
<Screencast :desktop="require('./marker.mp4')" :mobile="require('./marker-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="markerMp4" :mobile="markerMobileMp4"></Screencast>
|
||||
|
||||
## Share a link
|
||||
|
||||
FacilMap continuously updates the location hash (the part after the `#` in the address bar of your browser) to represent what you currently view on the map. More information can be found under [Share a link](../hash/).
|
||||
FacilMap continuously updates the location hash (the part after the `#` in the address bar of your browser) to represent what you currently view on the map. More information can be found under [Share a link](../share/).
|
||||
|
||||
When you long-click the map to open the map point information, this is also persisted in the location hash. This means that you can use the URL straight from the browser address bar to share a link to a specific map point.
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ While using a collaborative map, users can still use all of the non-collaborativ
|
|||
|
||||
When changing something on the map, there is no need to “Save” the map. Changes are applied immediately.
|
||||
|
||||
## Start a collaborative map
|
||||
## Create a map
|
||||
|
||||
To start a collaborative map, click the “Start collaborative map” button in the [toolbox](../ui/#toolbox).
|
||||
To create a collaborative map, click “Collaborative maps” and then “Create a new map” in the [toolbox](../ui/#toolbox).
|
||||
|
||||
![](./save.png)
|
||||
|
||||
|
@ -33,9 +33,23 @@ When creating a collaborative map, some random characters are proposed for the d
|
|||
| Change [map settings](../map-settings/) | ✘ | ✘ | ✔ |
|
||||
| [Delete map](../map-settings/#delete-the-map) | ✘ | ✘ | ✔ |
|
||||
|
||||
## Exit a collaborative map
|
||||
## Open an existing map
|
||||
|
||||
If you want to go back to the basic FacilMap, click on “Tools” in the [toolbox](../ui/#toolbox) and then “Exit collaborative map”.
|
||||
If someone has shared a link to a collaborative map with you, you can simply open that link in your browser to open the map. Alternatively, click “Collaborative maps” and then “Open an existing map” (or, if you are already viewing another map, “Open another map”) in the [toolbox](../ui/#toolbox). A dialog will open where you can paste the link into the text field on top and click “Open” to open the map.
|
||||
|
||||
In the bottom section of the dialog you can search for existing collaborative maps that people have [made public](../map-settings/#search-engines). Simply type in a search term and click the search icon to search for maps. You can use `*` as a wildcard and `?` as a single-character wildcard.
|
||||
|
||||
## Close a map
|
||||
|
||||
If you want to close a collaborative map and go back to the basic FacilMap, click “Collaborative maps” and then “Close …” (where “…” is the name of the open map) in the [toolbox](../ui/#toolbox).
|
||||
|
||||
## Bookmark a map
|
||||
|
||||
While you can simply add a bookmark in your browser to remember the link to a specific map, FacilMap also brings its own bookmark function.
|
||||
|
||||
To bookmark a particular map, open that map and then click “Collaborative maps” and then “Bookmark …” (where “…” is the name of the open map) in the [toolbox](../ui/#toolbox). This will add the map as a new item to the “Collaborative maps” menu. Bookmarks are stored in your browser, so they are not visible to anyone else. When bookmarking a map for the first time, your browser might ask you to allow FacilMap to store data in the persistent storage. Allowing this will make sure that your bookmarks are not removed when your system runs low in disk space or when you clear your browser history.
|
||||
|
||||
To reorder, rename or remove bookmarks, click “Manage bookmarks” (only visible if you have any bookmarks).
|
||||
|
||||
## Delete a collaborative map
|
||||
|
||||
|
|
|
@ -8,17 +8,26 @@ You are welcome to contribute code, improve the documentation or raise ideas on
|
|||
|
||||
Financial contributions are very much appreciated and can be sent through the following channels:
|
||||
|
||||
<div style="display: flex; flex-wrap: wrap">
|
||||
<a href="https://www.paypal.com/donate?hosted_button_id=FWR59UXY6HGGS" target="_blank" style="display: flex; flex-direction: column; align-items: center">
|
||||
<ClientOnly><qrcode value="https://www.paypal.com/donate?hosted_button_id=FWR59UXY6HGGS" :width="164"></qrcode></ClientOnly>
|
||||
PayPal
|
||||
</a>
|
||||
<a href="https://www.patreon.com/facilmap" target="_blank" style="display: flex; flex-direction: column; align-items: center">
|
||||
<ClientOnly><qrcode value="https://www.patreon.com/facilmap" :width="164"></qrcode></ClientOnly>
|
||||
Patreon
|
||||
</a>
|
||||
<a href="bitcoin:1PEfenaGXC9qNGQSuL5o6f6doZMYXRFiCv" style="display: flex; flex-direction: column; align-items: center">
|
||||
<ClientOnly><qrcode value="bitcoin:1PEfenaGXC9qNGQSuL5o6f6doZMYXRFiCv" :width="164"></qrcode></ClientOnly>
|
||||
Bitcoin
|
||||
</a>
|
||||
</div>
|
||||
<template v-for="{ width, gap, links } in [{
|
||||
width: 740,
|
||||
gap: 20,
|
||||
links: {
|
||||
'GitHub': 'https://github.com/sponsors/FacilMap',
|
||||
'Liberapay': 'https://liberapay.com/facilmap/',
|
||||
'Ko-fi': 'https://ko-fi.com/facilmap',
|
||||
'PayPal': 'https://www.paypal.com/donate?hosted_button_id=FWR59UXY6HGGS',
|
||||
'Patreon': 'https://www.patreon.com/facilmap',
|
||||
'Bitcoin': 'bitcoin:1PEfenaGXC9qNGQSuL5o6f6doZMYXRFiCv'
|
||||
}
|
||||
}]">
|
||||
<template v-for="size in [(width - gap * (Object.keys(links).length - 1)) / Object.keys(links).length]">
|
||||
<div style="display: flex; flex-wrap: wrap" :style="{ gap: `${gap}px` }">
|
||||
<template v-for="(url, label) in links">
|
||||
<a :href="url" target="_blank" style="display: flex; flex-direction: column; align-items: center">
|
||||
<qrcode :value="url" :size="size" level="M" render-as="svg" style="margin-bottom: 5px"></qrcode>
|
||||
{{label}}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
|
@ -2,26 +2,39 @@
|
|||
|
||||
A map can be exported as a file, in order to use it in another application or to create a backup.
|
||||
|
||||
To export a map, in the [toolbox](../ui/#toolbox), click “Tools” and then one of the “Export” options. Note that when a [filter](../filter/) is active, only the objects that match the filter are exported.
|
||||
To export a map, in the [toolbox](../ui/#toolbox), click “Tools” and then “Export”. This opens a dialog where you can configure in what format to export the map.
|
||||
|
||||
The exports are available under their own URL. In the context menu (right click) of the export links, you can copy the URL to use it elsewhere.
|
||||
## Formats
|
||||
|
||||
## GeoJSON
|
||||
### GPX
|
||||
|
||||
When exporting a map as GeoJSON, all markers and lines (or, if a [filter](../filter/) is active, those that match the filter) including their data fields, all [saved views](../views/), and all [types](../types/) that represent the exported markers/lines are exported. This means that if no filter is active, this is suitable to make a backup of the whole map (apart from the [map settings](../map-settings/) and the [edit history](../history/)). Such a file can also be [imported](../import/) again into a map to restore the backup.
|
||||
GPX is a format that is understood by many geographic apps and navigation devices. Exporting a map as GPX will export all markers and lines on the map.
|
||||
|
||||
## GPX
|
||||
When exporting to GPX, one of three “Route type” options needs to be selected:
|
||||
* **Track points:** Lines that are calculated routes will be exported as GPX tracks. This means that the whole course of the route is saved in the file.
|
||||
* **Track points, one file per line (ZIP file):** Like “Track points”, but rather than creating one GPX file containing all markers/lines of the map, a ZIP file will be generated that contains one GPX file with all markers and a folder with one GPX file per line. This is useful for importing the file into OsmAnd, which only supports one track per file.
|
||||
* **Route points:** Lines that are calculated routes will be exported as GPX routes. This means that only the route destinations are saved in the file, and the app or device that opens the file is responsible for calculating the best route.
|
||||
|
||||
GPX is a format that is understood by many geographic apps and navigation devices. Exporting a map as GPX will export all markers and lines on the map (or, if a [filter](../filter/) is active, those that match the filter). There are two options:
|
||||
* **Export as GPX (tracks):** Lines that are calculated routes will be exported as GPX tracks. This means that the whole course of the route is saved in the file.
|
||||
* **Export as GPX (routes):** Lines that are calculated routes will be exported as GPX routes. This means that only the route destinations are saved in the file, and the app or device that opens the file is responsible for calculating the best route.
|
||||
The marker/line description and any [custom fields](../types/) will be saved in the description of the GPX objects. The marker/line styles are not exported, with the exception of some basic style settings supported by OsmAnd.
|
||||
|
||||
The marker/line description and any [custom fields](../types/) will be saved in the description of the GPX objects.
|
||||
### GeoJSON
|
||||
|
||||
## Table
|
||||
When exporting a map as GeoJSON, all markers and lines including their data fields, all [saved views](../views/), and all [types](../types/) that represent the exported markers/lines are exported. This means that if no filter is active, this is suitable to make a backup of the whole map (apart from the [map settings](../map-settings/) and the [edit history](../history/)). Such a file can also be [imported](../import/) again into a map to restore the backup.
|
||||
|
||||
The table export is a static HTML export of all the markers and lines (or, if a [filter](../filter/) is active, those that match the filter) in table form. All the field values of the markers/lines are shown, along with some metadata (name, coordinates, distance, time).
|
||||
### HTML
|
||||
|
||||
A separate table for each [type](../types/) is shown. Individual types can be hidden by clicking the down arrow next to their heading.
|
||||
The HTML export will render a web page that contains a table for each [type](../types/) that lists each marker/line of that type along with all field values and some metadata (name, coordinates, distance, time). Types can be shown/hidden by clicking on the down arrow next to their heading, and the table can be sorted by an individual data attribute by clicking on its column header.
|
||||
|
||||
Table columns can be sorted by clicking on their header.
|
||||
For HTML exports, there additional “Copy to clipboard” export method is available. This will copy the table for a single type into the clipboard. Such a table can be pasted directly into a spreadsheet application such as EtherCalc or LibreOffice Calc.
|
||||
|
||||
### CSV
|
||||
|
||||
CSV files can be opened by most spreadsheet applications. A CSV export will contain all the markers/lines of a single type, along with their field values and some metadata (name, coordinates, distance, time). Note that CSV only supports plain text, so any rich text formatting will be lost.
|
||||
|
||||
## Generate a link
|
||||
|
||||
By default, the export dialog will create a file and download or open it. By selecting “Generate link” as the export method, you can copy a URL to the exported file instead. This URL will always generate the file with the lastest map data according to the export settings that you have defined, so you can use it to link to the export from a website or a browser bookmark or to integrate the export with another tool that should periodically create copies of your map data.
|
||||
|
||||
## Apply a filter
|
||||
|
||||
If you have an active [filter](../filter/), the export dialog will show an additional “Apply filter” option. When this option is enabled, the exported file will only contain the map objects that match the filter.
|
|
@ -1,3 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import detailsMp4 from "@source/users/files/details.mp4";
|
||||
import detailsMobileMp4 from "@source/users/files/details-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# Open geographic files
|
||||
|
||||
With FacilMap you can open geographic files (such as GPX or KML) exported by other apps/devices or by FacilMap itself.
|
||||
|
@ -30,7 +35,7 @@ The search box tab shows a list of all geographic objects in the opened file. Cl
|
|||
|
||||
By clicking the arrow on the right side of an object in the list, you can show some details about this object. You also have the option there to [use it as a route destination](../route/#use-map-points-as-destinations) (only for markers) or to add them to the map (only for [collaborative maps](../collaborative/)). To get back from the details, click on the blue arrow on the left of the heading.
|
||||
|
||||
<Screencast :desktop="require('./details.mp4')" :mobile="require('./details-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="detailsMp4" :mobile="detailsMobileMp4"></Screencast>
|
||||
|
||||
## Open an OpenStreetMap object
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Filters
|
||||
|
||||
Filters provide a way to temporarily show/hide certain markers/lines based on user-defined criteria. Filters are only affect what you currently see on the map, they are not persisted and do not affect what other people see on the map. Filters can be persisted as part of [saved views](../views/).
|
||||
Filters provide a way to temporarily show/hide certain markers/lines based on user-defined criteria. Filters only affect what you currently see on the map, they are not persisted and do not affect what other people see on the map. Filters can be persisted as part of [saved views](../views/).
|
||||
|
||||
If you simply want to show/hide objects of a certain type, clicking items in the [legend](../legend/) will toggle the visibility of those types by automatically applying a filter expression.
|
||||
|
||||
|
@ -16,6 +16,8 @@ Field values are available as `data`. By default, markers and objects have only
|
|||
|
||||
Be aware that in filter expression, comparison is case sensitive. So the above expression would not match an object whose status is “done”. For case-insensitive comparison, compare the lower-case values: `lower(data.Status) == "done"`.
|
||||
|
||||
Checkbox field values are internally represented by the values `0` (unchecked) and `1` (checked). For example, to show only values where the checkbox field “Confirmed” is checked, use `data.Confirmed == 1`.
|
||||
|
||||
The regular expression operator `~=` allows for more advanced text matching. For example, to show all objects whose name contains “Untitled”, use `lower(name) ~= "untitled"`. Regular expressions allow to define very complex criteria what the text should look like. There are plenty of tutorials online how to use regular expressions, for example [RegexOne](https://regexone.com/).
|
||||
|
||||
### Filter by type
|
||||
|
@ -24,6 +26,6 @@ To show only objects of a particular [type](../types/), use `typeId`. You can fi
|
|||
|
||||
## Share a link
|
||||
|
||||
FacilMap continuously updates the [location hash](../hash/) with your current view on the map. Your current filter expression is also saved in the location hash.
|
||||
FacilMap continuously updates the [location hash](../share/) with your current view on the map. Your current filter expression is also saved in the location hash.
|
||||
|
||||
If you want to share a link to a particular section of the map with a filter expression applied, simply apply the filter and then copy the URL from the address bar of your browser. An example of such a link would be `https://facilmap.org/my-map#6/51.910/8.789/Mpnk//typeId%3D%3D123`.
|
|
@ -1,42 +0,0 @@
|
|||
# Share a link
|
||||
|
||||
When you open `https://facilmap.org/`, the URL in the address bar of your browser will change to something like `https://facilmap.org/#8/52.462/13.491/MSfR`. The part starting with the `#` is called the *location hash* and is continuously updated by FacilMap to represent your current view of the map.
|
||||
|
||||
This means that if you want to share a link to a specific view of the map, you can simply copy the URL from the address bar of your browser.
|
||||
|
||||
The following information is stored in the location hash:
|
||||
* The coordinates of the centre of the part of the map that you are currently viewing
|
||||
* The current zoom level
|
||||
* The current [map style](../layers/) (base layer and overlays)
|
||||
* One of the following (depending which is active):
|
||||
* The selected [search](../search/) result, or the search term if none is selected
|
||||
* The [route](../route/)
|
||||
* The [map point](../click-marker/)
|
||||
* The selected [marker](../markers/) or [line](../lines/) (on [collaborative maps](../collaborative/))
|
||||
* The active [view](../views/) (on [collaborative maps](../collaborative/))
|
||||
* The active [filter](../filter/) (on [collaborative maps](../collaborative/))
|
||||
|
||||
## Technical details
|
||||
|
||||
The location can have one of the following formats:
|
||||
* Short format: `#q=<search term>`
|
||||
* Long format: `#<zoom>/<latitude>/<longitude>/<layers>/<search term>/<filter>`. If the filter and/or search term is empty, the trailing slashes are omitted.
|
||||
|
||||
The short format is used if the current map view equals the default view for the search. This means that the map is currently zoomed to the exact location where it would zoom to when opening the result of the search term, and no additional layers or filters are active.
|
||||
|
||||
The different components of the long format have the following meaning:
|
||||
* **zoom:** The zoom level of the map, minimum `0`, maximum `18` (might be higher or lower depending on the layers)
|
||||
* **latitude:** The latitude of the center of the map, minimum `-90`, maximum `90`
|
||||
* **longitude:** The longitude of the center of the map
|
||||
* **layers:** Each layer is identified by a four-character key. If overlays are enabled, they are appended with a `-`. For example `Mpnk` for only Mapnik or `Mpnk-Rlie-grid` for Mapnik, Relief and Graticule.
|
||||
* **search term:** Can be one of the following:
|
||||
* The name of a place. Will show all results for this search term.
|
||||
* The ID of a specific search result in the form `n123`, `w123`, `r123` (representing its corresponding OpenStreetMap node, way or relation ID)
|
||||
* A [route query](../route/#use-a-route-query), for example `Berlin to Hamburg`
|
||||
* [Coordinates](../search/#search-for-coordinates), for example `52.51704,13.38886` or `geo:52.51704,13.38886?z=11`
|
||||
* The URL of a [geographic file](../files/)
|
||||
* The ID of a [marker](../markers/) or [line](../lines/) in the form `m123` or `l123` (on [collaborative maps](../collaborative/))
|
||||
* The ID of a [view](../views/) in the form `v123` (on [collaborative maps](../collaborative/)). This only works in the short format of the location hash.
|
||||
* The ID of an OpenStreetMap object in the form `node 123`, `way 123`, `relation 123` or `trace 123`.
|
||||
* Anything else that can be typed into the [search form](../search/).
|
||||
* **filter:** The currently applied [filter](../filter/) (on [collaborative maps](../collaborative/))
|
|
@ -1,5 +1,7 @@
|
|||
# Getting help
|
||||
|
||||
If you have a question that is not answered by this documentation, feel free to to open a [discussion on GitHub](https://github.com/FacilMap/facilmap/discussions).
|
||||
If you have a question that is not answered by this documentation, feel free to to open a [discussion on GitHub](https://github.com/FacilMap/facilmap/discussions) or join the [Matrix chat room](https://matrix.to/#/#facilmap:rankenste.in).
|
||||
|
||||
If you have found an error or have a suggestion how FacilMap or this documentation could be improved, please raise an [issue on GitHub](https://github.com/FacilMap/facilmap/issues).
|
||||
If you have found an error or have a suggestion how FacilMap or this documentation could be improved, please raise an [issue on GitHub](https://github.com/FacilMap/facilmap/issues).
|
||||
|
||||
If you have found a translation mistake or would like to add a missing translation, you can contribute directly on [Weblate](https://hosted.weblate.org/projects/facilmap/).
|
|
@ -1,3 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import customImportMp4 from "@source/users/import/custom-import.mp4";
|
||||
import customImportMobileMp4 from "@source/users/import/custom-import-mobile.mp4";
|
||||
import typesMp4 from "@source/users/import/types.mp4";
|
||||
import typesMobileMp4 from "@source/users/import/types-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# Import geographic files
|
||||
|
||||
FacilMap allows you to [open geographic files](../files/) such as GeoJSON, GPX and KML. When opening a file on a collaborative map, some or all of the objects that are part of the file can be imported to be persisted on the map.
|
||||
|
@ -18,11 +25,11 @@ When [opening a file](../files/) created by FacilMap, FacilMap will offer some a
|
|||
|
||||
The saved views in the file will be shown at the top of the file objects and the types at the bottom. You can import them into the map by simply clicking the + on the right. The + will only be shown if an identical view/type does not exist yet on the map. Note that importing views/types will always add new ones rather than updating existing ones, so you might end up with two types called “Marker” and two types called “Line”, which you will have to resolve by hand. If you are importing into an empty map, you might want to [delete the existing types](../types/#delete-a-type) before the import to avoid confusion.
|
||||
|
||||
<Screencast :desktop="require('./types.mp4')" :mobile="require('./types-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="typesMp4" :mobile="typesMobileMp4"></Screencast>
|
||||
|
||||
To import all the markers/lines, click “Select all” at the bottom and then “Add selected items to map” and “Custom type mapping…”. This will open a dialog where you can define in detail which objects should be imported as what. If you have already import the types, you can choose the existing types, otherwise an option to import them is offered.
|
||||
|
||||
<Screencast :desktop="require('./custom-import.mp4')" :mobile="require('./custom-import-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="customImportMp4" :mobile="customImportMobileMp4"></Screencast>
|
||||
|
||||
The recommended workflow to import an exported map onto a new empty map is:
|
||||
1. Delete the default “Marker” and “Line” types on the map
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import layersMp4 from "@source/users/layers/layers.mp4";
|
||||
import layersMobileMp4 from "@source/users/layers/layers-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# Map styles (layers)
|
||||
|
||||
FacilMap embeds the map styles of different OpenStreetMap-based services. Different styles present geographic features in a different way and are useful for different purposes.
|
||||
|
@ -7,7 +12,7 @@ You can switch the style of the map by clicking on the “Map style” item in t
|
|||
2. The second section are so-called “overlays”. These are shown on top of the base layer to show additional information on the map. Multiple overlays can be shown at the same time.
|
||||
3. The third section contains some links to open the current map area on another map service (such as Google Maps). Clicking these links will open a new browser tab.
|
||||
|
||||
<Screencast :desktop="require('./layers.mp4')" :mobile="require('./layers-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="layersMp4" :mobile="layersMobileMp4"></Screencast>
|
||||
|
||||
## Base layers
|
||||
|
||||
|
@ -15,10 +20,12 @@ Base layers set the main style of the map. Only one base layer can be shown at t
|
|||
|
||||
| Base layer | Source | Purpose |
|
||||
|------------|--------|---------|
|
||||
| Lima Labs | [Lima Labs](https://maps.lima-labs.com/) | Good for a general geographic overview. Has a focus on car infrastructure and political borders. Supports high resolution displays. |
|
||||
| Mapnik | [OpenStreetMap](https://www.openstreetmap.org/) | Good for a general geographic overview. Has a focus on car infrastructure and political borders. |
|
||||
| TopPlus | [German state](https://www.bkg.bund.de/SharedDocs/Produktinformationen/BKG/DE/P-2017/170922-TopPlus-Web-Open.html) | Good for a general geographic overview, with more details than Mapnik. Has a focus on car infrastructure, political borders and topography. Unfortunately all labels are in German. |
|
||||
| TopPlus | [German state](https://gdz.bkg.bund.de/index.php/default/wms-topplusopen-wms-topplus-open.html) | Good for a general geographic overview, with more details than Mapnik. Has a focus on car infrastructure, political borders and topography. Unfortunately all labels are in German. |
|
||||
| Map1.eu | [Map1.eu](https://www.map1.eu/) | Has a focus on cities/towns, car infrastructure and political borders. Aims to have the same level of detail as a paper map. Only available for Europe. |
|
||||
| OpenTopoMap | [OpenTopoMap](https://opentopomap.org/) | Strong focus on topography, but also shows car and train infrastructure. Shows contour lines at higher zoom levels. |
|
||||
| CyclOSM | [CyclOSM](https://cyclomap.org/) | Strong focus on bicycle infrastructure and sign-posted bicycle routes. |
|
||||
| OpenCycleMap | [OpenCycleMap](https://www.opencyclemap.org/) | Strong focus on sign-posted bicycle routes. At higher zoom levels, details about cycling infrastructure are shown. |
|
||||
| Hike & Bike Map | [Hike & Bike Map](https://hikebikemap.org/) | Emphasizes hiking trails and off-road tracks. |
|
||||
| Mapnik Water | [FreieTonne](https://www.freietonne.de/) | Similar to Mapnik, but emphasizes lakes, rivers and the sea. |
|
||||
|
@ -40,4 +47,4 @@ Overlays are additional details that are shown on top of a base layer. Multiple
|
|||
| Graticule | FacilMap/[Leaflet.AutoGraticule](https://github.com/FacilMap/Leaflet.AutoGraticule) | Adds a grid of longitude and latitude coordinates to the map. |
|
||||
| Sea marks | [FreieTonne](https://www.freietonne.de/) | Shows details about light houses, weirs, buoys and many other features relevant for nautical navigation. |
|
||||
|
||||
If you know of a free service that provides an overlay and think it would be a useful addition to FacilMap, please raise an [issue on GitHub](https://github.com/FacilMap/facilmap/issues).
|
||||
If you know of a free service that provides an overlay and think it would be a useful addition to FacilMap, please raise an [issue on GitHub](https://github.com/FacilMap/facilmap/issues).
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import checkboxMp4 from "@source/users/legend/checkbox.mp4";
|
||||
import checkboxMobileMp4 from "@source/users/legend/checkbox-mobile.mp4";
|
||||
import fieldsMp4 from "@source/users/legend/fields.mp4";
|
||||
import fieldsMobileMp4 from "@source/users/legend/fields-mobile.mp4";
|
||||
import filterMp4 from "@source/users/legend/filter.mp4";
|
||||
import filterMobileMp4 from "@source/users/legend/filter-mobile.mp4";
|
||||
import typeMp4 from "@source/users/legend/type.mp4";
|
||||
import typeMobileMp4 from "@source/users/legend/type-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# Legend
|
||||
|
||||
A legend describes the meaning of the different styles of items. FacilMap shows the legend as a box in the bottom right of the screen on big screens and as a [search box](../ui/#search-box) tab on small screens. The legend is shown if [custom legend text](#show-custom-text) is defined or if at least one type is configured to be [shown in the legend](#show-custom-types).
|
||||
|
@ -16,15 +27,15 @@ When editing a [type](../types/), checking “Show in legend” will add an item
|
|||
|
||||
If the type has any style properties that are set to “Fixed”, the legend item will represent this style. Otherwise, a generic style is chosen. For example, if a marker type is configured to have its colour fixed as yellow, its shape fixed as a circle and its icon fixed as a heart, the legend item will show a yellow circle marker with a heart. But if a marker has none of its style fixed, the legend item will show a rainbox drop marker without an icon.
|
||||
|
||||
<Screencast :desktop="require('./type.mp4')" :mobile="require('./type-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="typeMp4" :mobile="typeMobileMp4"></Screencast>
|
||||
|
||||
If a type has dropdown or checkbox fields that [control the style](../types/#styles-based-on-field-values), additional legend items are shown for each dropdown option / checkbox state that indicate its style.
|
||||
|
||||
<Screencast :desktop="require('./fields.mp4')" :mobile="require('./fields-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="fieldsMp4" :mobile="fieldsMobileMp4"></Screencast>
|
||||
|
||||
For a checkbox, two items are shown that represent the checked and unchecked state and by default have the label “~~Field name~~” (strikethrough) and “Field name”. You can change the labels by editing the checkbox field.
|
||||
|
||||
<Screencast :desktop="require('./checkbox.mp4')" :mobile="require('./checkbox-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="checkboxMp4" :mobile="checkboxMobileMp4"></Screencast>
|
||||
|
||||
## Using the legend
|
||||
|
||||
|
@ -32,4 +43,4 @@ Sometimes icons on legend items can be small to see. By hovering over a legend i
|
|||
|
||||
You can click individual items or headings in the legend to hide/show items of this type on the map. Internally, this sets a [filter](../filter/), so it is not persisted on the map and does not affect what other users are seeing, it only temporarily adjusts what you see on the map (unless you persist it as part of a [saved view](../views/)).
|
||||
|
||||
<Screencast :desktop="require('./filter.mp4')" :mobile="require('./filter-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="filterMp4" :mobile="filterMobileMp4"></Screencast>
|
|
@ -1,6 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
import addResultMp4 from "@source/users/lines/add-result.mp4";
|
||||
import addResultMobileMp4 from "@source/users/lines/add-result-mobile.mp4";
|
||||
import addResultsMp4 from "@source/users/lines/add-results.mp4";
|
||||
import addResultsMobileMp4 from "@source/users/lines/add-results-mobile.mp4";
|
||||
import addRouteMp4 from "@source/users/lines/add-route.mp4";
|
||||
import addRouteMobileMp4 from "@source/users/lines/add-route-mobile.mp4";
|
||||
import dragMp4 from "@source/users/lines/drag.mp4";
|
||||
import dragMobileMp4 from "@source/users/lines/drag-mobile.mp4";
|
||||
import drawMp4 from "@source/users/lines/draw.mp4";
|
||||
import drawMobileMp4 from "@source/users/lines/draw-mobile.mp4";
|
||||
import editDetailsMp4 from "@source/users/lines/edit-details.mp4";
|
||||
import editDetailsMobileMp4 from "@source/users/lines/edit-details-mobile.mp4";
|
||||
import removeMp4 from "@source/users/lines/remove.mp4";
|
||||
import removeMobileMp4 from "@source/users/lines/remove-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# Lines
|
||||
|
||||
A line is a connection of two or more points on the map and has a name, a certain style (width, colour) and some data (such as a description). It can be a straight line or a calculated route, depending on its route mode. When you add a line to a map, it is permanently saved there and visible for anyone who is viewing the map.
|
||||
A line is a connection of two or more points on the map and has a name, a certain style (width, colour, stroke) and some data (such as a description). It can be a straight line or a calculated route, depending on its route mode. When you add a line to a map, it is permanently saved there and visible for anyone who is viewing the map.
|
||||
|
||||
By default, a collaborative map has one type of line called “Line” that has a description field and whose style can be configured freely. Other types of lines with fixed styles and additional fields can be defined using [custom types](../types/). For simplicity, the descriptions on this page are assuming that you are working with the default “Line” type.
|
||||
|
||||
|
@ -10,7 +27,7 @@ One way to add a line to a map is to draw it on the map. In the [toolbox](../ui/
|
|||
|
||||
When you draw lines, they will always connect their line points using straight lines. You can, however, choose a route mode later in the [line details](#edit-line-details). This means that the line points that you added will become route destinations.
|
||||
|
||||
<Screencast :desktop="require('./draw.mp4')" :mobile="require('./draw-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="drawMp4" :mobile="drawMobileMp4"></Screencast>
|
||||
|
||||
After saving the line, it will be called “Untitled line”, will have the default style and no description. As a next step, you might want to [edit the line details](#edit-line-details) to give it a name and change its style.
|
||||
|
||||
|
@ -20,7 +37,7 @@ Another way to add a line to the map is to calculate a [route](../route/) first
|
|||
|
||||
On the map, the route will still look the same, but it is now saved as a line, as you can see from the fact that a new search box tab appeared for the line details, and the line is not draggable anymore.
|
||||
|
||||
<Screencast :desktop="require('./add-route.mp4')" :mobile="require('./add-route-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="addRouteMp4" :mobile="addRouteMobileMp4"></Screencast>
|
||||
|
||||
## Add a search result as a line
|
||||
|
||||
|
@ -28,11 +45,11 @@ Some [search results](../search/) are line or polygons (for example when the res
|
|||
|
||||
In the search box tab of the search result, click on “Add to map” and then “Line”. The line will be created with the name of the result. If the line is a [custom type](../types/), a mapping from the result info to the line data is attempted, for example if the line has a field “Address”, it will be filled with the address of the result.
|
||||
|
||||
<Screencast :desktop="require('./add-result.mp4')" :mobile="require('./add-result-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="addResultMp4" :mobile="addResultMobileMp4"></Screencast>
|
||||
|
||||
You can also add multiple results at once. In the search form, select multiple items by using the “Select all” button or clicking individual items while holding the Ctrl or Shift key. Then click “Add selected items to map” and then “Line/polygon items as Line”. (Those selected results that are markers and not lines/polygons will not be added.)
|
||||
You can also add multiple results at once. In the search form, select multiple items by using the “Select all” button or clicking individual items while holding the Ctrl key. Then click “Add selected items to map” and then “Line/polygon items as Line”. (Those selected results that are markers and not lines/polygons will not be added.)
|
||||
|
||||
<Screencast :desktop="require('./add-results.mp4')" :mobile="require('./add-results-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="addResultsMp4" :mobile="addResultsMobileMp4"></Screencast>
|
||||
|
||||
## Show line details
|
||||
|
||||
|
@ -45,20 +62,23 @@ To edit the details of a line, select the line and then click “Edit data” in
|
|||
* **Routing mode:** The route mode of the line. By default, “straight line” is selected, but you can select something else to make the line points into route destinations. More information about route modes can be found under [routes](../route/#route-modes).
|
||||
* **Colour:** The colour of the line.
|
||||
* **Width:** The width of the line (in pixels). Use the + and − buttons to change the value.
|
||||
* **Stroke:** The stroke style of the line (solid, dashed or dotted).
|
||||
* **Description:** The description of the line. Will be shown in the line details. You can format the text using [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
|
||||
|
||||
Click “Save” to save your changes.
|
||||
|
||||
<Screencast :desktop="require('./edit-details.mp4')" :mobile="require('./edit-details-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="editDetailsMp4" :mobile="editDetailsMobileMp4"></Screencast>
|
||||
|
||||
## Edit line points
|
||||
|
||||
To change the course or the position of a line, select the line and then click “Edit waypoints” in its [search box](../ui/#search-box) tab. A message will appear on the top right of the screen and the tab will turn into a search form and the line will become draggable. You can now change the line in the same way that you would [change a route](../route/#drag-a-route), by changing the route destinations in the form or by dragging the line. When you are finished, click the “Finish” button in the message.
|
||||
|
||||
<Screencast :desktop="require('./drag.mp4')" :mobile="require('./drag-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="dragMp4" :mobile="dragMobileMp4"></Screencast>
|
||||
|
||||
## Remove a line
|
||||
|
||||
To remove a line from the map, select the line, click “Remove” and confirm the alert box. Note that removed lines will remain in the [edit history](../history/) of the map and can be seen and restored there by admins.
|
||||
|
||||
<Screencast :desktop="require('./remove.mp4')" :mobile="require('./remove-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="removeMp4" :mobile="removeMobileMp4"></Screencast>
|
||||
|
||||
You can also remove multiple lines at once by [handling multiple objects](../multiple/).
|
|
@ -1,3 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import locateMp4 from "@source/users/locate/locate.mp4";
|
||||
import locateMobileMp4 from "@source/users/locate/locate-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# Show your location
|
||||
|
||||
By clicking the crosshair icon on the top left of the screen (under the zoom buttons), FacilMap will attempt to zoom to your location on the map.
|
||||
|
@ -9,8 +14,10 @@ The cross-hair icon will be shown in one of these colours:
|
|||
* **Orange:** Your location is shown on the map and the map will follow you as your location changes.
|
||||
* **Blue:** Your location is shown on the map, but the map does not follow you.
|
||||
|
||||
Clicking the cross-hair icon will enable the orange mode. As soon as you move the map by hand, it will switch to blue mode. To go back to orange mode, first go back to black mode by clicking the icon once and then go to orange mode by clicking it again.
|
||||
Clicking the cross-hair icon will enable the orange mode. As soon as you move the map by hand, it will switch to blue mode. To go back to orange mode, click the icon again.
|
||||
|
||||
While your location is visible on the map, FacilMap will try to prevent your screen from turning off, which would be disruptive during live navigation. It will depend on your browser and on your operating system whether this works and whether your browser will ask you for permission for this first.
|
||||
|
||||
Your location will be indicated as a blue circle with a blue dot at the center. Your location may be anywhere within the circle, it is not necessarily at its centre. If the circle is very small, it means that your browser could determine your location very accurately. If the circle is very big, it means that your browser had trouble to make an accurate guess about your location, and you may be anywhere within the circle.
|
||||
|
||||
<Screencast :desktop="require('./locate.mp4')" :mobile="require('./locate-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="locateMp4" :mobile="locateMobileMp4"></Screencast>
|
|
@ -18,6 +18,8 @@ The map name is shown as the title of your browser tab and window. It will also
|
|||
|
||||
If you check the “Accessible for search engines” checkbox, search engines like Google or Duckduckgo will be allowed to list the read-only version of the map. Note that FacilMap itself does not report these maps to the search engines, but when they find them through a link on a public website, it is this setting that allows them to add the map to their results.
|
||||
|
||||
Enabling this will also make the map available in FacilMap’s list of public maps in the [open map dialog](../collaborative/#open-an-existing-map).
|
||||
|
||||
When this is enabled, an additional field “Short description” is shown. Search engines will list the map with the [map name](#map-name) and this description.
|
||||
|
||||
## Cluster markers
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import addMp4 from "@source/users/markers/add.mp4";
|
||||
import addMobileMp4 from "@source/users/markers/add-mobile.mp4";
|
||||
import addResultMp4 from "@source/users/markers/add-result.mp4";
|
||||
import addResultMobileMp4 from "@source/users/markers/add-result-mobile.mp4";
|
||||
import addResultsMp4 from "@source/users/markers/add-results.mp4";
|
||||
import addResultsMobileMp4 from "@source/users/markers/add-results-mobile.mp4";
|
||||
import moveMp4 from "@source/users/markers/move.mp4";
|
||||
import moveMobileMp4 from "@source/users/markers/move-mobile.mp4";
|
||||
import removeMp4 from "@source/users/markers/remove.mp4";
|
||||
import removeMobileMp4 from "@source/users/markers/remove-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# Markers
|
||||
|
||||
A marker is a point on the map that has a name, a certain style (shape, icon, size, colour) and some data (such as a description). When you add a marker to a map, it is permanently saved there and visible for anyone who is viewing the map.
|
||||
|
@ -8,21 +21,21 @@ By default, a collaborative map has one type of marker called “Marker” that
|
|||
|
||||
To add a marker, click on “Add” in the [toolbox](../ui/#toolbox) and then “Marker”. A message will appear that asks you to click on the map to add a marker, along with a “Cancel” button to cancel the operation. Once you click somewhere on the map, a marker is added there and a [search box](../ui/#search-box) tab opens with information about the marker.
|
||||
|
||||
<Screencast :desktop="require('./add.mp4')" :mobile="require('./add-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="addMp4" :mobile="addMobileMp4"></Screencast>
|
||||
|
||||
The new marker is called “Untitled marker”, has the default style and no description. As a next step, you might want to [edit the marker details](#edit-marker-details) to give it a name and change its style.
|
||||
|
||||
## Add a search result as a marker
|
||||
|
||||
An alternative way to add a marker is to add a [search result](../search/), a marker from an [opened geographic file](../files/) or a [map point](../click-marker/) as a marker to the collaborative map.
|
||||
An alternative way to add a marker is to add a [search result](../search/), a [POI](../pois/), a marker from an [opened geographic file](../files/) or a [map point](../click-marker/) as a marker to the collaborative map.
|
||||
|
||||
In the search box tab of that object, click on “Add to map” and then “Marker”. The marker will be created with the name of the result. If the marker is a [custom type](../types/), a mapping from the result info to the marker data is attempted, for example if the marker has a field “Address”, it will be filled with the address of the result.
|
||||
|
||||
<Screencast :desktop="require('./add-result.mp4')" :mobile="require('./add-result-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="addResultMp4" :mobile="addResultMobileMp4"></Screencast>
|
||||
|
||||
You can also add multiple results at once. In the search form, select multiple items by using the “Select all” button or clicking individual items while holding the Ctrl or Shift key. Then click “Add selected items to map” and then “Marker items as Marker”. (In case of an opened geographic file, some of the selected items may be polygons or polylines, these will not be added, as they are not “Marker items”.)
|
||||
You can also add multiple results at once. In the search form, select multiple items by using the “Select all” button or clicking individual items while holding the Ctrl key. Then click “Add selected items to map” and then “Marker items as Marker”. (In case of an opened geographic file, some of the selected items may be polygons or polylines, these will not be added, as they are not “Marker items”.)
|
||||
|
||||
<Screencast :desktop="require('./add-results.mp4')" :mobile="require('./add-results-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="addResultsMp4" :mobile="addResultsMobileMp4"></Screencast>
|
||||
|
||||
## Show marker details
|
||||
|
||||
|
@ -44,10 +57,12 @@ Click “Save” to save your changes.
|
|||
|
||||
To change the position of a marker, select the marker and then click “Move” in its [search box](../ui/#search-box) tab. A message will appear at the top right of the screen. Now drag the marker to the desired position, and once you are done, click “Finish” in the message. To keep it at its old position, cancel the operation by clicking “Cancel”.
|
||||
|
||||
<Screencast :desktop="require('./move.mp4')" :mobile="require('./move-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="moveMp4" :mobile="moveMobileMp4"></Screencast>
|
||||
|
||||
## Remove a marker
|
||||
|
||||
To remove a marker from the map, select the marker, click “Remove” and confirm the alert box. Note that removed markers will remain in the [edit history](../history/) of the map and can be seen and restored there by admins.
|
||||
|
||||
<Screencast :desktop="require('./remove.mp4')" :mobile="require('./remove-mobile.mp4')"></Screencast>
|
||||
<Screencast :desktop="removeMp4" :mobile="removeMobileMp4"></Screencast>
|
||||
|
||||
You can also remove multiple markers at once by [handling multiple objects](../multiple/).
|
|
@ -0,0 +1,17 @@
|
|||
# Handle multiple objects
|
||||
|
||||
You can select multiple markers and lines at once and perform batch operations on them.
|
||||
|
||||
## Select multiple objects
|
||||
|
||||
To select multiple objects, click them on the map while holding the Ctrl key on your keyboard. A new tab will appear in the [search box](../ui/#search-box) that lists all the selected objects. To exclude an object from the selection again, Ctrl+Click the item on the map or in the list of objects again. Note that clicking an object without holding Ctrl will clear the selection and select only that object.
|
||||
|
||||
Alternatively, you can hold Shift and draw a box on the map using your mouse. All objects that are inside the box will be selected. If you already have some items selected, holding Shift+Ctrl while drawing the box will add the objects in the box to the existing selection rather than resetting the existing selection.
|
||||
|
||||
**Note:** At the moment it is not possible yet to select multiple objects on devices without a keyboard (such as smartphones). This will be implemented soon.
|
||||
|
||||
## Remove multiple objects
|
||||
|
||||
To remove multiple objects at once, select them and then click “Remove” in the bottom of the search box tab and confirm the alert box.
|
||||
|
||||
Note that removed objects will remain in the [edit history](../history/) for admins to see and restore.
|
|
@ -0,0 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
import metadataMp4 from "@source/users/pois/metadata.mp4";
|
||||
import metadataMobileMp4 from "@source/users/pois/metadata-mobile.mp4";
|
||||
import poisMp4 from "@source/users/pois/pois.mp4";
|
||||
import poisMobileMp4 from "@source/users/pois/pois-mobile.mp4";
|
||||
import zoomMp4 from "@source/users/pois/zoom.mp4";
|
||||
import zoomMobileMp4 from "@source/users/pois/zoom-mobile.mp4";
|
||||
</script>
|
||||
|
||||
# POIs
|
||||
|
||||
Using the “POIs” menu in the [search box](../ui/#search-box), you can find amenities and points of interest (POIs) on the map. You can use it for example to show all post boxes, parking places, supermarkets or bus stops in an area.
|
||||
|
||||
Choose a category of POIs and then check some of the POI types to show them on the map. Since the list of available POI types is quite long, you can type something into the filter field on top to show only those checkboxes that contain that phrase. For example, if you type “parking” into the filter field, only the “Bicycle parking” and “Parking” checkboxes are shown.
|
||||
|
||||
When one or more checkboxes are checked, the POIs of this type are shown as black square markers on the map. Only the POIs in the current map view are loaded, so only when you move to a particular area on the map, the POIs for that area are loaded. A maximum of 50 POIs are loaded. If the current area of the map contains more than 50 POIs of the selected type, a yellow message will be shown on the top of the map (see more details [below](#zoom-limitations)). In that case none or only some of the available POIs are shown. Zoom in further to get a complete overview of POIs.
|
||||
|
||||
<Screencast :desktop="poisMp4" :mobile="poisMobileMp4"></Screencast>
|
||||
|
||||
|
||||
## Show details
|
||||
|
||||
Clicking on a POI marker will open an additional search box tab with some metadata, such as opening hours, access restrictions and contact details. The metadata tab can be closed by clicking on the X or clicking somewhere else on the map.
|
||||
|
||||
<Screencast :desktop="metadataMp4" :mobile="metadataMobileMp4"></Screencast>
|
||||
|
||||
|
||||
## Zoom limitations
|
||||
|
||||
To avoid overloading the server or your browser, a maximum of 50 POIs are shown on the map, and the server may take a maximum of 2 seconds to load them. When the limit is exceeded, a yellow message is shown on the top of the map. There is no fixed minimum zoom level, it depends on the density of POIs in the current map area. The yellow message may say one of the following things:
|
||||
* “Zoom in to show POIs.” – This is shown when you are zoomed out so far that the server took more than 2 seconds to look for POIs. To avoid overloading the server, the request was canceled and no POIs are shown.
|
||||
* “Not all POIs are shown because there are too many results. Zoom in to show all results.” – This means that the server found more than 50 POIs. To save resources, only the first 50 POIs found are shown on the map. This means that the results shown are not all that exist. To get a complete overview of the existing POIs, zoom in further.
|
||||
|
||||
<Screencast :desktop="zoomMp4" :mobile="zoomMobileMp4"></Screencast>
|
||||
|
||||
|
||||
## Share a link
|
||||
|
||||
When you enable POIs on the map, the types of POIs that you checked are stored in the [location hash](../share/). To share a link to a specific part of the map with specific types of POIs shown, simply navigate the map to that view and copy the URL from the address bar of your browser.
|
||||
|
||||
On collaborative maps, POI types can also be saved and shared as part of [saved views](../views/).
|
||||
|
||||
|
||||
## Custom query
|
||||
|
||||
FacilMap uses the [Overpass API](https://overpass-api.de/) to query the OpenStreetMap database for POIs. If the available types of POIs do not fulfill your needs, you can click the “Custom query” button at the bottom of the POI tab and type a custom query into the text field. The query will be applied automatically, there is no save button or something like that.
|
||||
|
||||
The custom query field only expects a [query statement](https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#The_Query_Statement), without any other Overpass commands. FacilMap will automatically enrich the query so that it searches only in the current map area and there is a limit of 50 results. The results will always be shown as markers. If a query returns OpenStreetMap ways or relations, the marker will be rendered at their geometric centre.
|
||||
|
||||
The Overpass API queries the OpenStreetMap database for objects based on their tags. An overview over the most common tags can be found [on the OpenStreetMap wiki](https://wiki.openstreetmap.org/wiki/Map_features).
|
||||
|
||||
Here are some example queries:
|
||||
* `nwr[amenity=parking]`: Parking places.
|
||||
* `(nwr[amenity=toilets]; -nwr[amenity=toilets][fee][fee!=no];)`: Toilets without a fee.
|
||||
* `(nwr[amenity=post_box]; nwr[amenity=post_office];)`: Post boxes and post offices.
|
||||
* `nwr[amenity=drinking_water][drinking_water=yes]`: Water fountains that are explicity marked as drinkable.
|
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
|
@ -6,17 +6,19 @@ FacilMap is a combination of services provided by FacilMap itself and third-part
|
|||
|
||||
## FacilMap itself
|
||||
|
||||
The following data is *sent* to the FacilMap server but is *not persisted* there:
|
||||
The following data is *processed* but *not persisted* by the FacilMap server:
|
||||
* Your **IP address**: When you open FacilMap, a connection to the server is made, which inevitably reveils your IP address to the server. FacilMap uses your IP address to guess your location using the [MaxMind GeoLite2](https://dev.maxmind.com/geoip/geoip2/geolite2/) database (to decide the initial map view), but it uses a local copy of that database rather than sending your IP address to MaxMind.
|
||||
* Your **map position**: When you have a [route](../route/) or a [collaborative map](../collaborative/) open, the current map position is sent to the server every time you move/zoom the map. (In response, the server will send any map objects in your current map view.)
|
||||
* Your **search term**: When you [search](../search/) for a place, open a [map point](../click-marker/) or calculate a [route](../route/), the search term is sent to the FacilMap server and relayed to third-party services from there.
|
||||
* Your **language settings**: If you have set custom language and/or unit user preferences, these are sent to the server on each request. Otherwise, the language preferences that your browser sends with each request are used.
|
||||
|
||||
The following data is sent to the FacilMap server and *persisted* there:
|
||||
The following data is *processed* and *persisted* by the FacilMap server:
|
||||
* When you calculate a [route](../route/), the details about this route are stored on the server until you close the route (or your browser window) again.
|
||||
* Any [markers](../markers/), [lines](../lines), [types](../types), [views](../views) that you add and any [map settings](../map-settings/) that you make on a [collaborative map](../collaborative/) is stored on the server. Deleting such objects will still keep a copy in the [change history](../history/). The only way to delete the data is to [delete the collaborative map](../map-settings/#delete-the-map).
|
||||
|
||||
The following data is persisted in your browser:
|
||||
* If you change the [zoom settings](../search/#zoom-settings), these are persisted in the local storage of your browser.
|
||||
* If you add [bookmarks](../collaborative/#bookmark-a-map), these are persisted in the local storage of your browser.
|
||||
* If you change the language or unit user preferences, these are persisted as a cookie in your browser.
|
||||
|
||||
## Layers
|
||||
|
||||
|
@ -29,7 +31,7 @@ FacilMap does not have any control over what these providers do with your data,
|
|||
|
||||
## Search/route
|
||||
|
||||
When you make a [search](../search/), calculate a [route](../route/) or open a [map point](../click-marker/), the search is relayed through the FacilMap server to the third-party provider. This means that only the search terms itself are sent to the provider, but your IP address or any cookies are not reveiled to the third party. The following providers are used:
|
||||
When you make a [search](../search/), calculate a [route](../route/) or open a [map point](../click-marker/), a third-party provider is used, so the search terms, your IP address and any cookies set by the provider may be transmitted. The following providers are used:
|
||||
* [Nominatim](https://nominatim.openstreetmap.org/) to resolve search terms or locations
|
||||
* [Mapbox](https://www.mapbox.com/) for routes with a simple [route mode](../route/#route-modes)
|
||||
* [OpenRouteService](https://openrouteservice.org/) for routes with an advanced [route mode](../route/#route-modes)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue