WME Wide-Angle Lens Cities

Find streets whose city doesn't match the boundaries of a polygon layer

  1. /// <reference path="../typescript-typings/globals/openlayers/index.d.ts" />
  2. /// <reference path="../typescript-typings/waze.d.ts" />
  3. /// <reference path="../typescript-typings/globals/jquery/index.d.ts" />
  4. /// <reference path="../typescript-typings/globals/geojson/index.d.ts" />
  5. /// <reference path="WME Wide-Angle Lens.user.ts" />
  6. /// <reference path="../typescript-typings/greasyfork.d.ts" />
  7. // ==UserScript==
  8. // @name WME Wide-Angle Lens Cities
  9. // @namespace https://gf.qytechs.cn/en/users/19861-vtpearce
  10. // @description Find streets whose city doesn't match the boundaries of a polygon layer
  11. // @author vtpearce and crazycaveman
  12. // @match https://*.waze.com/*editor*
  13. // @exclude https://*.waze.com/user/editor*
  14. // @exclude https://www.waze.com/discuss/*
  15. // @version 2025.04.10.001
  16. // @grant GM_xmlhttpRequest
  17. // @copyright 2020 vtpearce
  18. // @license CC BY-SA 4.0
  19. // @require https://gf.qytechs.cn/scripts/24851-wazewrap/code/WazeWrap.js
  20. // @connect gf.qytechs.cn
  21. // ==/UserScript==
  22. // @updateURL https://gf.qytechs.cn/scripts/418296-wme-wide-angle-lens-cities-beta/code/WME%20Wide-Angle%20Lens%20Cities.meta.js
  23. // @downloadURL https://gf.qytechs.cn/scripts/418296-wme-wide-angle-lens-cities-beta/code/WME%20Wide-Angle%20Lens%20Cities.user.js
  24. /*global W, OL, $, WazeWrap, WMEWAL, OpenLayers */
  25. var WMEWAL_Cities;
  26. (function (WMEWAL_Cities) {
  27. const SCRIPT_NAME = GM_info.script.name;
  28. const SCRIPT_VERSION = GM_info.script.version.toString();
  29. const DOWNLOAD_URL = GM_info.script.downloadURL;
  30. const updateText = '<ul>'
  31. + '<li>Update for plugin status.</li>'
  32. + '</ul>';
  33. const greasyForkPage = 'https://gf.qytechs.cn/scripts/40642';
  34. const wazeForumThread = 'https://www.waze.com/forum/viewtopic.php?t=206376';
  35. const ctlPrefix = "_wmewalCities";
  36. const minimumWALVersionRequired = "2025.04.10.001";
  37. let Operation;
  38. (function (Operation) {
  39. Operation[Operation["Equal"] = 1] = "Equal";
  40. Operation[Operation["NotEqual"] = 2] = "NotEqual";
  41. })(Operation || (Operation = {}));
  42. let pluginName = "WMEWAL-Cities";
  43. WMEWAL_Cities.Title = "Cities";
  44. WMEWAL_Cities.MinimumZoomLevel = 14;
  45. WMEWAL_Cities.SupportsSegments = true;
  46. WMEWAL_Cities.SupportsVenues = false;
  47. WMEWAL_Cities.SupportsSuggestedSegments = false;
  48. const settingsKey = "WMEWALCitiesSettings";
  49. const savedSettingsKey = "WMEWALCitiesSavedSettings";
  50. let settings = null;
  51. let savedSettings = [];
  52. let streets = null;
  53. let state;
  54. let stateName;
  55. let cityRegex = null;
  56. let cityPolygons = null;
  57. let initCount = 0;
  58. let savedSegments;
  59. function onWmeReady() {
  60. initCount++;
  61. if (WazeWrap && WazeWrap.Ready && typeof (WMEWAL) !== 'undefined' && WMEWAL && WMEWAL.RegisterPlugIn) {
  62. log('debug', 'WazeWrap and WMEWAL ready.');
  63. init();
  64. }
  65. else {
  66. if (initCount < 60) {
  67. log('debug', 'WazeWrap or WMEWAL not ready. Trying again...');
  68. setTimeout(onWmeReady, 1000);
  69. }
  70. else {
  71. log('error', 'WazeWrap or WMEWAL not ready. Giving up.');
  72. }
  73. }
  74. }
  75. function bootstrap() {
  76. if (W?.userscripts?.state.isReady) {
  77. onWmeReady();
  78. }
  79. else {
  80. document.addEventListener('wme-ready', onWmeReady, { once: true });
  81. }
  82. }
  83. async function init() {
  84. // Check to see if WAL is at the minimum verson needed
  85. if (!(typeof WMEWAL.IsAtMinimumVersion === "function" && WMEWAL.IsAtMinimumVersion(minimumWALVersionRequired))) {
  86. log('log', "WAL not at required minimum version.");
  87. WazeWrap.Alerts.info(GM_info.script.name, "Cannot load plugin because WAL is not at the required minimum version.&nbsp;" +
  88. "You might need to manually update it from <a href='https://gf.qytechs.cn/scripts/40641' target='_blank'>Greasy Fork镜像</a>.", true, false);
  89. return;
  90. }
  91. if (typeof Storage !== "undefined") {
  92. if (localStorage[settingsKey]) {
  93. settings = JSON.parse(localStorage[settingsKey]);
  94. }
  95. if (localStorage[savedSettingsKey]) {
  96. try {
  97. savedSettings = JSON.parse(WMEWAL.LZString.decompressFromUTF16(localStorage[savedSettingsKey]));
  98. }
  99. catch (e) { }
  100. if (typeof savedSettings === "undefined" || savedSettings === null || savedSettings.length === 0) {
  101. log('debug', "decompressFromUTF16 failed, attempting decompress");
  102. localStorage[savedSettingsKey + "Backup"] = localStorage[savedSettingsKey];
  103. try {
  104. savedSettings = JSON.parse(WMEWAL.LZString.decompress(localStorage[savedSettingsKey]));
  105. }
  106. catch (e) { }
  107. if (typeof savedSettings === "undefined" || savedSettings === null) {
  108. log('warn', "decompress failed, savedSetting unrecoverable. Using blank");
  109. savedSettings = [];
  110. }
  111. updateSavedSettings();
  112. }
  113. }
  114. }
  115. if (settings == null) {
  116. settings = {
  117. RoadTypeMask: WMEWAL.RoadType.Freeway,
  118. State: null,
  119. StateOperation: Operation.Equal,
  120. ExcludeJunctionBoxes: true,
  121. EditableByMe: true,
  122. CityRegex: null,
  123. CityRegexIgnoreCase: true,
  124. PolygonLayerUniqueName: null,
  125. IgnoreAlt: false,
  126. IncludeAltNames: false
  127. };
  128. }
  129. else {
  130. if (updateProperties()) {
  131. updateSettings();
  132. }
  133. }
  134. log('log', "Initialized");
  135. WazeWrap.Interface.ShowScriptUpdate(SCRIPT_NAME, SCRIPT_VERSION, updateText, greasyForkPage, wazeForumThread);
  136. WMEWAL.RegisterPlugIn(WMEWAL_Cities);
  137. }
  138. function GetTab() {
  139. let html = "<table style='border-collapse: separate; border-spacing:0px 1px;'>";
  140. html += "<tbody>";
  141. html += "<tr><td class='wal-heading'><b>Saved Filters</b></td></tr>";
  142. html += "<tr><td class='wal-indent' style='padding-bottom: 8px'>" +
  143. `<select id='${ctlPrefix}SavedSettings'></select><br/>` +
  144. `<button class='btn btn-primary' id='${ctlPrefix}LoadSetting' title='Load'>Load</button>` +
  145. `<button class='btn btn-primary' style='margin-left: 4px;' id='${ctlPrefix}SaveSetting' title='Save'>Save</button>` +
  146. `<button class='btn btn-primary' style='margin-left: 4px;' id='${ctlPrefix}DeleteSetting' title='Delete'>Delete</button></td></tr>`;
  147. html += "<tr><td class='wal-heading' style='border-top: 1px solid'>Output Options</td></tr>";
  148. html += `<tr><td class='wal-indent'><input type='checkbox' class='wal-check' id='${ctlPrefix}IncludeAlt'>` +
  149. `<label for='${ctlPrefix}IncludeAlt' class='wal-label'>Include Alt Names</label></td></tr>`;
  150. html += "<tr><td class='wal-heading' style='border-top: 1px solid; padding-top: 4px;'><b>Settings</b></td></tr>";
  151. html += "<tr><td><b>Polygon Layer:</b></td></tr>";
  152. html += "<tr><td class='wal-indent'>" +
  153. `<select id='${ctlPrefix}Layer'></select>` +
  154. "</td></tr>";
  155. html += `<tr><td><input id='${ctlPrefix}IgnoreAlt' type='checkbox' class='wal-check' checked='checked'/>` +
  156. `<label for='${ctlPrefix}IgnoreAlt' class='wal-label'>Only check primary name</label></td></tr>`;
  157. html += "<tr><td class='wal-heading' style='border-top: 1px solid; padding-top: 4px;'><b>Filters</b></td></tr>";
  158. html += "<tr><td><b>City RegEx:</b></td></tr>";
  159. html += `<tr><td class='wal-indent'><input type='text' id='${ctlPrefix}City' class='wal-textbox'/><br/>` +
  160. `<input id='${ctlPrefix}CityIgnoreCase' type='checkbox' class='wal-check'/>` +
  161. `<label for='${ctlPrefix}CityIgnoreCase' class='wal-label'>Ignore case</label></td></tr>`;
  162. html += "<tr><td><b>State:</b></td></tr>";
  163. html += "<tr><td class='wal-indent'>" +
  164. `<select id='${ctlPrefix}StateOp'>` +
  165. "<option value='" + Operation.Equal.toString() + "' selected='selected'>=</option>" +
  166. "<option value='" + Operation.NotEqual.toString() + "'>&lt;&gt;</option></select>" +
  167. `<select id='${ctlPrefix}State'></select></td></tr>`;
  168. html += "<tr><td><b>Road Type:</b></td></tr>";
  169. html += "<tr><td class='wal-indent'>" +
  170. `<button id='${ctlPrefix}RoadTypeAny' class='btn btn-primary' style='margin-right: 8px' title='Any'>Any</button>` +
  171. `<button id='${ctlPrefix}RoadTypeClear' class='btn btn-primary' title='Clear'>Clear</button>` +
  172. `<div><input type='checkbox' class='wal-check' checked='checked' id='${ctlPrefix}RoadTypeFreeway' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.Freeway}'/>` +
  173. `<label for='${ctlPrefix}RoadTypeFreeway' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.Freeway))}</label></div>` +
  174. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeRamp' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.Ramp}'/>` +
  175. `<label for='${ctlPrefix}RoadTypeRamp' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.Ramp))}</label></div>` +
  176. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeMajorHighway' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.MajorHighway}'/>` +
  177. `<label for='${ctlPrefix}RoadTypeMajorHighway' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.MajorHighway))}</label></div>` +
  178. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeMinorHighway' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.MinorHighway}'/>` +
  179. `<label for='${ctlPrefix}RoadTypeMinorHighway' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.MinorHighway))}</label></div>` +
  180. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypePrimary' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.PrimaryStreet}'/>` +
  181. `<label for='${ctlPrefix}RoadTypePrimary' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.PrimaryStreet))}</label></div>` +
  182. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeStreet' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.Street}'/>` +
  183. `<label for='${ctlPrefix}RoadTypeStreet' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.Street))}</label></div>` +
  184. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeAlley' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.Alley}'/>` +
  185. `<label for='${ctlPrefix}RoadTypeAlley' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.Alley))}</label></div>` +
  186. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeUnpaved' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.Unpaved}'/>` +
  187. `<label for='${ctlPrefix}RoadTypeUnpaved' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.Unpaved))}</label></div>` +
  188. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypePLR' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.ParkingLotRoad}'/>` +
  189. `<label for='${ctlPrefix}RoadTypePLR' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.ParkingLotRoad))}</label></div>` +
  190. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypePrivate' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.PrivateRoad}'/>` +
  191. `<label for='${ctlPrefix}RoadTypePrivate' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.PrivateRoad))}</label></div>` +
  192. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeFerry' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.Ferry}'/>` +
  193. `<label for='${ctlPrefix}RoadTypeFerry' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.Ferry))}</label></div>` +
  194. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeWT' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.WalkingTrail}'/>` +
  195. `<label for='${ctlPrefix}RoadTypeWT' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.WalkingTrail))}</label></div>` +
  196. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypePB' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.PedestrianBoardwalk}'/>` +
  197. `<label for='${ctlPrefix}RoadTypePB' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.PedestrianBoardwalk))}</label></div>` +
  198. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeStairway' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.Stairway}'/>` +
  199. `<label for='${ctlPrefix}RoadTypeStairway' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.Stairway))}</label></div>` +
  200. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeRR' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.Railroad}'/>` +
  201. `<label for='${ctlPrefix}RoadTypeRR' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.Railroad))}</label></div>` +
  202. `<div><input type='checkbox' class='wal-check' id='${ctlPrefix}RoadTypeRT' data-group='${ctlPrefix}RoadType' value='${WMEWAL.RoadType.RunwayTaxiway}'/>` +
  203. `<label for='${ctlPrefix}RoadTypeRT' class='wal-label'>${WMEWAL.TranslateRoadType(WMEWAL.RoadTypeBitmaskToWazeRoadType(WMEWAL.RoadType.RunwayTaxiway))}</label></div>` +
  204. "</td></tr>";
  205. html += `<tr><td><input id='${ctlPrefix}Editable' type='checkbox' class='wal-check'/>` +
  206. `<label for='${ctlPrefix}Editable' class='wal-label'>Editable by me</label></td></tr>`;
  207. html += `<tr><td><input id='${ctlPrefix}ExcludeJunctionBoxes' type='checkbox' class='wal-check' checked='checked'/>` +
  208. `<label for='${ctlPrefix}ExcludeJunctionBoxes' class='wal-label'>Exclude Junction Boxes</label></td></tr>`;
  209. html += "</tbody></table>";
  210. return html;
  211. }
  212. WMEWAL_Cities.GetTab = GetTab;
  213. function TabLoaded() {
  214. loadScriptUpdateMonitor();
  215. updateStates();
  216. updateLayers();
  217. updateUI();
  218. updateSavedSettingsList();
  219. $(`#${ctlPrefix}State`).on("focus", updateStates);
  220. $(`#${ctlPrefix}Layer`).on("focus", updateLayers);
  221. $(`#${ctlPrefix}RoadTypeAny`).on("click", function () {
  222. $(`input[data-group=${ctlPrefix}RoadType]`).prop("checked", true);
  223. });
  224. $(`#${ctlPrefix}RoadTypeClear`).on("click", function () {
  225. $(`input[data-group=${ctlPrefix}RoadType]`).prop("checked", false);
  226. });
  227. $(`#${ctlPrefix}LoadSetting`).on("click", loadSetting);
  228. $(`#${ctlPrefix}SaveSetting`).on("click", saveSetting);
  229. $(`#${ctlPrefix}DeleteSetting`).on("click", deleteSetting);
  230. }
  231. WMEWAL_Cities.TabLoaded = TabLoaded;
  232. function updateStates() {
  233. const selectState = $(`#${ctlPrefix}State`);
  234. // Preserve current selection
  235. const currentId = parseInt(selectState.val());
  236. selectState.empty();
  237. const stateObjs = [];
  238. stateObjs.push({ id: null, name: "" });
  239. for (let s in W.model.states.objects) {
  240. if (W.model.states.objects.hasOwnProperty(s)) {
  241. const st = W.model.states.getObjectById(parseInt(s));
  242. if (st.getAttribute('id') !== 1 && st.getAttribute('name').length > 0) {
  243. stateObjs.push({ id: st.getAttribute('id'), name: st.getAttribute('name') });
  244. }
  245. }
  246. }
  247. stateObjs.sort(function (a, b) {
  248. if (a.id == null) {
  249. return -1;
  250. }
  251. else {
  252. return a.name.localeCompare(b.name);
  253. }
  254. });
  255. for (let ix = 0; ix < stateObjs.length; ix++) {
  256. const so = stateObjs[ix];
  257. const stateOption = $("<option/>").text(so.name).attr("value", so.id);
  258. if (currentId != null && so.id === currentId) {
  259. stateOption.attr("selected", "selected");
  260. }
  261. selectState.append(stateOption);
  262. }
  263. }
  264. function updateLayers() {
  265. const selectLayer = $(`#${ctlPrefix}Layer`);
  266. const currentLayer = selectLayer.val();
  267. selectLayer.empty();
  268. const layers = [];
  269. for (let ixLayer = 0; ixLayer < W.map.layers.length; ixLayer++) {
  270. const layer = W.map.layers[ixLayer];
  271. if (layer.CLASS_NAME === "OL.Layer.Vector" || layer.CLASS_NAME === "OpenLayers.Layer.Vector") {
  272. const vectorLayer = layer;
  273. if (vectorLayer.features && vectorLayer.features.length > 0) {
  274. layers.push({
  275. uniqueName: vectorLayer.uniqueName,
  276. name: vectorLayer.name
  277. });
  278. }
  279. }
  280. }
  281. layers.sort(function (a, b) {
  282. return a.name.localeCompare(b.name);
  283. });
  284. for (let ix = 0; ix < layers.length; ix++) {
  285. const l = layers[ix];
  286. const layerOption = $("<option/>").text(l.name).attr("value", l.uniqueName);
  287. if (currentLayer != null && currentLayer === l.uniqueName) {
  288. layerOption.attr("selected", "selected");
  289. }
  290. selectLayer.append(layerOption);
  291. }
  292. }
  293. function updateSavedSettingsList() {
  294. const s = $(`#${ctlPrefix}SavedSettings`);
  295. s.empty();
  296. for (let ixSaved = 0; ixSaved < savedSettings.length; ixSaved++) {
  297. const opt = $("<option/>").attr("value", ixSaved).text(savedSettings[ixSaved].Name);
  298. s.append(opt);
  299. }
  300. }
  301. function updateUI() {
  302. $(`#${ctlPrefix}City`).val(settings.CityRegex || "");
  303. $(`#${ctlPrefix}CityIgnoreCase`).prop("checked", settings.CityRegexIgnoreCase);
  304. $(`#${ctlPrefix}State`).val(settings.State);
  305. $(`#${ctlPrefix}StateOp`).val(settings.StateOperation || Operation.Equal.toString());
  306. $(`#${ctlPrefix}RoadTypeFreeway`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.Freeway);
  307. $(`#${ctlPrefix}RoadTypeRamp`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.Ramp);
  308. $(`#${ctlPrefix}RoadTypeMajorHighway`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.MajorHighway);
  309. $(`#${ctlPrefix}RoadTypeMinorHighway`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.MinorHighway);
  310. $(`#${ctlPrefix}RoadTypePrimary`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.PrimaryStreet);
  311. $(`#${ctlPrefix}RoadTypeStreet`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.Street);
  312. $(`#${ctlPrefix}RoadTypeAlley`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.Alley);
  313. $(`#${ctlPrefix}RoadTypeUnpaved`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.Unpaved);
  314. $(`#${ctlPrefix}RoadTypePLR`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.ParkingLotRoad);
  315. $(`#${ctlPrefix}RoadTypePrivate`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.PrivateRoad);
  316. $(`#${ctlPrefix}RoadTypeFerry`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.Ferry);
  317. $(`#${ctlPrefix}RoadTypeWT`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.WalkingTrail);
  318. $(`#${ctlPrefix}RoadTypePB`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.PedestrianBoardwalk);
  319. $(`#${ctlPrefix}RoadTypeStairway`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.Stairway);
  320. $(`#${ctlPrefix}RoadTypeRR`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.Railroad);
  321. $(`#${ctlPrefix}RoadTypeRT`).prop("checked", settings.RoadTypeMask & WMEWAL.RoadType.RunwayTaxiway);
  322. $(`#${ctlPrefix}Editable`).prop("checked", settings.EditableByMe);
  323. $(`#${ctlPrefix}ExcludeJunctionBoxes`).prop("checked", settings.ExcludeJunctionBoxes);
  324. $(`#${ctlPrefix}Layer`).val(settings.PolygonLayerUniqueName);
  325. $(`#${ctlPrefix}IgnoreAlt`).prop("checked", settings.IgnoreAlt);
  326. $(`#${ctlPrefix}IncludeAlt`).prop("checked", settings.IncludeAltNames);
  327. }
  328. function loadSetting() {
  329. const selectedSetting = parseInt($(`#${ctlPrefix}SavedSettings`).val());
  330. if (selectedSetting == null || isNaN(selectedSetting) || selectedSetting < 0 || selectedSetting > savedSettings.length) {
  331. return;
  332. }
  333. const savedSetting = savedSettings[selectedSetting].Setting;
  334. for (let name in savedSetting) {
  335. if (settings.hasOwnProperty(name)) {
  336. settings[name] = savedSetting[name];
  337. }
  338. }
  339. updateUI();
  340. }
  341. function validateSettings(checkArea = true) {
  342. function addMessage(error) {
  343. message += ((message.length > 0 ? "\n" : "") + error);
  344. }
  345. let message = "";
  346. const s = getSettings();
  347. let mask = 0;
  348. $(`input[data-group=${ctlPrefix}RoadType]:checked`).each(function (ix, e) {
  349. mask = mask | parseInt(e.value);
  350. });
  351. if (mask === 0) {
  352. addMessage("Please select at least one road type");
  353. }
  354. const selectedState = $(`#${ctlPrefix}State`).val();
  355. if (nullif(selectedState, "") !== null && s.State === null) {
  356. addMessage("Invalid state selection");
  357. }
  358. let r = null;
  359. if (nullif(s.CityRegex, "") !== null) {
  360. try {
  361. r = (s.CityRegexIgnoreCase ? new RegExp(s.CityRegex, "i") : new RegExp(s.CityRegex));
  362. }
  363. catch (error) {
  364. addMessage("City RegEx is invalid");
  365. r == null;
  366. }
  367. }
  368. const layerUniqueName = $(`#${ctlPrefix}Layer`).val();
  369. if (nullif(layerUniqueName, "") !== null) {
  370. let layers = W.map.getLayersBy("uniqueName", layerUniqueName);
  371. if (layers.length === 0) {
  372. addMessage("Could not find layer.");
  373. }
  374. else if (layers.length > 1) {
  375. addMessage("More than one layer found");
  376. }
  377. else if (checkArea) {
  378. cityPolygons = [];
  379. for (let ixFeature = 0; ixFeature < layers[0].features.length; ixFeature++) {
  380. const feature = layers[0].features[ixFeature];
  381. const featureName = feature.attributes.name;
  382. if (nullif(featureName, "") !== null) {
  383. if (r !== null) {
  384. log('info', "Checking to see if " + featureName + " matches the city regex.");
  385. if (!r.test(featureName)) {
  386. continue;
  387. }
  388. }
  389. log("info", "Checking to see if " + featureName + " is in area");
  390. if (feature.geometry.intersects(WMEWAL.areaToScan)) {
  391. cityPolygons.push({
  392. name: featureName,
  393. geometry: feature.geometry.clone(),
  394. compressedName: featureName.replace(/\s/g, "")
  395. });
  396. }
  397. }
  398. }
  399. if (cityPolygons.length === 0) {
  400. addMessage("No features in the layer have an appropriate label to use as City Name and are in the scanned area");
  401. }
  402. }
  403. }
  404. else {
  405. addMessage("Please select a layer containing City polygons");
  406. }
  407. if (message.length > 0) {
  408. alert(pluginName + ": " + message);
  409. return false;
  410. }
  411. return true;
  412. }
  413. function saveSetting() {
  414. if (validateSettings(false)) {
  415. const s = getSettings();
  416. const sName = prompt("Enter a name for this setting");
  417. if (sName == null) {
  418. return;
  419. }
  420. // Check to see if there is already a name that matches this
  421. for (let ixSetting = 0; ixSetting < savedSettings.length; ixSetting++) {
  422. if (savedSettings[ixSetting].Name === sName) {
  423. if (confirm("A setting with this name already exists. Overwrite?")) {
  424. savedSettings[ixSetting].Setting = s;
  425. updateSavedSettings();
  426. }
  427. else {
  428. alert("Please pick a new name.");
  429. }
  430. return;
  431. }
  432. }
  433. const savedSetting = {
  434. Name: sName,
  435. Setting: s
  436. };
  437. savedSettings.push(savedSetting);
  438. updateSavedSettings();
  439. }
  440. }
  441. function getSettings() {
  442. const s = {
  443. RoadTypeMask: 0,
  444. State: null,
  445. StateOperation: parseInt($(`#${ctlPrefix}StateOp`).val()),
  446. ExcludeJunctionBoxes: $(`#${ctlPrefix}ExcludeJunctionBoxes`).prop("checked"),
  447. EditableByMe: $(`#${ctlPrefix}Editable`).prop("checked"),
  448. CityRegex: null,
  449. CityRegexIgnoreCase: $(`#${ctlPrefix}CityIgnoreCase`).prop("checked"),
  450. PolygonLayerUniqueName: $(`#${ctlPrefix}Layer`).val(),
  451. IgnoreAlt: $(`#${ctlPrefix}IgnoreAlt`).prop("checked"),
  452. IncludeAltNames: $(`#${ctlPrefix}IncludeAlt`).prop("checked")
  453. };
  454. $(`input[data-group=${ctlPrefix}RoadType]:checked`).each(function (ix, e) {
  455. s.RoadTypeMask = s.RoadTypeMask | parseInt(e.value);
  456. });
  457. const selectedState = $(`#${ctlPrefix}State`).val();
  458. if (nullif(selectedState, "") !== null) {
  459. const state = W.model.states.getObjectById(selectedState);
  460. if (state !== null) {
  461. s.State = state.getAttribute('id');
  462. }
  463. }
  464. const pattern = $(`#${ctlPrefix}City`).val();
  465. if (nullif(pattern, "") !== null) {
  466. s.CityRegex = pattern;
  467. }
  468. return s;
  469. }
  470. function deleteSetting() {
  471. const selectedSetting = parseInt($(`#${ctlPrefix}SavedSettings`).val());
  472. if (selectedSetting == null || isNaN(selectedSetting) || selectedSetting < 0 || selectedSetting > savedSettings.length) {
  473. return;
  474. }
  475. if (confirm("Are you sure you want to delete this saved setting?")) {
  476. savedSettings.splice(selectedSetting, 1);
  477. updateSavedSettings();
  478. }
  479. }
  480. function ScanStarted() {
  481. log("debug", "ScanStarted started.");
  482. savedSegments = [];
  483. streets = [];
  484. let allOk = validateSettings(true);
  485. if (allOk) {
  486. settings = getSettings();
  487. if (settings.State !== null) {
  488. state = W.model.states.getObjectById(settings.State);
  489. stateName = state.getAttribute('name');
  490. }
  491. else {
  492. state = null;
  493. stateName = null;
  494. }
  495. if (settings.CityRegex !== null) {
  496. cityRegex = (settings.CityRegexIgnoreCase ? new RegExp(settings.CityRegex, "i") : new RegExp(settings.CityRegex));
  497. }
  498. if (settings.RoadTypeMask & ~(WMEWAL.RoadType.Freeway | WMEWAL.RoadType.MajorHighway | WMEWAL.RoadType.MinorHighway | WMEWAL.RoadType.PrimaryStreet | WMEWAL.RoadType.Ramp)) {
  499. WMEWAL_Cities.MinimumZoomLevel = 16;
  500. }
  501. else {
  502. WMEWAL_Cities.MinimumZoomLevel = 14;
  503. }
  504. updateSettings();
  505. }
  506. return allOk;
  507. }
  508. WMEWAL_Cities.ScanStarted = ScanStarted;
  509. function ScanExtent(segments, venues) {
  510. log("debug", "ScanExtent: started.");
  511. return new Promise(resolve => {
  512. setTimeout(function () {
  513. const count = scan(segments);
  514. resolve({ ID: 'City', count });
  515. }, 0);
  516. });
  517. }
  518. WMEWAL_Cities.ScanExtent = ScanExtent;
  519. function scan(segments) {
  520. log("debug", "scan: started.");
  521. const extentStreets = [];
  522. let segment;
  523. const spaceRegex = /\s/g;
  524. const outputFields = WMEWAL.outputFields ?? ['CreatedEditor', 'LastEditor', 'LockLevel', 'Lat', 'Lon'];
  525. const includeLockLevel = outputFields.indexOf('LockLevel') > -1;
  526. function addSegment(s, incorrectCity, cityShouldBe, rId) {
  527. if (savedSegments.indexOf(s.getID()) === -1) {
  528. savedSegments.push(s.getID());
  529. const sid = s.getAttribute('primaryStreetID');
  530. const address = s.getAddress(W.model);
  531. let thisStreet = null;
  532. if (sid != null) {
  533. thisStreet = extentStreets.find(function (e) {
  534. let matches = (e.id === sid && e.roundaboutId === rId && e.roadType === s.getAttribute('roadType'));
  535. if (includeLockLevel) {
  536. matches &&= (e.lockLevel === (s.getAttribute('lockRank') | 0) + 1);
  537. }
  538. if (matches) {
  539. // Test for alt names
  540. for (let ixAlt = 0; ixAlt < e.altStreets.length && matches; ixAlt++) {
  541. matches = false;
  542. for (let ixSegAlt = 0; ixSegAlt < s.getAttribute('streetIDs').length && !matches; ixSegAlt++) {
  543. if (e.altStreets[ixAlt].id === s.getAttribute('streetIDs')[ixSegAlt]) {
  544. matches = true;
  545. }
  546. }
  547. }
  548. }
  549. return matches;
  550. });
  551. }
  552. if (thisStreet == null) {
  553. thisStreet = {
  554. id: sid,
  555. state: ((address && !address.attributes.isEmpty) ? address.attributes.state.getAttribute('name') : "No State"),
  556. name: ((address && !address.attributes.isEmpty && !address.attributes.street.getAttribute('isEmpty')) ? address.attributes.street.getAttribute('name') : "No street"),
  557. geometries: new OpenLayers.Geometry.Collection(),
  558. lockLevel: (s.getAttribute('lockRank') || 0) + 1,
  559. segments: [],
  560. roundaboutId: rId,
  561. altStreets: [],
  562. roadType: s.getAttribute('roadType'),
  563. incorrectCity: incorrectCity,
  564. cityShouldBe: cityShouldBe,
  565. city: null
  566. };
  567. if (settings.IncludeAltNames) {
  568. if (s.getAttribute('streetIDs') != null) {
  569. for (let ixAlt = 0; ixAlt < s.getAttribute('streetIDs').length; ixAlt++) {
  570. if (s.getAttribute('streetIDs')[ixAlt] != null) {
  571. const altStreet = W.model.streets.getObjectById(s.getAttribute('streetIDs')[ixAlt]);
  572. if (altStreet != null) {
  573. let altCityName = null;
  574. if (altStreet.getAttribute('cityID') != null) {
  575. const altCity = W.model.cities.getObjectById(altStreet.getAttribute('cityID'));
  576. if (altCity != null) {
  577. altCityName = altCity.hasName() ? altCity.getAttribute('name') : "No city";
  578. }
  579. }
  580. thisStreet.altStreets.push({
  581. id: s.getAttribute('streetIDs')[ixAlt],
  582. name: altStreet.getAttribute('name'),
  583. city: altCityName
  584. });
  585. }
  586. }
  587. }
  588. }
  589. }
  590. extentStreets.push(thisStreet);
  591. }
  592. thisStreet.segments.push({
  593. id: s.getAttribute('id'),
  594. center: s.getAttribute('geometry').getCentroid()
  595. });
  596. thisStreet.geometries.addComponents([s.getAttribute('geometry').clone()]);
  597. }
  598. }
  599. // Possibly change this
  600. for (let ix = 0; ix < segments.length; ix++) {
  601. segment = segments[ix];
  602. if (segment != null) {
  603. if ((WMEWAL.WazeRoadTypeToRoadTypeBitmask(segment.getAttribute('roadType')) & settings.RoadTypeMask) &&
  604. (!settings.EditableByMe || segment.arePropertiesEditable()) &&
  605. (!settings.ExcludeJunctionBoxes || !segment.isInBigJunction())) {
  606. const address = segment.getAddress(W.model);
  607. if (state != null) {
  608. if (address != null && address.attributes != null && !address.attributes.isEmpty && address.attributes.state != null) {
  609. if (settings.StateOperation === Operation.Equal && address.attributes.state.getAttribute('id') !== state.getAttribute('id') ||
  610. settings.StateOperation === Operation.NotEqual && address.attributes.state.getAttribute('id') === state.getAttribute('id')) {
  611. continue;
  612. }
  613. }
  614. else if (settings.StateOperation === Operation.Equal) {
  615. continue;
  616. }
  617. }
  618. const altCityNames = [];
  619. if (!settings.IgnoreAlt) {
  620. if (segment.getAttribute('streetIDs') != null) {
  621. for (let streetIx = 0; streetIx < segment.getAttribute('streetIDs').length; streetIx++) {
  622. if (segment.getAttribute('streetIDs')[streetIx] != null) {
  623. const street = W.model.streets.getObjectById(segment.getAttribute('streetIDs')[streetIx]);
  624. if (street != null) {
  625. if (street.getAttribute('cityID') != null) {
  626. const city = W.model.cities.getObjectById(street.getAttribute('cityID'));
  627. if (city != null) {
  628. altCityNames.push({
  629. hasName: city.hasName(),
  630. name: city.hasName() ? city.getAttribute('name') : null,
  631. compressedName: city.hasName() ? city.getAttribute('name').replace(spaceRegex, "") : null
  632. });
  633. }
  634. }
  635. }
  636. }
  637. }
  638. }
  639. }
  640. if (cityRegex != null) {
  641. let nameMatched = false;
  642. if (address.attributes != null && !address.attributes.isEmpty) {
  643. if (address.attributes.city != null && address.attributes.city.hasName()) {
  644. nameMatched = cityRegex.test(address.attributes.city.getAttribute('name'));
  645. }
  646. if (!nameMatched) {
  647. for (let altIx = 0; altIx < altCityNames.length && !nameMatched; altIx++) {
  648. if (altCityNames[altIx].hasName) {
  649. nameMatched = cityRegex.test(altCityNames[altIx].name);
  650. }
  651. }
  652. }
  653. }
  654. if (!nameMatched) {
  655. continue;
  656. }
  657. }
  658. if (!WMEWAL.IsSegmentInArea(segment)) {
  659. continue;
  660. }
  661. let cityMatches = true;
  662. const cityNames = [];
  663. let cityShouldBe = "";
  664. let incorrectCity = "";
  665. let anyBlankCity = false;
  666. if (address.attributes != null && address.attributes.city && address.attributes.city.hasName()) {
  667. cityNames.push({
  668. hasName: true,
  669. name: address.attributes.city.getAttribute('name'),
  670. compressedName: address.attributes.city.getAttribute('name').replace(spaceRegex, "")
  671. });
  672. }
  673. else {
  674. anyBlankCity = true;
  675. }
  676. for (let ixAlt = 0; ixAlt < altCityNames.length; ixAlt++) {
  677. if (altCityNames[ixAlt].hasName) {
  678. if (cityNames.find(function (c) {
  679. return c.compressedName === altCityNames[ixAlt].compressedName;
  680. })) {
  681. cityNames.push({
  682. hasName: true,
  683. name: altCityNames[ixAlt].name,
  684. compressedName: altCityNames[ixAlt].name.replace(spaceRegex, "")
  685. });
  686. }
  687. }
  688. else {
  689. anyBlankCity = true;
  690. }
  691. }
  692. if (cityNames.length > 0) {
  693. // Check to see if it's in any of the city polygons that are referenced
  694. for (let ixCityName = 0; ixCityName < cityNames.length && cityMatches; ixCityName++) {
  695. let foundAny = false;
  696. for (let ixCity = 0; ixCity < cityPolygons.length && cityMatches; ixCity++) {
  697. if (cityNames[ixCityName].compressedName === cityPolygons[ixCity].compressedName) {
  698. foundAny = true;
  699. if (!cityPolygons[ixCity].geometry.intersects(segment.getAttribute('geometry'))) {
  700. incorrectCity = cityNames[ixCityName].name;
  701. cityShouldBe = "No City";
  702. cityMatches = false;
  703. }
  704. }
  705. }
  706. if (!foundAny) {
  707. incorrectCity = cityNames[ixCityName].name;
  708. cityShouldBe = "No matching city polygon";
  709. cityMatches = false;
  710. }
  711. }
  712. }
  713. if (anyBlankCity && cityMatches) {
  714. // No city names listed, so check to see if it's inside any of the city polygons
  715. for (let ixCity = 0; ixCity < cityPolygons.length && cityMatches; ixCity++) {
  716. if (cityPolygons[ixCity].geometry.intersects(segment.getAttribute('geometry'))) {
  717. incorrectCity = "No City";
  718. cityShouldBe = cityPolygons[ixCity].name;
  719. cityMatches = false;
  720. }
  721. }
  722. }
  723. if (cityMatches) {
  724. continue;
  725. }
  726. if (!segment.isInRoundabout()) {
  727. addSegment(segment, incorrectCity, cityShouldBe, null);
  728. }
  729. else {
  730. const r = segment.getRoundabout().attributes;
  731. for (let rIx = 0; rIx < r.segIDs.length; rIx++) {
  732. addSegment(W.model.segments.getObjectById(r.segIDs[rIx]), incorrectCity, cityShouldBe, r.id);
  733. }
  734. }
  735. }
  736. }
  737. }
  738. for (let ix = 0; ix < extentStreets.length; ix++) {
  739. extentStreets[ix].center = extentStreets[ix].geometries.getCentroid(true);
  740. delete extentStreets[ix].geometries;
  741. streets.push(extentStreets[ix]);
  742. }
  743. log("debug", "scan: done");
  744. return streets.length;
  745. }
  746. function ScanComplete() {
  747. log("debug", "ScanComplete: started.");
  748. cityPolygons = null;
  749. if (streets.length === 0) {
  750. alert(pluginName + ": No streets found.");
  751. }
  752. else {
  753. streets.sort(function (a, b) {
  754. let cmp = getStreetName(a).localeCompare(getStreetName(b));
  755. if (cmp !== 0) {
  756. return cmp;
  757. }
  758. cmp = a.state.localeCompare(b.state);
  759. if (cmp !== 0) {
  760. return cmp;
  761. }
  762. if (a.lockLevel < b.lockLevel) {
  763. return -1;
  764. }
  765. else if (a.lockLevel > b.lockLevel) {
  766. return 1;
  767. }
  768. return 0;
  769. });
  770. const isCSV = (WMEWAL.outputTo & WMEWAL.OutputTo.CSV);
  771. const isTab = (WMEWAL.outputTo & WMEWAL.OutputTo.Tab);
  772. const addBOM = WMEWAL.addBOM ?? false;
  773. const outputFields = WMEWAL.outputFields ?? ['CreatedEditor', 'LastEditor', 'LockLevel', 'Lat', 'Lon'];
  774. const includeLockLevel = outputFields.indexOf('LockLevel') > -1;
  775. const includeLat = outputFields.indexOf('Lat') > -1;
  776. const includeLon = outputFields.indexOf('Lon') > -1;
  777. let lineArray;
  778. let columnArray;
  779. let w;
  780. let fileName;
  781. if (isCSV) {
  782. lineArray = [];
  783. columnArray = ["Name"];
  784. if (settings.IncludeAltNames) {
  785. columnArray.push("Alt Names");
  786. }
  787. columnArray.push("State", "Road Type");
  788. if (includeLockLevel) {
  789. columnArray.push("Lock Level");
  790. }
  791. columnArray.push("Incorrect City", "City Should Be");
  792. if (includeLat) {
  793. columnArray.push("Latitude");
  794. }
  795. if (includeLon) {
  796. columnArray.push("Longitude");
  797. }
  798. columnArray.push("Permalink");
  799. lineArray.push(columnArray);
  800. fileName = "Cities_" + WMEWAL.areaName + ".csv";
  801. }
  802. if (isTab) {
  803. w = window.open();
  804. w.document.write("<html><head><title>Cities</title></head><body>");
  805. w.document.write("<h3>Area: " + WMEWAL.areaName + "</h3>");
  806. w.document.write("<b>Filters</b>");
  807. if (cityRegex != null) {
  808. w.document.write("<br/>City Name matches " + cityRegex.source);
  809. if (settings.CityRegexIgnoreCase) {
  810. w.document.write(" (ignoring case)");
  811. }
  812. }
  813. if (stateName != null) {
  814. w.document.write("<br/>State " + (settings.StateOperation === Operation.NotEqual ? "does not equal " : "equals ") + stateName);
  815. }
  816. if (settings.ExcludeJunctionBoxes) {
  817. w.document.write("<br/>Junction boxes excluded");
  818. }
  819. if (settings.EditableByMe) {
  820. w.document.write("<br/>Editable by me");
  821. }
  822. w.document.write("</p><table style='border-collapse: separate; border-spacing: 8px 0px'><tr><th>Name</th>");
  823. if (settings.IncludeAltNames) {
  824. w.document.write("<th>Alt Names</th>");
  825. }
  826. w.document.write("<th>State</th>");
  827. w.document.write("<th>Road Type</th>");
  828. if (includeLockLevel) {
  829. w.document.write('<th>Lock Level</th>');
  830. }
  831. w.document.write("<th>Incorrect City</th><th>City Should Be</th>");
  832. if (includeLat) {
  833. w.document.write("<th>Latitude</th>");
  834. }
  835. if (includeLon) {
  836. w.document.write("<th>Longitude</th>");
  837. }
  838. w.document.write("<th>Permalink</th></tr>");
  839. }
  840. for (let ixStreet = 0; ixStreet < streets.length; ixStreet++) {
  841. const street = streets[ixStreet];
  842. const roadTypeText = WMEWAL.TranslateRoadType(street.roadType);
  843. if (street.name == null && street.roundaboutId == null) {
  844. for (let ixSeg = 0; ixSeg < street.segments.length; ixSeg++) {
  845. const segment = street.segments[ixSeg];
  846. const latlon = OpenLayers.Layer.SphericalMercator.inverseMercator(segment.center.x, segment.center.y);
  847. const plSeg = getSegmentPL(segment);
  848. if (isCSV) {
  849. columnArray = [getStreetName(street)];
  850. if (settings.IncludeAltNames) {
  851. columnArray.push("");
  852. }
  853. columnArray.push(`"${street.state}"`, `"${roadTypeText}"`);
  854. if (includeLockLevel) {
  855. columnArray.push(street.lockLevel.toString());
  856. }
  857. columnArray.push(`"${street.incorrectCity}"`, `"${street.cityShouldBe}"`);
  858. if (includeLat) {
  859. columnArray.push(latlon.lat.toString());
  860. }
  861. if (includeLon) {
  862. columnArray.push(latlon.lon.toString());
  863. }
  864. columnArray.push(`"${plSeg}"`);
  865. lineArray.push(columnArray);
  866. }
  867. if (isTab) {
  868. w.document.write("<tr><td>" + getStreetName(street) + "</td>");
  869. if (settings.IncludeAltNames) {
  870. w.document.write("<td>&nbsp;</td>");
  871. }
  872. w.document.write(`<td>${street.state}</td>`);
  873. w.document.write(`<td>${roadTypeText}</td>`);
  874. if (includeLockLevel) {
  875. w.document.write(`<td>${street.lockLevel}</td>`);
  876. }
  877. w.document.write(`<td>${street.incorrectCity}</td><td>${street.cityShouldBe}</td>`);
  878. if (includeLat) {
  879. w.document.write(`<td>${latlon.lat.toString()}</td>`);
  880. }
  881. if (includeLon) {
  882. w.document.write(`<td>${latlon.lon.toString()}</td>`);
  883. }
  884. w.document.write(`<td><a href='${plSeg}' target='_blank'>Permalink</a></td></tr>`);
  885. }
  886. }
  887. }
  888. else {
  889. const latlon = OpenLayers.Layer.SphericalMercator.inverseMercator(street.center.x, street.center.y);
  890. const plStreet = getStreetPL(street);
  891. let altNames = "";
  892. for (let ixAlt = 0; ixAlt < street.altStreets.length; ixAlt++) {
  893. if (ixAlt > 0) {
  894. altNames += "; ";
  895. }
  896. altNames += street.altStreets[ixAlt].name;
  897. if (street.altStreets[ixAlt].city != null) {
  898. altNames += ", " + street.altStreets[ixAlt].city;
  899. }
  900. }
  901. if (isCSV) {
  902. columnArray = [`"${getStreetName(street)}"`];
  903. if (settings.IncludeAltNames) {
  904. columnArray.push(`"${altNames}"`);
  905. }
  906. columnArray.push(`"${street.state}"`);
  907. columnArray.push(`"${roadTypeText}"`);
  908. if (includeLockLevel) {
  909. columnArray.push(street.lockLevel.toString());
  910. }
  911. columnArray.push(`"${street.incorrectCity}"`);
  912. columnArray.push(`"${street.cityShouldBe}"`);
  913. if (includeLat) {
  914. columnArray.push(latlon.lat.toString());
  915. }
  916. if (includeLon) {
  917. columnArray.push(latlon.lon.toString());
  918. }
  919. columnArray.push(`"${plStreet}"`);
  920. lineArray.push(columnArray);
  921. }
  922. if (isTab) {
  923. w.document.write(`<tr><td>${getStreetName(street)}</td>`);
  924. if (settings.IncludeAltNames) {
  925. w.document.write(`<td>${altNames}</td>`);
  926. }
  927. w.document.write(`<td>${street.state}</td>`);
  928. w.document.write(`<td>${roadTypeText}</td>`);
  929. if (includeLockLevel) {
  930. w.document.write(`<td>${street.lockLevel}</td>`);
  931. }
  932. w.document.write(`<td>${street.incorrectCity}</td><td>${street.cityShouldBe}</td>`);
  933. if (includeLat) {
  934. w.document.write(`<td>${latlon.lat.toString()}</td>`);
  935. }
  936. if (includeLon) {
  937. w.document.write(`<td>${latlon.lon.toString()}</td>`);
  938. }
  939. w.document.write(`<td><a href='${plStreet}' target='_blank'>Permalink</a></td></tr>`);
  940. }
  941. }
  942. }
  943. if (isCSV) {
  944. const csvContent = lineArray.join("\n") + "\n" + WMEWAL.getErrCsvText();
  945. const blobContent = [];
  946. if (addBOM) {
  947. blobContent.push('\uFEFF');
  948. }
  949. blobContent.push(csvContent);
  950. const blob = new Blob(blobContent, { type: "data:text/csv;charset=utf-8" });
  951. const link = document.createElement("a");
  952. const url = URL.createObjectURL(blob);
  953. link.setAttribute("href", url);
  954. link.setAttribute("download", fileName);
  955. const node = document.body.appendChild(link);
  956. link.click();
  957. document.body.removeChild(node);
  958. }
  959. if (isTab) {
  960. WMEWAL.writeErrText(w);
  961. w.document.write("</table></body></html>");
  962. w.document.close();
  963. w = null;
  964. }
  965. }
  966. streets = null;
  967. savedSegments = null;
  968. }
  969. WMEWAL_Cities.ScanComplete = ScanComplete;
  970. function ScanCancelled() {
  971. log("debug", "ScanCancelled: started.");
  972. ScanComplete();
  973. }
  974. WMEWAL_Cities.ScanCancelled = ScanCancelled;
  975. function getStreetPL(street) {
  976. const latlon = OpenLayers.Layer.SphericalMercator.inverseMercator(street.center.x, street.center.y);
  977. let url = WMEWAL.GenerateBasePL(latlon.lat, latlon.lon, WMEWAL.zoomLevel) + "&segments=";
  978. for (let ix = 0; ix < street.segments.length; ix++) {
  979. if (ix > 0) {
  980. url += ",";
  981. }
  982. url += street.segments[ix].id;
  983. }
  984. return url;
  985. }
  986. function getSegmentPL(segment) {
  987. const latlon = OpenLayers.Layer.SphericalMercator.inverseMercator(segment.center.x, segment.center.y);
  988. return WMEWAL.GenerateBasePL(latlon.lat, latlon.lon, 5) + "&segments=" + segment.id;
  989. }
  990. function getStreetName(street) {
  991. return street.name || "No street";
  992. }
  993. function updateProperties() {
  994. let upd = false;
  995. if (settings !== null) {
  996. if (!settings.hasOwnProperty("IgnoreAlt")) {
  997. settings.IgnoreAlt = false;
  998. upd = true;
  999. }
  1000. if (!settings.hasOwnProperty("IncludeAltNames")) {
  1001. settings.IncludeAltNames = false;
  1002. upd = true;
  1003. }
  1004. if (settings.hasOwnProperty("OutputTo")) {
  1005. delete settings["OutputTo"];
  1006. upd = true;
  1007. }
  1008. if (settings.hasOwnProperty("Version")) {
  1009. delete settings["Version"];
  1010. upd = true;
  1011. }
  1012. }
  1013. return upd;
  1014. }
  1015. function updateSavedSettings() {
  1016. if (typeof Storage !== "undefined") {
  1017. localStorage[savedSettingsKey] = WMEWAL.LZString.compressToUTF16(JSON.stringify(savedSettings));
  1018. }
  1019. updateSavedSettingsList();
  1020. }
  1021. function updateSettings() {
  1022. if (typeof Storage !== "undefined") {
  1023. localStorage[settingsKey] = JSON.stringify(settings);
  1024. }
  1025. }
  1026. function log(level, ...args) {
  1027. const t = new Date();
  1028. switch (level.toLocaleLowerCase()) {
  1029. case "debug":
  1030. case "verbose":
  1031. console.debug(`${SCRIPT_NAME}:`, ...args);
  1032. break;
  1033. case "info":
  1034. case "information":
  1035. console.info(`${SCRIPT_NAME}:`, ...args);
  1036. break;
  1037. case "warning":
  1038. case "warn":
  1039. console.warn(`${SCRIPT_NAME}:`, ...args);
  1040. break;
  1041. case "error":
  1042. console.error(`${SCRIPT_NAME}:`, ...args);
  1043. break;
  1044. case "log":
  1045. console.log(`${SCRIPT_NAME}:`, ...args);
  1046. break;
  1047. default:
  1048. break;
  1049. }
  1050. }
  1051. function nullif(s, nullVal) {
  1052. if (s !== null && s === nullVal) {
  1053. return null;
  1054. }
  1055. return s;
  1056. }
  1057. function loadScriptUpdateMonitor() {
  1058. let updateMonitor;
  1059. try {
  1060. updateMonitor = new WazeWrap.Alerts.ScriptUpdateMonitor(SCRIPT_NAME, SCRIPT_VERSION, DOWNLOAD_URL, GM_xmlhttpRequest);
  1061. updateMonitor.start();
  1062. }
  1063. catch (ex) {
  1064. log('error', ex);
  1065. }
  1066. }
  1067. bootstrap();
  1068. })(WMEWAL_Cities || (WMEWAL_Cities = {}));

QingJ © 2025

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