WME MapRaider

WME MapRaid Helper

  1. // ==UserScript==
  2. // @name:hu WME MapRaider
  3. // @name:en WME MapRaider
  4. // @description:hu Magyar MapRaid Segéd
  5. // @description:en WME MapRaid Helper
  6. // @copyright 2014-2017, ragacs
  7. // @name WME MapRaider
  8. // @description WME MapRaid Helper
  9. // @version 0.6
  10. // @include https://www.waze.com/editor*
  11. // @include https://www.waze.com/*/editor*
  12. // @include https://beta.waze.com/editor*
  13. // @include https://beta.waze.com/*/editor*
  14. // @namespace https://gf.qytechs.cn/users/6330
  15. // ==/UserScript==
  16.  
  17. var wmemr_version = "0.6";
  18. var wmemr_col_num = 20;
  19. var wmemr_col_str = 6;
  20. var wmemr_col_title = 7;
  21.  
  22. /* bootstrap, will call initialiseMapRaider() */
  23. function bootstrapMapRaider()
  24. {
  25. var bGreasemonkeyServiceDefined = false;
  26.  
  27. try {
  28. bGreasemonkeyServiceDefined = (typeof Components.interfaces.gmIGreasemonkeyService === "object");
  29. }
  30. catch (err) { /* Ignore */ }
  31.  
  32. if (typeof unsafeWindow === "undefined" || ! bGreasemonkeyServiceDefined) {
  33. unsafeWindow = ( function () {
  34. var dummyElem = document.createElement('p');
  35. dummyElem.setAttribute('onclick', 'return window;');
  36. return dummyElem.onclick();
  37. }) ();
  38. }
  39.  
  40. /* begin running the code! */
  41. setTimeout(initialiseMapRaider, 999);
  42. }
  43.  
  44. /* helper function */
  45. function getElementsByClassName(classname, node) {
  46. if(!node) node = document.getElementsByTagName("body")[0];
  47. var a = [];
  48. var re = new RegExp('\\b' + classname + '\\b');
  49. var els = node.getElementsByTagName("*");
  50. for (var i=0,j=els.length; i<j; i++)
  51. if (re.test(els[i].className)) a.push(els[i]);
  52. return a;
  53. }
  54.  
  55. function getId(node) {
  56. return document.getElementById(node);
  57. }
  58.  
  59. function processNewPermalinks()
  60. {
  61. var lines = getId("_taMapRaidInput").value.split("\n");
  62. var mapRaiderCounter = getId("_inMapRaidStart").value;
  63. if(lines.length == 1 && lines[0].search(/^CFG:\s*[A-Z]+,\s*[A-Z]+,\s*[A-Z]+$/i) == 0)
  64. {
  65. lines[0]=lines[0].toUpperCase();
  66. var cfg = lines[0].slice(lines[0].indexOf(":") + 1).split(",");
  67. cfg[0] = cfg[0].trim();
  68. cfg[1] = cfg[1].trim();
  69. cfg[2] = cfg[2].trim();
  70. wmemr_col_num = cfg[0].charCodeAt(0) - 65;
  71. if(cfg[0].length > 1) wmemr_col_num = (wmemr_col_num + 1) * 26 + cfg[0].charCodeAt(1) - 65;
  72. wmemr_col_str = cfg[1].charCodeAt(0) - 65;
  73. if(cfg[1].length > 1) wmemr_col_str = (wmemr_col_str + 1) * 26 + cfg[1].charCodeAt(1) - 65;
  74. wmemr_col_title = cfg[2].charCodeAt(0) - 65;
  75. if(cfg[2].length > 1) wmemr_col_title = (wmemr_col_title + 1) * 26 + cfg[2].charCodeAt(1) - 65;
  76. console.log("WME-MapRaider:" + wmemr_col_num);
  77. updateMapRaiderConfigStr();
  78. return;
  79. }
  80. if(lines.length < 2) return;
  81.  
  82. for(var i=0; i<lines.length; i++)
  83. {
  84. var linestr = "";
  85. var titlestr = "";
  86. var lon = "", lat = "", zoom = "6", segs = "";
  87. var cells = lines[i].split("\t");
  88. if(cells.length < 1) continue;
  89. for(var c=0; c < cells.length; c++)
  90. {
  91. // https://www.waze.com/editor/?lon=19.154683728784565&lat=47.46686402709748&zoom=6&marker=true&segments=102441276
  92. if(cells[c].search(/https:\/\/(www|beta).waze.com\/.*editor/) == 0)
  93. {
  94. lon = cells[c].replace(/.*lon=/,"").replace(/&.*$/,"");
  95. lat = cells[c].replace(/.*lat=/,"").replace(/&.*$/,"");
  96. if(cells[c].search("zoom") > 0)
  97. zoom = cells[c].replace(/.*zoom=/,"").replace(/&.*$/,"");
  98. if(cells[c].search("segments") > 0)
  99. segs = cells[c].replace(/.*segments=/,"").replace(/&.*$/,"");
  100. }
  101. else if(c == wmemr_col_str) linestr = cells[c];
  102. else if(c == wmemr_col_title) titlestr = cells[c];
  103. else if(c == wmemr_col_num) mapRaiderCounter = cells[c];
  104. }
  105. if(lon.length == 0 || lat.length == 0)
  106. continue;
  107. if(linestr.length == 0) linestr = "Bookmark";
  108. else linestr = mapRaiderCounter++ + ". (*)" + linestr;
  109. var item = document.createElement('div');
  110. var redx = document.createElement('img');
  111. redx.setAttribute("src", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABE0lEQVQ4jaWTsWoCURRED0EkyBbBDwhBxEJCEAsLf1BEUlgE8QusxMIiRbASEbEQSWURUoQUKUREUgWZNLvZm+tbU2ThFo+dmX0zsxfcI6gIOoKVYB/Ps6ArqHq8JeYEbcGXQBlzFDwILkPk0Rmin6dfIoKWeXkQvAVIO8G7OfcTctlcey9oCq4FLwa8FdRjbCJ+FNz5r68EUSyciGwFdWN1bPA9BHN31akTqRnywGE3CD4Cfn9EzpAl+LzIaLUK3JhzEWhk9b9wqt5zwWVyYsGGuHPkQSCTVx9iydS4FlwFPE8FkSAveDQ11hIbbQNeCoaBwGauwv5/fuWJMvaho7+XqXdCdkIVwb3SdT4oXedbj/8G3YhVhG0/RlMAAAAASUVORK5CYII=");
  112. redx.onclick = function(event) {
  113. event = event || window.event;
  114. var adiv = this.parentNode;
  115. adiv.parentNode.removeChild(adiv);
  116. event.stopPropagation();
  117. };
  118. var crosshair = document.createElement('img');
  119. crosshair.setAttribute("src", "data:image/png;base64,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");
  120. crosshair.setAttribute("width", "16");
  121. crosshair.onclick = function(event) {
  122. event = event || window.event;
  123. var tmplatlon=new OpenLayers.LonLat(this.parentNode.getAttribute("lon"), this.parentNode.getAttribute("lat"));
  124. tmplatlon.transform(Waze.map.displayProjection, Waze.map.getProjectionObject());
  125. Waze.map.setCenter(tmplatlon, this.parentNode.getAttribute("zoom"));
  126. event.stopPropagation();
  127. };
  128. item.innerHTML = linestr;
  129. item.appendChild(redx);
  130. item.appendChild(crosshair);
  131. item.setAttribute("lon", lon);
  132. item.setAttribute("lat", lat);
  133. item.setAttribute("zoom", zoom);
  134. item.setAttribute("segs", segs);
  135. item.setAttribute("title", titlestr);
  136. item.onclick = function(event) {
  137. event = event || window.event;
  138. if (event.ctrlKey)
  139. {
  140. this.removeAttribute("style");
  141. return;
  142. }
  143. var tmplatlon=new OpenLayers.LonLat(this.getAttribute("lon"), this.getAttribute("lat"));
  144. tmplatlon.transform(Waze.map.displayProjection, Waze.map.getProjectionObject());
  145. Waze.map.setCenter(tmplatlon, this.getAttribute("zoom"));
  146. var segs = this.getAttribute("segs");
  147. try
  148. {
  149. if(segs.length)
  150. {
  151. var segstrarray = segs.split(",");
  152. var segarray = [];
  153. var good = 0, bad = 0;
  154. for(var s=0; s < segstrarray.length; s++)
  155. {
  156. var aseg = Waze.model.segments.get(segstrarray[s]);
  157. if(aseg === undefined)
  158. bad++;
  159. else
  160. {
  161. good++;
  162. segarray.push(aseg);
  163. }
  164. }
  165. Waze.selectionManager.select(segarray);
  166. if(bad)
  167. {
  168. if(good)
  169. this.style.backgroundColor = "#dddd20";
  170. else
  171. this.style.backgroundColor = "#bb5020";
  172. }
  173. else
  174. this.style.backgroundColor = "#50bb20";
  175. }
  176. }
  177. catch(err)
  178. {
  179. this.style.backgroundColor = "#bb5020";
  180. }
  181. };
  182. getId("_divMapRaidPermalinks").appendChild(item);
  183. }
  184. getId("_taMapRaidInput").value = "";
  185. getId("_inMapRaidStart").value = mapRaiderCounter;
  186. }
  187.  
  188. function clearMapRaiderVisited()
  189. {
  190. var listedlinks=getId("_divMapRaidPermalinks").childNodes;
  191. for(var l = 0; l < listedlinks.length; l++)
  192. {
  193. var color = listedlinks.item(l).style.backgroundColor;
  194. if( color )
  195. {
  196. getId("_divMapRaidPermalinks").removeChild(listedlinks.item(l--));
  197. }
  198. }
  199. }
  200.  
  201. function clearMapRaiderAll()
  202. {
  203. getId("_divMapRaidPermalinks").innerHTML = '';
  204. }
  205.  
  206. function updateMapRaiderConfigStr()
  207. {
  208. var line = "Cfg: ";
  209. if(wmemr_col_num > 25) line += String.fromCharCode(~~(wmemr_col_num / 26) + 64);
  210. line += String.fromCharCode(wmemr_col_num % 26 + 65) + ", ";
  211. if(wmemr_col_str > 25) line += String.fromCharCode(~~(wmemr_col_str / 26) + 64);
  212. line += String.fromCharCode(wmemr_col_str % 26 + 65) + ", ";
  213. if(wmemr_col_title > 25) line += String.fromCharCode(~~(wmemr_col_title / 26) + 64);
  214. line += String.fromCharCode(wmemr_col_title % 26 + 65);
  215. getId("_divMapRaidConfig").innerHTML = line;
  216. }
  217.  
  218. /* =========================================================================== */
  219.  
  220. function initialiseMapRaider()
  221. {
  222. // global variables
  223. betaMode = location.hostname.match(/beta.waze.com/);
  224.  
  225. // add new box to left of the map
  226. var addon = document.createElement('section');
  227. addon.id = "mapraider-addon";
  228.  
  229. if (navigator.userAgent.match(/Chrome/)) {
  230. addon.innerHTML = '<b>'
  231. + 'WME MapRaider</b> &nbsp; v' + wmemr_version;
  232. } else {
  233. addon.innerHTML = '<b>'
  234. + 'WME MapRaider</b> &nbsp; v' + wmemr_version;
  235. }
  236.  
  237. section = document.createElement('p');
  238. section.style.padding = "8px 16px";
  239. //section.style.textIndent = "-16px";
  240. section.id = "nameMapRaider";
  241. section.innerHTML = '<button id="_btnMapRaidClearVisited" title="Tip: Use Ctrl+Click on items to clear visited state">Clear Visited</button>'
  242. + '<button id="_btnMapRaidClearAll">Clear All</button>'
  243. + '<div><b>Next No.:</b> &nbsp; <input type="number" value="1" title="Starting number (if index column is not found)" id="_inMapRaidStart"/></div>'
  244. + '<div><b>Table rows:</b> &nbsp; <textarea rows="500" cols="10" title="Copy here complete rows from the MapRaid table\n'
  245. + 'or use Cfg:<index column>,<name column>,<description column> to configure. Eg. Cfg: A, E, F" id="_taMapRaidInput"></textarea></div>'
  246. + '<div id="_divMapRaidConfig" title="Index Column, Name Column, Description Column"></div>'
  247. + '<div id="_divMapRaidPermalinks"></div>';
  248. addon.appendChild(section);
  249.  
  250. var userTabs = getId('user-info');
  251. var navTabs = getElementsByClassName('nav-tabs', userTabs)[0];
  252. var tabContent = getElementsByClassName('tab-content', userTabs)[0];
  253.  
  254. newtab = document.createElement('li');
  255. newtab.innerHTML = '<a href="#sidepanel-mapraider" data-toggle="tab">MapRaider</a>';
  256. navTabs.appendChild(newtab);
  257.  
  258. addon.id = "sidepanel-mapraider";
  259. addon.className = "tab-pane";
  260. tabContent.appendChild(addon);
  261. updateMapRaiderConfigStr();
  262.  
  263. getId('_taMapRaidInput').oninput = processNewPermalinks;
  264. getId('_btnMapRaidClearVisited').onclick = clearMapRaiderVisited;
  265. getId('_btnMapRaidClearAll').onclick = clearMapRaiderAll;
  266. }
  267.  
  268. /* engage! =================================================================== */
  269. bootstrapMapRaider();
  270.  
  271. /* end ======================================================================= */

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址