WME BDP Check (beta)

Check for possible BDP routes between two selected segments.

目前为 2021-07-28 提交的版本。查看 最新版本

  1. /* eslint-disable no-nested-ternary */
  2. // ==UserScript==
  3. // @name WME BDP Check (beta)
  4. // @namespace https://greasyfork.org/users/166843
  5. // @version 2021.07.28.01
  6. // @description Check for possible BDP routes between two selected segments.
  7. // @author dBsooner
  8. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
  9. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  10. // @grant none
  11. // @license GPLv3
  12. // ==/UserScript==
  13.  
  14. /* global document, localStorage, MutationObserver, window, $, performance, GM_info, W, WazeWrap */
  15.  
  16. const ALERT_UPDATE = true,
  17. DEBUG = true,
  18. LOAD_BEGIN_TIME = performance.now(),
  19. SCRIPT_AUTHOR = GM_info.script.author,
  20. SCRIPT_FORUM_URL = 'https://www.waze.com/forum/viewtopic.php?f=819&t=294789',
  21. SCRIPT_GF_URL = 'https://greasyfork.org/en/scripts/393407-wme-bdp-check',
  22. SCRIPT_NAME = GM_info.script.name.replace('(beta)', 'β'),
  23. SCRIPT_VERSION = GM_info.script.version,
  24. SCRIPT_VERSION_CHANGES = ['<b>NEW:</b> Check detour selection for unroutable segment types.',
  25. '<b>CHANGE:</b> WME map object references.',
  26. '<b>CHANGE:</b> Routes must be selected by clicking first bracketing segment first and second bracketing segment last.',
  27. '<b>BUGFIX:</b> Zoom levels 1-3 do not contain LS or PS segments.',
  28. '<b>BUGFIX:</b> Better handling of multiple segments in detour route connected to same final node.'],
  29. SETTINGS_STORE_NAME = 'WMEBDPC',
  30. sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds)),
  31. _timeouts = { bootstrap: undefined, saveSettingsToStorage: undefined },
  32. _editPanelObserver = new MutationObserver(mutations => {
  33. if ((W.selectionManager.getSegmentSelection().segments.length === 0) || ($('#WME-BDPC-BUTTONS-DIV').length > 0))
  34. return;
  35. const addedChildren = mutations.filter(mutation => (mutation.type === 'childList')).filter(mutatedChild => (mutatedChild.addedNodes.length > 0));
  36. if (addedChildren.filter(
  37. addedChild => (
  38. (addedChild.addedNodes[0].className
  39. && (addedChild.addedNodes[0].className.indexOf('segment') > -1)
  40. )
  41. || (addedChild.addedNodes[0].firstElementChild && addedChild.addedNodes[0].firstElementChild.className
  42. && (addedChild.addedNodes[0].firstElementChild.className.indexOf('segment') > -1)
  43. )
  44. )
  45. ).length > 0) {
  46. if (W.selectionManager.getSegmentSelection().segments.length < 2)
  47. insertCheckBDPButton(true);
  48. else
  49. insertCheckBDPButton();
  50. }
  51. });
  52. let _settings = {},
  53. _pathEndSegId,
  54. _restoreZoomLevel,
  55. _restoreMapCenter;
  56.  
  57. function log(message) { console.log('WME-BDPC:', message); }
  58. function logError(message) { console.error('WME-BDPC:', message); }
  59. function logWarning(message) { console.warn('WME-BDPC:', message); }
  60. function logDebug(message) {
  61. if (DEBUG)
  62. console.log('WME-BDPC:', message);
  63. }
  64.  
  65. async function loadSettingsFromStorage() {
  66. const defaultSettings = {
  67. lastSaved: 0,
  68. lastVersion: undefined
  69. },
  70. loadedSettings = $.parseJSON(localStorage.getItem(SETTINGS_STORE_NAME)),
  71. serverSettings = await WazeWrap.Remote.RetrieveSettings(SETTINGS_STORE_NAME);
  72. _settings = $.extend({}, defaultSettings, loadedSettings);
  73. if (serverSettings && (serverSettings.lastSaved > _settings.lastSaved))
  74. $.extend(_settings, serverSettings);
  75. _timeouts.saveSettingsToStorage = window.setTimeout(saveSettingsToStorage, 5000);
  76. return Promise.resolve();
  77. }
  78.  
  79. function saveSettingsToStorage() {
  80. checkTimeout({ timeout: 'saveSettingsToStorage' });
  81. if (localStorage) {
  82. _settings.lastVersion = SCRIPT_VERSION;
  83. _settings.lastSaved = Date.now();
  84. localStorage.setItem(SETTINGS_STORE_NAME, JSON.stringify(_settings));
  85. WazeWrap.Remote.SaveSettings(SETTINGS_STORE_NAME, _settings);
  86. logDebug('Settings saved.');
  87. }
  88. }
  89.  
  90. function showScriptInfoAlert() {
  91. if (ALERT_UPDATE && SCRIPT_VERSION !== _settings.lastVersion) {
  92. let releaseNotes = '';
  93. releaseNotes += '<p>What\'s new:</p>';
  94. if (SCRIPT_VERSION_CHANGES.length > 0) {
  95. releaseNotes += '<ul>';
  96. for (let idx = 0; idx < SCRIPT_VERSION_CHANGES.length; idx++)
  97. releaseNotes += `<li>${SCRIPT_VERSION_CHANGES[idx]}`;
  98. releaseNotes += '</ul>';
  99. }
  100. else {
  101. releaseNotes += '<ul><li>Nothing major.</ul>';
  102. }
  103. WazeWrap.Interface.ShowScriptUpdate(SCRIPT_NAME, SCRIPT_VERSION, releaseNotes, SCRIPT_GF_URL, SCRIPT_FORUM_URL);
  104. }
  105. }
  106.  
  107. function checkTimeout(obj) {
  108. if (obj.toIndex) {
  109. if (_timeouts[obj.timeout] && (_timeouts[obj.timeout][obj.toIndex] !== undefined)) {
  110. window.clearTimeout(_timeouts[obj.timeout][obj.toIndex]);
  111. _timeouts[obj.timeout][obj.toIndex] = undefined;
  112. }
  113. }
  114. else {
  115. if (_timeouts[obj.timeout] !== undefined)
  116. window.clearTimeout(_timeouts[obj.timeout]);
  117. _timeouts[obj.timeout] = undefined;
  118. }
  119. }
  120.  
  121. function getMidpoint(startSeg, endSeg) {
  122. let startCenter = startSeg.getCenter(),
  123. endCenter = endSeg.getCenter();
  124. startCenter = WazeWrap.Geometry.ConvertTo4326(startCenter.x, startCenter.y);
  125. endCenter = WazeWrap.Geometry.ConvertTo4326(endCenter.x, endCenter.y);
  126. let lon1 = startCenter.lon,
  127. lat1 = startCenter.lat,
  128. lat2 = endCenter.lat;
  129. const piDiv = Math.PI / 180,
  130. divPi = 180 / Math.PI,
  131. lon2 = endCenter.lon,
  132. dLon = ((lon2 - lon1) * piDiv);
  133. lat1 *= piDiv;
  134. lat2 *= piDiv;
  135. lon1 *= piDiv;
  136. const bX = Math.cos(lat2) * Math.cos(dLon),
  137. bY = Math.cos(lat2) * Math.sin(dLon),
  138. lat3 = (Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt((Math.cos(lat1) + bX) * (Math.cos(lat1) + bX) + bY * bY))) * divPi,
  139. lon3 = (lon1 + Math.atan2(bY, Math.cos(lat1) + bX)) * divPi;
  140. return WazeWrap.Geometry.ConvertTo900913(lon3, lat3);
  141. }
  142.  
  143. async function doZoom(restore = false, zoom = -1, coordObj = {}) {
  144. if ((zoom === -1) || (Object.entries(coordObj).length === 0))
  145. return Promise.resolve();
  146. W.map.setCenter(coordObj);
  147. if (W.map.getZoom() !== zoom)
  148. W.map.getOLMap().zoomTo(zoom);
  149. if (restore) {
  150. _restoreZoomLevel = undefined;
  151. _restoreMapCenter = undefined;
  152. }
  153. else {
  154. WazeWrap.Alerts.info(SCRIPT_NAME, 'Waiting for WME to populate after zoom level change.<br>Proceeding in 2 seconds...');
  155. await sleep(2000);
  156. $('#toast-container-wazedev > .toast-info').find('.toast-close-button').click();
  157. }
  158. return Promise.resolve();
  159. }
  160.  
  161. function rtgContinuityCheck(segs = []) {
  162. if (segs.length < 2)
  163. return false;
  164. const rtg = { 7: 'mH', 6: 'MHFW', 3: 'MHFW' },
  165. seg1rtg = rtg[segs[0].attributes.roadType];
  166. segs.splice(0, 1);
  167. return segs.every(el => seg1rtg === rtg[el.attributes.roadType]);
  168. }
  169.  
  170. function nameContinuityCheck(segs = []) {
  171. if (segs.length < 2)
  172. return false;
  173. const bs1StreetNames = [],
  174. bs2StreetNames = [],
  175. streetNames = [];
  176. let street;
  177. if (segs[0].attributes.primaryStreetID) {
  178. street = W.model.streets.getObjectById(segs[0].attributes.primaryStreetID);
  179. if (street && street.name && (street.name.length > 0)) {
  180. if (segs.length === 2)
  181. streetNames.push(street.name);
  182. else
  183. bs1StreetNames.push(street.name);
  184. }
  185. }
  186. if (segs[0].attributes.streetIDs.length > 0) {
  187. for (let i = 0; i < segs[0].attributes.streetIDs.length; i++) {
  188. street = W.model.streets.getObjectById(segs[0].attributes.streetIDs[i]);
  189. if (street && street.name && (street.name.length > 0)) {
  190. if (segs.length === 2)
  191. streetNames.push(street.name);
  192. else
  193. bs1StreetNames.push(street.name);
  194. }
  195. }
  196. }
  197. if (((segs.length === 2) && (streetNames.length === 0))
  198. || ((segs.length > 2) && (bs1StreetNames.length === 0)))
  199. return false;
  200. if (segs.length === 2) {
  201. if (segs[1].attributes.primaryStreetID) {
  202. street = W.model.streets.getObjectById(segs[1].attributes.primaryStreetID);
  203. if (street && street.name && streetNames.includes(street.name))
  204. return true;
  205. }
  206. if (segs[1].attributes.streetIDs.length > 0) {
  207. for (let i = 0; i < segs[1].attributes.streetIDs.length; i++) {
  208. street = W.model.streets.getObjectById(segs[1].attributes.streetIDs[i]);
  209. if (street && street.name && streetNames.includes(street.name))
  210. return true;
  211. }
  212. }
  213. }
  214. else {
  215. segs.splice(0, 1);
  216. const lastIdx = segs.length - 1;
  217. if (segs[lastIdx].attributes.primaryStreetID) {
  218. street = W.model.streets.getObjectById(segs[lastIdx].attributes.primaryStreetID);
  219. if (street && street.name && (street.name.length > 0))
  220. bs2StreetNames.push(street.name);
  221. }
  222. if (segs[lastIdx].attributes.streetIDs.length > 0) {
  223. for (let i = 0; i < segs[lastIdx].attributes.streetIDs.length; i++) {
  224. street = W.model.streets.getObjectById(segs[lastIdx].attributes.streetIDs[i]);
  225. if (street && street.name && (street.name.length > 0))
  226. bs2StreetNames.push(street.name);
  227. }
  228. }
  229. if (bs2StreetNames.length === 0)
  230. return false;
  231. segs.splice(-1, 1);
  232. return segs.every(el => {
  233. if (el.attributes.primaryStreetID) {
  234. street = W.model.streets.getObjectById(el.attributes.primaryStreetID);
  235. if (street && street.name && (bs1StreetNames.includes(street.name) || bs2StreetNames.includes(street.name)))
  236. return true;
  237. }
  238. if (el.attributes.streetIDs.length > 0) {
  239. for (let i = 0; i < el.attributes.streetIDs.length; i++) {
  240. street = W.model.streets.getObjectById(el.attributes.streetIDs[i]);
  241. if (street && street.name && (bs1StreetNames.includes(street.name) || bs2StreetNames.includes(street.name)))
  242. return true;
  243. }
  244. }
  245. return false;
  246. });
  247. }
  248. return false;
  249. }
  250.  
  251. async function findLiveMapRoutes(startSeg, endSeg, maxLength) {
  252. const start900913center = startSeg.getCenter(),
  253. end900913center = endSeg.getCenter(),
  254. start4326Center = WazeWrap.Geometry.ConvertTo4326(start900913center.x, start900913center.y),
  255. end4326Center = WazeWrap.Geometry.ConvertTo4326(end900913center.x, end900913center.y),
  256. url = (W.model.countries.getObjectById(235) || W.model.countries.getObjectById(40) || W.model.countries.getObjectById(182))
  257. ? '/RoutingManager/routingRequest'
  258. : W.model.countries.getObjectById(106)
  259. ? '/il-RoutingManager/routingRequest'
  260. : '/row-RoutingManager/routingRequest',
  261. data = {
  262. from: `x:${start4326Center.lon} y:${start4326Center.lat}`,
  263. to: `x:${end4326Center.lon} y:${end4326Center.lat}`,
  264. returnJSON: true,
  265. returnGeometries: true,
  266. returnInstructions: false,
  267. timeout: 60000,
  268. type: 'HISTORIC_TIME',
  269. nPaths: 6,
  270. clientVersion: '4.0.0',
  271. vehType: 'PRIVATE',
  272. options: 'AVOID_TOLL_ROADS:f,AVOID_PRIMARIES:f,AVOID_DANGEROUS_TURNS:f,AVOID_FERRIES:f,ALLOW_UTURNS:t'
  273. },
  274. returnRoutes = [];
  275. let jsonData;
  276. try {
  277. jsonData = await $.ajax({
  278. dataType: 'JSON',
  279. cache: false,
  280. url,
  281. data,
  282. traditional: true,
  283. dataFilter: retData => retData.replace(/NaN/g, '0')
  284. }).fail((response, textStatus, errorThrown) => {
  285. logWarning(`Route request failed ${(textStatus !== null ? `with ${textStatus}` : '')}\r\n${errorThrown}!`);
  286. });
  287. }
  288. catch (error) {
  289. logWarning(JSON.stringify(error));
  290. jsonData = { error };
  291. }
  292. if (!jsonData) {
  293. logWarning('No data returned.');
  294. }
  295. else if (jsonData.error !== undefined) {
  296. logWarning(((typeof jsonData.error === 'object') ? $.parseJSON(jsonData.error) : jsonData.error.replace('|', '\r\n')));
  297. }
  298. else {
  299. let routes = (jsonData.coords !== undefined) ? [jsonData] : [];
  300. if (jsonData.alternatives !== undefined)
  301. routes = routes.concat(jsonData.alternatives);
  302. routes.forEach(route => {
  303. const fullRouteSegIds = route.response.results.map(result => result.path.segmentId),
  304. fullRouteSegs = W.model.segments.getByIds(fullRouteSegIds);
  305. if (nameContinuityCheck(fullRouteSegs) && rtgContinuityCheck(fullRouteSegs)) {
  306. const routeDistance = route.response.results.map(result => result.length).slice(1, -1).reduce((a, b) => a + b);
  307. if (routeDistance < maxLength)
  308. returnRoutes.push(route.response.results.map(result => result.path.segmentId));
  309. }
  310. });
  311. }
  312. return new Promise(resolve => resolve(returnRoutes));
  313. }
  314.  
  315. function findDirectRoute(obj = {}) {
  316. const {
  317. maxLength, startSeg, startNode, endSeg, endNodeIds
  318. } = obj,
  319. processedSegs = [],
  320. sOutIds = startNode.attributes.segIDs.filter(segId => segId !== startSeg.attributes.id),
  321. segIdsFilter = (nextSegIds, alreadyProcessed) => nextSegIds.filter(value => alreadyProcessed.indexOf(value) === -1),
  322. getNextSegs = (nextSegIds, curSeg, nextNode) => {
  323. const rObj = { addPossibleRouteSegments: [] };
  324. for (let i = 0; i < nextSegIds.length; i++) {
  325. const nextSeg = W.model.segments.getObjectById(nextSegIds[i]);
  326. if (curSeg.isTurnAllowed(nextSeg, nextNode)
  327. && nameContinuityCheck([curSeg, nextSeg])
  328. && (nameContinuityCheck([startSeg, nextSeg]) || nameContinuityCheck([endSeg, nextSeg]))
  329. ) {
  330. if (!processedSegs.some(o => (o.fromSegId === curSeg.attributes.id) && (o.toSegId === nextSegIds[i]))) {
  331. rObj.addPossibleRouteSegments.push({ nextSegStartNode: nextNode, nextSeg });
  332. break;
  333. }
  334. }
  335. }
  336. return rObj;
  337. },
  338. returnRoutes = [];
  339. for (let i = 0, len = sOutIds.length; i < len; i++) {
  340. const sOut = W.model.segments.getObjectById(sOutIds[i]);
  341. if (startSeg.isTurnAllowed(sOut, startNode) && nameContinuityCheck([startSeg, sOut])) {
  342. const possibleRouteSegments = [{
  343. curSeg: startSeg,
  344. nextSegStartNode: startNode,
  345. nextSeg: sOut
  346. }];
  347. let curLength = 0;
  348. while (possibleRouteSegments.length > 0) {
  349. const idx = possibleRouteSegments.length - 1,
  350. curSeg = possibleRouteSegments[idx].nextSeg,
  351. curSegStartNode = possibleRouteSegments[idx].nextSegStartNode,
  352. curSegEndNode = curSeg.getOtherNode(curSegStartNode),
  353. curSegEndNodeSOutIds = segIdsFilter(curSegEndNode.attributes.segIDs, possibleRouteSegments.map(routeSeg => routeSeg.nextSeg.attributes.id));
  354. if ((endNodeIds.indexOf(curSegEndNode.attributes.id) > -1) && curSeg.isTurnAllowed(endSeg, curSegEndNode)) {
  355. returnRoutes.push([startSeg.attributes.id].concat(possibleRouteSegments.map(routeSeg => routeSeg.nextSeg.attributes.id), [endSeg.attributes.id]));
  356. possibleRouteSegments.splice(idx, 1);
  357. }
  358. else if ((curLength + curSeg.attributes.length) > maxLength) {
  359. possibleRouteSegments.splice(idx, 1);
  360. curLength -= curSeg.attributes.length;
  361. }
  362. else {
  363. const nextSegObj = getNextSegs(curSegEndNodeSOutIds, curSeg, curSegEndNode);
  364. if (nextSegObj.addPossibleRouteSegments.length > 0) {
  365. curLength += curSeg.attributes.length;
  366. possibleRouteSegments.push(nextSegObj.addPossibleRouteSegments[0]);
  367. processedSegs.push({ fromSegId: curSeg.attributes.id, toSegId: nextSegObj.addPossibleRouteSegments[0].nextSeg.attributes.id });
  368. }
  369. else {
  370. curLength -= curSeg.attributes.length;
  371. possibleRouteSegments.splice(idx, 1);
  372. }
  373. }
  374. }
  375. if (returnRoutes.length > 0)
  376. break;
  377. }
  378. else {
  379. processedSegs.push({ fromSegId: startSeg.attributes.id, toSegId: sOut.attributes.id });
  380. }
  381. }
  382. return returnRoutes;
  383. }
  384.  
  385. async function doCheckBDP(viaLM = false) {
  386. const selectedFeatures = W.selectionManager.getSelectedFeatures(),
  387. segmentSelection = W.selectionManager.getSegmentSelection(),
  388. numSelectedFeatureSegments = selectedFeatures.filter(feature => feature.model.type === 'segment').length;
  389. let startSeg,
  390. endSeg,
  391. directRoutes = [];
  392. if ((segmentSelection.segments.length < 2) || (numSelectedFeatureSegments < 2)) {
  393. WazeWrap.Alerts.error(SCRIPT_NAME, 'You must select either the two <i>bracketing segments</i> or an entire detour route with <i>bracketing segments</i>.');
  394. return;
  395. }
  396. if (segmentSelection.multipleConnectedComponents && ((segmentSelection.segments.length > 2) || (numSelectedFeatureSegments > 2))) {
  397. WazeWrap.Alerts.error(SCRIPT_NAME,
  398. 'If you select more than 2 segments, the selection of segments must be continuous.<br><br>'
  399. + 'Either select just the two bracketing segments or an entire detour route with bracketing segments.');
  400. return;
  401. }
  402. if (!segmentSelection.multipleConnectedComponents && (segmentSelection.segments.length === 2)) {
  403. WazeWrap.Alerts.error(SCRIPT_NAME, 'You selected only two segments and they connect to each other. There are no alternate routes.');
  404. return;
  405. }
  406. if (segmentSelection.segments.length === 2) {
  407. [startSeg, endSeg] = segmentSelection.segments;
  408. }
  409. else if (_pathEndSegId !== undefined) {
  410. if (segmentSelection.segments[0].attributes.id === _pathEndSegId) {
  411. [endSeg] = segmentSelection.segments;
  412. startSeg = segmentSelection.segments[segmentSelection.segments.length - 1];
  413. }
  414. else {
  415. [startSeg] = segmentSelection.segments;
  416. endSeg = segmentSelection.segments[segmentSelection.segments.length - 1];
  417. }
  418. const routeNodeIds = segmentSelection.segments.slice(1, -1).flatMap(segment => [segment.attributes.toNodeID, segment.attributes.fromNodeID]);
  419. if (routeNodeIds.some(nodeId => endSeg.attributes.fromNodeID === nodeId))
  420. endSeg.attributes.bdpcheck = { routeFarEndNodeId: endSeg.attributes.toNodeID };
  421. else
  422. endSeg.attributes.bdpcheck = { routeFarEndNodeId: endSeg.attributes.fromNodeID };
  423. }
  424. else {
  425. [startSeg] = segmentSelection.segments;
  426. endSeg = segmentSelection.segments[segmentSelection.segments.length - 1];
  427. const routeNodeIds = segmentSelection.segments.slice(1, -1).flatMap(segment => [segment.attributes.toNodeID, segment.attributes.fromNodeID]);
  428. if (routeNodeIds.some(nodeId => endSeg.attributes.fromNodeID === nodeId))
  429. endSeg.attributes.bdpcheck = { routeFarEndNodeId: endSeg.attributes.toNodeID };
  430. else
  431. endSeg.attributes.bdpcheck = { routeFarEndNodeId: endSeg.attributes.fromNodeID };
  432. }
  433. if ((startSeg.attributes.roadType < 3) || (startSeg.attributes.roadType === 4) || (startSeg.attributes.roadType === 5) || (startSeg.attributes.roadType > 7)
  434. || (endSeg.attributes.roadType < 3) || (endSeg.attributes.roadType === 4) || (endSeg.attributes.roadType === 5) || (endSeg.attributes.roadType > 7)
  435. ) {
  436. WazeWrap.Alerts.info(SCRIPT_NAME, 'At least one of the bracketing selected segments is not in the correct road type group for BDP.');
  437. return;
  438. }
  439. if (!rtgContinuityCheck([startSeg, endSeg])) {
  440. WazeWrap.Alerts.info(SCRIPT_NAME, 'One bracketing segment is a minor highway while the other is not. BDP only applies when bracketing segments are in the same road type group.');
  441. return;
  442. }
  443. if (!nameContinuityCheck([startSeg, endSeg])) {
  444. WazeWrap.Alerts.info(SCRIPT_NAME, 'The bracketing segments do not share a street name. BDP will not be applied to any route.');
  445. return;
  446. }
  447. const maxLength = (startSeg.attributes.roadType === 7) ? 5000 : 50000;
  448. if (segmentSelection.segments.length === 2) {
  449. if (((startSeg.attributes.roadType === 7) && (W.map.getZoom() > 4))
  450. || ((startSeg.attributes.roadType !== 7) && (W.map.getZoom() > 3))) {
  451. _restoreZoomLevel = W.map.getZoom();
  452. _restoreMapCenter = W.map.getCenter();
  453. await doZoom(false, (startSeg.attributes.roadType === 7) ? 4 : 3, getMidpoint(startSeg, endSeg));
  454. }
  455. if (viaLM) {
  456. directRoutes = directRoutes.concat(await findLiveMapRoutes(startSeg, endSeg, maxLength));
  457. }
  458. else {
  459. const startSegDirection = startSeg.getDirection(),
  460. endSegDirection = endSeg.getDirection();
  461. const startNodeObjs = [],
  462. endNodeObjs = [];
  463. if ((startSegDirection !== 2) && startSeg.getToNode())
  464. startNodeObjs.push(startSeg.getToNode());
  465. if ((startSegDirection !== 1) && startSeg.getFromNode())
  466. startNodeObjs.push(startSeg.getFromNode());
  467. if ((endSegDirection !== 2) && endSeg.getFromNode())
  468. endNodeObjs.push(endSeg.getFromNode());
  469. if ((endSegDirection !== 1) && endSeg.getToNode())
  470. endNodeObjs.push(endSeg.getToNode());
  471. for (let i = 0; i < startNodeObjs.length; i++) {
  472. const startNode = startNodeObjs[i];
  473. directRoutes = findDirectRoute({
  474. maxLength, startSeg, startNode, endSeg, endNodeIds: endNodeObjs.map(nodeObj => nodeObj && nodeObj.attributes.id)
  475. });
  476. if (directRoutes.length > 0)
  477. break;
  478. }
  479. }
  480. }
  481. else {
  482. const routeSegIds = W.selectionManager.getSegmentSelection().getSelectedSegments()
  483. .map(segment => segment.attributes.id)
  484. .filter(segId => (segId !== endSeg.attributes.id) && (segId !== startSeg.attributes.id)),
  485. endNodeObj = endSeg.getOtherNode(W.model.nodes.getObjectById(endSeg.attributes.bdpcheck.routeFarEndNodeId)),
  486. startSegDirection = startSeg.getDirection(),
  487. startNodeObjs = [],
  488. lastDetourSegId = routeSegIds.filter(el => endNodeObj.attributes.segIDs.includes(el));
  489. let lastDetourSeg;
  490. if (lastDetourSegId.length === 1) {
  491. lastDetourSeg = W.model.segments.getObjectById(lastDetourSegId);
  492. }
  493. else {
  494. const oneWayTest = W.model.segments.getByIds(lastDetourSegId).filter(seg => seg.isOneWay() && seg.isTurnAllowed(endSeg, endNodeObj));
  495. if (oneWayTest.length === 1) {
  496. [lastDetourSeg] = oneWayTest;
  497. }
  498. else {
  499. WazeWrap.Alerts.info(SCRIPT_NAME, `Could not determine the last detour segment. Please send ${SCRIPT_AUTHOR} a message with a PL describing this issue. Thank you!`);
  500. return;
  501. }
  502. }
  503. const detourSegs = segmentSelection.segments.slice(1, -1),
  504. detourSegTypes = [...new Set(detourSegs.map(segment => segment.attributes.roadType))];
  505. if ([9, 10, 16, 18, 19, 22].some(type => detourSegTypes.indexOf(type) > -1)) {
  506. WazeWrap.Alerts.info(SCRIPT_NAME, 'Your selection contains one more more segments with an unrouteable road type. The selected route is not a valid route.');
  507. return;
  508. }
  509. if (![1].some(type => detourSegTypes.indexOf(type) > -1)) {
  510. if (((startSeg.attributes.roadType === 7) && (W.map.getZoom() > 4))
  511. || ((startSeg.attributes.roadType !== 7) && (W.map.getZoom() > 3))) {
  512. _restoreZoomLevel = W.map.getZoom();
  513. _restoreMapCenter = W.map.getCenter();
  514. await doZoom(false, (startSeg.attributes.roadType === 7) ? 4 : 3, getMidpoint(startSeg, endSeg));
  515. }
  516. }
  517. if ((startSegDirection !== 2) && startSeg.getToNode())
  518. startNodeObjs.push(startSeg.getToNode());
  519. if ((startSegDirection !== 1) && startSeg.getFromNode())
  520. startNodeObjs.push(startSeg.getFromNode());
  521. if (nameContinuityCheck([lastDetourSeg, endSeg])) {
  522. WazeWrap.Alerts.info(SCRIPT_NAME, 'BDP will not be applied to this detour route because the last detour segment and the second bracketing segment share a common street name.');
  523. doZoom(true, _restoreZoomLevel, _restoreMapCenter);
  524. return;
  525. }
  526. if (rtgContinuityCheck([lastDetourSeg, endSeg])) {
  527. WazeWrap.Alerts.info(SCRIPT_NAME, 'BDP will not be applied to this detour route because the last detour segment and the second bracketing segment are in the same road type group.');
  528. doZoom(true, _restoreZoomLevel, _restoreMapCenter);
  529. return;
  530. }
  531. if (detourSegs.length < 2) {
  532. WazeWrap.Alerts.info(SCRIPT_NAME, 'BDP will not be applied to this detour route because it is less than 2 segments long.');
  533. doZoom(true, _restoreZoomLevel, _restoreMapCenter);
  534. return;
  535. }
  536. if (detourSegs.map(seg => seg.attributes.length).reduce((a, b) => a + b) > ((startSeg.attributes.roadType === 7) ? 500 : 5000)) {
  537. WazeWrap.Alerts.info(SCRIPT_NAME, `BDP will not be applied to this detour route because it is longer than ${((startSeg.attributes.roadType === 7) ? '500m' : '5km')}.`);
  538. doZoom(true, _restoreZoomLevel, _restoreMapCenter);
  539. return;
  540. }
  541. if (viaLM) {
  542. directRoutes = directRoutes.concat(await findLiveMapRoutes(startSeg, endSeg, maxLength));
  543. }
  544. else {
  545. for (let i = 0; i < startNodeObjs.length; i++) {
  546. const startNode = startNodeObjs[i];
  547. directRoutes = findDirectRoute({
  548. maxLength, startSeg, startNode, endSeg, endNodeIds: [endNodeObj.attributes.id]
  549. });
  550. if (directRoutes.length > 0)
  551. break;
  552. }
  553. }
  554. }
  555. if (directRoutes.length > 0) {
  556. WazeWrap.Alerts.confirm(SCRIPT_NAME,
  557. 'A <b>direct route</b> was found! Would you like to select the direct route?',
  558. () => {
  559. const segments = [];
  560. for (let i = 0; i < directRoutes[0].length; i++) {
  561. const seg = W.model.segments.getObjectById(directRoutes[0][i]);
  562. if (seg !== 'undefined')
  563. segments.push(seg);
  564. }
  565. W.selectionManager.setSelectedModels(segments);
  566. doZoom(true, _restoreZoomLevel, _restoreMapCenter);
  567. },
  568. () => { doZoom(true, _restoreZoomLevel, _restoreMapCenter); }, 'Yes', 'No');
  569. }
  570. else if (segmentSelection.segments.length === 2) {
  571. WazeWrap.Alerts.info(SCRIPT_NAME,
  572. 'No direct routes found between the two selected segments. A BDP penalty <b>will not</b> be applied to any routes.'
  573. + '<br><b>Note:</b> This could also be caused by the distance between the two selected segments is longer than than the allowed distance for detours.');
  574. doZoom(true, _restoreZoomLevel, _restoreMapCenter);
  575. }
  576. else {
  577. WazeWrap.Alerts.info(SCRIPT_NAME,
  578. 'No direct routes found between the possible detour bracketing segments. A BDP penalty <b>will not</b> be applied to the selected route.'
  579. + '<br><b>Note:</b> This could also be because any possible direct routes are very long, which would take longer to travel than taking the selected route (even with penalty).');
  580. doZoom(true, _restoreZoomLevel, _restoreMapCenter);
  581. }
  582. }
  583.  
  584. function insertCheckBDPButton(remove = false) {
  585. const $wmeButton = $('#WME-BDPC-WME'),
  586. $lmButton = $('#WME-BDPC-LM'),
  587. $buttonsDiv = $('#WME-BDPC-BUTTONS-DIV');
  588. if (remove) {
  589. if ($buttonsDiv.length > 0)
  590. $buttonsDiv.remove();
  591. return;
  592. }
  593. let htmlOut = '';
  594. if ($buttonsDiv.length === 0)
  595. htmlOut += '<div id="WME-BDPC-BUTTONS-DIV" style="margin:0 0 10px 10px;">';
  596. if ($wmeButton.length === 0)
  597. htmlOut += '<button id="WME-BDPC-WME" class="waze-btn waze-btn-small waze-btn-white" title="Check BDP of selected segments, via WME.">BDP Check (WME)</button>';
  598. if ($lmButton.length === 0)
  599. htmlOut += '<button id="WME-BDPC-LM" class="waze-btn waze-btn-small waze-btn-white" title="Check BDP of selected segments, via LM.">BDP Check (LM)</button>';
  600. if ($buttonsDiv.length === 0)
  601. htmlOut += '</div>';
  602. if (htmlOut !== '')
  603. $(htmlOut).insertAfter($('#edit-panel .segment .selection'));
  604. }
  605.  
  606. function pathSelected(evt) {
  607. if (evt && evt.feature && evt.feature.model && (evt.feature.model.type === 'segment'))
  608. _pathEndSegId = evt.feature.model.attributes.id;
  609. }
  610.  
  611. async function init() {
  612. log('Initializing.');
  613. await loadSettingsFromStorage();
  614. _editPanelObserver.observe(document.querySelector('#edit-panel > div'), {
  615. childList: true, attributes: false, attributeOldValue: false, characterData: false, characterDataOldValue: false, subtree: true
  616. });
  617. W.selectionManager.selectionMediator.on('map:selection:pathSelect', pathSelected);
  618. W.selectionManager.selectionMediator.on('map:selection:featureClick', () => { _pathEndSegId = undefined; });
  619. W.selectionManager.selectionMediator.on('map:selection:clickOut', () => { _pathEndSegId = undefined; });
  620. W.selectionManager.selectionMediator.on('map:selection:deselectKey', () => { _pathEndSegId = undefined; });
  621. W.selectionManager.selectionMediator.on('map:selection:featureBoxSelection', () => { _pathEndSegId = undefined; });
  622. if (W.selectionManager.getSegmentSelection().segments.length > 1) {
  623. $('.tabs-container').before(
  624. ' <div id="WME-BDPC-BUTTONS-DIV" style="margin:0 0 10px 10px;">'
  625. + ' <button id="WME-BDPC-WME" class="waze-btn waze-btn-small waze-btn-white" title="Check BDP of selected segments, via WME.">BDP Check (WME)</button>'
  626. + ' <button id="WME-BDPC-LM" class="waze-btn waze-btn-small waze-btn-white" title="Check BDP of selected segments, via LM." >BDP Check (LM)</button>'
  627. + ' </div>'
  628. );
  629. }
  630. $('#sidebar').on('click', '#WME-BDPC-WME', e => {
  631. e.preventDefault();
  632. doCheckBDP(false);
  633. });
  634. $('#sidebar').on('click', '#WME-BDPC-LM', e => {
  635. e.preventDefault();
  636. doCheckBDP(true);
  637. });
  638. showScriptInfoAlert();
  639. log(`Fully initialized in ${Math.round(performance.now() - LOAD_BEGIN_TIME)} ms.`);
  640. }
  641.  
  642. function bootstrap(tries) {
  643. if (W && W.map && W.model && $ && WazeWrap.Ready) {
  644. checkTimeout({ timeout: 'bootstrap' });
  645. log('Bootstrapping.');
  646. init();
  647. }
  648. else if (tries < 1000) {
  649. logDebug(`Bootstrap failed. Retrying ${tries} of 1000`);
  650. _timeouts.bootstrap = window.setTimeout(bootstrap, 200, ++tries);
  651. }
  652. else {
  653. logError('Bootstrap timed out waiting for WME to become ready.');
  654. }
  655. }
  656.  
  657. bootstrap(1);