FlagstackMap

Type filters, score sum and GPX exports for Flagstack Map

  1. // ==UserScript==
  2. // @name FlagstackMap
  3. // @namespace flagstackmap.rocka.de
  4. // @include https://www.flagstack.net/map*
  5. // @version 1.0.1
  6. // @grant unsafeWindow
  7. // @grant GM_getValue
  8. // @grant GM_setValue
  9. // @grant GM_addStyle
  10. // @grant GM_registerMenuCommand
  11. // @description Type filters, score sum and GPX exports for Flagstack Map
  12. // ==/UserScript==
  13. //
  14. // Features:
  15. // * filter visible Flags by type
  16. // * Total score display
  17. // * GPX Export: save visible Flags to a file for offline use
  18. // * change URL when clicking on Flag (reload to last shown Flag)
  19.  
  20. // History:
  21. // 1.0.1
  22. // * added POI flags
  23.  
  24.  
  25. (function () {
  26. var locale = 'de-DE',
  27. filters = JSON.parse(GM_getValue('filters')||'{}'),
  28. types = {
  29. own: {
  30. rgx: /_own_/,
  31. name: 'Own Flag',
  32. score: 0
  33. },
  34. captured: {
  35. rgx: /_captured_/,
  36. name: 'Captured Flag',
  37. score: 0
  38. },
  39. green: {
  40. rgx: /_green_/,
  41. name: 'Green Flag',
  42. score: 8
  43. },
  44. system: {
  45. rgx: /_orange_/,
  46. name: 'System Flag',
  47. score: 16
  48. },
  49. white: {
  50. rgx: /_white_/,
  51. name: 'White Flag',
  52. score: 10
  53. },
  54. personal: {
  55. rgx: /_personal_/,
  56. name: 'Personal Flag',
  57. score: 5
  58. },
  59. oracle: {
  60. rgx: /_oracle_/,
  61. name: 'Oracle Flag',
  62. score_min: 6,
  63. score_max: 40
  64. },
  65. premium: {
  66. rgx: /_premium_/,
  67. name: 'Premium Flag',
  68. score: 10
  69. },
  70. treasure: {
  71. rgx: /_treasure_/,
  72. name: 'Treasure Flag',
  73. score_min: 8,
  74. score_max: 60
  75. },
  76. company: {
  77. rgx: /_business_/,
  78. name: 'Company Flag',
  79. score: 1
  80. },
  81. party: {
  82. rgx: /_party_/,
  83. name: 'Party Flag',
  84. score: 0//?
  85. },
  86. team: {
  87. rgx: /_team_/,
  88. name: 'Team Flag',
  89. score: 0
  90. },
  91. poi:{
  92. rgx:/_poi_/,
  93. name:"POI",
  94. score_min:8,
  95. score_max:40
  96. },
  97. querfeldein_deutschland: {
  98. rgx: /\/(736699|7367(05|11|19|32|33)|10905(22|31|47|52|68|98)|10906(03|09|21|39))_/,
  99. name: 'Querfeldein durch Deutschland',
  100. score: 10,
  101. flags: {
  102. 736699: 'Schleswig-Holstein',
  103. 736705: 'Nordrhein-Westfalen',
  104. 736711: 'Saarland',
  105. 736719: 'Mecklenburg-Vorpommern',
  106. 736732: 'Sachsen',
  107. 736733: 'Thüringen',
  108. 1090522: 'Bremen',
  109. 1090531: 'Niedersachsen',
  110. 1090547: 'Hessen',
  111. 1090552: 'Rheinland-Pfalz',
  112. 1090568: 'Baden-Württemberg',
  113. 1090598: 'Hamburg',
  114. 1090603: 'Brandenburg',
  115. 1090609: 'Berlin',
  116. 1090621: 'Sachsen-Anhalt',
  117. 1090639: 'Bayern'
  118. }
  119. }
  120. },
  121. groups = {
  122. physical: ['treasure'],
  123. virtual: ['green', 'system', 'white', 'personal', 'oracle', 'premium', 'company', 'party'],
  124. special: ['querfeldein_deutschland','poi']
  125. },
  126. scoreElem,
  127. tmpl_gpx = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?><gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns="http://www.topografix.com/GPX/1/1" xmlns:rmc="urn:net:trekbuddy:1.0:nmea:rmc" creator="MunzeeMapNG" xmlns:wptx1="http://www.garmin.com/xmlschemas/WaypointExtension/v1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.garmin.com/xmlschemas/WaypointExtension/v1 http://www.garmin.com/xmlschemas/WaypointExtensionv1.xsd" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:ql="http://www.qlandkarte.org/xmlschemas/v1.1"><metadata><time><%time></time></metadata><%wpts><extensions/></gpx>',
  128. tmpl_wpt = '<wpt lon="<%lon>" lat="<%lat>"><time><%time></time><name><%name></name><cmt><%cmt></cmt><desc><![CDATA[<%desc>]]></desc><extensions><locus:icon>file:flagstack.zip:<%icon>.png</locus:icon></extensions></wpt>';
  129.  
  130. function isAnnotationVisible(annotation) {
  131. return unsafeWindow.map.getBounds().contains(annotation.getPosition());
  132. }
  133.  
  134. function refresh() {
  135. var score = 0, score_min = 0, score_max = 0, type_count = {};
  136. $.each(unsafeWindow.annotations, function (flag_id, flag) {
  137. $.each(types, function (type, type_info) {
  138. if (type_info.rgx.test(flag.icon)) {
  139. if (filters[type] && flag.map !== null) {
  140. flag.setMap(null);
  141. } else if (!filters[type]) {
  142. if (flag.map === null) {
  143. flag.setMap(unsafeWindow.map);
  144. }
  145. if (isAnnotationVisible(flag)) {
  146. score_min += type_info.score_min ? type_info.score_min : type_info.score;
  147. score_max += type_info.score_max ? type_info.score_max : type_info.score;
  148.  
  149. type_count[type] = (type_count[type] || 0) + 1;
  150. }
  151. }
  152. }
  153. });
  154. });
  155. score = (score_min + score_max) / 2;
  156. scoreElem.html(score.toLocaleString(locale) + ' Points');
  157. if (score_min < score_max) {
  158. scoreElem.append('<br /><span>(' + score_min.toLocaleString(locale) + ' - ' + score_max.toLocaleString(locale) + ')</span>');
  159. }
  160. var type_text = '';
  161. for (var type in type_count) {
  162. if (type_count[type] > 0 && (types[type].score > 0 || types[type].score_min > 0)) {
  163. type_text += type_count[type] + ' ' + (types[type].name || (type.charAt(0).toUpperCase() + type.slice(1))) + ' Flags (' +
  164. (types[type].score_min > 0 ? (types[type].score_min * type_count[type]).toLocaleString(locale) + ' - ' + (types[type].score_max * type_count[type]).toLocaleString(locale) : (types[type].score * type_count[type]).toLocaleString(locale)) + ")\n";
  165. }
  166. }
  167. scoreElem.attr('title', type_text);
  168. //console.log(type_text);
  169. }
  170.  
  171. function createLink(id) {
  172. return annotations[id] ? '/map/?highlight=1&id=' + id + '&latitude=' + annotations[id].position.lat() + '&longitude=' + annotations[id].position.lng() : '';
  173. }
  174.  
  175. function useTmpl(tmpl, data) {
  176. var out = tmpl;
  177. $.each(data, function (key, value) {
  178. out = out.replace('<%' + key + '>', value);
  179. });
  180. return out;
  181. }
  182.  
  183. function createGPX() {
  184. var wpts = "";
  185. $.each(unsafeWindow.annotations, function (flag_id, flag) {
  186. if (isAnnotationVisible(flag)) {
  187. $.each(types, function (type, type_info) {
  188. if (type_info.rgx.test(flag.icon) && !filters[type]) {
  189. wpts += useTmpl(tmpl_wpt, {
  190. lon: flag.getPosition().lng(),
  191. lat: flag.getPosition().lat(),
  192. time: "",
  193. name: flag.f_name + (type_info.name !== flag.f_name ? "(" + type_info.name + ")" : ""),
  194. cmt: flag.f_ownerUsername ? "by " + flag.f_ownerUsername : "",
  195. desc: flag.f_address,
  196. icon: type
  197. });
  198. }
  199. });
  200. }
  201. });
  202. if (wpts) {
  203. window.open('data:text/plain,' + useTmpl(tmpl_gpx, {
  204. time: (new Date()).toISOString(),
  205. wpts: wpts
  206. }));
  207. }
  208. console.log('done', wpts);
  209. }
  210.  
  211. GM_registerMenuCommand('Save Filters',function(){
  212. GM_setValue('filters', JSON.stringify(filters));
  213. },'s');
  214.  
  215. unsafeWindow.getMapItems = function () {
  216. var params = {
  217. section: 'mapItems',
  218. latitude: currentLocation.latitude,
  219. longitude: currentLocation.longitude,
  220. centerLocation: JSON.stringify(centerLocation),
  221. regionBounds: JSON.stringify(regionBounds),
  222. };
  223.  
  224. if (xhr) {
  225. xhr.abort();
  226. }
  227.  
  228. if (!requestID) {
  229. requestID = randomString();
  230. }
  231.  
  232. params.requestID = requestID;
  233.  
  234. if (responseIDs.length > 0) {
  235. params.responseIDs = JSON.stringify(responseIDs);
  236. }
  237.  
  238. xhr = $.getJSON('/api/?' + sid, params, function (jsonData) {
  239. if (jsonData.result === 'OK') {
  240. var newItems = 0;
  241.  
  242. if (typeof (jsonData.responseID) !== 'undefined') {
  243. responseIDs.push(jsonData.responseID);
  244. }
  245.  
  246. if (typeof (jsonData.items) !== 'undefined') {
  247. xhrItems = jsonData.items;
  248.  
  249. for (var i = 0, len = jsonData.items.length; i < len; i++) {
  250. var itemID = jsonData.items[i].id;
  251. if (typeof (annotations[itemID]) === 'undefined') {
  252. annotations[itemID] = createAnnotation(jsonData.items[i]);
  253. if (highlight === 1 && itemID === highlightItemID && highlightInit) {
  254. highlightInit = false;
  255. new google.maps.event.trigger(annotations[itemID], 'click');
  256. }
  257. newItems++;
  258. }
  259. }
  260. }
  261. } else if (jsonData.result == 'ERROR') {
  262. swal({
  263. title: jsonData.title,
  264. text: jsonData.message,
  265. type: 'error',
  266. animation: false
  267. });
  268. }
  269.  
  270. refresh();
  271. });
  272. };
  273.  
  274. function createFilterBtn(type){
  275. return '<dt>' + type.charAt(0).toUpperCase() + type.slice(1) + ' Flags</dt>' +
  276. '<dd><a href="#" class="filter" id="filter_'+type+'"><img src="/img/switch_'+(filters[type]?'off':'on')+'.png" width="63" height="32"></a></dd>';
  277. }
  278.  
  279. if ($('#map_filter')) {
  280. $('#map_filter').remove();
  281. }
  282. $( '<div id="map_filter">'+
  283. '<div class="grid">'+
  284. '<p class="title">Filter</p>'+
  285. '<div id="score_sum">0 Points</div>'+
  286. '<div style="clear:both"></div>'+
  287. '<div class="column">'+
  288. '<dl>'+
  289. createFilterBtn('own')+
  290. createFilterBtn('captured')+
  291. createFilterBtn('physical')+
  292. createFilterBtn('virtual')+
  293. createFilterBtn('special')+
  294. '</dl>'+
  295. '</div>'+
  296. '<div class="column">'+
  297. '<dl>'+
  298. createFilterBtn('green')+
  299. createFilterBtn('system')+
  300. createFilterBtn('white')+
  301. createFilterBtn('oracle')+
  302. createFilterBtn('premium')+
  303. '</dl>'+
  304. '</div>'+
  305. '<div class="column">'+
  306. '<dl>'+
  307. createFilterBtn('treasure')+
  308. createFilterBtn('personal')+
  309. createFilterBtn('company')+
  310. createFilterBtn('team')+
  311. createFilterBtn('party')+
  312. '</dl>'+
  313. '</div>'+
  314. '</div>'+
  315. '</div>'
  316. ).appendTo($('.container.white'));
  317.  
  318. $(document).off('click', '.filter').on('click', '.filter', function () {
  319. var type = $(this).attr('id').replace(/^filter_/, ''), i;
  320. filters[type] = !filters[type];
  321. if (groups[type]) {
  322. for (i = 0; i < groups[type].length; i++) {
  323. filters[groups[type][i]] = filters[type];
  324. $('#filter_' + groups[type][i]).children('img').attr('src', filters[type] ? '/img/switch_off.png' : '/img/switch_on.png');
  325. }
  326. }else{
  327. var el;
  328. $.each(groups,function(group,flags){
  329. if (filters[group] && !filters[type] && flags.indexOf(type) !== -1 && (el = $('#filter_'+group))) {
  330. filters[group] = false;
  331. el.children('img').attr('src', '/img/switch_on.png');
  332. }
  333. });
  334. }
  335. $(this).children('img').attr('src', filters[type] ? '/img/switch_off.png' : '/img/switch_on.png');
  336. refresh();
  337.  
  338. return false;
  339. });
  340.  
  341. $('#map-container').on('click', function () {
  342. if ($('.gm-style-iw .annotationInfoWindow').size()>0) {
  343. history.pushState(null, '', createLink($('.gm-style-iw .annotationInfoWindow')[0].getAttribute('data-id')));
  344. }
  345. });
  346.  
  347. GM_addStyle(
  348. ".gm_btn {margin: 10px;z-index: 0;position: absolute;cursor: pointer;right: 0px;top: 60px;}" +
  349. ".gm_btn > div {font-weight:bold;direction: ltr; overflow: hidden; text-align: center; position: relative; color: rgb(0, 0, 0); font-family: Roboto, Arial, sans-serif; -webkit-user-select: none; font-size: 11px; padding: 8px; -webkit-background-clip: padding-box; box-shadow: rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px; border-left-width: 0px; min-width: 39px; font-weight: 500; background-color: rgb(255, 255, 255); background-clip: padding-box;}" +
  350. ".gm_btn > div:hover {color:black;background-color: rgb(235, 235, 235);}" +
  351. "#score_sum {float:right;margin:0 55px 12px 0;font-size:x-large;line-height:22px}" +
  352. "#score_sum span {font-size:smaller}"
  353. );
  354.  
  355. scoreElem = $('#score_sum');
  356. $('footer').remove();
  357. //unsafeWindow.setMapFilter = setFilter;
  358.  
  359. jQuery(document).ready(function ($) {
  360. setTimeout(function () {
  361. $('<div id="createGPX" class="gm-style-mtc gm_btn"><div>GPX</div></div>')
  362. .on('click', createGPX).appendTo($('.gm-style'));
  363. }, 500);
  364. });
  365. })();