WME LaneTools

Adds highlights and tools to WME to supplement the lanes feature

  1. "use strict";
  2. // ==UserScript==
  3. // @name WME LaneTools
  4. // @namespace https://github.com/SkiDooGuy/WME-LaneTools
  5. // @version 2025.06.26.001
  6. // @description Adds highlights and tools to WME to supplement the lanes feature
  7. // @author SkiDooGuy, Click Saver by HBiede, Heuristics by kndcajun, assistance by jm6087
  8. // @match https://www.waze.com/editor*
  9. // @match https://www.waze.com/*/editor*
  10. // @match https://beta.waze.com/editor*
  11. // @match https://beta.waze.com/*/editor*
  12. // @exclude https://www.waze.com/user/editor*
  13. // @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
  14. // @require https://cdn.jsdelivr.net/npm/@turf/turf@7.2.0/turf.min.js
  15. // @require https://cdn.jsdelivr.net/npm/proj4@2.17.0/dist/proj4.min.js
  16. // @grant GM_xmlhttpRequest
  17. // @grant unsafeWindow
  18. // @connect gf.qytechs.cn
  19. // @contributionURL https://github.com/WazeDev/Thank-The-Authors
  20. // ==/UserScript==
  21. /* global W */
  22. /* global WazeWrap */
  23. // import type { KeyboardShortcut, Node, Segment, Selection, Turn, UserSession, WmeSDK } from "wme-sdk-typings";
  24. // import type { Position } from "geojson";
  25. // import _ from "underscore";
  26. // import * as turf from "@turf/turf";
  27. // import WazeWrap from "https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js";
  28. // import proj4 from "proj4";
  29. let sdk;
  30. unsafeWindow.SDK_INITIALIZED.then(() => {
  31. if (!unsafeWindow.getWmeSdk) {
  32. throw new Error("SDK is not installed");
  33. }
  34. sdk = unsafeWindow.getWmeSdk({
  35. scriptId: "wme-lane-tools",
  36. scriptName: "WME LaneTools",
  37. });
  38. console.log(`SDK v ${sdk.getSDKVersion()} on ${sdk.getWMEVersion()} initialized`);
  39. sdk.Events.once({ eventName: "wme-ready" }).then(ltInit);
  40. });
  41. function ltInit() {
  42. let Direction;
  43. (function (Direction) {
  44. Direction[Direction["REVERSE"] = -1] = "REVERSE";
  45. Direction[Direction["ANY"] = 0] = "ANY";
  46. Direction[Direction["FORWARD"] = 1] = "FORWARD";
  47. })(Direction || (Direction = {}));
  48. let LT_ROAD_TYPE;
  49. (function (LT_ROAD_TYPE) {
  50. // Streets
  51. LT_ROAD_TYPE[LT_ROAD_TYPE["NARROW_STREET"] = 22] = "NARROW_STREET";
  52. LT_ROAD_TYPE[LT_ROAD_TYPE["STREET"] = 1] = "STREET";
  53. LT_ROAD_TYPE[LT_ROAD_TYPE["PRIMARY_STREET"] = 2] = "PRIMARY_STREET";
  54. // Highways
  55. LT_ROAD_TYPE[LT_ROAD_TYPE["RAMP"] = 4] = "RAMP";
  56. LT_ROAD_TYPE[LT_ROAD_TYPE["FREEWAY"] = 3] = "FREEWAY";
  57. LT_ROAD_TYPE[LT_ROAD_TYPE["MAJOR_HIGHWAY"] = 6] = "MAJOR_HIGHWAY";
  58. LT_ROAD_TYPE[LT_ROAD_TYPE["MINOR_HIGHWAY"] = 7] = "MINOR_HIGHWAY";
  59. // Other drivable
  60. LT_ROAD_TYPE[LT_ROAD_TYPE["DIRT_ROAD"] = 8] = "DIRT_ROAD";
  61. LT_ROAD_TYPE[LT_ROAD_TYPE["FERRY"] = 14] = "FERRY";
  62. LT_ROAD_TYPE[LT_ROAD_TYPE["PRIVATE_ROAD"] = 17] = "PRIVATE_ROAD";
  63. LT_ROAD_TYPE[LT_ROAD_TYPE["PARKING_LOT_ROAD"] = 20] = "PARKING_LOT_ROAD";
  64. // Non-drivable
  65. LT_ROAD_TYPE[LT_ROAD_TYPE["WALKING_TRAIL"] = 5] = "WALKING_TRAIL";
  66. LT_ROAD_TYPE[LT_ROAD_TYPE["PEDESTRIAN_BOARDWALK"] = 10] = "PEDESTRIAN_BOARDWALK";
  67. LT_ROAD_TYPE[LT_ROAD_TYPE["STAIRWAY"] = 16] = "STAIRWAY";
  68. LT_ROAD_TYPE[LT_ROAD_TYPE["RAILROAD"] = 18] = "RAILROAD";
  69. LT_ROAD_TYPE[LT_ROAD_TYPE["RUNWAY"] = 19] = "RUNWAY";
  70. })(LT_ROAD_TYPE || (LT_ROAD_TYPE = {}));
  71. const MIN_DISPLAY_LEVEL = 14;
  72. const MIN_ZOOM_NON_FREEWAY = 17;
  73. // const DisplayLevels = {
  74. // MIN_ZOOM_ALL: 14,
  75. // MIN_ZOOM_NONFREEWAY: 17,
  76. // };
  77. let HeuristicsCandidate;
  78. (function (HeuristicsCandidate) {
  79. HeuristicsCandidate[HeuristicsCandidate["ERROR"] = -2] = "ERROR";
  80. HeuristicsCandidate[HeuristicsCandidate["FAIL"] = -1] = "FAIL";
  81. HeuristicsCandidate[HeuristicsCandidate["NONE"] = 0] = "NONE";
  82. HeuristicsCandidate[HeuristicsCandidate["PASS"] = 1] = "PASS";
  83. })(HeuristicsCandidate || (HeuristicsCandidate = {}));
  84. if (!WazeWrap.Ready) {
  85. setTimeout(() => {
  86. ltInit();
  87. }, 100);
  88. return;
  89. }
  90. const LANETOOLS_VERSION = `${GM_info.script.version}`;
  91. const GF_LINK = "https://gf.qytechs.cn/en/scripts/537219-wme-lanetools";
  92. const DOWNLOAD_URL = "https://gf.qytechs.cn/en/scripts/537219-wme-lanetools";
  93. const FORUM_LINK = "https://www.waze.com/discuss/t/script-wme-lanetools/53136";
  94. const LI_UPDATE_NOTES = `NEW:<br>
  95. - Conversion to WME SDK<br>
  96. - <b>ENABLE LT Layers To See Markings on the Map</b><br>
  97. - Point Updates to GF vs. GITHUB<br><br>
  98. KNOWN ISSUE:<br>
  99. - Some tab UI enhancements may not work as expected.<br>`;
  100. const LANETOOLS_DEBUG_LEVEL = 5;
  101. const configArray = {};
  102. const RBSArray = { failed: false };
  103. const IsBeta = location.href.indexOf("beta.waze.com") !== -1;
  104. const env = IsBeta ? "beta" : "production";
  105. const TAB_TRANSLATIONS = {
  106. // Default english values
  107. default: {
  108. enabled: "Enabled",
  109. disabled: "Disabled",
  110. toggleShortcut: "Toggle Shortcut",
  111. UIEnhance: "Tab UI Enhancements",
  112. autoWidth: "Auto-open road width",
  113. autoOpen: "Auto-open lanes tab",
  114. autoExpand: "Auto-expand lane editor",
  115. autoFocus: "Auto-focus lane input",
  116. reOrient: "Re-orient lane icons",
  117. enClick: "Enable ClickSaver",
  118. clickStraight: "All straight lanes",
  119. clickTurn: "Turn lanes",
  120. mapHighlight: "Map Highlights",
  121. laneLabel: "Lane labels",
  122. nodeHigh: "Node highlights",
  123. LAOHigh: "Lane angle overrides",
  124. CSOHigh: "Continue straight overrides",
  125. heuristics: "Lane heuristics candidates",
  126. posHeur: "Positive heuristics candidate",
  127. negHeur: "Negative heuristics candidate",
  128. highColor: "Highlight Colors",
  129. colTooltip: "Click to toggle color inputs",
  130. selAllTooltip: "Click on turn name to toggle all lane associations",
  131. fwdCol: "Fwd (A>B)",
  132. revCol: "Rev (B>A)",
  133. labelCol: "Labels",
  134. errorCol: "Lane errors",
  135. laneNodeCol: "Nodes with lanes",
  136. nodeTIOCol: "Nodes with TIOs",
  137. LAOCol: "Segs with TIOs",
  138. viewCSCol: "View only CS",
  139. hearCSCol: "View and hear CS",
  140. heurPosCol: "Lane heuristics likely",
  141. heurNegCol: "Lane heuristics - not qualified",
  142. advTools: "Advanced Tools",
  143. quickTog: "Quick toggle all lanes",
  144. showRBS: "Use RBS heuristics",
  145. delFwd: "Delete FWD Lanes",
  146. delRev: "Delete Rev Lanes",
  147. delOpp: "This segment is one-way but has lanes set in the opposite direction. Click here to delete them",
  148. csIcons: "Highlight CS Icons",
  149. highlightOverride: "Only highlight if segment layer active",
  150. addTIO: "Include TIO in lanes tab",
  151. labelTIO: "TIO",
  152. defaultTIO: "Waze Selected",
  153. noneTIO: "None",
  154. tlTIO: "Turn Left",
  155. trTIO: "Turn Right",
  156. klTIO: "Keep Left",
  157. krTIO: "Keep Right",
  158. conTIO: "Continue",
  159. elTIO: "Exit Left",
  160. erTIO: "Exit Right",
  161. uturnTIO: "U-Turn",
  162. enIcons: "Display lane icons on map",
  163. IconsRotate: "Rotate map icons with segment direction",
  164. },
  165. "en-us": {
  166. enabled: "Enabled",
  167. disabled: "Disabled",
  168. toggleShortcut: "Toggle Shortcut",
  169. UIEnhance: "Tab UI Enhancements",
  170. autoWidth: "Auto-open road width",
  171. autoOpen: "Auto-open lanes tab",
  172. autoExpand: "Auto-expand lane editor",
  173. autoFocus: "Auto-focus lane input",
  174. reOrient: "Re-orient lane icons",
  175. enClick: "Enable ClickSaver",
  176. clickStraight: "All straight lanes",
  177. clickTurn: "Turn lanes",
  178. mapHighlight: "Map Highlights",
  179. laneLabel: "Lane labels",
  180. nodeHigh: "Node highlights",
  181. LAOHigh: "Lane angle overrides",
  182. CSOHigh: "Continue straight overrides",
  183. heuristics: "Lane heuristics candidates",
  184. posHeur: "Positive heuristics candidate",
  185. negHeur: "Negative heuristics candidate",
  186. highColor: "Highlight Colors",
  187. colTooltip: "Click to toggle color inputs",
  188. selAllTooltip: "Click on turn name to toggle all lane associations",
  189. fwdCol: "Fwd (A>B)",
  190. revCol: "Rev (B>A)",
  191. labelCol: "Labels",
  192. errorCol: "Lane errors",
  193. laneNodeCol: "Nodes with lanes",
  194. nodeTIOCol: "Nodes with TIOs",
  195. LAOCol: "Segs with TIOs",
  196. viewCSCol: "View only CS",
  197. hearCSCol: "View and hear CS",
  198. heurPosCol: "Lane heuristics likely",
  199. heurNegCol: "Lane heuristics - not qualified",
  200. advTools: "Advanced Tools",
  201. quickTog: "Quick toggle all lanes",
  202. showRBS: "Use RBS heuristics",
  203. delFwd: "Delete FWD Lanes",
  204. delRev: "Delete Rev Lanes",
  205. delOpp: "This segment is one-way but has lanes set in the opposite direction. Click here to delete them",
  206. csIcons: "Highlight CS Icons",
  207. highlightOverride: "Only highlight if segment layer active",
  208. addTIO: "Include TIO in lanes tab",
  209. labelTIO: "TIO",
  210. defaultTIO: "Waze Selected",
  211. noneTIO: "None",
  212. tlTIO: "Turn Left",
  213. trTIO: "Turn Right",
  214. klTIO: "Keep Left",
  215. krTIO: "Keep Right",
  216. conTIO: "Continue",
  217. elTIO: "Exit Left",
  218. erTIO: "Exit Right",
  219. uturnTIO: "U-Turn",
  220. enIcons: "Display lane icons on map",
  221. IconsRotate: "Rotate map icons with segment direction",
  222. },
  223. };
  224. let MAX_LEN_HEUR; // Maximum length of segment where lane heuristics applied (Specified in Wiki).
  225. let MAX_PERP_DIF; // Updated 2020-09, based on experiments
  226. let MAX_PERP_DIF_ALT; // New 2020-09, based on experiments
  227. let MAX_PERP_TO_CONSIDER; // Don't even consider perp angles outside of this tolerance
  228. let MAX_STRAIGHT_TO_CONSIDER; // Don't even consider straight angles inside of this tolerance
  229. let MAX_STRAIGHT_DIF; // IN TESTING; updated 2020-09
  230. let lt_scanArea_recursive = 0;
  231. let LtSettings;
  232. let strings;
  233. // let _turnInfo = [];
  234. let _turnData = {};
  235. let laneCount = 0;
  236. const LTHighlightLayer = { name: "LT Highlights Layer" };
  237. const LTNamesLayer = { name: "LT Names Layer" };
  238. const LTLaneGraphics = { name: "LT Lane Graphics" };
  239. let _pickleColor;
  240. let UpdateObj;
  241. let MultiAction;
  242. let SetTurn;
  243. let shortcutsDisabled = false;
  244. let isRBS = false;
  245. let allowCpyPst = false;
  246. let langLocality = "default";
  247. let LANG;
  248. let seaPickle;
  249. function applyNodeHightlightStyle(properties) {
  250. return properties.styleName === "nodeStyle" && properties.layerName === LTHighlightLayer.name;
  251. }
  252. function applyVectorHightlightStyle(properties) {
  253. return properties.styleName === "vectorStyle" && properties.layerName === LTHighlightLayer.name;
  254. }
  255. function applyBoxStyle(properties) {
  256. return properties.styleName === "boxStyle" && properties.layerName === LTLaneGraphics.name;
  257. }
  258. function applyIconBoxStyle(properties) {
  259. return properties.styleName === "iconBoxStyle" && properties.layerName === LTLaneGraphics.name;
  260. }
  261. function applyIconStyle(properties) {
  262. return properties.styleName === "iconStyle" && properties.layerName === LTLaneGraphics.name;
  263. }
  264. const styleConfig = {
  265. styleContext: {
  266. nameStyleLabelColor: (context) => {
  267. return LtSettings.LabelColor;
  268. },
  269. nameStyleLaneNum: (context) => {
  270. return context?.feature?.properties?.style?.laneNumLabel;
  271. },
  272. highlightStrokeColor: (context) => {
  273. return context?.feature?.properties?.style?.stroke;
  274. },
  275. hightlightStrokeWidth: (context) => {
  276. return context?.feature?.properties?.style?.strokeWidth;
  277. },
  278. hightlightStrokeOpacity: (context) => {
  279. return context?.feature?.properties?.style?.strokeOpacity;
  280. },
  281. hightlightStrokeDashStyle: (context) => {
  282. return context?.feature?.properties?.style?.strokeDashstyle;
  283. },
  284. highlightFillColor: (context) => {
  285. return context?.feature?.properties?.style?.fillColor;
  286. },
  287. highlightPointRadius: (context) => {
  288. return context?.feature?.properties?.style?.pointRadius;
  289. },
  290. externalGraphic: (context) => {
  291. return context?.feature?.properties?.style?.externalGraphic;
  292. },
  293. graphicHeight: (context) => {
  294. return context?.feature?.properties?.style?.graphicHeight;
  295. },
  296. graphicWidth: (context) => {
  297. return context?.feature?.properties?.style?.graphicWidth;
  298. },
  299. rotation: (context) => {
  300. return context?.feature?.properties?.style?.rotation;
  301. },
  302. backgroundGraphic: (context) => {
  303. return context?.feature?.properties?.style?.backgroundGraphic;
  304. },
  305. backgroundHeight: (context) => {
  306. return context?.feature?.properties?.style?.backgroundHeight;
  307. },
  308. backgroundWidth: (context) => {
  309. return context?.feature?.properties?.style?.backgroundWidth;
  310. },
  311. backgroundXOffset: (context) => {
  312. return context?.feature?.properties?.style?.backgroundXOffset;
  313. },
  314. backgroundYOffset: (context) => {
  315. return context?.feature?.properties?.style?.backgroundYOffset;
  316. },
  317. },
  318. styleRules: [
  319. {
  320. predicate: (properties) => { return properties.layerName === LTNamesLayer.name; },
  321. style: {
  322. fontFamily: "Open Sans, Alef, helvetica, sans-serif, monospace",
  323. labelColor: "${nameStyleLabelColor}",
  324. labelText: "${nameStyleLaneNum}",
  325. labelOutlineColor: "black",
  326. fontColor: "${nameStyleLabelColor}",
  327. fontSize: "16",
  328. labelXOffset: 15,
  329. labelYOffset: -15,
  330. labelOutlineWidth: "3",
  331. label: "${nameStyleLaneNum}",
  332. angle: "",
  333. labelAlign: "cm",
  334. strokeWidth: 0,
  335. pointRadius: 0,
  336. },
  337. },
  338. {
  339. predicate: applyNodeHightlightStyle,
  340. style: {
  341. fillColor: "${highlightFillColor}",
  342. pointRadius: "${highlightPointRadius}",
  343. fillOpacity: 0.9,
  344. strokeWidth: 0,
  345. },
  346. },
  347. {
  348. predicate: applyVectorHightlightStyle,
  349. style: {
  350. strokeColor: "${highlightStrokeColor}",
  351. stroke: "${highlightStrokeColor}",
  352. strokeWidth: "${hightlightStrokeWidth}",
  353. strokeOpacity: "${hightlightStrokeOpacity}",
  354. strokeDashstyle: "${hightlightStrokeDashStyle}",
  355. },
  356. },
  357. {
  358. predicate: applyBoxStyle,
  359. style: {
  360. strokeColor: "#ffffff",
  361. strokeOpacity: 1,
  362. strokeWidth: 8,
  363. fillColor: "#ffffff",
  364. },
  365. },
  366. {
  367. predicate: applyIconBoxStyle,
  368. style: {
  369. strokeColor: "#000000",
  370. strokeOpacity: 1,
  371. strokeWidth: 1,
  372. fillColor: "#26bae8",
  373. },
  374. },
  375. {
  376. predicate: applyIconStyle,
  377. style: {
  378. externalGraphic: "${externalGraphic}",
  379. graphicHeight: "${graphicHeight}",
  380. graphicWidth: "${graphicWidth}",
  381. fillColor: "#26bae8",
  382. fillOpacity: 1,
  383. backgroundColor: "#26bae8",
  384. strokeColor: "#26bae8",
  385. rotation: "${rotation}",
  386. backgroundGraphic: "${backgroundGraphic}",
  387. backgroundHeight: "${backgroundHeight}",
  388. backgroundWidth: "${backgroundWidth}",
  389. backgroundXOffset: "${backgroundXOffset}",
  390. backgroundYOffset: "${backgroundYOffset}",
  391. },
  392. },
  393. ],
  394. };
  395. console.log("LaneTools: initializing...");
  396. function laneToolsBootstrap(tries = 0) {
  397. console.log("Lane Tools: Initializing...");
  398. initLaneTools();
  399. console.log("Lane Tools: Initialization Finished.");
  400. }
  401. function initLaneTools() {
  402. startScriptUpdateMonitor();
  403. seaPickle = sdk.State.getUserInfo();
  404. UpdateObj = require("Waze/Action/UpdateObject");
  405. MultiAction = require("Waze/Action/MultiAction");
  406. SetTurn = require("Waze/Model/Graph/Actions/SetTurn");
  407. const ltCss = [
  408. '.lt-wrapper {position:relative;width:100%;font-size:12px;font-family:"Rubik", "Boing-light", sans-serif;user-select:none;}',
  409. ".lt-section-wrapper {display:block;width:100%;padding:4px;}",
  410. ".lt-section-wrapper.border {border-bottom:1px solid grey;margin-bottom:5px;}",
  411. ".lt-option-container {padding:3px;}",
  412. ".lt-option-container.color {text-decoration:none;}",
  413. 'input[type="checkbox"].lt-checkbox {position:relative;top:3px;vertical-align:top;margin:0;}',
  414. 'input[type="text"].lt-color-input {position:relative;width:70px;padding:3px;border:2px solid black;border-radius:6px;}',
  415. 'input[type="text"].lt-color-input:focus {outline-width:0;}',
  416. "label.lt-label {position:relative;max-width:90%;font-weight:normal;padding-left:5px}",
  417. ".lt-Toolbar-Container {display:none;position:absolute;background-color:orange;border-radius:6px;border:1.5px solid;box-size:border-box;z-index:1050;}",
  418. ".lt-Toolbar-Wrapper {position:relative;padding:3px;}",
  419. ".lt-toolbar-button-container {display:inline-block;padding:5px;}",
  420. ".lt-toolbar-button {position:relative;display:block;width:60px;height:25px;border-radius:6px;font-size:12px;}",
  421. ".lt-add-Width {display:inline-block;width:15px;height:15px;border:1px solid black;border-radius:8px;margin:0 3px 0 3px;line-height: 1.5;text-align:center;font-size:10px;}",
  422. ".lt-add-Width:hover {border:1px solid #26bae8;background-color:#26bae8;cursor:pointer;}",
  423. ".lt-add-lanes {display:inline-block;width:15px;height:15px;border:1px solid black;border-radius:8px;margin:0 3px 0 3px;line-height: 1.5;text-align:center;font-size:10px;}",
  424. ".lt-add-lanes:hover {border:1px solid #26bae8;background-color:#26bae8;cursor:pointer;}",
  425. ".lt-chkAll-lns {display:inline-block;width:20px;height:20px;text-decoration:underline;font-weight:bold;font-size:10px;padding-left:3px;cursor:pointer;}",
  426. ".lt-tio-select {max-width:80%;color:rgb(32, 33, 36);background-color:rgb(242, 243, 244);border:0px;border-radius:6px;padding:0 16px 0 10px;cursor:pointer;}",
  427. "#lt-color-title {display:block;width:100%;padding:5px 0 5px 0;font-weight:bold;text-decoration:underline;cursor:pointer;}",
  428. ].join(" ");
  429. const $ltTab = $("<div>");
  430. $ltTab.html = [
  431. `<div class='lt-wrapper' id='lt-tab-wrapper'>
  432. <div class='lt-section-wrapper' id='lt-tab-body'>
  433. <div class='lt-section-wrapper border' style='border-bottom:2px double grey;'>
  434. <a href='https://www.waze.com/forum/viewtopic.php?f=819&t=301158' style='font-weight:bold;font-size:12px;text-decoration:underline;' target='_blank'>LaneTools - v${LANETOOLS_VERSION}</a>
  435. <div>
  436. <div style='display:inline-block;'><span class='lt-trans-tglshcut'></span>:<span id='lt-EnableShortcut' style='padding-left:10px;'></span></div>
  437. <div class='lt-option-container' style='float:right;'>
  438. <input type=checkbox class='lt-checkbox' id='lt-ScriptEnabled' />
  439. <label class='lt-label' for='lt-ScriptEnabled'><span class='lt-trans-enabled'></span></label>
  440. </div>
  441. </div>
  442. </div>
  443. <div class='lt-section-wrapper' id='lt-LaneTabFeatures'>
  444. <div class='lt-section-wrapper border'>
  445. <span style='font-weight:bold;'><span id='lt-trans-uiEnhance'></span></span>
  446. <div class='lt-option-container' style='float:right;'>
  447. <input type=checkbox class='lt-checkbox' id='lt-UIEnable' />
  448. <label class='lt-label' for='lt-UIEnable'><span class='lt-trans-enabled'></span></label>
  449. </div>
  450. </div>
  451. <div id='lt-UI-wrapper'>
  452. <div class='lt-option-container' style='margin-bottom:5px;'>
  453. <div style='display:inline-block;'><span class='lt-trans-tglshcut'></span>:<span id='lt-UIEnhanceShortcut' style='padding-left:10px;'></span></div>
  454. </div>
  455. <div class='lt-option-container'>
  456. <input type=checkbox class='lt-checkbox' id='lt-AutoOpenWidth' />
  457. <label class='lt-label' for='lt-AutoOpenWidth'><span id='lt-trans-autoWidth'></span></label>
  458. </div>
  459. <div class='lt-option-container'>
  460. <input type=checkbox class='lt-checkbox' id='lt-AutoLanesTab' />
  461. <label class='lt-label' for='lt-AutoLanesTab'><span id='lt-trans-autoTab'></span></label>
  462. </div>
  463. <div class='lt-option-container' style='display:none;'>
  464. <input type=checkbox class='lt-checkbox' id='lt-AutoExpandLanes' />
  465. <label class='lt-label' for='lt-AutoExpandLanes'><span title="Feature disabled as of Aug 27, 2022 to prevent flickering issue" id='lt-trans-autoExpand'></span></label>
  466. </div>
  467. <div class='lt-option-container'>
  468. <input type=checkbox class='lt-checkbox' id='lt-AutoFocusLanes' />
  469. <label class='lt-label' for='lt-AutoFocusLanes'><span id='lt-trans-autoFocus'></span></label>
  470. </div>
  471. <div class='lt-option-container'>
  472. <input type=checkbox class='lt-checkbox' id='lt-highlightCSIcons' />
  473. <label class='lt-label' for='lt-highlightCSIcons'><span id='lt-trans-csIcons'></span></label>
  474. </div>
  475. <div class='lt-option-container' style='display:none;'>
  476. <input type=checkbox class='lt-checkbox' id='lt-ReverseLanesIcon' />
  477. <label class='lt-label' for='lt-ReverseLanesIcon'><span title="Feature disabled as of July 21, 2023 because lanes displayed wrong" id='lt-trans-orient'></span></label>
  478. </div>
  479. <div class='lt-option-container' style='display:none;'>
  480. <input type=checkbox class='lt-checkbox' id='lt-AddTIO' />
  481. <label class='lt-label' for='lt-AddTIO'><span id='lt-trans-AddTIO'></span></label>
  482. </div>
  483. <div class='lt-option-container'>
  484. <input type=checkbox class='lt-checkbox' id='lt-ClickSaveEnable' />
  485. <label class='lt-label' for='lt-ClickSaveEnable'><span id='lt-trans-enClick'></span></label>
  486. </div>
  487. <div class='lt-option-container clk-svr' style='padding-left:10%;'>
  488. <input type=checkbox class='lt-checkbox' id='lt-ClickSaveStraight' />
  489. <label class='lt-label' for='lt-ClickSaveStraight'><span id='lt-trans-straClick'></span></label>
  490. </div>
  491. <div class='lt-option-container clk-svr' style='padding-left:10%;'>
  492. <input type=checkbox class='lt-checkbox' id='lt-ClickSaveTurns' />
  493. <label class='lt-label' for='lt-ClickSaveTurns'><span id='lt-trans-turnClick'></span></label>
  494. </div>
  495. </div>
  496. </div>
  497. <div class='lt-section-wrapper'>
  498. <div class='lt-section-wrapper border'>
  499. <span style='font-weight:bold;'><span id='lt-trans-mapHigh'></span></span>
  500. <div class='lt-option-container' style='float:right;'>
  501. <input type=checkbox class='lt-checkbox' id='lt-HighlightsEnable' />
  502. <label class='lt-label' for='lt-HighlightsEnable'><span class='lt-trans-enabled'></span></label>
  503. </div>
  504. </div>
  505. <div id='lt-highlights-wrapper'>
  506. <div class='lt-option-container' style='margin-bottom:5px;'>
  507. <div style='display:inline-block;'><span class='lt-trans-tglshcut'></span>:<span id='lt-HighlightShortcut' style='padding-left:10px;'></span></div>
  508. </div>
  509. <div class='lt-option-container'>
  510. <input type=checkbox class='lt-checkbox' id='lt-IconsEnable' />
  511. <label class='lt-label' for='lt-IconsEnable'><span id='lt-trans-enIcons'></span></label>
  512. </div>
  513. <div class='lt-option-container'>
  514. <input type=checkbox class='lt-checkbox' id='lt-IconsRotate' />
  515. <label class='lt-label' for='lt-IconsRotate'><span id='lt-trans-IconsRotate'></span></label>
  516. </div>
  517. <div class='lt-option-container'>
  518. <input type=checkbox class='lt-checkbox' id='lt-LabelsEnable' />
  519. <label class='lt-label' for='lt-LabelsEnable'><span id='lt-trans-lnLabel'></span></label>
  520. </div>
  521. <div class='lt-option-container'>
  522. <input type=checkbox class='lt-checkbox' id='lt-NodesEnable' />
  523. <label class='lt-label' for='lt-NodesEnable'><span id='lt-trans-nodeHigh'></span></label>
  524. </div>
  525. <div class='lt-option-container'>
  526. <input type=checkbox class='lt-checkbox' id='lt-LIOEnable' />
  527. <label class='lt-label' for='lt-LIOEnable'><span id='lt-trans-laOver'></span></label>
  528. </div>
  529. <div class='lt-option-container'>
  530. <input type=checkbox class='lt-checkbox' id='lt-CSEnable' />
  531. <label class='lt-label' for='lt-CSEnable'><span id='lt-trans-csOver'></span></label>
  532. </div>
  533. <div class='lt-option-container'>
  534. <input type=checkbox class='lt-checkbox' id='lt-highlightOverride' />
  535. <label class='lt-label' for='lt-highlightOverride'><span id='lt-trans-highOver'></span></label>
  536. </div>
  537. </div>
  538. </div>
  539. <div class='lt-section-wrapper'>
  540. <div class='lt-section-wrapper border'>
  541. <span style='font-weight:bold;'><span id='lt-trans-heurCan'></span></span>
  542. <div class='lt-option-container' style='float:right;'>
  543. <input type=checkbox class='lt-checkbox' id='lt-LaneHeuristicsChecks' />
  544. <label class='lt-label' for='lt-LaneHeuristicsChecks'><span class='lt-trans-enabled'></span></label>
  545. </div>
  546. </div>
  547. <div id='lt-heur-wrapper'>
  548. <div class='lt-option-container' style='margin-bottom:5px;'>
  549. <div style='display:inline-block;'><span class='lt-trans-tglshcut'></span>:<span id='lt-LaneHeurChecksShortcut' style='padding-left:10px;'></span></div>
  550. </div>
  551. <div class='lt-option-container'>
  552. <input type=checkbox class='lt-checkbox' id='lt-LaneHeurPosHighlight' />
  553. <label class='lt-label' for='lt-LaneHeurPosHighlight'><span id='lt-trans-heurPos'></span></label>
  554. </div>
  555. <div class='lt-option-container'>
  556. <input type=checkbox class='lt-checkbox' id='lt-LaneHeurNegHighlight' />
  557. <label class='lt-label' for='lt-LaneHeurNegHighlight'><span id='lt-trans-heurNeg'></span></label>
  558. </div>
  559. </div>
  560. </div>
  561. <div class='lt-section-wrapper'>
  562. <div class='lt-section-wrapper'>
  563. <span id='lt-color-title' data-original-title='${TAB_TRANSLATIONS[langLocality].colTooltip}'><span id='lt-trans-highCol'></span>:</span>
  564. <div id='lt-color-inputs' style='display:none;'>
  565. <div class='lt-option-container color'>
  566. <input type=color class='lt-color-input' id='lt-ABColor' />
  567. <label class='lt-label' for='lt-ABColor' id='lt-ABColorLabel'><span id='lt-trans-fwdCol'></span></label>
  568. </div>
  569. <div class='lt-option-container color'>
  570. <input type=color class='lt-color-input' id='lt-BAColor' />
  571. <label class='lt-label' for='lt-BAColor' id='lt-BAColorLabel'><span id='lt-trans-revCol'></span></label>
  572. </div>
  573. <div class='lt-option-container color'>
  574. <input type=color class='lt-color-input' id='lt-LabelColor' />
  575. <label class='lt-label' for='lt-LabelColor' id='lt-LabelColorLabel'><span id='lt-trans-labelCol'></span></label>
  576. </div>
  577. <div class='lt-option-container color'>
  578. <input type=color class='lt-color-input' id='lt-ErrorColor' />
  579. <label class='lt-label' for='lt-ErrorColor' id='lt-ErrorColorLabel'><span id='lt-trans-errorCol'></span></label>
  580. </div>
  581. <div class='lt-option-container color'>
  582. <input type=color class='lt-color-input' id='lt-NodeColor' />
  583. <label class='lt-label' for='lt-NodeColor' id='lt-NodeColorLabel'><span id='lt-trans-nodeCol'></span></label>
  584. </div>
  585. <div class='lt-option-container color'>
  586. <input type=color class='lt-color-input' id='lt-TIOColor' />
  587. <label class='lt-label' for='lt-TIOColor' id='lt-TIOColorLabel'><span id='lt-trans-tioCol'></span></label>
  588. </div>
  589. <div class='lt-option-container color'>
  590. <input type=color class='lt-color-input' id='lt-LIOColor' />
  591. <label class='lt-label' for='lt-TIOColor' id='lt-LIOColorLabel'><span id='lt-trans-laoCol'></span></label>
  592. </div>
  593. <div class='lt-option-container color'>
  594. <input type=color class='lt-color-input' id='lt-CS1Color' />
  595. <label class='lt-label' for='lt-CS1Color' id='lt-CS1ColorLabel'><span id='lt-trans-viewCol'></span></label>
  596. </div>
  597. <div class='lt-option-container color'>
  598. <input type=color class='lt-color-input' id='lt-CS2Color' />
  599. <label class='lt-label' for='lt-CS2Color' id='lt-CS2ColorLabel'><span id='lt-trans-hearCol'></span></label>
  600. </div>
  601. <div class='lt-option-container color'>
  602. <input type=color class='lt-color-input' id='lt-HeurColor' />
  603. <label class='lt-label' for='lt-HeurColor' id='lt-HeurColorLabel'><span id='lt-trans-posCol'></span></label>
  604. </div>
  605. <div class='lt-option-container color'>
  606. <input type=color class='lt-color-input' id='lt-HeurFailColor' />
  607. <label class='lt-label' for='lt-HeurFailColor' id='lt-HeurFailColorLabel'><span id='lt-trans-negCol'></span></label>
  608. </div>
  609. </div>
  610. </div>
  611. </div>
  612. <div class='lt-section-wrapper' id='lt-adv-tools' style='display:none;'>
  613. <div class='lt-section-wrapper border'>
  614. <span style='font-weight:bold;'><span id='lt-trans-advTools'>></span></span>
  615. </div>
  616. <div class='lt-option-container'>
  617. <input type=checkbox class='lt-checkbox' id='lt-SelAllEnable' />
  618. <label class='lt-label' for='lt-SelAllEnable' ><span id='lt-trans-quickTog'></span></label>
  619. </div>
  620. <div class='lt-option-container' id='lt-serverSelectContainer' style='display:none;'>
  621. <input type=checkbox class='lt-checkbox' id='lt-serverSelect' />
  622. <label class='lt-label' for='lt-serverSelect'><span id='lt-trans-heurRBS'></span></label>
  623. </div>
  624. <div class='lt-option-container' id='lt-cpy-pst' style='display:none;'>
  625. <input type=checkbox class='lt-checkbox' id='lt-CopyEnable' />
  626. <label class='lt-label' for='lt-CopyEnable'>Copy/Paste Lanes</label>
  627. <span style='font-weight: bold;'>(**Caution** - double check results, feature still in Dev)</span>
  628. </div>
  629. <div id='lt-sheet-link' style='display:none;'>
  630. <a href='https://docs.google.com/spreadsheets/d/1_3sF09sMOid_us37j5CQqJZlBGGr1vI_3Rrmp5K-KCQ/edit?usp=sharing' target='_blank'>LT Config Sheet</a>
  631. </div>
  632. </div>
  633. </div>
  634. </div>`,
  635. ].join(" ");
  636. const $ltButtons = $("<div>");
  637. $ltButtons.html([
  638. `<div class='lt-Toolbar-Container' id="lt-toolbar-container">
  639. <div class='lt-Toolbar-Wrapper'>
  640. <div class='lt-toolbar-button-container'>
  641. <button type='button' class='lt-toolbar-button' id='copyA-button'>Copy A</button>
  642. </div>
  643. <div class='lt-toolbar-button-container'>
  644. <button type='button' class='lt-toolbar-button' id='copyB-button'>Copy B</button>
  645. </div>
  646. <div class='lt-toolbar-button-container'>
  647. <button type='button' class='lt-toolbar-button' id='pasteA-button'>Paste A</button>
  648. </div>
  649. <div class='lt-toolbar-button-container'>
  650. <button type='button' class='lt-toolbar-button' id='pasteB-button'>Paste B</button>
  651. </div>
  652. </div>
  653. </div>`,
  654. ].join(" "));
  655. _pickleColor = seaPickle?.rank;
  656. const proceedReady = _pickleColor && _pickleColor >= 0;
  657. if (proceedReady) {
  658. // WazeWrap.Interface.Tab("LT", $ltTab.html, setupOptions, "LT");
  659. sdk.Sidebar.registerScriptTab().then((r) => {
  660. r.tabLabel.innerHTML = "LT";
  661. r.tabPane.innerHTML = $ltTab.html;
  662. setupOptions().then(() => {
  663. scanArea();
  664. lanesTabSetup();
  665. displayLaneGraphics();
  666. });
  667. });
  668. $(`<style type="text/css">${ltCss}</style>`).appendTo("head");
  669. $("#map").append($ltButtons.html());
  670. WazeWrap.Interface.ShowScriptUpdate(GM_info.script.name, GM_info.script.version, LI_UPDATE_NOTES, GF_LINK, FORUM_LINK);
  671. console.log("LaneTools: loaded");
  672. }
  673. else {
  674. console.error("LaneTools: loading error....");
  675. }
  676. }
  677. function startScriptUpdateMonitor() {
  678. try {
  679. const updateMonitor = new WazeWrap.Alerts.ScriptUpdateMonitor(GM_info.script.name, GM_info.script.version, DOWNLOAD_URL, GM_xmlhttpRequest, DOWNLOAD_URL);
  680. updateMonitor.start();
  681. }
  682. catch (ex) {
  683. // Report, but don't stop if ScriptUpdateMonitor fails.
  684. console.error("WME LaneTools:", ex);
  685. }
  686. }
  687. async function setupOptions() {
  688. function setOptionStatus() {
  689. // Set check boxes based on last use
  690. setChecked("lt-ScriptEnabled", LtSettings.ScriptEnabled);
  691. setChecked("lt-UIEnable", LtSettings.UIEnable);
  692. setChecked("lt-AutoOpenWidth", LtSettings.AutoOpenWidth);
  693. setChecked("lt-AutoExpandLanes", LtSettings.AutoExpandLanes);
  694. setChecked("lt-AutoLanesTab", LtSettings.AutoLanesTab);
  695. setChecked("lt-HighlightsEnable", LtSettings.HighlightsEnable);
  696. setChecked("lt-LabelsEnable", LtSettings.LabelsEnable);
  697. setChecked("lt-NodesEnable", LtSettings.NodesEnable);
  698. setChecked("lt-LIOEnable", LtSettings.LIOEnable);
  699. setChecked("lt-CSEnable", LtSettings.CSEnable);
  700. setChecked("lt-highlightOverride", LtSettings.highlightOverride);
  701. setChecked("lt-CopyEnable", LtSettings.CopyEnable);
  702. setChecked("lt-SelAllEnable", LtSettings.SelAllEnable);
  703. setChecked("lt-serverSelect", LtSettings.serverSelect);
  704. setChecked("lt-AutoFocusLanes", LtSettings.AutoFocusLanes);
  705. setChecked("lt-ReverseLanesIcon", LtSettings.ReverseLanesIcon);
  706. setChecked("lt-ClickSaveEnable", LtSettings.ClickSaveEnable);
  707. setChecked("lt-ClickSaveStraight", LtSettings.ClickSaveStraight);
  708. setChecked("lt-ClickSaveTurns", LtSettings.ClickSaveTurns);
  709. setChecked("lt-LaneHeurPosHighlight", LtSettings.LaneHeurPosHighlight);
  710. setChecked("lt-LaneHeurNegHighlight", LtSettings.LaneHeurNegHighlight);
  711. setChecked("lt-LaneHeuristicsChecks", LtSettings.LaneHeuristicsChecks);
  712. setChecked("lt-highlightCSIcons", LtSettings.highlightCSIcons);
  713. setChecked("lt-AddTIO", LtSettings.AddTIO);
  714. setChecked("lt-IconsEnable", LtSettings.IconsEnable);
  715. setChecked("lt-IconsRotate", LtSettings.IconsRotate);
  716. setValue("lt-ABColor", LtSettings.ABColor);
  717. setValue("lt-BAColor", LtSettings.BAColor);
  718. setValue("lt-LabelColor", LtSettings.LabelColor);
  719. setValue("lt-ErrorColor", LtSettings.ErrorColor);
  720. setValue("lt-NodeColor", LtSettings.NodeColor);
  721. setValue("lt-TIOColor", LtSettings.TIOColor);
  722. setValue("lt-LIOColor", LtSettings.LIOColor);
  723. setValue("lt-CS1Color", LtSettings.CS1Color);
  724. setValue("lt-CS2Color", LtSettings.CS2Color);
  725. setValue("lt-HeurColor", LtSettings.HeurColor);
  726. setValue("lt-HeurFailColor", LtSettings.HeurFailColor);
  727. if (!getId("lt-ClickSaveEnable")?.checked) {
  728. $(".lt-option-container.clk-svr").hide();
  729. }
  730. if (!getId("lt-UIEnable")?.checked) {
  731. $("#lt-UI-wrapper").hide();
  732. }
  733. if (!getId("lt-HighlightsEnable")?.checked) {
  734. $("#lt-highlights-wrapper").hide();
  735. }
  736. if (!getId("lt-LaneHeuristicsChecks")?.checked) {
  737. $("#lt-heur-wrapper").hide();
  738. }
  739. function setChecked(checkboxId, checked) {
  740. $(`#${checkboxId}`).prop("checked", checked);
  741. }
  742. function setValue(inputId, color) {
  743. const inputElem = $(`#${inputId}`);
  744. inputElem.attr("value", color);
  745. inputElem.css("border", `2px solid ${color}`);
  746. }
  747. }
  748. await loadSettings();
  749. await loadSpreadsheet();
  750. initLaneGuidanceClickSaver();
  751. // Layer for highlights
  752. sdk.Map.addLayer({
  753. layerName: LTHighlightLayer.name,
  754. styleRules: styleConfig.styleRules,
  755. zIndexing: true,
  756. styleContext: styleConfig.styleContext,
  757. });
  758. sdk.LayerSwitcher.addLayerCheckbox(LTHighlightLayer);
  759. sdk.Map.setLayerVisibility({
  760. layerName: LTHighlightLayer.name,
  761. visibility: LtSettings.highlightsVisible && LtSettings.HighlightsEnable,
  762. });
  763. // LTHighlightLayer = new OpenLayers.Layer.Vector("LTHighlightLayer", { uniqueName: "_LTHighlightLayer" });
  764. // W.map.addLayer(LTHighlightLayer);
  765. // LTHighlightLayer.setVisibility(true);
  766. // Layer for future use of lane association icons...
  767. // LTLaneGraphics = new OpenLayers.Layer.Vector("LTLaneGraphics", { uniqueName: "LTLaneGraphics" });
  768. // W.map.addLayer(LTLaneGraphics);
  769. // LTLaneGraphics.setVisibility(true);
  770. sdk.Map.addLayer({
  771. layerName: LTLaneGraphics.name,
  772. styleRules: styleConfig.styleRules,
  773. zIndexing: true,
  774. styleContext: styleConfig.styleContext,
  775. });
  776. sdk.LayerSwitcher.addLayerCheckbox(LTLaneGraphics);
  777. sdk.Map.setLayerVisibility({
  778. layerName: LTLaneGraphics.name,
  779. visibility: LtSettings.ltGraphicsVisible,
  780. });
  781. sdk.Events.on({
  782. eventName: "wme-layer-checkbox-toggled",
  783. eventHandler: (payload) => {
  784. sdk.Map.setLayerVisibility({
  785. layerName: payload.name,
  786. visibility: payload.checked,
  787. });
  788. if (payload.name === LTLaneGraphics.name) {
  789. LtSettings.ltGraphicsVisible = payload.checked;
  790. }
  791. else if (payload.name === LTHighlightLayer.name) {
  792. LtSettings.highlightsVisible = payload.checked;
  793. }
  794. else if (payload.name === LTNamesLayer.name) {
  795. LtSettings.ltNamesVisible = payload.checked;
  796. }
  797. saveSettings();
  798. if (payload.checked)
  799. scanArea();
  800. },
  801. });
  802. sdk.Events.on({
  803. eventName: "wme-save-finished",
  804. eventHandler: (payload) => {
  805. if (payload.success) {
  806. scanArea();
  807. lanesTabSetup();
  808. displayLaneGraphics();
  809. }
  810. },
  811. });
  812. sdk.Map.addLayer({
  813. layerName: LTNamesLayer.name,
  814. styleRules: styleConfig.styleRules,
  815. zIndexing: true,
  816. styleContext: styleConfig.styleContext,
  817. });
  818. // Layer for lane text
  819. sdk.LayerSwitcher.addLayerCheckbox(LTNamesLayer);
  820. sdk.Map.setLayerVisibility({
  821. layerName: LTNamesLayer.name,
  822. visibility: LtSettings.ltNamesVisible,
  823. });
  824. sdk.LayerSwitcher.setLayerCheckboxChecked({
  825. name: LTLaneGraphics.name,
  826. isChecked: LtSettings.ltGraphicsVisible,
  827. });
  828. sdk.LayerSwitcher.setLayerCheckboxChecked({
  829. name: LTHighlightLayer.name,
  830. isChecked: LtSettings.highlightsVisible,
  831. });
  832. sdk.LayerSwitcher.setLayerCheckboxChecked({ name: LTNamesLayer.name, isChecked: LtSettings.ltNamesVisible });
  833. sdk.Events.on({
  834. eventName: "wme-map-move-end",
  835. eventHandler: () => {
  836. scanArea();
  837. displayLaneGraphics();
  838. },
  839. });
  840. sdk.Events.on({
  841. eventName: "wme-map-zoom-changed",
  842. eventHandler: () => {
  843. scanArea();
  844. displayLaneGraphics();
  845. },
  846. });
  847. sdk.Events.on({
  848. eventName: "wme-selection-changed",
  849. eventHandler: () => {
  850. scanArea();
  851. lanesTabSetup();
  852. displayLaneGraphics();
  853. },
  854. });
  855. // Add keyboard shortcuts
  856. try {
  857. const enableHighlightsShortcut = {
  858. shortcutId: "enableHighlights",
  859. description: "Toggle lane highlights",
  860. callback: toggleHighlights,
  861. shortcutKeys: "",
  862. };
  863. sdk.Shortcuts.createShortcut(enableHighlightsShortcut);
  864. // new WazeWrap.Interface.Shortcut(
  865. // "enableHighlights",
  866. // "Toggle lane highlights",
  867. // "wmelt",
  868. // "Lane Tools",
  869. // LtSettings.enableHighlights,
  870. // toggleHighlights,
  871. // null
  872. // ).add();
  873. const enableUIEnhancementsShortcut = {
  874. callback: toggleUIEnhancements,
  875. shortcutId: "enableUIEnhancements",
  876. description: "Toggle UI Enhancements",
  877. shortcutKeys: "",
  878. };
  879. sdk.Shortcuts.createShortcut(enableUIEnhancementsShortcut);
  880. // new WazeWrap.Interface.Shortcut(
  881. // "enableUIEnhancements",
  882. // "Toggle UI enhancements",
  883. // "wmelt",
  884. // "Lane Tools",
  885. // LtSettings.enableUIEnhancements,
  886. // toggleUIEnhancements,
  887. // null
  888. // ).add();
  889. const enableHeuristicsShortcut = {
  890. callback: toggleLaneHeuristicsChecks,
  891. shortcutId: "enableHeuristics",
  892. description: "Toggle heuristic highlights",
  893. shortcutKeys: "",
  894. };
  895. sdk.Shortcuts.createShortcut(enableHeuristicsShortcut);
  896. // new WazeWrap.Interface.Shortcut(
  897. // "enableHeuristics",
  898. // "Toggle heuristic highlights",
  899. // "wmelt",
  900. // "Lane Tools",
  901. // LtSettings.enableHeuristics,
  902. // toggleLaneHeuristicsChecks,
  903. // null
  904. // ).add();
  905. const enableScriptShortcut = {
  906. shortcutId: "enableScript",
  907. description: "Toggle script",
  908. callback: toggleScript,
  909. shortcutKeys: "",
  910. };
  911. sdk.Shortcuts.createShortcut(enableScriptShortcut);
  912. // new WazeWrap.Interface.Shortcut(
  913. // "enableScript",
  914. // "Toggle script",
  915. // "wmelt",
  916. // "Lane Tools",
  917. // LtSettings.enableScript,
  918. // toggleScript,
  919. // null
  920. // ).add();
  921. }
  922. catch (e) {
  923. console.log("LT: Error creating shortcuts. This feature will be disabled.");
  924. $("#lt-EnableShortcut").text(`${TAB_TRANSLATIONS[langLocality].disabled}`);
  925. $("#lt-HighlightShortcut").text(`${TAB_TRANSLATIONS[langLocality].disabled}`);
  926. $("#lt-UIEnhanceShortcut").text(`${TAB_TRANSLATIONS[langLocality].disabled}`);
  927. $("#lt-LaneHeurChecksShortcut").text(`${TAB_TRANSLATIONS[langLocality].disabled}`);
  928. shortcutsDisabled = true;
  929. }
  930. // Setup user options now that the settings are loaded
  931. const highlights = $("#lt-HighlightsEnable");
  932. const colorTitle = $("#lt-color-title");
  933. const heurChecks = $("#lt-LaneHeuristicsChecks");
  934. setOptionStatus();
  935. setTimeout(() => {
  936. updateShortcutLabels();
  937. }, 50);
  938. setHeuristics();
  939. setLocalisation();
  940. if (_pickleColor && _pickleColor > 0) {
  941. let featureList = "LaneTools: The following special access features are enabled: ";
  942. $("#lt-adv-tools").css("display", "block");
  943. const quickTog = $("#lt-trans-quickTog");
  944. quickTog.attr("data-original-title", `${strings.selAllTooltip}`);
  945. quickTog.tooltip();
  946. _.each(RBSArray, (u) => {
  947. if (seaPickle !== null && u[0] === seaPickle.userName) {
  948. if (u[1] === "1") {
  949. isRBS = true;
  950. }
  951. if (u[2] === "1") {
  952. allowCpyPst = true;
  953. }
  954. }
  955. });
  956. if (isRBS) {
  957. $("#lt-serverSelectContainer").css("display", "block");
  958. featureList += "RBS Heuristics";
  959. }
  960. if (allowCpyPst) {
  961. $("#lt-sheet-link").css({
  962. display: "block",
  963. margin: "2px",
  964. });
  965. const ltSheetLinkAnchor = $("#lt-sheet-link > a");
  966. ltSheetLinkAnchor.css({
  967. padding: "2px",
  968. border: "2px solid black",
  969. "border-radius": "6px",
  970. "text-decoration": "none",
  971. });
  972. ltSheetLinkAnchor
  973. .on("mouseenter", function () {
  974. $(this).css("background-color", "orange");
  975. })
  976. .on("mouseleave", function () {
  977. $(this).css("background-color", "#eeeeee");
  978. });
  979. // $('#lt-cpy-pst').css('display', 'block');
  980. // $('.lt-Toolbar-Container').draggable({ containment: 'parent', zIndex: '100' });
  981. // WazeWrap.Events.register('selectionchanged', null, displayToolbar);
  982. $(".lt-toolbar-button").on("click", function () {
  983. if ($(this)[0].id === "copyA-button") {
  984. copyLaneInfo("A");
  985. }
  986. if ($(this)[0].id === "copyB-button") {
  987. copyLaneInfo("B");
  988. }
  989. if ($(this)[0].id === "pasteA-button") {
  990. pasteLaneInfo("A");
  991. }
  992. if ($(this)[0].id === "pasteB-button") {
  993. pasteLaneInfo("B");
  994. }
  995. });
  996. featureList = isRBS ? `${featureList}, Copy/Paste` : `${featureList}Copy/Paste`;
  997. }
  998. if (isRBS || allowCpyPst) {
  999. console.log(featureList);
  1000. }
  1001. }
  1002. else {
  1003. $("#lt-LaneTabFeatures").css("display", "none");
  1004. }
  1005. $(".lt-checkbox").on("click", function () {
  1006. const settingName = $(this)[0].id.substring(3);
  1007. LtSettings[settingName] = this.checked;
  1008. saveSettings();
  1009. });
  1010. $(".lt-color-input").on("change", function () {
  1011. const settingName = $(this)[0].id.substring(3);
  1012. LtSettings[settingName] = this.value;
  1013. saveSettings();
  1014. $(`#lt-${settingName}`).css("border", `2px solid ${this.value}`);
  1015. removeHighlights();
  1016. scanArea();
  1017. });
  1018. $("#lt-ScriptEnabled").on("click", () => {
  1019. if (getId("lt-ScriptEnabled")?.checked) {
  1020. scanArea();
  1021. }
  1022. else {
  1023. removeHighlights();
  1024. removeLaneGraphics();
  1025. }
  1026. });
  1027. highlights.on("click", () => {
  1028. if (getId("lt-HighlightsEnable")?.checked) {
  1029. scanArea();
  1030. }
  1031. else {
  1032. removeHighlights();
  1033. }
  1034. scanArea();
  1035. });
  1036. $("#lt-LabelsEnable").on("click", () => {
  1037. if (getId("lt-LabelsEnable")?.checked) {
  1038. scanArea();
  1039. }
  1040. else {
  1041. removeHighlights();
  1042. scanArea();
  1043. }
  1044. });
  1045. $("#lt-NodesEnable").on("click", () => {
  1046. if (getId("lt-NodesEnable")?.checked) {
  1047. scanArea();
  1048. }
  1049. else {
  1050. removeHighlights();
  1051. scanArea();
  1052. }
  1053. });
  1054. $("#lt-LIOEnable").on("click", () => {
  1055. if (getId("lt-LIOEnable")?.checked) {
  1056. scanArea();
  1057. }
  1058. else {
  1059. removeHighlights();
  1060. scanArea();
  1061. }
  1062. });
  1063. $("#lt-IconsEnable").on("click", () => {
  1064. if (getId("lt-IconsEnable")?.checked) {
  1065. displayLaneGraphics();
  1066. }
  1067. else {
  1068. removeLaneGraphics();
  1069. }
  1070. });
  1071. $("#lt-highlightOverride").on("click", () => {
  1072. if (getId("lt-highlightOverride")?.checked) {
  1073. scanArea();
  1074. }
  1075. else {
  1076. removeHighlights();
  1077. scanArea();
  1078. }
  1079. });
  1080. colorTitle.on("click", () => {
  1081. $("#lt-color-inputs").toggle();
  1082. });
  1083. $("#lt-ClickSaveEnable").on("click", () => {
  1084. $(".lt-option-container.clk-svr").toggle();
  1085. });
  1086. $("#lt-UIEnable").on("click", () => {
  1087. $("#lt-UI-wrapper").toggle();
  1088. removeLaneGraphics();
  1089. });
  1090. highlights.on("click", () => {
  1091. $("#lt-highlights-wrapper").toggle();
  1092. });
  1093. heurChecks.on("click", () => {
  1094. $("#lt-heur-wrapper").toggle();
  1095. });
  1096. heurChecks.on("click", () => {
  1097. if (getId("lt-LaneHeuristicsChecks")?.checked) {
  1098. scanArea();
  1099. }
  1100. else {
  1101. removeHighlights();
  1102. scanArea();
  1103. }
  1104. });
  1105. $("#lt-LaneHeurPosHighlight").on("click", () => {
  1106. if (getId("lt-LaneHeurPosHighlight")?.checked) {
  1107. scanArea();
  1108. }
  1109. else {
  1110. removeHighlights();
  1111. scanArea();
  1112. }
  1113. });
  1114. $("#lt-LaneHeurNegHighlight").on("click", () => {
  1115. if (getId("lt-LaneHeurNegHighlight")?.checked) {
  1116. scanArea();
  1117. }
  1118. else {
  1119. removeHighlights();
  1120. scanArea();
  1121. }
  1122. });
  1123. $("#lt-serverSelect").on("click", () => {
  1124. setHeuristics();
  1125. removeHighlights();
  1126. scanArea();
  1127. });
  1128. // Watches for the shortcut dialog to close and updates UI
  1129. const el = $.fn.hide;
  1130. $.fn.hide = function () {
  1131. this.trigger("hide");
  1132. return el.apply(this, arguments);
  1133. };
  1134. $("#keyboard-dialog").on("hide", () => {
  1135. checkShortcutsChanged();
  1136. });
  1137. colorTitle.tooltip();
  1138. }
  1139. async function loadSettings() {
  1140. const localSettings = JSON.parse(localStorage.getItem("LT_Settings"));
  1141. const serverSettings = await WazeWrap.Remote.RetrieveSettings("LT_Settings");
  1142. if (!serverSettings) {
  1143. console.error("LaneTools: Error communicating with WW settings server");
  1144. }
  1145. const defaultSettings = {
  1146. lastSaveAction: 0,
  1147. ScriptEnabled: true,
  1148. UIEnable: true,
  1149. AutoOpenWidth: false,
  1150. AutoExpandLanes: false,
  1151. AutoLanesTab: false,
  1152. HighlightsEnable: true,
  1153. LabelsEnable: true,
  1154. NodesEnable: true,
  1155. ABColor: "#990033",
  1156. BAColor: "#0033cc",
  1157. LabelColor: "#FFAD08",
  1158. ErrorColor: "#F50E0E",
  1159. NodeColor: "#66ccff",
  1160. TIOColor: "#ff9900",
  1161. LIOColor: "#ff9900",
  1162. CS1Color: "#04E6F6",
  1163. CS2Color: "#8F47FA",
  1164. HeurColor: "#00aa00",
  1165. HeurFailColor: "#E804F6",
  1166. CopyEnable: false,
  1167. SelAllEnable: false,
  1168. serverSelect: false,
  1169. LIOEnable: true,
  1170. CSEnable: true,
  1171. AutoFocusLanes: true,
  1172. ReverseLanesIcon: false,
  1173. ClickSaveEnable: true,
  1174. ClickSaveStraight: false,
  1175. ClickSaveTurns: true,
  1176. enableScript: "",
  1177. enableHighlights: "",
  1178. enableUIEnhancements: "",
  1179. enableHeuristics: "",
  1180. LaneHeurNegHighlight: false,
  1181. LaneHeurPosHighlight: false,
  1182. LaneHeuristicsChecks: false,
  1183. highlightCSIcons: false,
  1184. highlightOverride: true,
  1185. AddTIO: false,
  1186. IconsEnable: true,
  1187. IconsRotate: true,
  1188. highlightsVisible: false,
  1189. ltGraphicsVisible: false,
  1190. ltNamesVisible: false,
  1191. };
  1192. LtSettings = $.extend({}, defaultSettings, localSettings);
  1193. if (serverSettings && serverSettings.lastSaveAction > LtSettings.lastSaveAction) {
  1194. $.extend(LtSettings, serverSettings);
  1195. // console.log('LaneTools: server settings used');
  1196. }
  1197. else {
  1198. // console.log('LaneTools: local settings used');
  1199. }
  1200. }
  1201. async function saveSettings() {
  1202. const { ScriptEnabled, HighlightsEnable, LabelsEnable, NodesEnable, UIEnable, AutoLanesTab, AutoOpenWidth, AutoExpandLanes, ABColor, BAColor, LabelColor, ErrorColor, NodeColor, TIOColor, LIOColor, CS1Color, CS2Color, CopyEnable, SelAllEnable, serverSelect, LIOEnable, CSEnable, AutoFocusLanes, ReverseLanesIcon, ClickSaveEnable, ClickSaveStraight, ClickSaveTurns, enableScript, enableHighlights, enableUIEnhancements, enableHeuristics, HeurColor, HeurFailColor, LaneHeurPosHighlight, LaneHeurNegHighlight, LaneHeuristicsChecks, highlightCSIcons, highlightOverride, AddTIO, IconsEnable, IconsRotate, highlightsVisible, ltGraphicsVisible, ltNamesVisible, } = LtSettings;
  1203. const localSettings = {
  1204. lastSaveAction: Date.now(),
  1205. ScriptEnabled,
  1206. HighlightsEnable,
  1207. LabelsEnable,
  1208. NodesEnable,
  1209. UIEnable,
  1210. AutoOpenWidth,
  1211. AutoLanesTab,
  1212. AutoExpandLanes,
  1213. ABColor,
  1214. BAColor,
  1215. LabelColor,
  1216. ErrorColor,
  1217. NodeColor,
  1218. TIOColor,
  1219. LIOColor,
  1220. CS1Color,
  1221. CS2Color,
  1222. CopyEnable,
  1223. SelAllEnable,
  1224. serverSelect,
  1225. LIOEnable,
  1226. CSEnable,
  1227. AutoFocusLanes,
  1228. ReverseLanesIcon,
  1229. ClickSaveEnable,
  1230. ClickSaveStraight,
  1231. ClickSaveTurns,
  1232. enableScript,
  1233. enableHighlights,
  1234. enableUIEnhancements,
  1235. enableHeuristics,
  1236. HeurColor,
  1237. HeurFailColor,
  1238. LaneHeurPosHighlight,
  1239. LaneHeurNegHighlight,
  1240. LaneHeuristicsChecks,
  1241. highlightCSIcons,
  1242. highlightOverride,
  1243. AddTIO,
  1244. IconsEnable,
  1245. IconsRotate,
  1246. highlightsVisible,
  1247. ltGraphicsVisible,
  1248. ltNamesVisible,
  1249. };
  1250. // Grab keyboard shortcuts and store them for saving
  1251. // for (const name in W.accelerators.Actions) {
  1252. // const { shortcut, group } = W.accelerators.Actions[name];
  1253. // if (group === "wmelt") {
  1254. for (const shortcut of sdk.Shortcuts.getAllShortcuts()) {
  1255. localSettings[shortcut.shortcutId] = shortcut.shortcutKeys;
  1256. }
  1257. // Required for the instant update of changes to the keyboard shortcuts on the UI
  1258. LtSettings = localSettings;
  1259. if (localStorage) {
  1260. localStorage.setItem("LT_Settings", JSON.stringify(localSettings));
  1261. }
  1262. const serverSave = await WazeWrap.Remote.SaveSettings("LT_Settings", localSettings);
  1263. if (serverSave === null) {
  1264. console.warn("LaneTools: User PIN not set in WazeWrap tab");
  1265. }
  1266. else {
  1267. if (serverSave === false) {
  1268. console.error("LaneTools: Unable to save settings to server");
  1269. }
  1270. }
  1271. }
  1272. async function loadSpreadsheet() {
  1273. let connected = false;
  1274. const apiKey = "AIzaSyDZjmkSx5xWc-86hsAIzedgDgRgy8vB7BQ";
  1275. const settingsFailFunc = (jqXHR, _textStatus, errorThrown) => {
  1276. console.error("LaneTools: Error loading settings:", errorThrown);
  1277. };
  1278. const rbsFailFunc = (_jqXHR, _textStatus, errorThrown) => {
  1279. console.error("LaneTools: Error loading RBS:", errorThrown);
  1280. if (!RBSArray.failed) {
  1281. WazeWrap.Alerts.error(GM_info.script.name, "Unable to load heuristics data for LG. This feature will not be available");
  1282. RBSArray.failed = true;
  1283. }
  1284. };
  1285. const translationsFailFunc = (jqXHR, textStatus, errorThrown) => {
  1286. console.error("LaneTools: Error loading trans:", errorThrown);
  1287. };
  1288. try {
  1289. await $.getJSON(`https://sheets.googleapis.com/v4/spreadsheets/1_3sF09sMOid_us37j5CQqJZlBGGr1vI_3Rrmp5K-KCQ/values/Translations!A2:C?key=${apiKey}`)
  1290. .done(async (transArray) => {
  1291. if (transArray.values.length > 0) {
  1292. _.each(transArray.values, (t) => {
  1293. if (!TAB_TRANSLATIONS[t[1]] && Number.parseInt(t[2], 10) === 1) {
  1294. TAB_TRANSLATIONS[t[1]] = JSON.parse(t[0]);
  1295. }
  1296. });
  1297. }
  1298. else {
  1299. translationsFailFunc(null, null, "Failed to get any translations");
  1300. }
  1301. })
  1302. .fail(translationsFailFunc);
  1303. }
  1304. catch (e) {
  1305. translationsFailFunc(null, null, e);
  1306. }
  1307. try {
  1308. await $.getJSON(`https://sheets.googleapis.com/v4/spreadsheets/1_3sF09sMOid_us37j5CQqJZlBGGr1vI_3Rrmp5K-KCQ/values/Angles!A2:B?key=${apiKey}`)
  1309. .done((serverSettings) => {
  1310. if (serverSettings.values.length > 0) {
  1311. _.each(serverSettings.values, (v) => {
  1312. if (!configArray[v[1]]) {
  1313. configArray[v[1]] = JSON.parse(v[0]);
  1314. }
  1315. });
  1316. connected = true;
  1317. }
  1318. else {
  1319. settingsFailFunc();
  1320. }
  1321. })
  1322. .fail(settingsFailFunc);
  1323. }
  1324. catch (e) {
  1325. settingsFailFunc(null, null, e);
  1326. }
  1327. try {
  1328. await $.getJSON(`https://sheets.googleapis.com/v4/spreadsheets/1_3sF09sMOid_us37j5CQqJZlBGGr1vI_3Rrmp5K-KCQ/values/RBS_Access!A2:C?key=${apiKey}`)
  1329. .done((allowedRBS) => {
  1330. if (allowedRBS.values.length > 0) {
  1331. for (let i = 0; i < allowedRBS.values.length; i++) {
  1332. RBSArray[i] = allowedRBS.values[i];
  1333. }
  1334. RBSArray.failed = false;
  1335. }
  1336. else {
  1337. rbsFailFunc();
  1338. }
  1339. })
  1340. .fail(rbsFailFunc);
  1341. }
  1342. catch (e) {
  1343. rbsFailFunc(null, null, e);
  1344. }
  1345. if (connected) {
  1346. _.each(configArray, (serverKey) => {
  1347. for (const k in serverKey) {
  1348. if (serverKey.hasOwnProperty(k)) {
  1349. const keyValue = serverKey[k];
  1350. serverKey[k] = Number.parseFloat(keyValue);
  1351. }
  1352. }
  1353. });
  1354. }
  1355. }
  1356. function setLocalisation() {
  1357. // langLocality = I18n.currentLocale().toLowerCase();
  1358. const locale = sdk.Settings.getLocale();
  1359. LANG = locale.localeCode.toLowerCase();
  1360. if (!(LANG in TAB_TRANSLATIONS))
  1361. langLocality = "en-us";
  1362. else
  1363. langLocality = LANG;
  1364. if (!(langLocality in TAB_TRANSLATIONS)) {
  1365. langLocality = "en";
  1366. }
  1367. if (TAB_TRANSLATIONS[langLocality]) {
  1368. strings = TAB_TRANSLATIONS[langLocality];
  1369. }
  1370. else if (langLocality.includes("-") && TAB_TRANSLATIONS[langLocality.split("-")[0]]) {
  1371. strings = TAB_TRANSLATIONS[langLocality.split("-")[0]];
  1372. }
  1373. // If there is no value set in any of the translated strings then use the defaults
  1374. for (const transString of Object.keys(strings)) {
  1375. if (strings[transString] === "") {
  1376. strings[transString] = TAB_TRANSLATIONS.default[transString];
  1377. }
  1378. }
  1379. $(".lt-trans-enabled").text(strings.enabled);
  1380. $(".lt-trans-tglshcut").text(strings.toggleShortcut);
  1381. $("#lt-trans-uiEnhance").text(strings.UIEnhance);
  1382. $("#lt-trans-autoTab").text(strings.autoOpen);
  1383. $("#lt-trans-autoWidth").text(strings.autoWidth);
  1384. $("#lt-trans-autoExpand").text(strings.autoExpand);
  1385. $("#lt-trans-autoFocus").text(strings.autoFocus);
  1386. $("#lt-trans-orient").text(strings.reOrient);
  1387. $("#lt-trans-enClick").text(strings.enClick);
  1388. $("#lt-trans-straClick").text(strings.clickStraight);
  1389. $("#lt-trans-turnClick").text(strings.clickTurn);
  1390. $("#lt-trans-mapHigh").text(strings.mapHighlight);
  1391. $("#lt-trans-lnLabel").text(strings.laneLabel);
  1392. $("#lt-trans-nodeHigh").text(strings.nodeHigh);
  1393. $("#lt-trans-laOver").text(strings.LAOHigh);
  1394. $("#lt-trans-csOver").text(strings.CSOHigh);
  1395. $("#lt-trans-heurCan").text(strings.heuristics);
  1396. $("#lt-trans-heurPos").text(strings.posHeur);
  1397. $("#lt-trans-heurNeg").text(strings.negHeur);
  1398. $("#lt-trans-highCol").text(strings.highColor);
  1399. $("#lt-trans-fwdCol").text(strings.fwdCol);
  1400. $("#lt-trans-revCol").text(strings.revCol);
  1401. $("#lt-trans-labelCol").text(strings.labelCol);
  1402. $("#lt-trans-errorCol").text(strings.errorCol);
  1403. $("#lt-trans-nodeCol").text(strings.laneNodeCol);
  1404. $("#lt-trans-tioCol").text(strings.nodeTIOCol);
  1405. $("#lt-trans-laoCol").text(strings.LAOCol);
  1406. $("#lt-trans-viewCol").text(strings.viewCSCol);
  1407. $("#lt-trans-hearCol").text(strings.hearCSCol);
  1408. $("#lt-trans-posCol").text(strings.heurPosCol);
  1409. $("#lt-trans-negCol").text(strings.heurNegCol);
  1410. $("#lt-trans-advTools").text(strings.advTools);
  1411. $("#lt-trans-quickTog").text(strings.quickTog);
  1412. $("#lt-trans-heurRBS").text(strings.showRBS);
  1413. $("#lt-trans-csIcons").text(strings.csIcons);
  1414. $("#lt-trans-highOver").text(strings.highlightOverride);
  1415. $("#lt-trans-AddTIO").text(strings.addTIO);
  1416. $("#lt-trans-enIcons").text(strings.enIcons);
  1417. $("#lt-trans-IconsRotate").text(strings.IconsRotate);
  1418. $("#lt-color-title").attr("data-original-title", strings.colTooltip);
  1419. if (shortcutsDisabled) {
  1420. $("#lt-EnableShortcut").text(`${strings.disabled}`);
  1421. $("#lt-HighlightShortcut").text(`${strings.disabled}`);
  1422. $("#lt-UIEnhanceShortcut").text(`${strings.disabled}`);
  1423. $("#lt-LaneHeurChecksShortcut").text(`${strings.disabled}`);
  1424. }
  1425. }
  1426. function setHeuristics() {
  1427. if (RBSArray.failed) {
  1428. return;
  1429. }
  1430. const angles = isRBS && getId("lt-serverSelect").checked ? configArray.RBS : configArray.RPS;
  1431. MAX_LEN_HEUR = angles.MAX_LEN_HEUR;
  1432. MAX_PERP_DIF = angles.MAX_PERP_DIF;
  1433. MAX_PERP_DIF_ALT = angles.MAX_PERP_DIF_ALT;
  1434. MAX_PERP_TO_CONSIDER = angles.MAX_PERP_TO_CONSIDER;
  1435. MAX_STRAIGHT_TO_CONSIDER = angles.MAX_STRAIGHT_TO_CONSIDER;
  1436. MAX_STRAIGHT_DIF = angles.MAX_STRAIGHT_DIF;
  1437. }
  1438. // Checks the WME value of a shortcut (from the shortcut menu) against the scripts value and saves if they are different
  1439. function checkShortcutsChanged() {
  1440. let triggerSave = false;
  1441. for (const name in W.accelerators.Actions) {
  1442. const { shortcut, group } = W.accelerators.Actions[name];
  1443. if (group === "wmelt") {
  1444. let TempKeys = "";
  1445. if (shortcut) {
  1446. if (shortcut.altKey === true) {
  1447. TempKeys += "A";
  1448. }
  1449. if (shortcut.shiftKey === true) {
  1450. TempKeys += "S";
  1451. }
  1452. if (shortcut.ctrlKey === true) {
  1453. TempKeys += "C";
  1454. }
  1455. if (TempKeys !== "") {
  1456. TempKeys += "+";
  1457. }
  1458. if (shortcut.keyCode) {
  1459. TempKeys += shortcut.keyCode;
  1460. }
  1461. }
  1462. else {
  1463. TempKeys = "-1";
  1464. }
  1465. if (LtSettings[name] !== TempKeys) {
  1466. triggerSave = true;
  1467. console.log(`LaneTools: Stored shortcut ${name}: ${LtSettings[name]} changed to ${TempKeys}`);
  1468. break;
  1469. }
  1470. }
  1471. }
  1472. if (triggerSave) {
  1473. saveSettings();
  1474. setTimeout(() => {
  1475. updateShortcutLabels();
  1476. }, 200);
  1477. }
  1478. }
  1479. // Pulls the keyboard shortcuts from the script and returns a machine value
  1480. function getKeyboardShortcut(shortcut) {
  1481. if (shortcut === null)
  1482. return null;
  1483. const keys = LtSettings[shortcut];
  1484. if (typeof keys !== "string")
  1485. return null;
  1486. let val = "";
  1487. if (keys.indexOf("+") > -1) {
  1488. const specialKeys = keys.split("+")[0];
  1489. for (let i = 0; i < specialKeys.length; i++) {
  1490. if (val.length > 0) {
  1491. val += "+";
  1492. }
  1493. if (specialKeys[i] === "C") {
  1494. val += "Ctrl";
  1495. }
  1496. if (specialKeys[i] === "S") {
  1497. val += "Shift";
  1498. }
  1499. if (specialKeys[i] === "A") {
  1500. val += "Alt";
  1501. }
  1502. }
  1503. if (val.length > 0) {
  1504. val += "+";
  1505. }
  1506. let num = Number.parseInt(keys.split("+")[1]);
  1507. if (num >= 96 && num <= 105) {
  1508. // Numpad keys
  1509. num -= 48;
  1510. val += "[num pad]";
  1511. }
  1512. val += String.fromCharCode(num);
  1513. }
  1514. else {
  1515. let num = Number.parseInt(keys, 10);
  1516. if (num >= 96 && num <= 105) {
  1517. // Numpad keys
  1518. num -= 48;
  1519. val += "[num pad]";
  1520. }
  1521. val += String.fromCharCode(num);
  1522. }
  1523. return val;
  1524. }
  1525. // Updates the UI tab with the shortcut values
  1526. function updateShortcutLabels() {
  1527. if (!shortcutsDisabled) {
  1528. $("#lt-EnableShortcut").text(getKeyboardShortcut("enableScript") || "");
  1529. $("#lt-HighlightShortcut").text(getKeyboardShortcut("enableHighlights") || "");
  1530. $("#lt-UIEnhanceShortcut").text(getKeyboardShortcut("enableUIEnhancements") || "");
  1531. $("#lt-LaneHeurChecksShortcut").text(getKeyboardShortcut("enableHeuristics") || "");
  1532. }
  1533. }
  1534. function getLegacySegObj(id) {
  1535. return W.model.segments.getObjectById(id);
  1536. }
  1537. function getSegObj(id) {
  1538. if (!id)
  1539. return null;
  1540. return sdk.DataModel.Segments.getById({ segmentId: id });
  1541. }
  1542. function getNodeObj(id) {
  1543. if (id === null)
  1544. return null;
  1545. return sdk.DataModel.Nodes.getById({ nodeId: id });
  1546. }
  1547. function getLegacyNodeObj(id) {
  1548. return W.model.nodes.getObjectById(id);
  1549. }
  1550. function lanesTabSetup() {
  1551. // hook into edit panel on the left
  1552. if (getId("edit-panel")?.getElementsByTagName("wz-tabs").length === 0) {
  1553. setTimeout(lanesTabSetup, 500);
  1554. //console.log('Edit panel not yet loaded.');
  1555. return;
  1556. }
  1557. // const selSeg = W.selectionManager.getSelectedWMEFeatures();
  1558. const selection = sdk.Editing.getSelection();
  1559. let fwdDone = false;
  1560. let revDone = false;
  1561. let isRotated = false;
  1562. let expandEditTriggered = false;
  1563. // Highlights junction node when hovering over left panel
  1564. function hoverNodeTo() {
  1565. // W.model.nodes.getObjectById(W.selectionManager.getSegmentSelection().segments[0].attributes.toNodeID)
  1566. // .attributes.geometry;
  1567. // W.model.nodes.get(W.selectionManager.getSegmentSelection().segments[0].attributes.toNodeID).attributes.geometry
  1568. const selSeg = isSegmentSelected(selection)
  1569. ? sdk.DataModel.Segments.getById({ segmentId: selection?.ids[0] })
  1570. : null;
  1571. const nodeB = selSeg?.toNodeId ? sdk.DataModel.Nodes.getById({ nodeId: selSeg.toNodeId }) : null;
  1572. nodeB && document.getElementById(nodeB?.id.toString());
  1573. // document.getElementById(
  1574. // W.model.nodes.getObjectById(W.selectionManager.getSegmentSelection().segments[0].attributes.toNodeID)
  1575. // .attributes.geometry.id
  1576. // );
  1577. // document.getElementById(W.model.nodes.get(W.selectionManager.getSegmentSelection().segments[0].attributes.toNodeID).attributes.geometry.id)
  1578. console.log("hovering to B");
  1579. }
  1580. function hoverNodeFrom() {
  1581. // W.model.nodes.getObjectById(W.selectionManager.getSegmentSelection().segments[0].attributes.fromNodeID)
  1582. // .attributes.geometry;
  1583. const selSeg = isSegmentSelected(selection)
  1584. ? sdk.DataModel.Segments.getById({ segmentId: selection?.ids[0] })
  1585. : null;
  1586. const nodeA = selSeg?.fromNodeId ? sdk.DataModel.Nodes.getById({ nodeId: selSeg.fromNodeId }) : null;
  1587. nodeA && document.getElementById(nodeA?.id.toString());
  1588. // W.model.nodes.get(W.selectionManager.getSegmentSelection().segments[0].attributes.fromNodeID).attributes.geometry
  1589. // document.getElementById(
  1590. // W.model.nodes.getObjectById(W.selectionManager.getSegmentSelection().segments[0].attributes.fromNodeID)
  1591. // .attributes.geometry.id
  1592. // );
  1593. // document.getElementById(W.model.nodes.get(W.selectionManager.getSegmentSelection().segments[0].attributes.fromNodeID).attributes.geometry.id)
  1594. console.log("hovering to A");
  1595. }
  1596. function showAddLaneGuidance(laneDir) {
  1597. insertSelAll(laneDir);
  1598. addLnsBtns(laneDir);
  1599. adjustSpace();
  1600. focusEle();
  1601. applyButtonListeners();
  1602. // if (getId("lt-AddTIO")?.checked) addTIOUI(laneDir);
  1603. }
  1604. function updateUI() {
  1605. // if (eventInfo !== null) {
  1606. // eventInfo.stopPropagation();
  1607. // }
  1608. // if (getId('lt-ReverseLanesIcon').checked && !isRotated) {
  1609. // rotateArrows();
  1610. // }
  1611. if (getId("lt-highlightCSIcons")?.checked) {
  1612. colorCSDir();
  1613. }
  1614. // Add delete buttons and preselected lane number buttons to UI
  1615. if (_pickleColor && _pickleColor >= 1) {
  1616. const selSeg = isSegmentSelected(selection)
  1617. ? sdk.DataModel.Segments.getById({ segmentId: selection?.ids[0] })
  1618. : null;
  1619. if (getId("li-del-opp-btn"))
  1620. $("#li-del-opp-btn").remove();
  1621. if (getId("li-del-fwd-btn"))
  1622. $("#li-del-fwd-btn").remove();
  1623. if (getId("li-del-rev-btn"))
  1624. $("#li-del-rev-btn").remove();
  1625. const $fwdButton = $(`<button type="button" id="li-del-fwd-btn" style="height:20px;background-color:white;border:1px solid grey;border-radius:8px;">${strings.delFwd}</button>`);
  1626. const $revButton = $(`<button type="button" id="li-del-rev-btn" style="height:20px;background-color:white;border:1px solid grey;border-radius:8px;">${strings.delRev}</button>`);
  1627. const $oppButton = $(`<button type="button" id="li-del-opp-btn" style="height:auto;background-color:orange;border:1px solid grey;border-radius:8px; margin-bottom:5px;">${strings.delOpp}</button>`);
  1628. const $btnCont1 = $('<div style="display:inline-block;position:relative;" />');
  1629. const $btnCont2 = $('<div style="display:inline-block;position:relative;" />');
  1630. const $btnCont3 = $('<div style="display:inline-block;position:relative;" />');
  1631. $fwdButton.appendTo($btnCont1);
  1632. $revButton.appendTo($btnCont2);
  1633. $oppButton.appendTo($btnCont3);
  1634. const delFwd = $("#li-del-fwd-btn");
  1635. const delRev = $("#li-del-rev-btn");
  1636. const delOpp = $("#li-del-opp-btn");
  1637. delFwd.off();
  1638. delRev.off();
  1639. delOpp.off();
  1640. if (!getId("li-del-rev-btn") && !revDone && selSeg?.toNodeLanesCount && selSeg.toNodeLanesCount > 0) {
  1641. if ($(".rev-lanes > div.lane-instruction.lane-instruction-from > div.instruction").length > 0) {
  1642. $btnCont2.prependTo(".rev-lanes > div.lane-instruction.lane-instruction-from > div.instruction");
  1643. $(".rev-lanes > div.lane-instruction.lane-instruction-from > div.instruction").css("border-bottom", `4px dashed ${LtSettings.BAColor}`);
  1644. }
  1645. else if (selSeg.isAtoB) {
  1646. //jm6087
  1647. $oppButton.prop("title", "rev");
  1648. $oppButton.prependTo("#edit-panel > div > div > div > div.segment-edit-section > wz-tabs > wz-tab.lanes-tab");
  1649. }
  1650. }
  1651. else {
  1652. $(".rev-lanes > div.lane-instruction.lane-instruction-from > div.instruction").css("border-bottom", `4px dashed ${LtSettings.BAColor}`);
  1653. }
  1654. if (!getId("li-del-fwd-btn") &&
  1655. !fwdDone &&
  1656. selSeg?.fromNodeLanesCount &&
  1657. selSeg.fromNodeLanesCount > 0) {
  1658. if ($(".fwd-lanes > div.lane-instruction.lane-instruction-from > div.instruction").length > 0) {
  1659. $btnCont1.prependTo(".fwd-lanes > div.lane-instruction.lane-instruction-from > div.instruction");
  1660. $(".fwd-lanes > div.lane-instruction.lane-instruction-from > div.instruction").css("border-bottom", `4px dashed ${LtSettings.ABColor}`);
  1661. }
  1662. else if (selSeg.isBtoA) {
  1663. //jm6087
  1664. $oppButton.prop("title", "fwd");
  1665. $oppButton.prependTo("#edit-panel > div > div > div > div.segment-edit-section > wz-tabs > wz-tab.lanes-tab");
  1666. }
  1667. }
  1668. else {
  1669. $(".fwd-lanes > div.lane-instruction.lane-instruction-from > div.instruction").css("border-bottom", `4px dashed ${LtSettings.ABColor}`);
  1670. }
  1671. $("#li-del-fwd-btn").on("click", () => {
  1672. delLanes("fwd");
  1673. fwdDone = true;
  1674. setTimeout(() => {
  1675. updateUI();
  1676. }, 200);
  1677. });
  1678. $("#li-del-rev-btn").on("click", () => {
  1679. delLanes("rev");
  1680. revDone = true;
  1681. setTimeout(() => {
  1682. updateUI();
  1683. }, 200);
  1684. });
  1685. $("#li-del-opp-btn").on("click", function () {
  1686. const dir = $(this).prop("title");
  1687. delLanes(dir);
  1688. if (dir === "rev") {
  1689. revDone = true;
  1690. }
  1691. else {
  1692. fwdDone = true;
  1693. }
  1694. updateUI();
  1695. });
  1696. }
  1697. waitForElementLoaded(".fwd-lanes > div.lane-instruction.lane-instruction-to > div.instruction > div.lane-edit > .edit-lane-guidance").then((elem) => {
  1698. $(elem).off();
  1699. $(elem).on("click", () => {
  1700. showAddLaneGuidance("fwd");
  1701. });
  1702. });
  1703. waitForElementLoaded(".rev-lanes > div.lane-instruction.lane-instruction-to > div.instruction > div.lane-edit > .edit-lane-guidance").then((elem) => {
  1704. $(elem).off();
  1705. $(elem).on("click", () => {
  1706. showAddLaneGuidance("rev");
  1707. });
  1708. });
  1709. if (!fwdDone && !revDone && !expandEditTriggered) {
  1710. expandEdit();
  1711. }
  1712. adjustSpace();
  1713. }
  1714. function applyButtonListeners() {
  1715. $(".apply-button.waze-btn.waze-btn-blue").off();
  1716. $(".cancel-button").off();
  1717. const fwdLanes = $(".fwd-lanes");
  1718. const revLanes = $(".rev-lanes");
  1719. fwdLanes.find(".apply-button.waze-btn.waze-btn-blue").on("click", () => {
  1720. fwdDone = true;
  1721. updateUI();
  1722. });
  1723. revLanes.find(".apply-button.waze-btn.waze-btn-blue").on("click", () => {
  1724. revDone = true;
  1725. updateUI();
  1726. });
  1727. fwdLanes.find(".cancel-button").on("click", () => {
  1728. fwdDone = true;
  1729. updateUI();
  1730. });
  1731. revLanes.find(".cancel-button").on("click", () => {
  1732. revDone = true;
  1733. updateUI();
  1734. });
  1735. }
  1736. function expandEdit() {
  1737. expandEditTriggered = true;
  1738. if (getId("lt-AutoExpandLanes")?.checked) {
  1739. if (!fwdDone) {
  1740. }
  1741. if (!revDone) {
  1742. }
  1743. }
  1744. if (getId("lt-AutoOpenWidth")?.checked) {
  1745. if (!fwdDone) {
  1746. $(".fwd-lanes").find(".set-road-width > wz-button").trigger("click"); // ADDED
  1747. }
  1748. if (!revDone) {
  1749. $(".rev-lanes").find(".set-road-width > wz-button").trigger("click"); // ADDED
  1750. }
  1751. }
  1752. }
  1753. function adjustSpace() {
  1754. $(".fwd-lanes > div > .direction-lanes").css({ padding: "5px 5px 10px", "margin-bottom": "10px" });
  1755. $(".rev-lanes > div > .direction-lanes").css({ padding: "5px 5px 10px", margin: "0px" });
  1756. $(".fwd-lanes > div > .lane-instruction.lane-instruction-to > .instruction > .lane-edit > .edit-region > div > .controls.direction-lanes-edit").css("padding-top", "10px");
  1757. $(".rev-lanes > div > .lane-instruction.lane-instruction-to > .instruction > .lane-edit > .edit-region > div > .controls.direction-lanes-edit").css("padding-top", "10px");
  1758. $(".fwd-lanes > div.lane-instruction.lane-instruction-to > div.instruction > div.edit-region > div > div > div:nth-child(1)").css("margin-bottom", "4px");
  1759. $(".rev-lanes > div.lane-instruction.lane-instruction-to > div.instruction > div.edit-region > div > div > div:nth-child(1)").css("margin-bottom", "4px");
  1760. }
  1761. function getLaneItems(count, class_names_list) {
  1762. const itemsList = [];
  1763. const classString = class_names_list.join(" ");
  1764. const idStringBase = class_names_list.join("-");
  1765. for (let i = 1; i <= count; ++i) {
  1766. const idString = `${idStringBase}-${i.toString()}`;
  1767. const selectorString = `<div class="${classString}" id="${idString}">${i.toString()}</div>`;
  1768. const newItem = $(selectorString).css({
  1769. padding: "1px 1px 1px 1px",
  1770. margin: "0 3px 0 3px",
  1771. border: "1px solid black",
  1772. "border-radius": "8px",
  1773. "border-color": "black",
  1774. height: "15px",
  1775. width: "15px",
  1776. "text-align": "center",
  1777. "line-height": "1.5",
  1778. "font-size": "10px",
  1779. display: "inline-block",
  1780. });
  1781. $(selectorString).on("hover", function () {
  1782. $(this).css({
  1783. border: "1px solid #26bae8",
  1784. "background-color": "#26bae8",
  1785. cursor: "pointer",
  1786. });
  1787. });
  1788. itemsList.push(newItem);
  1789. }
  1790. return itemsList;
  1791. }
  1792. function setupLaneCountControls(parentSelector, classNamesList) {
  1793. const jqueryClassSelector = `.${classNamesList.join(".")}`;
  1794. $(jqueryClassSelector).on("click", function () {
  1795. $(jqueryClassSelector).css({ "background-color": "transparent", color: "black" });
  1796. $(this).css({ "background-color": "navy", color: "white" });
  1797. });
  1798. }
  1799. function addLnsBtns(laneDir) {
  1800. // Add predetermined lane values
  1801. if (laneDir !== "fwd" && laneDir !== "rev") {
  1802. throw new Error(`Direction ${laneDir} is not supported`);
  1803. }
  1804. const dirLanesClass = `.${laneDir}-lanes`;
  1805. const addLanesTag = `lt-${laneDir}-add-lanes`;
  1806. const addWidthTag = `lt-${laneDir}-add-Width`;
  1807. const lanes = $(dirLanesClass);
  1808. if (lanes.find(".lane-instruction-to").children().length > 0x0 && !getId(addLanesTag)) {
  1809. const addLanesItem = $(`<div style="display:inline-flex;flex-direction:row;justify-content:space-around;margin-top:4px;position:relative;" id="${addLanesTag}" />`);
  1810. const classNamesList = ["lt-add-lanes", laneDir];
  1811. const laneCountsToAppend = getLaneItems(10, classNamesList);
  1812. for (let idx = 0; idx < laneCountsToAppend.length; ++idx) {
  1813. addLanesItem.append(laneCountsToAppend[idx]);
  1814. }
  1815. const prependSelector = `${dirLanesClass} > div.lane-instruction.lane-instruction-to > div.instruction > div.edit-region > div`;
  1816. // let prependSelector = dirLanesClass + "> div > div > div.lane-instruction.lane-instruction-to > div.instruction > div.edit-region > div.controls.direction-lanes-edit > div.form-group > div.controls-container";
  1817. waitForElementLoaded(prependSelector).then((elm) => {
  1818. const prependElement = $(prependSelector);
  1819. prependElement.prepend(addLanesItem);
  1820. setupLaneCountControls(lanes, classNamesList);
  1821. $(".lt-add-lanes").on("click", function () {
  1822. const numAddStr = $(this).text();
  1823. const numAdd = Number.parseInt(numAddStr, 10);
  1824. if ($(this).hasClass(`lt-add-lanes ${laneDir}`)) {
  1825. // As of React >=15.6. Triggering change or input events on the input form cannot be
  1826. // done via jquery selectors. Which means that they have to be triggered via
  1827. // React native calls.
  1828. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  1829. const inputForm = document.querySelector(`wz-card${dirLanesClass} input[name=laneCount]`);
  1830. nativeInputValueSetter.call(inputForm, numAdd);
  1831. const inputEvent = new Event("input", { bubbles: true });
  1832. inputForm?.dispatchEvent(inputEvent);
  1833. const changeEvent = new Event("change", { bubbles: true });
  1834. inputForm?.dispatchEvent(changeEvent);
  1835. }
  1836. });
  1837. });
  1838. }
  1839. // if (revLanes.find(".direction-lanes").children().length > 0x0 && !getId("lt-rev-add-lanes")) {
  1840. // let revLanesItem = $(
  1841. // '<div style="display:inline-flex;flex-direction:row;justify-content:space-around;margin-top:4px;" id="lt-rev-add-lanes" />'),
  1842. // classNamesList = [ "lt-add-lanes", "rev" ], laneCountsToAppend = getLaneItems(10, classNamesList);
  1843. // for (let idx = 0; idx < laneCountsToAppend.length; ++idx) {
  1844. // revLanesItem.append(laneCountsToAppend[idx]);
  1845. // }
  1846. // let prependSelector = '.rev-lanes > div > div > div.lane-instruction.lane-instruction-to > div.instruction > div.edit-region > div.controls.direction-lanes-edit > div.form-group > div.controls-container';
  1847. // waitForElementLoaded(prependSelector).then((elm) => {
  1848. // let revPrependTo = $(prependSelector);
  1849. // revPrependTo.prepend(revLanesItem);
  1850. // // revLanesItem.appendTo('.rev-lanes > div > div > div.lane-instruction.lane-instruction-to > div.instruction > div.edit-region > div > div > div:nth-child(1)');
  1851. // setupLaneCountControls(revLanes, classNamesList);
  1852. // $('.lt-add-lanes').on("click",function () {
  1853. // let numAdd = $(this).text();
  1854. // numAdd = Number.parseInt(numAdd, 10);
  1855. // if ($(this).hasClass('lt-add-lanes rev')) {
  1856. // // As of React >=15.6. Triggering change or input events on the input form cannot be
  1857. // // done via jquery selectors. Which means that they have to be triggered via
  1858. // // React native calls.
  1859. // let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  1860. // let inputForm = document.querySelector("div.rev-lanes input[name=laneCount]");
  1861. // nativeInputValueSetter.call(inputForm, numAdd);
  1862. // let inputEvent = new Event('input', {bubbles: true});
  1863. // inputForm.dispatchEvent(inputEvent);
  1864. // let changeEvent = new Event('change', {bubbles: true});
  1865. // inputForm.dispatchEvent(changeEvent);
  1866. // }
  1867. // });
  1868. //
  1869. // })
  1870. // }
  1871. //if (lanes.find(".direction-lanes").children().length > 0 && !getId(addWidthTag)) {
  1872. // let addFwdLanes =
  1873. // $('<div style="display:inline-flex;flex-direction:row;width:100%;" id="'+addWidthTag+'" />'),
  1874. // classNamesList = ["lt-add-Width", laneDir], laneCountsToAppend = getLaneItems(8, classNamesList);
  1875. // for (let idx = 0; idx < laneCountsToAppend.length; ++idx) {
  1876. // addFwdLanes.append(laneCountsToAppend[idx]);
  1877. // }
  1878. // let lnSelector = $(dirLanesClass + " > div > .lane-instruction.lane-instruction-from > .instruction > .road-width-edit > div > div > div > .lane-width-card")
  1879. // addFwdLanes.prependTo(lnSelector);
  1880. // setupLaneCountControls(lnSelector, classNamesList);
  1881. //}
  1882. // if (revLanes.find(".direction-lanes").children().length > 0 && !getId("lt-rev-add-Width")) {
  1883. // let appendRevLanes =
  1884. // $('<div style="display:inline-flex;flex-direction:row;width:100%;" id="lt-rev-add-Width" />'),
  1885. // classNamesList = [ "lt-add-Width", "rev" ], laneCountsToAppend = getLaneItems(8, classNamesList);
  1886. // for (let idx = 0; idx < laneCountsToAppend.length; ++idx) {
  1887. // appendRevLanes.append(laneCountsToAppend[idx]);
  1888. // }
  1889. // let lnSelector = $(".rev-lanes > div > div > .lane-instruction.lane-instruction-from > .instruction > .road-width-edit > div > div > div > .lane-width-card");
  1890. // appendRevLanes.prependTo(lnSelector);
  1891. // setupLaneCountControls(lnSelector, classNamesList);
  1892. // }
  1893. $(".lt-add-Width").on("click", function () {
  1894. const numAddStr = $(this).text();
  1895. const numAdd = Number.parseInt(numAddStr, 10);
  1896. if ($(this).hasClass(`lt-add-Width ${laneDir}`)) {
  1897. const lanes = $(dirLanesClass);
  1898. lanes.find("#number-of-lanes").val(numAdd);
  1899. lanes.find("#number-of-lanes").trigger("change");
  1900. lanes.find("#number-of-lanes").trigger("focus");
  1901. }
  1902. // if ($(this).hasClass('lt-add-Width rev')) {
  1903. // const revLanes = $('.rev-lanes');
  1904. // revLanes.find('#number-of-lanes').val(numAdd);
  1905. // revLanes.find('#number-of-lanes').trigger("change");
  1906. // revLanes.find('#number-of-lanes').trigger("focus");
  1907. // }
  1908. });
  1909. }
  1910. function focusEle() {
  1911. // Places the focus on the relevant lanes # input if the direction exists
  1912. const autoFocusLanes = getId("lt-AutoFocusLanes");
  1913. if (autoFocusLanes?.checked) {
  1914. const fwdLanes = $(".fwd-lanes");
  1915. const revLanes = $(".rev-lanes");
  1916. if (fwdLanes.find(".edit-region").children().length > 0 && !fwdDone) {
  1917. fwdLanes.find(".form-control").trigger("focus");
  1918. }
  1919. else if (revLanes.find(".edit-region").children().length > 0 && !revDone) {
  1920. revLanes.find(".form-control").trigger("focus");
  1921. }
  1922. }
  1923. }
  1924. function insertSelAll(dir) {
  1925. const setAllEnable = getId("lt-SelAllEnable");
  1926. if (setAllEnable?.checked) {
  1927. $(".street-name").css("user-select", "none");
  1928. const inputDirection = dir === "fwd" ? $(".fwd-lanes").find(".form-control")[0] : $(".rev-lanes").find(".form-control")[0];
  1929. const startVal = $(inputDirection).val();
  1930. // Toggles all checkboxes in turns row
  1931. $(inputDirection).on("change", function () {
  1932. let boxDirection;
  1933. if ($(this).parents(".fwd-lanes").length) {
  1934. boxDirection = $(".fwd-lanes").find(".controls-container.turns-region");
  1935. }
  1936. else if ($(this).parents(".rev-lanes").length) {
  1937. boxDirection = $(".rev-lanes").find(".controls-container.turns-region");
  1938. }
  1939. boxDirection = $(".street-name", boxDirection);
  1940. for (let p = 0; p < boxDirection.length; p++) {
  1941. $(boxDirection[p]).off();
  1942. $(boxDirection[p]).on("click", function () {
  1943. const secParent = $(this).get(0);
  1944. const contParent = secParent.parentElement;
  1945. const chkBxs = $(".checkbox-large.checkbox-white", contParent);
  1946. const firstCheckInv = !getId(chkBxs[0].id)?.checked;
  1947. for (let i = 0; i < chkBxs.length; i++) {
  1948. const checkBox = $(`#${chkBxs[i].id}`);
  1949. checkBox.prop("checked", firstCheckInv);
  1950. checkBox.change();
  1951. }
  1952. });
  1953. }
  1954. });
  1955. if (startVal > 0) {
  1956. $(inputDirection).trigger("change");
  1957. }
  1958. }
  1959. }
  1960. function colorCSDir() {
  1961. const selSeg = isSegmentSelected(selection) && selection?.objectType === "segment"
  1962. ? sdk.DataModel.Segments.getById({ segmentId: selection.ids[0] })
  1963. : null;
  1964. if (!selSeg)
  1965. return;
  1966. const fwdNode = getNodeObj(selSeg?.toNodeId);
  1967. const revNode = getNodeObj(selSeg?.fromNodeId);
  1968. const fwdConfig = checkLanesConfiguration(selSeg, fwdNode, fwdNode ? fwdNode.connectedSegmentIds : [], selSeg?.toNodeLanesCount);
  1969. const revConfig = checkLanesConfiguration(selSeg, revNode, revNode ? revNode.connectedSegmentIds : [], selSeg?.fromNodeLanesCount);
  1970. if (fwdConfig.csMode > 0) {
  1971. const csColor = fwdConfig.csMode === 1 ? LtSettings.CS1Color : LtSettings.CS2Color;
  1972. const arrowDiv = $("#segment-edit-lanes > div > div > div.fwd-lanes > div.lane-instruction.lane-instruction-to > div.instruction > div.lane-arrows > div").children();
  1973. for (let i = 0; i < arrowDiv.length; i++) {
  1974. if (arrowDiv[i].title === fwdConfig.csStreet) {
  1975. $(arrowDiv[i]).css("background-color", csColor);
  1976. }
  1977. }
  1978. }
  1979. if (revConfig.csMode > 0) {
  1980. const csColor = revConfig.csMode === 1 ? LtSettings.CS1Color : LtSettings.CS2Color;
  1981. const arrowDiv = $("#segment-edit-lanes > div > div > div.rev-lanes > div.lane-instruction.lane-instruction-to > div.instruction > div.lane-arrows > div").children();
  1982. for (let i = 0; i < arrowDiv.length; i++) {
  1983. if (arrowDiv[i].title === revConfig.csStreet) {
  1984. $(arrowDiv[i]).css("background-color", csColor);
  1985. }
  1986. }
  1987. }
  1988. }
  1989. // Rotates lane display arrows in lane tab for South directions
  1990. // Function written by Dude495 and modified by SkiDooGuy to fit into LaneTools better
  1991. function rotateArrows() {
  1992. const direction = document.getElementsByClassName("heading");
  1993. const boxDiv = $(".lane-arrows > div").get();
  1994. for (let i = 0; i < direction.length; i++) {
  1995. if (direction[i]?.textContent?.includes("south")) {
  1996. const arrows = $(boxDiv[i]).children();
  1997. $(arrows).css("transform", "rotate(180deg)");
  1998. $(boxDiv[i]).append(arrows.get().reverse());
  1999. }
  2000. }
  2001. isRotated = true;
  2002. }
  2003. // Begin lanes tab enhancements
  2004. if (getId("lt-UIEnable")?.checked && getId("lt-ScriptEnabled")?.checked) {
  2005. if (isSegmentSelected(selection)) {
  2006. // Check to ensure that there is only one segment object selected, then setup click event
  2007. waitForElementLoaded(".lanes-tab").then((elm) => {
  2008. formatLanesTab(getId("lt-AutoLanesTab")?.checked || elm.isActive);
  2009. });
  2010. //$('.lanes-tab').on("click",(event) => {
  2011. // fwdDone = false;
  2012. // revDone = false;
  2013. // updateUI(event);
  2014. //});
  2015. }
  2016. else if (selection && selection.ids.length === 2) {
  2017. // We have exactly TWO features selected. Check heuristics and highlight
  2018. scanHeuristicsCandidates(selection);
  2019. }
  2020. }
  2021. function formatLanesTab(clickTab = false, tries = 0) {
  2022. if ($(".tabs-labels > div:nth-child(3)", $(".segment-edit-section > wz-tabs")[0].shadowRoot).length > 0) {
  2023. fwdDone = false;
  2024. revDone = false;
  2025. $(".tabs-labels > div:nth-child(3)", $(".segment-edit-section > wz-tabs")[0].shadowRoot).on("click", () => {
  2026. fwdDone = false;
  2027. revDone = false;
  2028. updateUI();
  2029. });
  2030. if (clickTab) {
  2031. // If the auto open lanes option is enabled, initiate a click event on the Lanes tab element
  2032. waitForElementLoaded(".lanes-tab").then((elm) => {
  2033. $(".tabs-labels > div:nth-child(3)", $(".segment-edit-section > wz-tabs")[0].shadowRoot).trigger("click");
  2034. });
  2035. }
  2036. }
  2037. else if (tries < 500) {
  2038. setTimeout(() => {
  2039. formatLanesTab(clickTab, tries + 1);
  2040. }, 200);
  2041. }
  2042. else {
  2043. console.error("LaneTools: Failed to click lanes tab");
  2044. }
  2045. }
  2046. }
  2047. // Toggles parts of script when the keyboard shortcut is used
  2048. function toggleScript() {
  2049. $("#lt-ScriptEnabled").trigger("click");
  2050. }
  2051. function toggleHighlights() {
  2052. $("#lt-HighlightsEnable").trigger("click");
  2053. }
  2054. function toggleUIEnhancements() {
  2055. $("#lt-UIEnable").trigger("click");
  2056. }
  2057. function toggleLaneHeuristicsChecks() {
  2058. $("#lt-LaneHeuristicsChecks").trigger("click");
  2059. }
  2060. function displayToolbar() {
  2061. const objSelected = sdk.Editing.getSelection();
  2062. const scriptEnabled = getId("lt-ScriptEnabled");
  2063. const copyEnable = getId("lt-CopyEnable");
  2064. if (scriptEnabled?.checked && copyEnable && copyEnable.checked && objSelected && objSelected.ids.length === 1) {
  2065. if (objSelected.objectType === "segment") {
  2066. const map = sdk.Map.getMapViewportElement();
  2067. $("#lt-toolbar-container").css({
  2068. display: "block",
  2069. left: map.width() * 0.1,
  2070. top: map.height() * 0.1,
  2071. });
  2072. }
  2073. }
  2074. else {
  2075. $("#lt-toolbar-container").css("display", "none");
  2076. }
  2077. }
  2078. function getId(ele) {
  2079. return document.getElementById(ele);
  2080. }
  2081. function isSegment(obj) {
  2082. return obj && "roadType" in obj;
  2083. }
  2084. function isSegmentSelected(selection) {
  2085. return (selection && selection.objectType === "segment") || false;
  2086. }
  2087. // returns true if object is within window bounds and above zoom threshold
  2088. function onScreen(obj, curZoomLevel) {
  2089. if (!obj || !obj.geometry) {
  2090. return false;
  2091. }
  2092. // Either FREEWAY or Zoom >=4
  2093. if (curZoomLevel >= MIN_ZOOM_NON_FREEWAY || (isSegment(obj) && obj.roadType === LT_ROAD_TYPE.FREEWAY)) {
  2094. // var ext = W.map.getOLExtent();
  2095. var ext = sdk.Map.getMapExtent();
  2096. return true;
  2097. }
  2098. return false;
  2099. }
  2100. // borrowed from JAI
  2101. function getCardinalAngle(nodeId, segment) {
  2102. if (nodeId == null || segment == null) {
  2103. return null;
  2104. }
  2105. let ja_dx;
  2106. let ja_dy;
  2107. if (segment.fromNodeId === nodeId) {
  2108. const sp = lt_get_second_point(segment);
  2109. const fp = lt_get_first_point(segment);
  2110. if (!sp || !fp)
  2111. return null;
  2112. ja_dx = sp[0] - fp[0];
  2113. ja_dy = sp[1] - fp[1];
  2114. }
  2115. else {
  2116. const next_to_last = lt_get_next_to_last_point(segment);
  2117. const last_point = lt_get_last_point(segment);
  2118. if (!next_to_last || !last_point)
  2119. return null;
  2120. ja_dx = next_to_last[0] - last_point[0];
  2121. ja_dy = next_to_last[1] - last_point[1];
  2122. }
  2123. const angle_rad = Math.atan2(ja_dy, ja_dx);
  2124. let angle_deg = ((angle_rad * 180) / Math.PI) % 360;
  2125. if (angle_deg < 0)
  2126. angle_deg = angle_deg + 360;
  2127. // console.log('Cardinal: ' + Math.round(angle_deg));
  2128. return Math.round(angle_deg);
  2129. }
  2130. // borrowed from JAI
  2131. function lt_get_first_point(segment) {
  2132. return segment?.geometry.coordinates[0];
  2133. // return segment.geometry.components[0];
  2134. }
  2135. // borrowed from JAI
  2136. function lt_get_last_point(segment) {
  2137. return segment?.geometry.coordinates.at(-1);
  2138. // return segment.geometry.components[segment.geometry.components.length - 1];
  2139. }
  2140. // borrowed from JAI
  2141. function lt_get_second_point(segment) {
  2142. return segment?.geometry.coordinates[1];
  2143. // return segment.geometry.components[1];
  2144. }
  2145. // borrowed from JAI
  2146. function lt_get_next_to_last_point(segment) {
  2147. return segment?.geometry.coordinates.at(-2);
  2148. // return segment.geometry.components[segment.geometry.components.length - 2];
  2149. }
  2150. function delLanes(dir) {
  2151. const selObjs = W.selectionManager.getSelectedWMEFeatures();
  2152. const selSeg = selObjs[0]._wmeObject;
  2153. const turnGraph = W.model.getTurnGraph();
  2154. const mAction = new MultiAction();
  2155. let node;
  2156. let conSegs;
  2157. let updates = {};
  2158. // mAction.setModel(W.model);
  2159. if (dir === "fwd") {
  2160. updates.fwdLaneCount = 0;
  2161. node = getLegacyNodeObj(selSeg.attributes.toNodeID);
  2162. conSegs = node.getSegmentIds();
  2163. // const fwdLanes = $('.fwd-lanes');
  2164. // fwdLanes.find('.form-control').val(0);
  2165. // fwdLanes.find('.form-control').trigger("change");
  2166. }
  2167. if (dir === "rev") {
  2168. updates.revLaneCount = 0;
  2169. node = getLegacyNodeObj(selSeg.attributes.fromNodeID);
  2170. conSegs = node.getSegmentIds();
  2171. // const revLanes = $('.rev-lanes');
  2172. // revLanes.find('.form-control').val(0);
  2173. // revLanes.find('.form-control').trigger("change");
  2174. }
  2175. mAction.doSubAction(W.model, new UpdateObj(selSeg, updates));
  2176. for (let i = 0; i < conSegs.length; i++) {
  2177. let turnStatus = turnGraph.getTurnThroughNode(node, selSeg, getLegacySegObj(conSegs[i]));
  2178. let turnData = turnStatus.getTurnData();
  2179. if (turnData.hasLanes()) {
  2180. turnData = turnData.withLanes();
  2181. turnStatus = turnStatus.withTurnData(turnData);
  2182. mAction.doSubAction(W.model, new SetTurn(turnGraph, turnStatus));
  2183. }
  2184. }
  2185. mAction._description = "Deleted lanes and turn associations";
  2186. W.model.actionManager.add(mAction);
  2187. }
  2188. function removeHighlights() {
  2189. sdk.Map.removeAllFeaturesFromLayer({ layerName: LTHighlightLayer.name });
  2190. sdk.Map.removeAllFeaturesFromLayer({ layerName: LTNamesLayer.name });
  2191. }
  2192. function removeLaneGraphics() {
  2193. sdk.Map.removeAllFeaturesFromLayer({ layerName: LTLaneGraphics.name });
  2194. }
  2195. function applyName(geo, fwdLnsCount, revLnsCount) {
  2196. // if (!fwdLnsCount) fwdLnsCount = 0;
  2197. // if (!revLnsCount) revLnsCount = 0;
  2198. const laneNum = `${fwdLnsCount} / ${revLnsCount}`;
  2199. const lnLabel = turf.point(geo, { styleName: "nameStyle", layerName: LTNamesLayer.name, style: { laneNumLabel: laneNum } }, { id: `point_${geo.toString()}` });
  2200. sdk.Map.addFeatureToLayer({ feature: lnLabel, layerName: LTNamesLayer.name });
  2201. }
  2202. function highlightSegment(objGeo, direction, applyDash, applyLabels, fwdLnsCount, revLnsCount, applyLioHighlight, csMode, isBad, heur, heurOverHighlight) {
  2203. const VectorStyle = {
  2204. DASH_THIN: 1,
  2205. DASH_THICK: 2,
  2206. HIGHLIGHT: 10,
  2207. OVER_HIGHLIGHT: 20,
  2208. };
  2209. // fwdLnsCount = !fwdLnsCount ? 0 : fwdLnsCount;
  2210. // revLnsCount = !revLnsCount ? 0 : revLnsCount;
  2211. // const geo = objGeo.clone();
  2212. const applyCSHighlight = getId("lt-CSEnable")?.checked;
  2213. // Need to rework this to account for segment length, cause of geo adjustment and such
  2214. if (objGeo.length > 2) {
  2215. const geoLength = objGeo.length;
  2216. const geoMiddle = geoLength / 2;
  2217. const fwdPoint = geoLength % 2 ? Math.ceil(geoMiddle) - 1 : Math.ceil(geoMiddle);
  2218. const revPoint = geoLength % 2 ? Math.floor(geoMiddle) + 1 : Math.floor(geoMiddle);
  2219. if (direction === Direction.FORWARD) {
  2220. const newString = buildGeoComponentString(objGeo, fwdPoint, geoLength);
  2221. if (applyDash) {
  2222. createVector(newString, LtSettings.ABColor, VectorStyle.DASH_THIN);
  2223. } // draw dashed line
  2224. drawHighlight(newString, applyLioHighlight, isBad, heur, heurOverHighlight); // draw highlight
  2225. }
  2226. else if (direction === Direction.REVERSE) {
  2227. const newString = buildGeoComponentString(objGeo, 0, revPoint);
  2228. if (applyDash) {
  2229. createVector(newString, LtSettings.BAColor, VectorStyle.DASH_THIN);
  2230. }
  2231. drawHighlight(newString, applyLioHighlight, isBad, heur, heurOverHighlight);
  2232. }
  2233. // Add the label only on the forward pass, or reverse if there are no forward lanes
  2234. if (applyLabels && (direction === Direction.FORWARD || fwdLnsCount === 0)) {
  2235. if (geoLength % 2) {
  2236. applyName(objGeo[fwdPoint], fwdLnsCount, revLnsCount);
  2237. }
  2238. else {
  2239. const p0 = objGeo[revPoint - 1];
  2240. const p1 = objGeo[fwdPoint];
  2241. const newPoint = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
  2242. // let newPoint = new OpenLayers.getOLGeometry().Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
  2243. // let newPoint = new OpenLayers.Geometry.Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
  2244. // var newPoint = {
  2245. // id: "pointNode_" + (p0[0] + p1[0]) / 2 + " " + (p0[1] + p1[1]) / 2,
  2246. // geometry: {
  2247. // coordinates: [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2],
  2248. // type: "Point",
  2249. // },
  2250. // type: "Feature",
  2251. // properties: { styleName: "styleNode", layerName: LTHighlightLayer.name },
  2252. // };
  2253. applyName(newPoint, fwdLnsCount, revLnsCount);
  2254. }
  2255. }
  2256. }
  2257. else {
  2258. const p0 = objGeo[0];
  2259. const p1 = objGeo[1];
  2260. // let point1 = new OpenLayers.getOLGeometry().Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
  2261. // let point1 = new OpenLayers.Geometry.Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2);
  2262. const p1C = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
  2263. const pVector = [p1C];
  2264. // var point1 = {
  2265. // id: "pointNode_" + (p0[0] + p1[0]) / 2 + " " + (p0[1] + p1[1]) / 2,
  2266. // geometry: {
  2267. // coordinates: p1C,
  2268. // type: "Point",
  2269. // },
  2270. // type: "Feature",
  2271. // properties: { styleName: "vectorStyle", layerName: LTHighlightLayer.name },
  2272. // };
  2273. if (direction === Direction.FORWARD) {
  2274. const p2C = [objGeo[1][0], objGeo[1][1]];
  2275. pVector.push(p2C);
  2276. const newString = turf.lineString(pVector, { styleName: "vectorStyle", layerName: LTHighlightLayer.name }, { id: `line_${pVector.toString()}` });
  2277. if (applyDash) {
  2278. createVector(newString, LtSettings.ABColor, VectorStyle.DASH_THIN);
  2279. }
  2280. drawHighlight(newString, applyLioHighlight, isBad, heur, heurOverHighlight);
  2281. }
  2282. else if (direction === Direction.REVERSE) {
  2283. const p2C = [objGeo[0][0], objGeo[0][1]];
  2284. pVector.push(p2C);
  2285. const newString = turf.lineString(pVector, { styleName: "vectorStyle", layerName: LTHighlightLayer.name }, { id: `line_${pVector.toString()}` });
  2286. if (applyDash) {
  2287. createVector(newString, LtSettings.BAColor, VectorStyle.DASH_THIN);
  2288. }
  2289. drawHighlight(newString, applyLioHighlight, isBad, heur, heurOverHighlight);
  2290. }
  2291. // Add the label only on the forward pass, or reverse if there are no forward lanes
  2292. if (applyLabels && (direction === Direction.FORWARD || fwdLnsCount === 0)) {
  2293. applyName(p1C, fwdLnsCount, revLnsCount);
  2294. }
  2295. }
  2296. function buildGeoComponentString(geometry, from, to) {
  2297. const components = [];
  2298. let cIdx = 0;
  2299. for (let i = from; i < to; i++) {
  2300. components[cIdx++] = geometry[i];
  2301. }
  2302. return turf.lineString(components, { styleName: "vectorStyle", layerName: LTHighlightLayer.name }, { id: `line_${components.toString()}` });
  2303. }
  2304. function drawHighlight(newString, lio, bad, heurNom, heurOverHighlight = false) {
  2305. if (bad) {
  2306. createVector(newString, LtSettings.ErrorColor, VectorStyle.OVER_HIGHLIGHT);
  2307. return;
  2308. }
  2309. if (lio) {
  2310. createVector(newString, LtSettings.LIOColor, VectorStyle.HIGHLIGHT);
  2311. }
  2312. if (csMode === 1 && applyCSHighlight) {
  2313. createVector(newString, LtSettings.CS1Color, VectorStyle.HIGHLIGHT);
  2314. }
  2315. if (csMode === 2 && applyCSHighlight) {
  2316. createVector(newString, LtSettings.CS2Color, VectorStyle.HIGHLIGHT);
  2317. }
  2318. if (heurNom === HeuristicsCandidate.PASS) {
  2319. createVector(newString, LtSettings.HeurColor, heurOverHighlight ? VectorStyle.OVER_HIGHLIGHT : VectorStyle.HIGHLIGHT);
  2320. }
  2321. else if (heurNom === HeuristicsCandidate.FAIL) {
  2322. createVector(newString, LtSettings.HeurFailColor, heurOverHighlight ? VectorStyle.OVER_HIGHLIGHT : VectorStyle.HIGHLIGHT);
  2323. }
  2324. }
  2325. function createVector(geoCom, lineColor, style) {
  2326. // let newVector = new OpenLayers.Feature.Vector(geoCom, {}, {});
  2327. // LTHighlightLayer.addFeatures([newVector]);
  2328. const stroke = lineColor;
  2329. let strokeOpacity = 1;
  2330. let strokeWidth = 15;
  2331. let strokeDashArray = [];
  2332. switch (style) {
  2333. case VectorStyle.DASH_THICK:
  2334. strokeWidth = 8;
  2335. strokeDashArray = [8, 10];
  2336. break;
  2337. case VectorStyle.DASH_THIN:
  2338. strokeWidth = 4;
  2339. strokeDashArray = [10, 10];
  2340. break;
  2341. case VectorStyle.HIGHLIGHT:
  2342. strokeWidth = 15;
  2343. strokeOpacity = 0.6;
  2344. break;
  2345. case VectorStyle.OVER_HIGHLIGHT:
  2346. strokeWidth = 18;
  2347. strokeOpacity = 0.85;
  2348. break;
  2349. default:
  2350. break;
  2351. }
  2352. geoCom.properties = geoCom.properties ? geoCom.properties : {};
  2353. geoCom.properties.style = {
  2354. strokeColor: stroke,
  2355. stroke: stroke,
  2356. strokeWidth: strokeWidth,
  2357. strokeOpacity: strokeOpacity,
  2358. strokeDashstyle: strokeDashArray.join(" "),
  2359. };
  2360. sdk.Map.addFeatureToLayer({ feature: geoCom, layerName: LTHighlightLayer.name });
  2361. }
  2362. // LTHighlightLayer.setZIndex(2880);
  2363. }
  2364. function highlightNode(objGeo, color, overSized = false) {
  2365. // const geo = objGeo.clone();
  2366. // const highlight = new OpenLayers.Feature.Vector(geo, {});
  2367. if (!objGeo)
  2368. return;
  2369. const newString = {
  2370. id: `Node_${objGeo.toString()}`,
  2371. geometry: {
  2372. type: "Point",
  2373. coordinates: objGeo,
  2374. },
  2375. type: "Feature",
  2376. properties: {
  2377. styleName: "nodeStyle",
  2378. layerName: LTHighlightLayer.name,
  2379. style: {
  2380. fillColor: color,
  2381. pointRadius: overSized ? 18 : 10,
  2382. },
  2383. },
  2384. };
  2385. // let nodeStyle = {
  2386. // fillColor: color,
  2387. // pointRadius: overSized ? 18 : 10,
  2388. // fillOpacity: 0.9,
  2389. // strokeWidth: 0,
  2390. // };
  2391. // Object.assign(styleRules.nodeHighlightStyle.style, nodeStyle);
  2392. // LTHighlightLayer.addFeatures([highlight]);
  2393. sdk.Map.addFeatureToLayer({ feature: newString, layerName: LTHighlightLayer.name });
  2394. // const node = document.getElementById(geo.id);
  2395. // if (node) {
  2396. // node.setAttribute("fill", color);
  2397. // node.setAttribute("r", overSized ? "18" : "10");
  2398. // node.setAttribute("fill-opacity", "0.9");
  2399. // node.setAttribute("stroke-width", "0");
  2400. // }
  2401. }
  2402. const lt_scanArea_timer = {
  2403. timeoutID: -1,
  2404. start: function () {
  2405. this.cancel();
  2406. this.timeoutID = window.setTimeout(() => {
  2407. this.calculate();
  2408. }, 500);
  2409. },
  2410. calculate: function () {
  2411. scanArea_real();
  2412. this.timeoutID = -1;
  2413. },
  2414. cancel: function () {
  2415. if (typeof this.timeoutID === "number") {
  2416. window.clearTimeout(this.timeoutID);
  2417. this.timeoutID = -1;
  2418. lt_scanArea_recursive = 0;
  2419. }
  2420. },
  2421. };
  2422. function scanArea() {
  2423. // Use a delay timer to ensure the DOM is settled
  2424. lt_scanArea_recursive = 3;
  2425. scanArea_real();
  2426. }
  2427. function scanArea_real() {
  2428. const isEnabled = getId("lt-ScriptEnabled")?.checked;
  2429. const mapHighlights = getId("lt-HighlightsEnable")?.checked;
  2430. const heurChecks = getId("lt-LaneHeuristicsChecks")?.checked;
  2431. // const zoomLevel = W.map.getZoom() != null ? W.map.getZoom() : 16;
  2432. const zoomLevel = sdk.Map.getZoomLevel();
  2433. const highOverride = getId("lt-highlightOverride")?.checked; // jm6087
  2434. const layerCheck = W.layerSwitcherController.getTogglerState("ITEM_ROAD") ||
  2435. W.layerSwitcherController.getTogglerState("ITEM_ROAD_V2"); //jm6087
  2436. removeHighlights();
  2437. // console.log(zoomLevel);
  2438. if (zoomLevel < MIN_DISPLAY_LEVEL) {
  2439. return;
  2440. }
  2441. // If segment layer is checked (true) or (segment layer is not checked (false) and highlight override is set to show only when segment layer on - not checked (false)
  2442. if (layerCheck || (!layerCheck && !highOverride)) {
  2443. //jm6087
  2444. if (isEnabled && (mapHighlights || heurChecks)) {
  2445. scanSegments(sdk.DataModel.Segments.getAll(), false);
  2446. }
  2447. if (isEnabled && heurChecks) {
  2448. // const selFeat = W.selectionManager.getSelectedWMEFeatures();
  2449. const selectedFeat = sdk.Editing.getSelection();
  2450. if (selectedFeat?.objectType === "segment")
  2451. scanHeuristicsCandidates(selectedFeat);
  2452. }
  2453. } //jm6087
  2454. }
  2455. // Given two features, checks if they are segments, and their path qualifies for heuristics; then highlight
  2456. function scanHeuristicsCandidates(selection) {
  2457. const segs = [];
  2458. let count = 0;
  2459. for (let idx = 0; selection && idx < selection.ids.length; ++idx) {
  2460. if (typeof selection.ids[idx] === "string") {
  2461. lt_log(`Segment ID: ${selection.ids[idx]} reported as Segment ID incorrectly`, 1);
  2462. }
  2463. const seg = sdk.DataModel.Segments.getById({ segmentId: selection.ids[idx] });
  2464. if (!seg)
  2465. continue;
  2466. count = segs.push(seg);
  2467. }
  2468. // _.each(features, (f) => {
  2469. // if (f && f._wmeObject && f._wmeObject.type === "segment") {
  2470. // count = segs.push(f._wmeObject);
  2471. // }
  2472. // });
  2473. scanSegments(segs, true);
  2474. return count;
  2475. }
  2476. // Check all given segments for heuristics qualification
  2477. function scanSegments(segments, selectedSegsOverride = false) {
  2478. const heurChecks = getId("lt-LaneHeuristicsChecks")?.checked ?? false;
  2479. const heurScan_PosHighlight = heurChecks && (getId("lt-LaneHeurPosHighlight")?.checked ?? false);
  2480. const heurScan_NegHighlight = heurChecks && (getId("lt-LaneHeurNegHighlight")?.checked ?? false);
  2481. const mapHighlights = getId("lt-HighlightsEnable")?.checked ?? false;
  2482. const applyLioHighlight = mapHighlights && (getId("lt-LIOEnable")?.checked ?? false);
  2483. const applyLabels = mapHighlights && (getId("lt-LabelsEnable")?.checked ?? false);
  2484. const zoomLevel = sdk.Map.getZoomLevel();
  2485. // const turnGraph = W.model.getTurnGraph();
  2486. // console.log(zoomLevel);
  2487. _.each(segments, (s) => {
  2488. if (onScreen(s, zoomLevel)) {
  2489. // const sAtts = s.getAttributes();
  2490. const tryRedo = false;
  2491. const segLength = s.length; //lt_segment_length(s);
  2492. // FORWARD
  2493. tryRedo || scanSegment_Inner(s, Direction.FORWARD, segLength, tryRedo);
  2494. // If errors encountered, scan again. (Usually this is an issue with first loading of DOM after zoom or long pan)
  2495. if (tryRedo && lt_scanArea_recursive > 0) {
  2496. lt_log("LT errors found, scanning again", 2);
  2497. removeHighlights();
  2498. lt_scanArea_recursive--;
  2499. lt_scanArea_timer.start();
  2500. return;
  2501. }
  2502. tryRedo || scanSegment_Inner(s, Direction.REVERSE, segLength, tryRedo);
  2503. // If errors encountered, scan again. (Usually this is an issue with first loading of DOM after zoom or long pan)
  2504. if (tryRedo && lt_scanArea_recursive > 0) {
  2505. lt_log("LT errors found, scanning again", 2);
  2506. removeHighlights();
  2507. lt_scanArea_recursive--;
  2508. lt_scanArea_timer.start();
  2509. }
  2510. }
  2511. });
  2512. function scanSegment_Inner(seg, direction, segLength, tryRedo) {
  2513. const fwdLaneCount = seg.fromNodeLanesCount;
  2514. const revLaneCount = seg.toNodeLanesCount;
  2515. if (fwdLaneCount + revLaneCount === 0)
  2516. return;
  2517. let node = getNodeObj(seg.toNodeId);
  2518. let oppNode = getNodeObj(seg.fromNodeId);
  2519. let laneCount = fwdLaneCount;
  2520. let oppLaneCount = revLaneCount;
  2521. if (direction !== Direction.FORWARD) {
  2522. node = getNodeObj(seg.fromNodeId);
  2523. oppNode = getNodeObj(seg.toNodeId);
  2524. laneCount = revLaneCount;
  2525. oppLaneCount = fwdLaneCount;
  2526. }
  2527. let tlns = false;
  2528. let tio = false;
  2529. let badLn = false;
  2530. let lio = false;
  2531. let csMode = 0;
  2532. let heurCand = HeuristicsCandidate.NONE;
  2533. let entrySeg = null;
  2534. const entrySegRef = {
  2535. seg: 0,
  2536. direction: Direction.ANY,
  2537. };
  2538. // CHECK LANES & HEURISTICS
  2539. if (node !== null && onScreen(node, zoomLevel)) {
  2540. const nodeSegs = node.connectedSegmentIds;
  2541. if (laneCount && laneCount > 0) {
  2542. const config = checkLanesConfiguration(seg, node, nodeSegs, laneCount);
  2543. tlns = config.tlns;
  2544. tio = config.tio;
  2545. lio = config.lio;
  2546. badLn = config.badLn;
  2547. csMode = config.csMode;
  2548. tryRedo = badLn || tryRedo;
  2549. }
  2550. // 1/1/21: Only check for heuristics on segments <50m. IMPORTANT because now we're checking segments regardless of lanes
  2551. if (heurChecks && segLength <= MAX_LEN_HEUR) {
  2552. // Check Heuristics regardless of heurChecks, because we want to report Errors even if Heur highlights are off
  2553. heurCand = isHeuristicsCandidate(seg, node, nodeSegs, oppNode, laneCount, segLength, entrySegRef);
  2554. if (heurCand === HeuristicsCandidate.ERROR) {
  2555. // fwdHeurCand = HeuristicsCandidate.NONE;
  2556. badLn = true;
  2557. }
  2558. if (!heurChecks) {
  2559. heurCand = HeuristicsCandidate.NONE;
  2560. }
  2561. else if (heurCand !== HeuristicsCandidate.NONE) {
  2562. entrySeg = { ...entrySegRef };
  2563. }
  2564. }
  2565. }
  2566. // HIGHLIGHTS
  2567. if (!selectedSegsOverride) {
  2568. // Full scan highlights
  2569. let heur = HeuristicsCandidate.NONE;
  2570. if ((heurScan_PosHighlight && heurCand === HeuristicsCandidate.PASS) ||
  2571. (heurScan_NegHighlight && heurCand === HeuristicsCandidate.FAIL)) {
  2572. heur = heurCand;
  2573. }
  2574. if (laneCount && (laneCount > 0 || heur !== null || badLn)) {
  2575. highlightSegment(seg.geometry.coordinates, direction, mapHighlights, applyLabels, fwdLaneCount, revLaneCount, lio && applyLioHighlight, csMode, badLn, heur, false);
  2576. }
  2577. // Nodes highlights
  2578. if (mapHighlights && getId("lt-NodesEnable")?.checked) {
  2579. if (tlns) {
  2580. highlightNode(node?.geometry.coordinates, LtSettings.NodeColor);
  2581. // highlightNode(node.geometry, `${LtSettings.NodeColor}`);
  2582. }
  2583. if (tio) {
  2584. highlightNode(node?.geometry.coordinates, LtSettings.TIOColor);
  2585. // highlightNode(node.geometry, `${LtSettings.TIOColor}`);
  2586. }
  2587. }
  2588. }
  2589. else {
  2590. // Selected segment highlights
  2591. lt_log(`candidate(f):${heurCand}`);
  2592. if (heurCand !== HeuristicsCandidate.NONE) {
  2593. if (entrySeg && segments.findIndex((element) => element.id === entrySeg.seg) > -1) {
  2594. const nodeColor = heurCand === HeuristicsCandidate.PASS ? LtSettings.NodeColor : LtSettings.HeurFailColor;
  2595. highlightSegment(seg.geometry.coordinates, direction, false, false, 0, 0, false, csMode, badLn, heurCand, true);
  2596. const eSeg = sdk.DataModel.Segments.getById({ segmentId: entrySeg.seg });
  2597. if (eSeg) {
  2598. highlightSegment(eSeg?.geometry.coordinates, entrySeg.direction, false, false, 0, 0, false, 0, false, heurCand, true);
  2599. }
  2600. highlightNode(node?.geometry.coordinates, nodeColor, true);
  2601. highlightNode(oppNode?.geometry.coordinates, nodeColor, true);
  2602. }
  2603. }
  2604. }
  2605. return tryRedo;
  2606. }
  2607. }
  2608. function checkLanesConfiguration(s, node, segs, numLanes) {
  2609. const laneConfig = {
  2610. tlns: false,
  2611. tio: false,
  2612. badLn: false,
  2613. lio: false,
  2614. csMode: 0,
  2615. csStreet: null,
  2616. };
  2617. const turnLanes = [];
  2618. // const turnGraph = W.model.getTurnGraph();
  2619. // const pturns = turnGraph.getAllPathTurns();
  2620. const fromTurns = sdk.DataModel.Turns.getTurnsFromSegment({ segmentId: s.id });
  2621. const toTurns = sdk.DataModel.Turns.getTurnsToSegment({ segmentId: s.id });
  2622. const pturns = fromTurns.filter((t) => t.isPathTurn);
  2623. pturns.push(...toTurns.filter((t) => t.isPathTurn));
  2624. const jpturns = fromTurns.filter((t) => t.isJunctionBoxTurn);
  2625. jpturns.push(...toTurns.filter((t) => t.isJunctionBoxTurn));
  2626. const zoomLevel = sdk.Map.getZoomLevel();
  2627. function addTurns(fromLns, toLns) {
  2628. if (toLns === undefined || fromLns === undefined)
  2629. return;
  2630. for (let k = fromLns; k < toLns + 1; k++) {
  2631. let newValue = true;
  2632. for (let j = 0; j < turnLanes.length; j++) {
  2633. if (turnLanes[j] === k) {
  2634. newValue = false;
  2635. }
  2636. }
  2637. if (newValue) {
  2638. turnLanes.push(k);
  2639. }
  2640. }
  2641. }
  2642. for (let i = 0; i < segs.length; i++) {
  2643. if (segs[i] === s.id)
  2644. continue;
  2645. const seg2 = getSegObj(segs[i]);
  2646. const turnsThrough = !node ? [] : sdk.DataModel.Turns.getTurnsThroughNode({ nodeId: node?.id });
  2647. for (let idx = 0; idx < turnsThrough.length; ++idx) {
  2648. const t = turnsThrough[idx];
  2649. if (t.isUTurn || (t.fromSegmentId !== s.id && t.toSegmentId !== segs[i]))
  2650. continue;
  2651. // const turnData = turnGraph.getTurnThroughNode(node, s, seg2).getTurnData();
  2652. if (t.isAllowed) {
  2653. // Check for turn instruction override
  2654. if (t.instructionOpCode !== null) {
  2655. laneConfig.tio = true;
  2656. }
  2657. // Check for lanes
  2658. if (t.lanes !== null) {
  2659. laneConfig.tlns = true;
  2660. // Check for lane angle override
  2661. if (t.lanes.angleOverride !== null) {
  2662. laneConfig.lio = true;
  2663. }
  2664. // Check for Continue Straight override
  2665. // 1 is for view only, 2 is for view and hear
  2666. const primaryStreetId = seg2?.primaryStreetId;
  2667. if (primaryStreetId && primaryStreetId !== null && s.primaryStreetId === primaryStreetId) {
  2668. if (t.lanes.guidanceMode === "display") {
  2669. laneConfig.csMode = 1;
  2670. laneConfig.csStreet = sdk.DataModel.Streets.getById({
  2671. streetId: primaryStreetId,
  2672. })?.name;
  2673. }
  2674. else if (t.lanes.guidanceMode === "display-and-voice") {
  2675. laneConfig.csMode = 2;
  2676. laneConfig.csStreet = sdk.DataModel.Streets.getById({
  2677. streetId: primaryStreetId,
  2678. })?.name;
  2679. }
  2680. }
  2681. const fromLns = t.lanes.fromLaneIndex;
  2682. const toLns = t.lanes.toLaneIndex;
  2683. addTurns(fromLns, toLns);
  2684. }
  2685. }
  2686. }
  2687. }
  2688. // check paths
  2689. for (let i = 0; i < pturns.length; i++) {
  2690. if (pturns[i].lanes !== null) {
  2691. const fromLns = pturns[i].lanes?.fromLaneIndex;
  2692. const toLns = pturns[i].lanes?.toLaneIndex;
  2693. addTurns(fromLns, toLns);
  2694. }
  2695. }
  2696. // check turns in JBs
  2697. // const jb = W.model.bigJunctions.getObjectArray();
  2698. for (let t = 0; t < jpturns.length; t++) {
  2699. const tdat = jpturns[t].lanes;
  2700. if (tdat) {
  2701. addTurns(tdat.fromLaneIndex, tdat.toLaneIndex);
  2702. }
  2703. }
  2704. turnLanes.sort();
  2705. for (let z = 0; z < turnLanes.length; z++) {
  2706. if (turnLanes[z] !== z) {
  2707. laneConfig.badLn = true;
  2708. }
  2709. }
  2710. if (numLanes && turnLanes.length < numLanes && onScreen(node, zoomLevel)) {
  2711. laneConfig.badLn = true;
  2712. }
  2713. return laneConfig;
  2714. }
  2715. function setTurns(direction) {
  2716. const clickSaveEnabled = getId("lt-ClickSaveEnable");
  2717. if (!clickSaveEnabled?.checked) {
  2718. return;
  2719. }
  2720. const lanesPane = document.getElementsByClassName(direction)[0];
  2721. if (!lanesPane)
  2722. return;
  2723. const left = lanesPane.getElementsByClassName("angle--135").length > 0
  2724. ? "angle--135"
  2725. : lanesPane.getElementsByClassName("angle--90").length > 0
  2726. ? "angle--90"
  2727. : "angle--45";
  2728. const right = lanesPane.getElementsByClassName("angle-135").length > 0
  2729. ? "angle-135"
  2730. : lanesPane.getElementsByClassName("angle-90").length > 0
  2731. ? "angle-90"
  2732. : "angle-45";
  2733. const turnSections = lanesPane.getElementsByClassName("turn-lane-edit-container");
  2734. let setLeft = false;
  2735. let setRight = false;
  2736. const alreadySet = [].slice
  2737. .call(turnSections)
  2738. .reduce((acc, turn) => acc +
  2739. [].slice
  2740. .call(turn.getElementsByTagName("input"))
  2741. .reduce((acc, input) => (input.checked === true ? acc + 1 : acc), 0), 0);
  2742. if (alreadySet === 0) {
  2743. for (let i = 0; i < turnSections.length; i++) {
  2744. const turnSection = turnSections[i];
  2745. // Check if the lanes are already set. If already set, don't change anything.
  2746. const laneCheckboxes = turnSection.getElementsByTagName("wz-checkbox");
  2747. if (laneCheckboxes && laneCheckboxes.length > 0) {
  2748. if (getId("lt-ClickSaveTurns")?.checked) {
  2749. if (turnSection.getElementsByClassName(left).length > 0 &&
  2750. laneCheckboxes[0].checked !== undefined &&
  2751. laneCheckboxes[0].checked === false) {
  2752. setLeft = true;
  2753. laneCheckboxes[0].click();
  2754. }
  2755. else if (turnSection.getElementsByClassName(right).length > 0 &&
  2756. laneCheckboxes[laneCheckboxes.length - 1].checked !== undefined &&
  2757. laneCheckboxes[laneCheckboxes.length - 1].checked === false) {
  2758. setRight = true;
  2759. laneCheckboxes[laneCheckboxes.length - 1].click();
  2760. }
  2761. }
  2762. }
  2763. }
  2764. for (let i = 0; i < turnSections.length; i++) {
  2765. const turnSection = turnSections[i];
  2766. const laneCheckboxes = turnSection.getElementsByTagName("wz-checkbox");
  2767. if (setRight) {
  2768. // Clear All Lanes Except the Right most for right turn
  2769. if (turnSection.getElementsByClassName(right).length > 0) {
  2770. for (let j = 0; j < laneCheckboxes.length - 1; ++j) {
  2771. waitForElementLoaded("input[type='checkbox']", laneCheckboxes[j].shadowRoot);
  2772. if (laneCheckboxes[j].checked)
  2773. laneCheckboxes[j].click();
  2774. }
  2775. }
  2776. }
  2777. if (setLeft) {
  2778. // Clear all Lanes except left most for left turn
  2779. if (turnSection.getElementsByClassName(left).length > 0) {
  2780. for (let j = 1; j < laneCheckboxes.length; ++j) {
  2781. waitForElementLoaded("input[type='checkbox']", laneCheckboxes[j].shadowRoot);
  2782. if (laneCheckboxes[j].checked)
  2783. laneCheckboxes[j].click();
  2784. }
  2785. }
  2786. }
  2787. if (turnSection.getElementsByClassName("angle-0").length > 0) {
  2788. // Set all lanes for straight turns
  2789. for (let j = 0; j < laneCheckboxes.length; j++) {
  2790. waitForElementLoaded("input[type='checkbox']", laneCheckboxes[j].shadowRoot);
  2791. if (laneCheckboxes[j].checked === false) {
  2792. if (j === 0 && (LtSettings.ClickSaveStraight || setLeft === false)) {
  2793. laneCheckboxes[j].click();
  2794. }
  2795. else if (j === laneCheckboxes.length - 1 &&
  2796. (LtSettings.ClickSaveStraight || setRight === false)) {
  2797. laneCheckboxes[j].click();
  2798. }
  2799. else if (j !== 0 && j !== laneCheckboxes.length - 1) {
  2800. laneCheckboxes[j].click();
  2801. }
  2802. }
  2803. }
  2804. }
  2805. }
  2806. }
  2807. }
  2808. function waitForElementLoaded(selector, root = undefined) {
  2809. return new Promise((resolve) => {
  2810. if (!root) {
  2811. if (document.querySelector(selector)) {
  2812. return resolve(document.querySelector(selector));
  2813. }
  2814. const observer = new MutationObserver((mutations) => {
  2815. if (document.querySelector(selector)) {
  2816. observer.disconnect();
  2817. resolve(document.querySelector(selector));
  2818. }
  2819. });
  2820. observer.observe(document.body, {
  2821. childList: true,
  2822. subtree: true,
  2823. });
  2824. }
  2825. else {
  2826. if (root.querySelector(selector)) {
  2827. return resolve(root.querySelector(selector));
  2828. }
  2829. const observer = new MutationObserver((mutations) => {
  2830. if (root.querySelector(selector)) {
  2831. observer.disconnect();
  2832. resolve(root.querySelector(selector));
  2833. }
  2834. });
  2835. observer.observe(root, {
  2836. childList: true,
  2837. subtree: true,
  2838. });
  2839. }
  2840. });
  2841. }
  2842. function processLaneNumberChange() {
  2843. const parent = $(this).parents().eq(8);
  2844. const elem = parent[0];
  2845. const className = elem.className;
  2846. const numLanes = Number.parseInt($(this).val(), 10);
  2847. waitForElementLoaded(".turn-lane-checkbox").then((elem) => {
  2848. setTurns(className, numLanes);
  2849. });
  2850. const laneCountNums = $(this).parents().find(".lt-add-lanes");
  2851. if (laneCountNums.length > 0) {
  2852. const counterClassName = laneCountNums[0].className;
  2853. const selectorClassName = `.${counterClassName.replace(" ", ".")}`;
  2854. const counterClassToSelectName = `#${counterClassName.replace(" ", "-")}-${numLanes.toString()}`;
  2855. $(selectorClassName).css({ "background-color": "transparent", color: "black" });
  2856. $(counterClassToSelectName).css({ "background-color": "navy", color: "white" });
  2857. }
  2858. }
  2859. function initLaneGuidanceClickSaver() {
  2860. const laneObserver = new MutationObserver((mutations) => {
  2861. // if (
  2862. // W.selectionManager.getSelectedWMEFeatures()[0] &&
  2863. // W.selectionManager.getSelectedWMEFeatures()[0].featureType === "segment" &&
  2864. // getId("lt-ScriptEnabled").checked
  2865. // )
  2866. const selection = sdk.Editing.getSelection();
  2867. if (selection?.objectType === "segment" && getId("lt-ScriptEnabled")?.checked) {
  2868. const laneCountElement = document.getElementsByName("laneCount");
  2869. for (let idx = 0; idx < laneCountElement.length; idx++) {
  2870. laneCountElement[idx].addEventListener("keyup", processLaneNumberChange, false);
  2871. laneCountElement[idx].addEventListener("change", processLaneNumberChange, false);
  2872. }
  2873. }
  2874. });
  2875. laneObserver.observe(document.getElementById("edit-panel"), {
  2876. childList: true,
  2877. subtree: true,
  2878. });
  2879. // console.log('LaneTools: Click Saver Module loaded');
  2880. }
  2881. function isHeuristicsCandidate(segCandidate, curNodeExit, nodeExitSegIds, curNodeEntry, laneCount, segLength, inSegRef) {
  2882. // CRITERIA FOR HEURISTICS, as described on the wiki: https://wazeopedia.waze.com/wiki/USA/User:Nzahn1/Lanes#Mapping_lanes_on_divided_roadways
  2883. // 1. Both left and right turns are possible at the intersection;
  2884. // 2. The two portions of the divided roadway are essentially parallel to each other;
  2885. // 3. The two intersecting roads are more or less perpendicular to each other;
  2886. // 4. The median segment in question is 50 m or shorter; and
  2887. // 5. The number of lanes entering the intersection is equal to the total number of lanes exiting the intersection
  2888. // (total number of lanes exiting intersection = number of lanes on the median segment +
  2889. // number of right-turn only lanes on the entry segment -- other words, no new lanes are added in the median).
  2890. // MY OBSERVATIONS
  2891. // 11. We must have an incoming segment supplemenatary to outgoing segment 1. (alt-incoming)
  2892. // 12. That alt-incoming segment must be within perpendicular tolerance to BOTH the median segment and the incoming segment.
  2893. if (nodeExitSegIds == null || curNodeEntry == null || laneCount == null || inSegRef == null) {
  2894. lt_log("isHeuristicsCandidate received bad argument (null)", 1);
  2895. return HeuristicsCandidate.NONE;
  2896. }
  2897. let outSeg2 = null;
  2898. let outTurnAngle2 = null;
  2899. let outSeg2IsHeurFail = 0;
  2900. let inSeg = null;
  2901. let inAzm = null;
  2902. let inTurnAngle = null;
  2903. let inSegIsHeurFail = 0;
  2904. let altIncomingSeg = null;
  2905. let altInAzm = null;
  2906. let altInIsHeurFail = 0;
  2907. let inNumLanesThrough = 0;
  2908. // #4 first: Check the length (and get outta' here if not)
  2909. // 1/1/21: This is now redundant with outer loop. But leaving it in just in case...
  2910. if (segLength > MAX_LEN_HEUR) {
  2911. return HeuristicsCandidate.NONE;
  2912. }
  2913. // Get current segment heading at the node
  2914. const segId = segCandidate.id;
  2915. const segEndAzm = lt_getBearing(curNodeExit.id, segCandidate, true);
  2916. const segBeginAzm = lt_getBearing(curNodeEntry.id, segCandidate);
  2917. let out1TargetAngle = -90.0; // For right-hand side of the road countries (right-turn)
  2918. let out2TargetAngle = 90.0; // (left-turn)
  2919. const segAddress = sdk.DataModel.Segments.getAddress({ segmentId: segCandidate.id });
  2920. if (segAddress.street === null) {
  2921. lt_log(`Unable to process Heuristics on Segment: ${segCandidate.id} as it has no Primary Street Set`, 1);
  2922. return HeuristicsCandidate.NONE;
  2923. }
  2924. if (segAddress.country?.isLeftHandTraffic) {
  2925. out1TargetAngle = 90.0; // left turn
  2926. out2TargetAngle = -90.0; // right turn
  2927. }
  2928. lt_log("==================================================================================", 2);
  2929. lt_log(`Checking heuristics candidate: seg ${segId} node ${curNodeExit.id} azm ${segEndAzm} nodeExitSegIds:${nodeExitSegIds.length}`, 2);
  2930. // Find the incoming segment, and validate angle to cursegment
  2931. const nodeEntrySegIds = curNodeEntry.connectedSegmentIds;
  2932. for (let ii = 0; ii < nodeEntrySegIds.length; ii++) {
  2933. let thisTimeFail = 0;
  2934. if (nodeEntrySegIds[ii] === segId) {
  2935. continue;
  2936. } // ignore same segment as our original
  2937. const is = getSegObj(nodeEntrySegIds[ii]);
  2938. // Check turn from this seg to candidate seg
  2939. if (is !== null && !lt_is_turn_allowed(is, curNodeEntry, segCandidate)) {
  2940. continue;
  2941. }
  2942. const ia = lt_getBearing(curNodeEntry.id, is, true); // absolute math azimuth
  2943. // const ita: number | null = lt_turn_angle(ia, segBeginAzm); // turn angle
  2944. const ita = lt_turn_angle_seg_to_seg(is, curNodeEntry, segCandidate);
  2945. lt_log(`Turn angle from inseg ${nodeEntrySegIds[ii]}: ${ita}(${ia},${segBeginAzm})`, 3);
  2946. if (ita !== null && Math.abs(ita) > MAX_STRAIGHT_DIF) {
  2947. // tolerance met?
  2948. if (Math.abs(ita) > MAX_STRAIGHT_TO_CONSIDER) {
  2949. continue;
  2950. }
  2951. lt_log(` Not eligible as inseg: ${ita}`, 2);
  2952. thisTimeFail = HeuristicsCandidate.FAIL;
  2953. }
  2954. // const turnsThrough = turnGraph.getTurnThroughNode(curNodeEntry, is, segCandidate);
  2955. // const turnData = turnsThrough.getTurnData();
  2956. // const turnsThrough = sdk.DataModel.Turns.getTurnsThroughNode({ nodeId: curNodeEntry.id });
  2957. // let turnData = turnsThrough[tidx];
  2958. function getMatchingTurn(node, from, to) {
  2959. const turns = sdk.DataModel.Turns.getTurnsThroughNode({ nodeId: node.id });
  2960. if (from !== null) {
  2961. for (let idx = 0; idx < turns.length; ++idx) {
  2962. if (turns[idx].fromSegmentId === from.id && turns[idx].toSegmentId === to.id)
  2963. return turns[idx];
  2964. }
  2965. }
  2966. return null;
  2967. }
  2968. const turnData = getMatchingTurn(curNodeEntry, is, segCandidate);
  2969. if (turnData === null || !turnData.lanes) {
  2970. lt_log(`Straight turn has no lanes:${nodeEntrySegIds[ii]} to ${segId}`, 3);
  2971. continue; // No lanes? Don't even think about it. (Not a candidate)
  2972. }
  2973. // #5 Ensure this (straight) turn motion has lanes, and lane count matches; otherwise ERROR
  2974. // 1/1/21: One exception. If laneCount is 0, and there is exactly 1 straight incoming lane, then treat it as equal. (Conversation with @jm6087)
  2975. const nl = turnData.lanes.toLaneIndex - turnData.lanes.fromLaneIndex + 1;
  2976. if (nl !== laneCount && !(laneCount === 0 && nl === 1)) {
  2977. lt_log("Straight turn lane count does not match", 2);
  2978. thisTimeFail = HeuristicsCandidate.ERROR; // Failed lane match should give us an ERROR
  2979. }
  2980. // Only one segment allowed // TBD ??? For now, don't allow more than one.
  2981. if (inSeg !== null && thisTimeFail >= inSegIsHeurFail) {
  2982. if (inSegIsHeurFail === 0 && thisTimeFail === 0) {
  2983. lt_log(`Error: >1 qualifying entry segment for ${segCandidate.id}: ${inSeg.id},${is?.id}`, 2);
  2984. lt_log("==================================================================================", 2);
  2985. return 0; // just stop here
  2986. }
  2987. }
  2988. inSeg = is;
  2989. inAzm = ia;
  2990. inTurnAngle = ita;
  2991. inNumLanesThrough = nl;
  2992. inSegIsHeurFail = thisTimeFail;
  2993. if (!inSegRef) {
  2994. const newSegRef = {
  2995. seg: 0,
  2996. direction: Direction.ANY,
  2997. };
  2998. inSegRef = newSegRef;
  2999. }
  3000. if (inSeg)
  3001. inSegRef.seg = inSeg.id;
  3002. inSegRef.direction = inSeg?.toNodeId === curNodeEntry.id ? Direction.FORWARD : Direction.REVERSE;
  3003. }
  3004. if (inSeg === null) {
  3005. lt_log("== No inseg found ==================================================================", 2);
  3006. return 0; // otherwise wait for later
  3007. }
  3008. lt_log(`Found inseg candidate: ${inSeg.id} ${inSegIsHeurFail === 0 ? "" : "(failed)"}`, 2);
  3009. // #3(a) Determine the outgoing segment 2 (the 2nd turn) and validate turn angle
  3010. for (let ii = 0; ii < nodeExitSegIds.length; ii++) {
  3011. let thisTimeFail = 0;
  3012. if (nodeExitSegIds[ii] === segId) {
  3013. continue;
  3014. } // ignore same segment as our original
  3015. const os = getSegObj(nodeExitSegIds[ii]);
  3016. // Check turn from candidate seg to this seg
  3017. if (!lt_is_turn_allowed(segCandidate, curNodeExit, os)) {
  3018. continue;
  3019. }
  3020. const oa = lt_getBearing(curNodeExit.id, os); // absolute math azimuth
  3021. const ota = lt_turn_angle_seg_to_seg(segCandidate, curNodeExit, os); // turn angle
  3022. lt_log(`Turn angle to outseg2 ${nodeExitSegIds[ii]}: ${ota}(${segEndAzm},${oa})`, 2);
  3023. // Just to be sure, we can't do Heuristics if there's a chance to turn right (RH)
  3024. if (ota !== null && Math.abs(out1TargetAngle - ota) < MAX_PERP_TO_CONSIDER) {
  3025. // tolerance met?
  3026. return HeuristicsCandidate.NONE;
  3027. }
  3028. // Ok now check our turn angle
  3029. if (ota !== null && Math.abs(out2TargetAngle - ota) > MAX_PERP_DIF) {
  3030. // tolerance met?
  3031. if (Math.abs(out2TargetAngle - ota) > MAX_PERP_TO_CONSIDER) {
  3032. continue;
  3033. } // too far out of tolerance to care (don't consider it a candidate at all)
  3034. lt_log(` Not eligible as outseg2: ${ota}`, 2);
  3035. thisTimeFail = HeuristicsCandidate.FAIL;
  3036. }
  3037. // Only one segment allowed // TBD ??? For now, don't allow more than one.
  3038. if (outSeg2 !== null && thisTimeFail >= outSeg2IsHeurFail) {
  3039. if (outSeg2IsHeurFail === 0 && thisTimeFail === 0) {
  3040. lt_log(`Error: >1 qualifying exit2 segment for ${segCandidate.id}: ${outSeg2.id},${os?.id}`, 2);
  3041. lt_log("==================================================================================", 2);
  3042. return 0; // just stop here
  3043. }
  3044. }
  3045. outSeg2 = os;
  3046. outTurnAngle2 = ota;
  3047. outSeg2IsHeurFail = thisTimeFail;
  3048. }
  3049. if (outSeg2 === null) {
  3050. lt_log("== No Outseg2 found ==================================================================", 2);
  3051. return 0;
  3052. }
  3053. lt_log(`Found outseg2 candidate: ${outSeg2.id} ${outSeg2IsHeurFail === 0 ? "" : "(failed)"}`, 2);
  3054. // #11 & 12: The Segment 1 that matters is the incoming (parallel to outgoing seg2)
  3055. for (let ii = 0; ii < nodeEntrySegIds.length; ii++) {
  3056. if (nodeEntrySegIds[ii] === segId || nodeEntrySegIds[ii] === inSeg.id) {
  3057. // ignore same segment as our original
  3058. continue;
  3059. }
  3060. const ai1 = getSegObj(nodeEntrySegIds[ii]);
  3061. let thisTimeFail = 0;
  3062. // Ensure the segment is one-way TOWARD the node (incoming direction)
  3063. const sourceSegment = ((ai1?.isBtoA && ai1.fromNodeId === curNodeEntry.id) || (ai1?.isAtoB && ai1.toNodeId === curNodeEntry.id));
  3064. if ((ai1?.isAtoB && ai1.toNodeId !== curNodeEntry.id) || (ai1?.isBtoA && ai1.fromNodeId !== curNodeEntry.id)) {
  3065. continue;
  3066. }
  3067. // Check turn from this seg to our segment
  3068. const ia = lt_getBearing(curNodeEntry.id, ai1, true); // absolute math azimuth
  3069. // 12. Check angle from inseg to this seg (se)
  3070. // Since we already have azm of this seg TOWARD the node, just check the supplementary turn angle. Must also be within tolerance. (See Geometry proof :)
  3071. // const tta: number | null = lt_turn_angle(inAzm, ia);
  3072. let tta;
  3073. if (sourceSegment) {
  3074. tta = lt_turn_angle_seg_to_seg(ai1, curNodeEntry, inSeg);
  3075. }
  3076. else
  3077. tta = lt_turn_angle_seg_to_seg(inSeg, curNodeEntry, ai1);
  3078. lt_log(`Turn angle from inseg (supplementary) ${nodeEntrySegIds[ii]}: ${tta}(${inAzm},${ia})`, 3);
  3079. if (tta !== null && Math.abs(out1TargetAngle - tta) > MAX_PERP_DIF_ALT) {
  3080. // tolerance met?
  3081. if (Math.abs(out1TargetAngle - tta) > MAX_PERP_TO_CONSIDER) {
  3082. // too far out of tolerance to care (don't consider it a candidate at all)
  3083. continue;
  3084. }
  3085. lt_log(` Not eligible as altIn1: ${tta}`, 3);
  3086. thisTimeFail = HeuristicsCandidate.FAIL;
  3087. }
  3088. // Only one segment allowed // TBD ??? For now, don't allow more than one.
  3089. if (altIncomingSeg !== null) {
  3090. // If the new candidate is worse than what we already have, just move on
  3091. if (thisTimeFail < altInIsHeurFail) {
  3092. continue;
  3093. }
  3094. // If they both are good, then error
  3095. if (altInIsHeurFail === 0 && thisTimeFail === 0) {
  3096. lt_log(`Error: >1 qualifying segment for ${segCandidate.id}: ${altIncomingSeg.id},${ai1?.id}`, 2);
  3097. lt_log("==================================================================================", 2);
  3098. return HeuristicsCandidate.FAIL;
  3099. }
  3100. } // If the new candidate is better than the old, then assign our candidate to the new one (below)
  3101. altIncomingSeg = ai1;
  3102. altInAzm = ia;
  3103. altInIsHeurFail = thisTimeFail;
  3104. }
  3105. if (altIncomingSeg === null) {
  3106. lt_log("== No alt incoming-1 segment found ==================================================================", 2);
  3107. return 0;
  3108. }
  3109. lt_log(`Alt incoming-1 segment found: ${altIncomingSeg.id} ${altInIsHeurFail === 0 ? "" : "(failed)"}`, 2);
  3110. // Have we found a failure candidate?
  3111. if (inSegIsHeurFail < 0 || altInIsHeurFail < 0 || outSeg2IsHeurFail < 0) {
  3112. lt_log(`Found a failed candidate for ${segId} ( ${Math.min(inSegIsHeurFail, altInIsHeurFail, outSeg2IsHeurFail)})`, 2);
  3113. // NOTE: IF any seg is a FAIL, then return FAIL (not Error)
  3114. if (inSegIsHeurFail === HeuristicsCandidate.FAIL ||
  3115. altInIsHeurFail === HeuristicsCandidate.FAIL ||
  3116. outSeg2IsHeurFail === HeuristicsCandidate.FAIL) {
  3117. return HeuristicsCandidate.FAIL;
  3118. }
  3119. return HeuristicsCandidate.ERROR;
  3120. }
  3121. // We have a winner!!!
  3122. lt_log(`Found a heuristics candidate! ${segId} to ${outSeg2.id} at ${outTurnAngle2}`, 2);
  3123. return 1;
  3124. ////////////////////////////////////////////// end of func /////////////////////////////////////////////////////////
  3125. // get the absolute angle for a segment at an end point - borrowed from JAI
  3126. function lt_getBearing(nodeId, segment, toNode = false) {
  3127. if (nodeId === null || segment === null) {
  3128. return null;
  3129. }
  3130. // let ja_dx: number;
  3131. // let ja_dy: number;
  3132. let startPos, endPos;
  3133. if (segment.fromNodeId === nodeId) {
  3134. const secondPoint = lt_get_second_point(segment);
  3135. const firstPoint = lt_get_first_point(segment);
  3136. if (!secondPoint || !firstPoint) {
  3137. throw new Error("Missing Start and end Point of the Segment");
  3138. }
  3139. // ja_dx = secondPoint[0] - firstPoint[0];
  3140. // ja_dy = secondPoint[1] - firstPoint[1];
  3141. if (toNode) {
  3142. startPos = secondPoint;
  3143. endPos = firstPoint;
  3144. }
  3145. else {
  3146. startPos = firstPoint;
  3147. endPos = secondPoint;
  3148. }
  3149. }
  3150. else {
  3151. const nextToLastPoint = lt_get_next_to_last_point(segment);
  3152. const lastPoint = lt_get_last_point(segment);
  3153. if (!nextToLastPoint || !lastPoint) {
  3154. throw new Error("Missing Points at the End of the Segment");
  3155. }
  3156. // ja_dx = nextToLastPoint[0] - lastPoint[0];
  3157. // ja_dy = nextToLastPoint[1] - lastPoint[1];
  3158. if (toNode) {
  3159. endPos = lastPoint;
  3160. startPos = nextToLastPoint;
  3161. }
  3162. else {
  3163. startPos = lastPoint;
  3164. endPos = nextToLastPoint;
  3165. }
  3166. }
  3167. // const angle_rad = Math.atan2(ja_dy, ja_dx);
  3168. // const angle_deg = ((angle_rad * 180) / Math.PI) % 360;
  3169. const angle_deg = turf.bearing(startPos, endPos);
  3170. lt_log(`Azm from node ${nodeId} / ${segment.id}: ${angle_deg}`, 3);
  3171. return angle_deg;
  3172. }
  3173. // function lt_getBearing_to_node(nodeId: number | null, segment: Segment | null) {
  3174. // if (!nodeId || !segment) return null;
  3175. // const fromAzm = lt_getBearing(nodeId, segment);
  3176. // if (fromAzm === null) return null;
  3177. // let toAzm = fromAzm + 180.0;
  3178. // if (toAzm >= 180.0) {
  3179. // toAzm -= 360.0;
  3180. // }
  3181. // lt_log(`Azm to node ${nodeId} / ${segment.id}: ${toAzm}`, 3);
  3182. // return toAzm;
  3183. // }
  3184. /** Get absolute angle between 2 inputs.
  3185. * @param aIn absolute s_in angle (to node)
  3186. * @param aOut absolute s_out angle (from node)
  3187. * @returns {number}
  3188. */
  3189. function lt_turn_angle(aIn, aOut) {
  3190. if (aIn === null || aOut === null)
  3191. return null;
  3192. let angleInAdjusted = aIn;
  3193. let angleOutAdjusted = aOut;
  3194. while (aOut > 180.0) {
  3195. angleOutAdjusted -= 360.0;
  3196. }
  3197. while (aOut < -180.0) {
  3198. angleOutAdjusted += 360.0;
  3199. }
  3200. while (aIn > 180.0) {
  3201. angleInAdjusted -= 360.0;
  3202. }
  3203. while (aIn < -180.0) {
  3204. angleInAdjusted += 360.0;
  3205. }
  3206. let a = angleOutAdjusted - angleInAdjusted;
  3207. a += a > 180 ? -360 : a < -180 ? 360 : 0;
  3208. lt_log(`Turn ${angleInAdjusted},${angleOutAdjusted}: ${a}`, 3);
  3209. return a;
  3210. }
  3211. function lt_turn_angle_seg_to_seg(inSeg, connectorNode, outSeg) {
  3212. let inPoint;
  3213. let outPoint;
  3214. if (inSeg.fromNodeId === connectorNode.id) {
  3215. inPoint = lt_get_second_point(inSeg);
  3216. }
  3217. else if (inSeg.toNodeId === connectorNode.id) {
  3218. inPoint = lt_get_next_to_last_point(inSeg);
  3219. }
  3220. if (outSeg.fromNodeId === connectorNode.id) {
  3221. outPoint = lt_get_second_point(outSeg);
  3222. }
  3223. else if (outSeg.toNodeId === connectorNode.id) {
  3224. outPoint = lt_get_next_to_last_point(outSeg);
  3225. }
  3226. if (!inPoint || !outPoint)
  3227. return null;
  3228. let turnAngle = turf.angle(inPoint, connectorNode.geometry.coordinates, outPoint);
  3229. turnAngle -= (turnAngle > 180 ? 360 : 0);
  3230. turnAngle = (turnAngle > 0) ? 180 - turnAngle : -180 - turnAngle;
  3231. return turnAngle;
  3232. }
  3233. function lt_is_turn_allowed(s_from, via_node, s_to) {
  3234. const turnsThrough = sdk.DataModel.Turns.getTurnsThroughNode({ nodeId: via_node.id });
  3235. function isTurnAllowedBySegDirections(from, to) {
  3236. const result = {
  3237. allowedBySegDirections: false,
  3238. allowed: false,
  3239. };
  3240. if (from !== null && to !== null) {
  3241. for (let tidx = 0; tidx < turnsThrough.length; ++tidx) {
  3242. if (turnsThrough[tidx].fromSegmentId === from.id && turnsThrough[tidx].toSegmentId === to.id) {
  3243. result.allowed = turnsThrough[tidx].isAllowed;
  3244. result.allowedBySegDirections = true;
  3245. break;
  3246. }
  3247. }
  3248. }
  3249. return result;
  3250. }
  3251. const permissions = isTurnAllowedBySegDirections(s_from, s_to);
  3252. lt_log(`Allow from ${s_from.id} to ${s_to !== null ? s_to.id : 0} via ${via_node.id}? ${permissions.allowedBySegDirections} | ${permissions.allowed}`, 3);
  3253. // Is there a driving direction restriction?
  3254. if (!permissions.allowedBySegDirections) {
  3255. lt_log("Driving direction restriction applies", 3);
  3256. return false;
  3257. }
  3258. // Is turn allowed by other means (e.g. turn restrictions)?
  3259. if (!permissions.allowed) {
  3260. lt_log("Other restriction applies", 3);
  3261. return false;
  3262. }
  3263. // TBD: Do we need to consider restrictions?
  3264. /*if(s_to.attributes.fromNodeID === via_node.attributes.id) {
  3265. lt_log("FWD direction",3);
  3266. return ja_is_car_allowed_by_restrictions(s_to.attributes.fwdRestrictions);
  3267. } else {
  3268. lt_log("REV direction",3);
  3269. return ja_is_car_allowed_by_restrictions(s_to.attributes.revRestrictions);
  3270. } */
  3271. return true;
  3272. }
  3273. }
  3274. // Segment Length - borrowed from JAI
  3275. // function lt_segment_length(segment: Segment) {
  3276. // // let len = segment.geometry.getGeodesicLength(W.map.olMap.projection);
  3277. // // let len = olSphere.getLength(segment.geometry);
  3278. // const len = 0;
  3279. // // let len = segment.geometry.getGeodesicLength(W.map.olMap.projection);
  3280. // lt_log(`segment: ${segment.id} computed len: ${len} `, 3);
  3281. // return len;
  3282. // }
  3283. function lt_log(lt_log_msg, lt_log_level = 1) {
  3284. // ##NO_FF_START##
  3285. // Firefox addons should not use console.(log|error|debug), so these lines
  3286. // are removed by the FF addon packaging script.
  3287. if (lt_log_level <= LANETOOLS_DEBUG_LEVEL) {
  3288. console.log("LaneTools Dev Msg: ", lt_log_msg);
  3289. }
  3290. // ##NO_FF_END##
  3291. }
  3292. function copyLaneInfo(side) {
  3293. _turnInfo = [];
  3294. const selFeatures = sdk.Editing.getSelection();
  3295. const seg = selFeatures[0]._wmeObject;
  3296. const segAtt = seg.getFeatureAttributes();
  3297. const segGeo = seg.geometry.components;
  3298. const nodeID = side === "A" ? segAtt.fromNodeID : segAtt.toNodeID;
  3299. laneCount = side === "A" ? segAtt.revLaneCount : segAtt.fwdLaneCount;
  3300. console.log(laneCount);
  3301. const node = getNodeObj(nodeID);
  3302. const conSegs = node.getSegmentIds();
  3303. // const turnGraph = W.model.getTurnGraph();
  3304. let geoPoint1;
  3305. if (side === "A") {
  3306. geoPoint1 = segGeo[1];
  3307. }
  3308. else {
  3309. geoPoint1 = segGeo[segGeo.length - 2];
  3310. }
  3311. let ja_dx = geoPoint1.x - node.geometry.x;
  3312. let ja_dy = geoPoint1.y - node.geometry.y;
  3313. let angleRad = Math.atan2(ja_dy, ja_dx);
  3314. const angleDeg = ((angleRad * 180) / Math.PI) % 360;
  3315. for (let i = 0; i < conSegs.length; i++) {
  3316. const seg2 = getSegObj(conSegs[i]);
  3317. const seg2Att = seg2?.getFeatureAttributes();
  3318. const seg2Geo = seg2?.geometry.components;
  3319. let geoPoint2;
  3320. let seg2Dir;
  3321. const turnInfo = turnGraph.getTurnThroughNode(node, seg, seg2).getTurnData();
  3322. if (turnInfo.state === 1 && turnInfo.lanes) {
  3323. if (seg2Att.fromNodeID === nodeID) {
  3324. seg2Dir = "A";
  3325. }
  3326. else {
  3327. seg2Dir = "B";
  3328. }
  3329. if (seg2Dir === "A") {
  3330. geoPoint2 = seg2Geo[1];
  3331. }
  3332. else {
  3333. geoPoint2 = seg2Geo[seg2Geo.length - 2];
  3334. }
  3335. ja_dx = geoPoint2.x - node.geometry.x;
  3336. ja_dy = geoPoint2.y - node.geometry.y;
  3337. angleRad = Math.atan2(ja_dy, ja_dx);
  3338. let tempAngle = ((angleRad * 180) / Math.PI) % 360;
  3339. if (angleDeg < 0)
  3340. tempAngle = angleDeg - tempAngle;
  3341. _turnData = {};
  3342. let laneData = turnInfo.getLaneData();
  3343. _turnData.id = seg2.attributes.id;
  3344. _turnData.order = tempAngle;
  3345. _turnData.lanes = laneData;
  3346. _turnInfo.push(_turnData);
  3347. }
  3348. _turnInfo.sort((a, b) => (a.order > b.order ? 1 : -1));
  3349. }
  3350. console.log(_turnInfo);
  3351. }
  3352. function pasteLaneInfo(side) {
  3353. const mAction = new MultiAction();
  3354. // mAction.setModel(W.model);
  3355. const selFeatures = W.selectionManager.getSelectedWMEFeatures();
  3356. const seg = selFeatures[0]._wmeObject;
  3357. const segGeo = seg.geometry.components;
  3358. const segAtt = seg.getFeatureAttributes();
  3359. const nodeID = side === "A" ? segAtt.fromNodeID : segAtt.toNodeID;
  3360. // let sortA = _cpyDir == side ? 1 : -1;
  3361. // let sortB = _cpyDir == side ? -1 : 1;
  3362. let geoPoint1;
  3363. const node = getNodeObj(nodeID);
  3364. const conSegs = node.getSegmentIds();
  3365. // const turnGraph = W.model.getTurnGraph();
  3366. let pasteData = {};
  3367. const pasteInfo = [];
  3368. if (side === "A") {
  3369. geoPoint1 = segGeo[1];
  3370. }
  3371. else {
  3372. geoPoint1 = segGeo[segGeo.length - 2];
  3373. }
  3374. let ja_dx = geoPoint1.x - node.geometry.x;
  3375. let ja_dy = geoPoint1.y - node.geometry.y;
  3376. let angleRad = Math.atan2(ja_dy, ja_dx);
  3377. const angleDeg = ((angleRad * 180) / Math.PI) % 360;
  3378. for (let i = 0; i < conSegs.length; i++) {
  3379. const seg2 = getSegObj(conSegs[i]);
  3380. const seg2Att = seg2.attributes;
  3381. const seg2Geo = seg2.geometry.components;
  3382. let geoPoint2 = {};
  3383. let seg2Dir;
  3384. const turnInfo = turnGraph.getTurnThroughNode(node, seg, seg2).getTurnData();
  3385. if (seg2Att.fromNodeID === nodeID) {
  3386. seg2Dir = "A";
  3387. }
  3388. else {
  3389. seg2Dir = "B";
  3390. }
  3391. if (seg2Dir === "A") {
  3392. geoPoint2 = seg2Geo[1];
  3393. }
  3394. else {
  3395. geoPoint2 = seg2Geo[seg2Geo.length - 2];
  3396. }
  3397. if (turnInfo.state === 1) {
  3398. pasteData = {};
  3399. ja_dx = geoPoint2.x - node.geometry.x;
  3400. ja_dy = geoPoint2.y - node.geometry.y;
  3401. angleRad = Math.atan2(ja_dy, ja_dx);
  3402. let tempAngle = ((angleRad * 180) / Math.PI) % 360;
  3403. if (angleDeg < 0)
  3404. tempAngle = angleDeg - tempAngle;
  3405. pasteData.id = seg2Att.id;
  3406. pasteData.order = tempAngle;
  3407. pasteInfo.push(pasteData);
  3408. }
  3409. pasteInfo.sort((a, b) => (a.order > b.order ? 1 : -1));
  3410. }
  3411. console.log(pasteInfo);
  3412. if (_turnInfo.length === pasteInfo.length) {
  3413. if (side === "A") {
  3414. mAction.doSubAction(W.model, new UpdateObj(seg, { revLaneCount: laneCount }));
  3415. }
  3416. else {
  3417. mAction.doSubAction(W.model, new UpdateObj(seg, { fwdLaneCount: laneCount }));
  3418. }
  3419. for (let k = 0; k < pasteInfo.length; k++) {
  3420. const pasteTurn = {};
  3421. // Copy turn data into temp object
  3422. for (let q = 0; q < _turnInfo.length; q++) {
  3423. pasteTurn[q] = _turnInfo[q];
  3424. }
  3425. // If pasting in the opposite direction, reverse the lane associations
  3426. /* if (_cpyDir != side) {
  3427. for (let z=0;z < pasteTurn.length; z++) {
  3428. pasteTurn[z].lanes.arrowAngle = pasteTurn[z].lanes.arrowAngle * -1;
  3429. }
  3430. } */
  3431. const toSeg = getSegObj(pasteInfo[k].id);
  3432. let turnStatus = turnGraph.getTurnThroughNode(node, seg, toSeg);
  3433. let turnData = turnStatus.getTurnData();
  3434. turnData = turnData.withLanes(pasteTurn[k].lanes);
  3435. turnStatus = turnStatus.withTurnData(turnData);
  3436. mAction.doSubAction(W.model, new SetTurn(turnGraph, turnStatus));
  3437. }
  3438. mAction._description = "Pasted some lane stuff";
  3439. W.model.actionManager.add(mAction);
  3440. lanesTabSetup.formatLanesTab(true);
  3441. }
  3442. else {
  3443. WazeWrap.Alerts.warning(GM_info.script.name, "There are a different number of enabled turns on this segment/node");
  3444. }
  3445. }
  3446. function getIcons(dir) {
  3447. const tempEle = [];
  3448. let svgcount = 0;
  3449. for (let i = 0; i < dir.length; i++) {
  3450. //if (dir[i].id !== "") {
  3451. const temp = {
  3452. uturn: false,
  3453. miniuturn: false,
  3454. svg: [],
  3455. };
  3456. const uTurnDisplay = $(dir[i])
  3457. .find(".uturn")
  3458. .css("display");
  3459. const miniUturnDisplay = $(dir[i])
  3460. .find(".small-uturn")
  3461. .css("display");
  3462. temp.uturn = uTurnDisplay && uTurnDisplay !== "none";
  3463. temp.miniuturn = miniUturnDisplay && miniUturnDisplay !== "none";
  3464. temp.svg = $(dir[i])
  3465. .find("svg")
  3466. .map(function () {
  3467. return this;
  3468. })
  3469. .get();
  3470. if (temp.svg.length > 0) {
  3471. svgcount++;
  3472. }
  3473. tempEle[i] = temp;
  3474. }
  3475. return svgcount > 0 ? tempEle : false;
  3476. }
  3477. function convertToBase64(svgs) {
  3478. const serial = new XMLSerializer();
  3479. _.each(svgs, (obj) => {
  3480. try {
  3481. const svg = obj.svg[0];
  3482. const tmp = serial.serializeToString(svg);
  3483. obj.svg = `data:image/svg+xml;base64,${window.btoa(tmp)}`;
  3484. }
  3485. catch (e) {
  3486. // console.log(e);
  3487. }
  3488. });
  3489. return svgs;
  3490. }
  3491. function getStartPoints(node, featDis, numIcons, sign, isLeftDrive) {
  3492. const start = !featDis || !featDis.start ? 0 : featDis.start;
  3493. const boxheight = !featDis || !featDis.boxheight ? 0 : featDis.boxheight;
  3494. const boxincwidth = !featDis || !featDis.boxincwidth ? 0 : featDis.boxincwidth;
  3495. const nodePos = proj4("EPSG:4326", "EPSG:3857", node.geometry.coordinates);
  3496. const leftDriveModifier = isLeftDrive ? -1 : 1;
  3497. const leftOffset = isLeftDrive ? featDis.leftOffset : 0;
  3498. switch (sign) {
  3499. case 0:
  3500. return proj4("EPSG:3857", "EPSG:4326", [nodePos[0] + leftDriveModifier * start * 2 - leftOffset, nodePos[1] + boxheight]);
  3501. // x: node.geometry.x + (featDis.start * 2),
  3502. // y: node.geometry.y + (featDis.boxheight)
  3503. case 1:
  3504. return proj4("EPSG:3857", "EPSG:4326", [nodePos[0] + leftDriveModifier * boxheight - leftOffset, nodePos[1] + boxincwidth * numIcons]);
  3505. // x: node.geometry.x + featDis.boxheight,
  3506. // y: node.geometry.y + (featDis.boxincwidth * numIcons/1.8)
  3507. case 2:
  3508. return proj4("EPSG:3857", "EPSG:4326", [
  3509. nodePos[0] - leftDriveModifier * (start + boxincwidth * numIcons),
  3510. nodePos[1] + boxheight,
  3511. ]);
  3512. // x: node.geometry.x - (featDis.start + (featDis.boxincwidth * numIcons)),
  3513. // y: node.geometry.y + (featDis.start + featDis.boxheight)
  3514. case 3:
  3515. return proj4("EPSG:3857", "EPSG:4326", [
  3516. nodePos[0] + leftDriveModifier * (start + boxincwidth) - leftOffset,
  3517. nodePos[1] - (start + boxheight),
  3518. ]);
  3519. // x: node.geometry.x + (featDis.start + featDis.boxincwidth),
  3520. // y: node.geometry.y - (featDis.start + featDis.boxheight)
  3521. case 4:
  3522. return proj4("EPSG:3857", "EPSG:4326", [
  3523. nodePos[0] - leftDriveModifier * (start + boxheight * 3) - leftOffset,
  3524. nodePos[1] + (boxincwidth + numIcons * 0.5),
  3525. ]);
  3526. // x: node.geometry.x - (featDis.start + (featDis.boxheight * 1.5)),
  3527. // y: node.geometry.y - (featDis.start + (featDis.boxincwidth * numIcons * 1.5))
  3528. case 5:
  3529. return proj4("EPSG:3857", "EPSG:4326", [nodePos[0] + leftDriveModifier * (start + boxincwidth) - leftOffset, nodePos[1] + start]);
  3530. // x: node.geometry.x + (featDis.start + featDis.boxincwidth/2),
  3531. // y: node.geometry.y + (featDis.start/2)
  3532. case 6:
  3533. return proj4("EPSG:3857", "EPSG:4326", [
  3534. nodePos[0] - leftDriveModifier * start - leftOffset,
  3535. nodePos[1] - start * ((boxincwidth * numIcons) / 2),
  3536. ]);
  3537. // x: node.geometry.x - (featDis.start),
  3538. // y: node.geometry.y - (featDis.start * (featDis.boxincwidth * numIcons/2))
  3539. case 7:
  3540. return proj4("EPSG:3857", "EPSG:4326", [
  3541. nodePos[0] - leftDriveModifier * start * boxincwidth * numIcons - leftOffset,
  3542. nodePos[1] + start,
  3543. ]);
  3544. // x: node.geometry.x - (featDis.start * (featDis.boxincwidth * numIcons/2)),
  3545. // y: node.geometry.y - (featDis.start)
  3546. default:
  3547. break;
  3548. }
  3549. return [];
  3550. }
  3551. function getFeatDistance() {
  3552. const label_distance = {
  3553. start: undefined,
  3554. boxheight: undefined,
  3555. boxincwidth: undefined,
  3556. iconbordermargin: undefined,
  3557. iconborderheight: undefined,
  3558. iconborderwidth: undefined,
  3559. graphicHeight: undefined,
  3560. graphicWidth: undefined,
  3561. leftOffset: undefined
  3562. };
  3563. switch (sdk.Map.getZoomLevel()) {
  3564. case 22:
  3565. label_distance.start = 2;
  3566. label_distance.boxheight = 1.7;
  3567. label_distance.boxincwidth = 1.1;
  3568. label_distance.iconbordermargin = 0.1;
  3569. label_distance.iconborderheight = 1.6;
  3570. label_distance.iconborderwidth = 1;
  3571. label_distance.graphicHeight = 42;
  3572. label_distance.graphicWidth = 25;
  3573. label_distance.leftOffset = 2;
  3574. break;
  3575. case 21:
  3576. label_distance.start = 2;
  3577. label_distance.boxheight = 3.2;
  3578. label_distance.boxincwidth = 2.2;
  3579. label_distance.iconbordermargin = 0.2;
  3580. label_distance.iconborderheight = 3;
  3581. label_distance.iconborderwidth = 2;
  3582. label_distance.graphicHeight = 42;
  3583. label_distance.graphicWidth = 25;
  3584. label_distance.leftOffset = 2;
  3585. break;
  3586. case 20:
  3587. label_distance.start = 2;
  3588. label_distance.boxheight = 5.2;
  3589. label_distance.boxincwidth = 3.8;
  3590. label_distance.iconbordermargin = 0.3;
  3591. label_distance.iconborderheight = 4.9;
  3592. label_distance.iconborderwidth = 3.5;
  3593. label_distance.graphicHeight = 42;
  3594. label_distance.graphicWidth = 25;
  3595. label_distance.leftOffset = 8;
  3596. break;
  3597. case 19:
  3598. label_distance.start = 3;
  3599. label_distance.boxheight = 10.0;
  3600. label_distance.boxincwidth = 7.2;
  3601. label_distance.iconbordermargin = 0.4;
  3602. label_distance.iconborderheight = 9.6;
  3603. label_distance.iconborderwidth = 6.8;
  3604. label_distance.graphicHeight = 42;
  3605. label_distance.graphicWidth = 25;
  3606. label_distance.leftOffset = 8;
  3607. break;
  3608. case 18:
  3609. label_distance.start = 3;
  3610. label_distance.boxheight = 20.0;
  3611. label_distance.boxincwidth = 14.0;
  3612. label_distance.iconbordermargin = 0.5;
  3613. label_distance.iconborderheight = 19.5;
  3614. label_distance.iconborderwidth = 13.5;
  3615. label_distance.graphicHeight = 42;
  3616. label_distance.graphicWidth = 25;
  3617. label_distance.leftOffset = 16;
  3618. break;
  3619. case 17:
  3620. label_distance.start = 10;
  3621. label_distance.boxheight = 39.0;
  3622. label_distance.boxincwidth = 28.0;
  3623. label_distance.iconbordermargin = 1.0;
  3624. label_distance.iconborderheight = 38.0;
  3625. label_distance.iconborderwidth = 27.0;
  3626. label_distance.graphicHeight = 42;
  3627. label_distance.graphicWidth = 25;
  3628. label_distance.leftOffset = 16;
  3629. break;
  3630. case 16:
  3631. label_distance.start = 15;
  3632. label_distance.boxheight = 80.0;
  3633. label_distance.boxincwidth = 55;
  3634. label_distance.iconbordermargin = 2.0;
  3635. label_distance.iconborderheight = 78.0;
  3636. label_distance.iconborderwidth = 53;
  3637. label_distance.graphicHeight = 42;
  3638. label_distance.graphicWidth = 25;
  3639. label_distance.leftOffset = 32;
  3640. break;
  3641. case 15:
  3642. label_distance.start = 2;
  3643. label_distance.boxheight = 120.0;
  3644. label_distance.boxincwidth = 90;
  3645. label_distance.iconbordermargin = 3.0;
  3646. label_distance.iconborderheight = 117.0;
  3647. label_distance.iconborderwidth = 87;
  3648. label_distance.graphicHeight = 42;
  3649. label_distance.graphicWidth = 25;
  3650. label_distance.leftOffset = 32;
  3651. break;
  3652. case 14:
  3653. label_distance.start = 2;
  3654. label_distance.boxheight = 5.2;
  3655. label_distance.boxincwidth = 3.8;
  3656. label_distance.iconbordermargin = 0.3;
  3657. label_distance.iconborderheight = 4.9;
  3658. label_distance.iconborderwidth = 3.5;
  3659. label_distance.graphicHeight = 42;
  3660. label_distance.graphicWidth = 25;
  3661. label_distance.leftOffset = 32;
  3662. break;
  3663. // case 13:
  3664. // label_distance.start = 2;
  3665. // label_distance.boxheight = 5.2;
  3666. // label_distance.boxincwidth = 3.8;
  3667. // label_distance.iconbordermargin = .3;
  3668. // label_distance.iconborderheight = 4.9;
  3669. // label_distance.iconborderwidth = 3.5;
  3670. // label_distance.graphicHeight = 42;
  3671. // label_distance.graphicWidth = 25;
  3672. // break;
  3673. }
  3674. return label_distance;
  3675. }
  3676. function drawIcons(seg, node, imgs, isLeftDrive = false) {
  3677. if (!seg || !node)
  3678. return;
  3679. const featDis = getFeatDistance();
  3680. let deg = getCardinalAngle(node.id, seg);
  3681. if (!deg)
  3682. return;
  3683. const points = [];
  3684. let operatorSign = 0;
  3685. const numIcons = imgs.length;
  3686. // Orient all icons straight up if the rotate option isn't enabled
  3687. if (!getId("lt-IconsRotate")?.checked)
  3688. deg = -90;
  3689. // Rotate in the style is clockwise, the rotate() func is counterclockwise
  3690. if (deg === 0) {
  3691. deg += 180;
  3692. operatorSign = 1;
  3693. }
  3694. else if (deg > 0 && deg <= 30) {
  3695. deg += 2 * (90 - deg);
  3696. // console.log('Math stuff2: ' + deg);
  3697. operatorSign = 1;
  3698. }
  3699. else if (deg >= 330 && deg <= 360) {
  3700. deg -= 180 - 2 * (360 - deg);
  3701. // console.log('Math stuff2: ' + deg);
  3702. operatorSign = 1;
  3703. }
  3704. else if (deg > 30 && deg < 60) {
  3705. deg -= 90 - 2 * (360 - deg);
  3706. // console.log('Math stuff3: ' + deg);
  3707. operatorSign = 2;
  3708. }
  3709. else if (deg >= 60 && deg <= 120) {
  3710. deg -= 90 - 2 * (360 - deg);
  3711. // console.log('Math stuff4: ' + deg);
  3712. operatorSign = 2;
  3713. }
  3714. else if (deg > 120 && deg < 150) {
  3715. deg -= 90 - 2 * (360 - deg);
  3716. // console.log('Math stuff5: ' + deg);
  3717. operatorSign = 7;
  3718. }
  3719. else if (deg >= 150 && deg <= 210) {
  3720. deg = 180 - deg;
  3721. // console.log('Math stuff6: ' + deg);
  3722. operatorSign = 4;
  3723. }
  3724. else if (deg > 210 && deg < 240) {
  3725. deg -= 90 - 2 * (360 - deg);
  3726. // console.log('Math stuff7: ' + deg);
  3727. operatorSign = 6;
  3728. }
  3729. else if (deg >= 240 && deg <= 300) {
  3730. deg -= 180 - 2 * (360 - deg);
  3731. // console.log('Math stuff8: ' + deg);
  3732. operatorSign = 3;
  3733. }
  3734. else if (deg > 300 && deg < 330) {
  3735. deg -= 180 - 2 * (360 - deg);
  3736. // console.log('Math stuff9: ' + deg);
  3737. operatorSign = 5;
  3738. }
  3739. else {
  3740. console.log("LT: icon angle is out of bounds");
  3741. }
  3742. const iconRotate = deg > 315 ? deg : deg + 90;
  3743. const boxRotate = 360 - iconRotate;
  3744. // console.log(deg);
  3745. // console.log(operatorSign);
  3746. // Determine start point respective to node based on segment angle
  3747. // let boxRotate = deg * -1;
  3748. const startPoint = getStartPoints(node, featDis, numIcons, operatorSign, isLeftDrive);
  3749. if (!startPoint[0] || !startPoint[1])
  3750. return;
  3751. // Box coords
  3752. // var boxPoint1 = new OpenLayers.Geometry.Point(startPoint.x, startPoint.y + featDis.boxheight);
  3753. // var boxPoint2 = new OpenLayers.Geometry.Point(
  3754. // startPoint.x + featDis.boxincwidth * numIcons,
  3755. // startPoint.y + featDis.boxheight
  3756. // );
  3757. // var boxPoint3 = new OpenLayers.Geometry.Point(startPoint.x + featDis.boxincwidth * numIcons, startPoint.y);
  3758. // var boxPoint4 = new OpenLayers.Geometry.Point(startPoint.x, startPoint.y);
  3759. let boxPoint1 = proj4("EPSG:4326", "EPSG:3857", startPoint);
  3760. boxPoint1[1] += !featDis || !featDis.boxheight ? 0 : featDis.boxheight;
  3761. boxPoint1 = proj4("EPSG:3857", "EPSG:4326", boxPoint1);
  3762. let boxPoint2 = proj4("EPSG:4326", "EPSG:3857", startPoint);
  3763. boxPoint2[0] += !featDis || !featDis.boxincwidth ? 0 : featDis.boxincwidth * numIcons;
  3764. boxPoint2[1] += !featDis || !featDis.boxheight ? 0 : featDis.boxheight;
  3765. boxPoint2 = proj4("EPSG:3857", "EPSG:4326", boxPoint2);
  3766. let boxPoint3 = proj4("EPSG:4326", "EPSG:3857", startPoint);
  3767. boxPoint3[0] += !featDis || !featDis.boxincwidth ? 0 : featDis.boxincwidth * numIcons;
  3768. boxPoint3 = proj4("EPSG:3857", "EPSG:4326", boxPoint3);
  3769. const boxPoint4 = startPoint;
  3770. points.push(boxPoint1, boxPoint2, boxPoint3, boxPoint4, boxPoint1);
  3771. // Object.assign(styleRules.boxStyle.style, {
  3772. // strokeColor: "#ffffff",
  3773. // strokeOpacity: 1,
  3774. // strokeWidth: 8,
  3775. // fillColor: "#ffffff",
  3776. // // rotate: boxRotate
  3777. // });
  3778. // let boxRing = new OpenLayers.Geometry.LinearRing(points);
  3779. // centerPoint = boxRing.getCentroid();
  3780. // boxRing.rotate(boxRotate, centerPoint);
  3781. // let boxVector = new OpenLayers.Feature.Vector(boxRing, null, boxStyle);
  3782. let turfBoxRing = turf.polygon([points]);
  3783. turfBoxRing = turf.transformRotate(turfBoxRing, -1 * boxRotate);
  3784. const centerPoint = turf.centroid(turfBoxRing);
  3785. const boxRing = turf.polygon(turfBoxRing.geometry.coordinates, { styleName: "boxStyle", layerName: LTLaneGraphics.name }, { id: `polygon_${points.toString()}` });
  3786. // LTLaneGraphics.addFeatures([boxVector]);
  3787. sdk.Map.addFeatureToLayer({ feature: boxRing, layerName: LTLaneGraphics.name });
  3788. let num = 0;
  3789. _.each(imgs, (img) => {
  3790. const iconPoints = [];
  3791. // Icon Background
  3792. // var iconPoint1 = new OpenLayers.Geometry.Point(
  3793. // startPoint.x + featDis.boxincwidth * num + featDis.iconbordermargin,
  3794. // startPoint.y + featDis.iconborderheight
  3795. // );
  3796. let iconPoint1 = proj4("EPSG:4326", "EPSG:3857", startPoint);
  3797. iconPoint1[0] += !featDis
  3798. ? 0
  3799. : (!featDis.boxincwidth ? 0 : featDis.boxincwidth) * num +
  3800. (!featDis.iconbordermargin ? 0 : featDis.iconbordermargin);
  3801. iconPoint1[1] += !featDis || !featDis.iconborderheight ? 0 : featDis.iconborderheight;
  3802. iconPoint1 = proj4("EPSG:3857", "EPSG:4326", iconPoint1);
  3803. // var iconPoint2 = new OpenLayers.Geometry.Point(
  3804. // startPoint.x + featDis.boxincwidth * num + featDis.iconborderwidth,
  3805. // startPoint.y + featDis.iconborderheight
  3806. // );
  3807. let iconPoint2 = proj4("EPSG:4326", "EPSG:3857", startPoint);
  3808. iconPoint2[0] += !featDis
  3809. ? 0
  3810. : (!featDis.boxincwidth ? 0 : featDis.boxincwidth) * num +
  3811. (!featDis.iconborderwidth ? 0 : featDis.iconborderwidth);
  3812. iconPoint2[1] += !featDis || !featDis.iconborderheight ? 0 : featDis.iconborderheight;
  3813. iconPoint2 = proj4("EPSG:3857", "EPSG:4326", iconPoint2);
  3814. // var iconPoint3 = new OpenLayers.Geometry.Point(
  3815. // startPoint.x + featDis.boxincwidth * num + featDis.iconborderwidth,
  3816. // startPoint.y + featDis.iconbordermargin
  3817. // );
  3818. let iconPoint3 = proj4("EPSG:4326", "EPSG:3857", startPoint);
  3819. iconPoint3[0] += !featDis
  3820. ? 0
  3821. : (!featDis.boxincwidth ? 0 : featDis.boxincwidth) * num +
  3822. (!featDis.iconborderwidth ? 0 : featDis.iconborderwidth);
  3823. iconPoint3[1] += !featDis || !featDis.iconbordermargin ? 0 : featDis.iconbordermargin;
  3824. iconPoint3 = proj4("EPSG:3857", "EPSG:4326", iconPoint3);
  3825. // var iconPoint4 = new OpenLayers.Geometry.Point(
  3826. // startPoint.x + featDis.boxincwidth * num + featDis.iconbordermargin,
  3827. // startPoint.y + featDis.iconbordermargin
  3828. // );
  3829. let iconPoint4 = proj4("EPSG:4326", "EPSG:3857", startPoint);
  3830. iconPoint4[0] += !featDis
  3831. ? 0
  3832. : (!featDis.boxincwidth ? 0 : featDis.boxincwidth) * num +
  3833. (!featDis.iconbordermargin ? 0 : featDis.iconbordermargin);
  3834. iconPoint4[1] += !featDis || !featDis.iconbordermargin ? 0 : featDis.iconbordermargin;
  3835. iconPoint4 = proj4("EPSG:3857", "EPSG:4326", iconPoint4);
  3836. iconPoints.push(iconPoint1, iconPoint2, iconPoint3, iconPoint4, iconPoint1);
  3837. // Object.assign(styleRules.iconBoxStyle.style, {
  3838. // strokeColor: "#000000",
  3839. // strokeOpacity: 1,
  3840. // strokeWidth: 1,
  3841. // fillColor: "#26bae8",
  3842. // // rotate: boxRotate
  3843. // });
  3844. // let iconBoxRing = new OpenLayers.Geometry.LinearRing(iconPoints);
  3845. let turfIconBoxRing = turf.polygon([iconPoints]);
  3846. turfIconBoxRing = turf.transformRotate(turfIconBoxRing, -1 * boxRotate, { pivot: centerPoint.geometry });
  3847. const iconBoxRing = turf.polygon(turfIconBoxRing.geometry.coordinates, { styleName: "iconBoxStyle", layerName: LTLaneGraphics.name }, { id: `polygon_${iconPoints.toString()}` });
  3848. // iconBoxRing.rotate(boxRotate, centerPoint);
  3849. // let iconBoxVector = new OpenLayers.Feature.Vector(iconBoxRing, null, iconBoxStyle);
  3850. // LTLaneGraphics.addFeatures([iconBoxVector]);
  3851. sdk.Map.addFeatureToLayer({ feature: iconBoxRing, layerName: LTLaneGraphics.name });
  3852. // Icon coords
  3853. const arrowOrigin = turf.centroid(turfIconBoxRing);
  3854. // let iconStart = new OpenLayers.Geometry.Point(arrowOrigin.x, arrowOrigin.y);
  3855. let ulabel = "";
  3856. const usize = {
  3857. x: undefined,
  3858. y: undefined,
  3859. };
  3860. const uoffset = {
  3861. x: undefined,
  3862. y: undefined,
  3863. };
  3864. if (img.uturn === true) {
  3865. ulabel = `https://web-assets.waze.com/webapps/wme/${sdk.getWMEVersion()}-${env}/font/989fe58ac11ed7d3/u-turn-small.svg`;
  3866. usize.x = 0.6;
  3867. usize.y = 0.6;
  3868. uoffset.x = -7;
  3869. uoffset.y = -12;
  3870. }
  3871. if (img.miniuturn === true) {
  3872. ulabel = `https://web-assets.waze.com/webapps/wme/${sdk.getWMEVersion()}-${env}/font/989fe58ac11ed7d3/u-turn-small.svg`;
  3873. usize.x = 0.3;
  3874. usize.y = 0.25;
  3875. uoffset.x = -8;
  3876. uoffset.y = 4;
  3877. }
  3878. const iconStart = turf.point(arrowOrigin.geometry.coordinates, {
  3879. styleName: "iconStyle",
  3880. layerName: LTLaneGraphics.name,
  3881. style: {
  3882. externalGraphic: img.svg,
  3883. graphicHeight: featDis.graphicHeight,
  3884. graphicWidth: featDis.graphicWidth,
  3885. fillColor: "#26bae8",
  3886. fillOpacity: 1,
  3887. backgroundColor: "#26bae8",
  3888. strokeColor: "#26bae8",
  3889. rotation: iconRotate,
  3890. backgroundGraphic: ulabel,
  3891. backgroundHeight: !featDis || !featDis.graphicHeight || !usize.y
  3892. ? undefined
  3893. : featDis.graphicHeight * usize.y,
  3894. backgroundWidth: !featDis || !featDis.graphicWidth || !usize.x ? undefined : featDis.graphicWidth * usize.x,
  3895. backgroundXOffset: uoffset.x,
  3896. backgroundYOffset: uoffset.y,
  3897. },
  3898. }, { id: `point_${iconPoints.toString()}` });
  3899. sdk.Map.addFeatureToLayer({ layerName: LTLaneGraphics.name, feature: iconStart });
  3900. num++;
  3901. });
  3902. // LTLaneGraphics.setZIndex(2890);
  3903. }
  3904. function displayLaneGraphics() {
  3905. removeLaneGraphics();
  3906. const selection = sdk.Editing.getSelection();
  3907. if (!getId("lt-ScriptEnabled")?.checked ||
  3908. !getId("lt-IconsEnable")?.checked ||
  3909. selection == null ||
  3910. selection?.objectType !== "segment" ||
  3911. (selection.ids && selection.ids.length !== 1))
  3912. return;
  3913. const seg = sdk.DataModel.Segments.getById({ segmentId: selection.ids[0] });
  3914. if (!seg)
  3915. return;
  3916. const zoomLevel = sdk.Map.getZoomLevel();
  3917. if (zoomLevel < 15 ||
  3918. (seg.roadType !== (LT_ROAD_TYPE.FREEWAY || LT_ROAD_TYPE.MAJOR_HIGHWAY || LT_ROAD_TYPE.MINOR_HIGHWAY) &&
  3919. zoomLevel < 16))
  3920. return;
  3921. let isLeftDrive = false;
  3922. if (seg.primaryStreetId) {
  3923. let primaryStreet = sdk.DataModel.Streets.getById({ streetId: seg.primaryStreetId });
  3924. if (primaryStreet?.cityId) {
  3925. let city = sdk.DataModel.Cities.getById({ cityId: primaryStreet.cityId });
  3926. if (city?.countryId) {
  3927. let country = sdk.DataModel.Countries.getById({ countryId: city.countryId });
  3928. isLeftDrive = country?.isLeftHandTraffic || false;
  3929. }
  3930. }
  3931. }
  3932. waitForElementLoaded(".lanes-tab > .lanes > div > .direction-lanes").then(() => {
  3933. const fwdEle = seg?.fromNodeLanesCount && seg.fromNodeLanesCount > 0
  3934. ? getIcons($(".fwd-lanes")
  3935. .find(".lane-arrow")
  3936. .map(function () {
  3937. return this;
  3938. })
  3939. .get())
  3940. : false;
  3941. const revEle = seg?.toNodeLanesCount && seg.toNodeLanesCount > 0
  3942. ? getIcons($(".rev-lanes")
  3943. .find(".lane-arrow")
  3944. .map(function () {
  3945. return this;
  3946. })
  3947. .get())
  3948. : false;
  3949. const fwdImgs = fwdEle !== false ? convertToBase64(fwdEle) : false;
  3950. const revImgs = revEle !== false ? convertToBase64(revEle) : false;
  3951. if (fwdEle) {
  3952. if (fwdEle.length === 0) {
  3953. // setTimeout(displayLaneGraphics, 200);
  3954. return;
  3955. }
  3956. drawIcons(seg, !seg || !seg.toNodeId ? null : sdk.DataModel.Nodes.getById({ nodeId: seg?.toNodeId }), fwdImgs, isLeftDrive);
  3957. }
  3958. if (revEle) {
  3959. if (revEle.length === 0) {
  3960. // setTimeout(displayLaneGraphics, 200);
  3961. return;
  3962. }
  3963. drawIcons(seg, !seg || !seg.fromNodeId ? null : sdk.DataModel.Nodes.getById({ nodeId: seg?.fromNodeId }), revImgs, isLeftDrive);
  3964. }
  3965. });
  3966. // There are now 23 zoom levels where 22 is fully zoomed and currently 14 is where major road types load data and 16 loads the rest
  3967. }
  3968. laneToolsBootstrap();
  3969. }

QingJ © 2025

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