WME E95

Setup road properties in one click

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

  1. // ==UserScript==
  2. // @name WME E95
  3. // @version 0.4.10
  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. // @include https://beta.waze.com/editor*
  10. // @include https://beta.waze.com/*/editor*
  11. // @exclude https://www.waze.com/user/editor*
  12. // @exclude https://beta.waze.com/user/editor*
  13. // @icon data:image/png;base64,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
  14. // @grant none
  15. // @supportURL https://github.com/AntonShevchuk/wme-e95/issues
  16. // @namespace https://greasyfork.org/uk/scripts/382614-wme-e95
  17. // ==/UserScript==
  18. /* jshint esversion: 6 */
  19. /* global require */
  20.  
  21. (function ($, WazeApi) {
  22. 'use strict';
  23.  
  24. let WazeActionUpdateObject = require("Waze/Action/UpdateObject");
  25. let WazeActionUpdateFeatureAddress = require("Waze/Action/UpdateFeatureAddress");
  26.  
  27. // Road Types
  28. // I18n.translations.uk.segment.road_types
  29. const types = {
  30. street: 1,
  31. primary: 2,
  32. // ...
  33. offroad: 8,
  34. // ...
  35. private: 17,
  36. // ...
  37. parking: 20,
  38. };
  39. // Road colors by type
  40. const colors = {
  41. '1': '#ffffeb',
  42. '2': '#f0ea58',
  43. // ...
  44. '8': '#867342',
  45. // ...
  46. '17': '#beba6c',
  47. // ...
  48. '20': '#ababab'
  49. };
  50.  
  51. // Road Flags
  52. // for setup flags use binary operators
  53. // e.g. flags.tunnel | flags.headlights
  54. const flags = {
  55. tunnel: 0b00000001,
  56. // ??? : 0b00000010,
  57. // ??? : 0b00000100,
  58. // ??? : 0b00001000,
  59. unpaved: 0b00010000,
  60. headlights: 0b00100000,
  61. };
  62.  
  63. // Buttons:
  64. // title - for buttons
  65. // keyCode - key for shortcuts (Alt + 1..9)
  66. // detectCity - try to detect city name by closures segments
  67. // clearCity - clear city name
  68. // attributes - native settings for model object
  69. // TODO:
  70. // – check permissions for user level lower than 2
  71. const buttons = {
  72. A: {
  73. title: 'PLR',
  74. keyCode: 49,
  75. detectCity: true,
  76. attributes: {
  77. fwdMaxSpeed: 5,
  78. revMaxSpeed: 5,
  79. fwdMaxSpeedUnverified: false,
  80. revMaxSpeedUnverified: false,
  81. roadType: types.parking,
  82. flags: 0,
  83. lockRank: 0,
  84. }
  85. },
  86. B: {
  87. title: 'Pr20',
  88. keyCode: 50,
  89. detectCity: true,
  90. attributes: {
  91. fwdMaxSpeed: 20,
  92. revMaxSpeed: 20,
  93. fwdMaxSpeedUnverified: false,
  94. revMaxSpeedUnverified: false,
  95. roadType: types.private,
  96. flags: 0,
  97. lockRank: 0,
  98. }
  99. },
  100. C: {
  101. title: 'Pr50',
  102. keyCode: 51,
  103. detectCity: true,
  104. attributes: {
  105. fwdMaxSpeed: 50,
  106. revMaxSpeed: 50,
  107. fwdMaxSpeedUnverified: false,
  108. revMaxSpeedUnverified: false,
  109. roadType: types.private,
  110. flags: 0,
  111. lockRank: 0,
  112. }
  113. },
  114. D: {
  115. title: 'St50',
  116. keyCode: 52,
  117. detectCity: true,
  118. attributes: {
  119. fwdMaxSpeed: 50,
  120. revMaxSpeed: 50,
  121. roadType: types.street,
  122. flags: 0,
  123. lockRank: 0,
  124. }
  125. },
  126. E: {
  127. title: 'PS50',
  128. keyCode: 53,
  129. detectCity: true,
  130. attributes: {
  131. fwdMaxSpeed: 50,
  132. revMaxSpeed: 50,
  133. fwdMaxSpeedUnverified: false,
  134. revMaxSpeedUnverified: false,
  135. roadType: types.primary,
  136. flags: 0,
  137. lockRank: 1,
  138. }
  139. },
  140. F: {
  141. title: 'OR',
  142. keyCode: 54,
  143. clearCity: true,
  144. attributes: {
  145. fwdMaxSpeed: 90,
  146. revMaxSpeed: 90,
  147. fwdMaxSpeedUnverified: false,
  148. revMaxSpeedUnverified: false,
  149. roadType: types.offroad,
  150. lockRank: 0,
  151. }
  152. },
  153. G: {
  154. title: 'Pr90',
  155. keyCode: 55,
  156. clearCity: true,
  157. attributes: {
  158. fwdMaxSpeed: 90,
  159. revMaxSpeed: 90,
  160. fwdMaxSpeedUnverified: false,
  161. revMaxSpeedUnverified: false,
  162. roadType: types.private,
  163. lockRank: 0,
  164. }
  165. },
  166. H: {
  167. title: 'St90',
  168. keyCode: 56,
  169. clearCity: true,
  170. attributes: {
  171. fwdMaxSpeed: 90,
  172. revMaxSpeed: 90,
  173. fwdMaxSpeedUnverified: false,
  174. revMaxSpeedUnverified: false,
  175. roadType: types.street,
  176. lockRank: 0,
  177. }
  178. },
  179. I: {
  180. title: 'PS90',
  181. keyCode: 57,
  182. clearCity: true,
  183. attributes: {
  184. fwdMaxSpeed: 90,
  185. revMaxSpeed: 90,
  186. fwdMaxSpeedUnverified: false,
  187. revMaxSpeedUnverified: false,
  188. roadType: types.primary,
  189. lockRank: 1,
  190. }
  191. },
  192. };
  193.  
  194. // Regions settings, will be merged with default values
  195. // Default values is actual for Ukraine
  196. const speed = {
  197. '20': {
  198. fwdMaxSpeed: 20,
  199. revMaxSpeed: 20,
  200. },
  201. '60': {
  202. fwdMaxSpeed: 60,
  203. revMaxSpeed: 60,
  204. }
  205. };
  206. const preset = {
  207. headlights: {
  208. attributes: {
  209. flags: flags.headlights
  210. }
  211. },
  212. pr60: {
  213. title: 'Pr60',
  214. attributes: speed["60"]
  215. },
  216. st60: {
  217. title: 'St60',
  218. attributes: speed["60"]
  219. },
  220. ps60: {
  221. title: 'PS60',
  222. attributes: speed["60"]
  223. },
  224. };
  225. const region = {
  226. // Belarus
  227. 'BO': {
  228. A: {
  229. attributes: speed["20"]
  230. },
  231. C: preset.pr60,
  232. D: preset.st60,
  233. E: preset.ps60,
  234. F: {
  235. title: 'SUP',
  236. attributes: {
  237. roadType: types.street,
  238. flags: flags.unpaved,
  239. }
  240. }
  241. },
  242. // Russian Federation
  243. 'RS': {
  244. C: preset.pr60,
  245. D: preset.st60,
  246. E: preset.ps60,
  247. },
  248. // Ukraine
  249. 'UP': {
  250. F: preset.headlights,
  251. G: preset.headlights,
  252. H: preset.headlights,
  253. I: preset.headlights,
  254. }
  255. };
  256.  
  257. // Get Button settings
  258. function getButtonConfig(index) {
  259. let btn = {};
  260. if (region[WazeApi.model.countries.top.abbr]
  261. && region[WazeApi.model.countries.top.abbr][index]) {
  262. $.extend(true, btn, buttons[index], region[WazeApi.model.countries.top.abbr][index]);
  263. } else {
  264. btn = buttons[index];
  265. }
  266. return btn;
  267. }
  268.  
  269. // Update segment attributes
  270. function setupRoad(segment, settings, options = []) {
  271. let addr = segment.getAddress().attributes;
  272. // Change address
  273. let address = {
  274. countryID: addr.country ? addr.country.id : WazeApi.model.countries.top.id,
  275. stateID: addr.state ? addr.state.id : WazeApi.model.states.top.id,
  276. cityName: addr.city ? addr.city.attributes.name : null,
  277. streetName: addr.street ? addr.street.name : null,
  278. };
  279. // Settings: Clear city
  280. if (settings.clearCity) {
  281. address.cityName = null;
  282. }
  283. // Settings: Detect city
  284. if (settings.detectCity && options.cityName) {
  285. address.cityName = options.cityName;
  286. }
  287. // Check city
  288. address.emptyCity = (address.cityName === null);
  289. // Check street
  290. address.emptyStreet = (address.streetName === null) || (address.streetName === '');
  291. // Update segment properties
  292. WazeApi.model.actionManager.add(
  293. new WazeActionUpdateObject(
  294. segment,
  295. settings.attributes
  296. )
  297. );
  298. // Update segment address
  299. WazeApi.model.actionManager.add(
  300. new WazeActionUpdateFeatureAddress(
  301. segment,
  302. address,
  303. {
  304. streetIDField: 'primaryStreetID'
  305. }
  306. )
  307. );
  308. }
  309.  
  310. // Update street handler
  311. function processHandler() {
  312. process(this.dataset.e95);
  313. }
  314.  
  315. function process(index) {
  316. // Get all selected segments
  317. let selected = WazeApi.selectionManager.getSelectedFeatures();
  318. let segments = [];
  319. let options = {};
  320. // Fill segments array
  321. for (let i = 0, total = selected.length; i < total; i++) {
  322. segments.push(WazeApi.model.segments.getObjectById(selected[i].model.attributes.id))
  323. }
  324. // Filter segments array
  325. segments = segments.filter(segment => segment && segment.getPermissions());
  326. // Try to detect city
  327. if (getButtonConfig(index).detectCity) {
  328. console.log('E-95: city detection');
  329. let cityName = null;
  330. for (let i = 0, total = segments.length; i < total; i++) {
  331. console.log('E95: attempt ' + (i + 1));
  332. cityName = detectCity(segments[i]);
  333. if (cityName) {
  334. options.cityName = cityName;
  335. break;
  336. }
  337. }
  338. console.log('E-95: detected city ' + cityName);
  339. }
  340.  
  341. for (let i = 0, total = segments.length; i < total; i++) {
  342. setupRoad(segments[i], getButtonConfig(index), options);
  343. }
  344. }
  345.  
  346. // Detect city name by connected segments
  347. function detectCity(segment) {
  348. // Check cityName of the segment
  349. if (segment.getAddress().getCity() && !segment.getAddress().getCity().isEmpty()) {
  350. return segment.getAddress().getCity().getName();
  351. }
  352. let cityName = null;
  353. // TODO: replace follow magic with segment.getConnectedSegments() and segment.getConnectedSegmentsByDirection() when it will work
  354. let connected = WazeApi.model.nodes.getObjectById(segment.getAttributes().fromNodeID).getSegmentIds(); // segments from point A
  355. connected = connected.concat(WazeApi.model.nodes.getObjectById(segment.getAttributes().toNodeID).getSegmentIds()); // segments from point B
  356. connected.filter(id => id !== segment.getID());
  357.  
  358. for (let i = 0, total = connected.length; i < total; i++) {
  359. let city = WazeApi.model.segments.getObjectById(connected[i]).getAddress().getCity();
  360. // skip segments with empty cities
  361. if (city && !city.isEmpty()) {
  362. cityName = city.getName();
  363. break;
  364. }
  365. }
  366. return cityName;
  367. }
  368.  
  369. // Create UI controls everytime when updated DOM of sidebar
  370. // Uses native JS function for better performance
  371. function createUI() {
  372. // container for buttons
  373. let controls = document.createElement('div');
  374. controls.className = 'controls';
  375. // create all buttons
  376. for (let btn in buttons) {
  377. let button = document.createElement('button');
  378. let buttonConfig = getButtonConfig(btn);
  379. button.className = 'waze-btn waze-btn-small road-e95 road-e95-' + btn;
  380. button.style.backgroundColor = colors[buttonConfig.attributes.roadType];
  381. button.innerHTML = buttonConfig.title;
  382. button.dataset.e95 = btn;
  383. controls.appendChild(button);
  384. }
  385.  
  386. let label = document.createElement('label');
  387. label.className = 'control-label';
  388. label.innerHTML = 'Quick properties';
  389.  
  390. let group = document.createElement('div');
  391. group.className = 'form-group e95';
  392. group.appendChild(label);
  393. group.appendChild(controls);
  394.  
  395. document.getElementById('segment-edit-general').prepend(group);
  396. }
  397.  
  398. function init() {
  399. // Check for changes in the edit-panel
  400. // TODO: try to find solutions to handle native event
  401. let speedLimitsObserver = new MutationObserver(function (mutations) {
  402. mutations.forEach(function (mutation) {
  403. for (let i = 0, total = mutation.addedNodes.length; i < total; i++) {
  404. let node = mutation.addedNodes[i];
  405. // Only fire up if it's a node
  406. if (node.nodeType === Node.ELEMENT_NODE &&
  407. node.querySelector('div.selection') &&
  408. node.querySelector('div.hide-walking-trail').style.display !== 'none' &&
  409. !node.querySelector('div.form-group.e95')) {
  410. createUI();
  411. }
  412. }
  413. });
  414. });
  415.  
  416. speedLimitsObserver.observe(document.getElementById('edit-panel'), {childList: true, subtree: true});
  417. console.log('E95: observer was run');
  418.  
  419. // Handler for all buttons
  420. $('#edit-panel').on('click', 'button.road-e95', processHandler);
  421.  
  422. // Handler for button shortcuts
  423. $(document).on('keyup', function (e) {
  424. if (e.altKey && !e.ctrlKey && !e.shiftKey) {
  425. for (let btn in buttons) {
  426. if (buttons[btn].keyCode === e.which) {
  427. process(btn);
  428. break;
  429. }
  430. }
  431. }
  432. });
  433. console.log('E95: handler was initialized');
  434.  
  435. // Apply styles
  436. let style = document.createElement('style');
  437. style.type = 'text/css';
  438. style.innerHTML =
  439. 'button.waze-btn.road-e95 { margin: 0 4px 4px 0; padding: 2px; width: 42px; } ' +
  440. '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); } ' +
  441. 'button.waze-btn.road-e95-E { margin-right: 42px; }' +
  442. 'button.waze-btn.road-e95-F { margin-right: 50px; }'
  443. ;
  444. document.getElementsByTagName('head')[0].appendChild(style);
  445. }
  446.  
  447. // Bootstrap plugin
  448. function bootstrap(tries = 1) {
  449. console.log('E95: attempt ' + tries);
  450. if (WazeApi &&
  451. WazeApi.map &&
  452. WazeApi.model &&
  453. WazeApi.loginManager.user) {
  454. console.log('E95: was initialized');
  455. init();
  456. } else if (tries < 100) {
  457. tries++;
  458. setTimeout(() => bootstrap(tries), 800);
  459. } else {
  460. console.error('E95: initialization failed');
  461. }
  462. }
  463.  
  464. console.log('E95: initialization');
  465. bootstrap();
  466. })(window.jQuery, window.W);