WME E95

Setup road properties in one click

当前为 2019-05-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WME E95
  3. // @version 0.4.1
  4. // @description Setup road properties in one click
  5. // @author Anton Shevchuk
  6. // @license MIT License
  7. // @include https://www.waze.com/editor*
  8. // @include https://www.waze.com/*/editor*
  9. // @exclude https://www.waze.com/user/editor*
  10. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABKCAMAAABHCGBGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAC91BMVEUAAAAAVQAAfgAAgAAAfwAAfwAAfwAAgAAAgAAAfwAAgAAAbQAAgAAAgAAAgAAAfwAAfwAAgAAAfwAAgAAAgAAAfgAAgAAAgAAAgAAAfwAAgAAAgAAAgAAAgAAAfwAAfwAAgAAAgAAAfgAAfwAAgAAAgAAAfwAAgAAAfgAAgAAAfwAAfgAAfwAAgAAAfwAWihY0mTREoUQIgwhxuHHM5cz+/v7///9wt3BBoEHi8OLh8OFAn0Bfr1/6/Pperl49nj35/PnJ5Mmw17Cq1KrK5MpCoEL7/fuEwYQRiBGGwob8/fzj8eMJhAlNpk1Opk7N5s2JxInL5csVihUTiRMUiRQzmTPO5s7P588ymDJHo0e427hJpEm22rYejh50uXS02bTd7t31+vXv9++Yy5iazJr9/v3R6NE7nTvC4MJFokXw9/D4+/hptGnc7dwjkSP2+vbp9OkBgAG/379LpUtUqVQDgQOx2LHn8+dlsmUajBorlSuRyJHE4cQQhxAXixfe7t4YixhSqFInkycdjh3Y69hVqlU5nDl3u3cqlCqLxYu83bzx+PHA38A3mze+3r6i0KIEgQSu1q41mjVcrVxRqFGgz6Do8+gwlzCSyJIFggUSiBKFwoVrtWsHgwej0aN4u3h2unbs9ewLhQtos2gPhw9ttm3D4cPF4sV+vn4bjRs8nTwgjyDT6dNarFpMpUzm8uYCgALH48dZrFmfz5+t1q2s1axqtGq53Lkuli7k8eQokyjG4sbX69fr9escjRwmkiby+PKDwYN8vXzg7+CPx48KhAoZjBlksWTl8uXt9u30+fTa7Nputm5yuHJvt2+n06eIw4i63LrB4MGCwIKHw4cGgga327d7vXu93r2czZxWqlYtli0Ohg46nDpmsmbq9OqMxYyl0qXZ7Nnz+fMlkiWm0qYplCk4mzhjsWOdzp0slSwhkCGZzJkkkSR/v39Qp1Cr1avI48gikCIMhQzQ59C12rWezp4xmDFPp0/f799drl0/nz8+nj7T2Y5EAAAALnRSTlMAA0WUz+f9UNbVTgeepAi/vQaboFJPBNhIl9DO6ujplZJGQ9dMmp+8TdTTS83mVCRELwAAAAFiS0dENke/iNEAAAAHdElNRQfhCAkSBiQpT1KFAAAFFElEQVRo3u2aeVxUVRTHH6vaKi202EKUrXqEAWbchkbCAUYIhhlhUCEgyYUKwVDT3EorxQXRCtFJ0QQCSypbbDFrUhM3tCZJWxXbFytarP7ozj133rx5PP18au579Ufnn5nfWe53GObed+67TxCoBQWHhIaFg8oWHhYaEhwkSKxHT4ABMbGqW8wAgF5niNgzzwJdXHyCXgNLiDfo4OxzGPdcGDhIr5kNHgK9Iyj4PBhq1GtoxkQ438O9AG42UT0s6RaVLSmZ/oXG4XChIERepBtMhDklFTSw1DQLgY1I7xkpXAxxHm4GaGS3esgGCBYugUzyLg00syyCs0KIcGk2mUdmm3ZgO/mTE+x9hMtGkk+QAxpaLgE6LhfC88jrKC3BowgwL1yA0eR1jJbgMQQ4Gv4H/x3LL7itsMhSnHN7LB/w6dZZSVr22BLRfcc46QDjT18ZIHjCRKm/xCCbL6qBM0plkTt9A9ylIvjuMnnE7BAHmKQeuLyieyiz3DtAmXrgyUqxe1iwUq8eeAqTU6fdmz59Bvt138eCMwMFn3omzrJgxuw5VM5l5Psx+gCqef94Hp8aPJ9lPCi5xBJ7CNXDqJJVAC/AhIVePbOK6kWoFsuiHMFL5FN3KdVF1VTEYnSZCuB5mFAjOpajA6fyChSPqAB+FBN8/dJj6FhORSKKWhXAbLlcKTrq0LGKitUoUlQAmzDBKTpWouNxKtagWKsCuB4TZomOdeiooGIVivXwxIaGqcb6xqYnuYELMaFZdLSgYyMVtSieenoTG8i0ppUT+BlMKBAdz6JjEBXPodjs6xP0C3UBdiAsYyyqYWIJW7snUvG8QuHmVi7gF5icwHQz29SXUfWiUqWBC/glJrfkU/nyFm8zQOUrSpXFdh5geJXp11Zvdb7etE280UCDGxVL3+ACflM5bKJBl3fn/9b07Tt2vs1UIRdw6i7FcBsNsphpMm4Od+tlK3sgYNgjC2ArXU9j7IbKXpZaw355+zjMY9JXTfHj7m+XfZ1SOyC7WgUEhvSDEm7FOlytlLfZ61kXygcM1e+wxktf+q6brVbvKWaOxDQXJzBp6tOSqyy7xh/yXAM6aMX7ynd62EzmBpYa/nSblL8b6erCGezGK/RhxeB2HK5KDXANVhxRDO7g/T+WGE7rtkrF4BAcLpcL+IMPP6pd8nG+VzbQgg76/vDeTz6tOHqsU8xdi8Md5wHOwYxpTHYmSHYSRzEWJ0828AAnYYaVyWUoP6PiOIrdbhacy4b7nAc4Tu9t6CR9QTzGvmCxL7Hp+IpdM1ytAW1T2af5mi38JWktuiPfMLETYzYzS23MsLm/PdTG1HfAA6x0Y8fl/W73K9UVbeUD/r57RFw9Os0KdQXABwwH5YETvgEU+pMfKnmBf6z39+f+5Bug/IS8qqsOeIFhnF/301jn1ybM8C/qsAM/MLQcEJ3Hmqpl8y0v01cyaSjvm6gxWY2bSktH/LzB3j1W3pzSMHubpbjrl/nu/9ht43/taOAKh9aHIV0E+GuYEGr3HP/YteNme45/bH2EK8FKPkG7duAFdIMbJVwFBvLO8ptW3BUWuquIFiKv1nl2WZb2Oi2wtiwP96SzV5Ag9GDHuGar+se4Vnr9MA6EazwHyH0hUduD69/hWnpiHtEb/nBpxz35J1yHR/VCZAg491m1eTgheY8T+kaIz0VcfwPZQTvUfxzDQdaLG2+SPggSFB3Vr7/6D6D07xcVzR5A+Quc3V7EBkSKDQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wOC0wOVQxODowNjozNSswMDowMN3fGMwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDgtMDlUMTg6MDY6MzUrMDA6MDCsgqBwAAAAAElFTkSuQmCC
  11. // @grant none
  12. // @supportURL https://github.com/AntonShevchuk/wme-e95/issues
  13. // @namespace http://tampermonkey.net/
  14. // ==/UserScript==
  15. /* jshint esversion: 6 */
  16. /* global require */
  17.  
  18. (function($, WazeApi) {
  19. 'use strict';
  20.  
  21. let WazeActionUpdateObject = require("Waze/Action/UpdateObject");
  22. let WazeActionUpdateFeatureAddress = require("Waze/Action/UpdateFeatureAddress");
  23.  
  24. // Road Types
  25. // I18n.translations.uk.segment.road_types
  26. const types = {
  27. street : 1,
  28. primary : 2,
  29. // ...
  30. offroad : 8,
  31. // ...
  32. private : 17,
  33. // ...
  34. parking : 20,
  35. };
  36. // Road colors by type
  37. const colors = {
  38. '1' : '#ffffeb',
  39. '2' : '#f0ea58',
  40. // ...
  41. '8' : '#867342',
  42. // ...
  43. '17' : '#beba6c',
  44. // ...
  45. '20' : '#ababab'
  46. };
  47.  
  48. // Road Flags
  49. // for setup flags use binary operators
  50. // e.g. flags.tunnel | flags.headlights
  51. const flags = {
  52. tunnel : 0b00000001,
  53. // ??? : 0b00000010,
  54. // ??? : 0b00000100,
  55. // ??? : 0b00001000,
  56. unpaved : 0b00010000,
  57. headlights : 0b00100000,
  58. };
  59.  
  60. // Buttons:
  61. // title - for buttons
  62. // keyCode - key for shortcuts (Alt+...)
  63. // detectCity - try to detect city name by closures segments
  64. // clearCity - clear city name
  65. // attributes - native settings for object
  66. // TODO:
  67. // – check permissions for user level lower than 2
  68. const buttons = {
  69. A: {
  70. title: 'P5',
  71. keyCode: 49,
  72. detectCity: true,
  73. attributes: {
  74. fwdMaxSpeed: 5,
  75. revMaxSpeed: 5,
  76. roadType: types.parking,
  77. flags: 0,
  78. }
  79. },
  80. B: {
  81. title: 'Pr20',
  82. keyCode: 50,
  83. detectCity: true,
  84. attributes: {
  85. fwdMaxSpeed: 20,
  86. revMaxSpeed: 20,
  87. roadType: types.private,
  88. flags: 0,
  89. }
  90. },
  91. C: {
  92. title: 'Pr50',
  93. keyCode: 51,
  94. detectCity: true,
  95. attributes: {
  96. fwdMaxSpeed: 50,
  97. revMaxSpeed: 50,
  98. roadType: types.private,
  99. flags: 0,
  100. }
  101. },
  102. D: {
  103. title: 'St50',
  104. keyCode: 52,
  105. detectCity: true,
  106. attributes: {
  107. fwdMaxSpeed: 50,
  108. revMaxSpeed: 50,
  109. roadType: types.street,
  110. flags: 0,
  111. }
  112. },
  113. E: {
  114. title: 'PR50',
  115. keyCode: 53,
  116. detectCity: true,
  117. attributes: {
  118. fwdMaxSpeed: 50,
  119. revMaxSpeed: 50,
  120. roadType: types.primary,
  121. flags: 0,
  122. lockRank: 1,
  123. }
  124. },
  125. F: {
  126. title: 'OR',
  127. keyCode: 54,
  128. clearCity: true,
  129. attributes: {
  130. fwdMaxSpeed: 90,
  131. revMaxSpeed: 90,
  132. roadType: types.offroad,
  133. flags: flags.headlights,
  134. }
  135. },
  136. G: {
  137. title: 'Pr90',
  138. keyCode: 55,
  139. clearCity: true,
  140. attributes: {
  141. fwdMaxSpeed: 90,
  142. revMaxSpeed: 90,
  143. roadType: types.private,
  144. flags: flags.headlights,
  145. }
  146. },
  147. H: {
  148. title: 'St90',
  149. keyCode: 56,
  150. clearCity: true,
  151. attributes: {
  152. fwdMaxSpeed: 90,
  153. revMaxSpeed: 90,
  154. roadType: types.street,
  155. flags: flags.headlights,
  156. }
  157. },
  158. I: {
  159. title: 'PR90',
  160. keyCode: 57,
  161. clearCity: true,
  162. attributes: {
  163. fwdMaxSpeed: 90,
  164. revMaxSpeed: 90,
  165. roadType: types.primary,
  166. flags: flags.headlights,
  167. lockRank: 1,
  168. }
  169. },
  170. };
  171.  
  172. // Update segment attributes
  173. function setupRoad(segment, settings, options = []) {
  174. let addr = segment.getAddress().attributes;
  175. // Change address
  176. let address = {
  177. countryID: addr.country ? addr.country.id : WazeApi.model.countries.top.id,
  178. stateID: addr.state ? addr.state.id : WazeApi.model.states.top.id,
  179. cityName: addr.city ? addr.city.attributes.name : null,
  180. streetName: addr.street ? addr.street.name : null,
  181. };
  182.  
  183. // Settings: Clear city
  184. if (settings.clearCity) {
  185. address.cityName = null;
  186. }
  187.  
  188. // Settings: Detect city
  189. if (settings.detectCity && options.cityName) {
  190. address.cityName = options.cityName;
  191. }
  192.  
  193. // Check city
  194. address.emptyCity = (address.cityName === null);
  195.  
  196. // Check street
  197. address.emptyStreet = (address.streetName === null);
  198.  
  199. // Update segment properties
  200. WazeApi.model.actionManager.add(
  201. new WazeActionUpdateObject(
  202. segment,
  203. settings.attributes
  204. )
  205. );
  206. // Update segment address
  207. WazeApi.model.actionManager.add(
  208. new WazeActionUpdateFeatureAddress(
  209. segment,
  210. address,
  211. {
  212. streetIDField: 'primaryStreetID'
  213. }
  214. )
  215. );
  216. }
  217.  
  218. // Update street handler
  219. function processHandler() {
  220. process(this.dataset.e95);
  221. }
  222. function process(index) {
  223. // Get all selected segments
  224. let selected = WazeApi.selectionManager.getSelectedFeatures();
  225. let segments = [];
  226. let options = {};
  227. // Fill segments array
  228. for (let i = 0, total = selected.length; i < total; i++) {
  229. segments.push(WazeApi.model.segments.getObjectById(selected[i].model.attributes.id))
  230. }
  231. // Filter segments array
  232. segments.filter(segment => segment && segment.getPermissions());
  233. // Try to detect city
  234. if (buttons[index].detectCity) {
  235. console.log('E-95: city detection');
  236. let cityName = null;
  237. for (let i = 0, total = segments.length; i < total; i++) {
  238. console.log('E95: attempt ' + (i+1));
  239. cityName = detectCity(segments[i]);
  240. if (cityName) {
  241. options.cityName = cityName;
  242. break;
  243. }
  244. }
  245. console.log('E-95: detected city ' + cityName);
  246. }
  247.  
  248. for (let i = 0, total = segments.length; i < total; i++) {
  249. setupRoad(segments[i], buttons[index], options);
  250. }
  251. }
  252.  
  253. // Detect city name by connected segments
  254. // Get cities
  255. // W.model.cities.getValidCities();
  256. // Calculate distance to city center
  257. // W.model.cities.getObjectById(644304).getAttributes().geometry.distanceTo(W.model.segments.getObjectById(374688209).getAttributes().geometry);
  258. function detectCity(segment) {
  259. // Check cityName of the segment
  260. if (segment.getAddress().getCity()) {
  261. return segment.getAddress().getCity().getName();
  262. }
  263. let cityName = null;
  264. // TODO: replace follow magic with segment.getConnectedSegments() and segment.getConnectedSegmentsByDirection() when it will work
  265. let connected = WazeApi.model.nodes.getObjectById(segment.getAttributes().fromNodeID).getSegmentIds(); // segments from point A
  266. connected = connected.concat(WazeApi.model.nodes.getObjectById(segment.getAttributes().toNodeID).getSegmentIds()); // segments from point B
  267. connected.filter(id => id !== segment.getID());
  268.  
  269. for (let i = 0, total = connected.length; i < total; i++) {
  270. let city = WazeApi.model.segments.getObjectById(connected[i]).getAddress().getCity();
  271. // skip segments with empty cities
  272. if (city && !city.isEmpty()) {
  273. cityName = city.getName();
  274. break;
  275. }
  276. }
  277. return cityName;
  278. }
  279.  
  280. // Create UI controls everytime when updated DOM of sidebar
  281. // Uses native JS function for better performance
  282. function createUI() {
  283. // container for buttons
  284. let controls = document.createElement('div');
  285. controls.className = 'controls';
  286. // create all buttons
  287. for (let btn in buttons) {
  288. let button = document.createElement('button');
  289. button.className = 'waze-btn waze-btn-small road-e95 road-e95-' + btn;
  290. button.style.backgroundColor = colors[buttons[btn].attributes.roadType];
  291. button.innerHTML = buttons[btn].title;
  292. button.dataset.e95 = btn;
  293. controls.appendChild(button);
  294. }
  295.  
  296. let label = document.createElement('label');
  297. label.className = 'control-label';
  298. label.innerHTML = 'Quick properties';
  299.  
  300. let group = document.createElement('div');
  301. group.className = 'form-group e95';
  302. group.appendChild(label);
  303. group.appendChild(controls);
  304.  
  305. document.getElementById('segment-edit-general').prepend(group);
  306. }
  307.  
  308. function init() {
  309. // Check for changes in the edit-panel
  310. // TODO: try to find solutions to handle native event
  311. var speedlimitsObserver = new MutationObserver(function(mutations) {
  312. mutations.forEach(function(mutation) {
  313. for (let i = 0, total = mutation.addedNodes.length; i < total; i++) {
  314. let node = mutation.addedNodes[i];
  315. // Only fire up if it's a node
  316. if (node.nodeType === Node.ELEMENT_NODE &&
  317. node.querySelector('div.selection') &&
  318. !node.querySelector('div.form-group.e95')) {
  319. createUI();
  320. }
  321. }
  322. });
  323. });
  324.  
  325. speedlimitsObserver.observe(document.getElementById('edit-panel'), { childList: true, subtree: true });
  326. console.log('E95: observer was run');
  327.  
  328. // Handler for all buttons
  329. $('#edit-panel').on('click', 'button.road-e95', processHandler);
  330.  
  331. // Handler for button shortcuts
  332. $(document).on('keyup', function(e) {
  333. if (e.altKey && !e.ctrlKey && !e.shiftKey) {
  334. for (let btn in buttons) {
  335. if (buttons[btn].keyCode === e.which) {
  336. process(btn);
  337. break;
  338. }
  339. }
  340. }
  341. });
  342. console.log('E95: handler was initialized');
  343.  
  344. // Apply styles
  345. let style = document.createElement('style');
  346. style.type = 'text/css';
  347. style.innerHTML =
  348. 'button.waze-btn.road-e95 { margin: 0 4px 4px 0; padding: 2px 8px; min-width: 36px;} ' +
  349. 'button.waze-btn.road-e95:hover { box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1), inset 0 0 100px 100px rgba(255, 255, 255, 0.3); } ' +
  350. 'button.waze-btn.road-e95-F { margin-right: 51px; }'
  351. ;
  352. document.getElementsByTagName('head')[0].appendChild(style);
  353. }
  354.  
  355. // Bootstrap plugin
  356. function bootstrap(tries = 1) {
  357. console.log('E95: attempt ' + tries);
  358. if (WazeApi &&
  359. WazeApi.map &&
  360. WazeApi.model &&
  361. WazeApi.loginManager.user) {
  362. console.log('E95: was initialized');
  363. init();
  364. } else if (tries < 100) {
  365. tries++;
  366. setTimeout(() => bootstrap(tries), 800);
  367. } else {
  368. console.error('E95: initialization failed');
  369. }
  370. }
  371.  
  372. console.log('E95: initialization');
  373. bootstrap();
  374. })(window.jQuery, window.W);