WME Geometries

Import geometry files into Waze Map Editor. Supports GeoJSON, GML, WKT, KML and GPX.

  1. "use strict";
  2. // ==UserScript==
  3. // @name WME Geometries
  4. // @version 2025.04.18.001
  5. // @description Import geometry files into Waze Map Editor. Supports GeoJSON, GML, WKT, KML and GPX.
  6. // @match https://www.waze.com/*/editor*
  7. // @match https://www.waze.com/editor*
  8. // @match https://beta.waze.com/*
  9. // @exclude https://www.waze.com/*user/*editor/*
  10. // @require https://cdn.jsdelivr.net/npm/@tmcw/togeojson@7.1.1/dist/togeojson.umd.min.js
  11. // @require https://unpkg.com/@terraformer/wkt
  12. // @require https://cdn.jsdelivr.net/npm/gml2geojson@0.0.7/dist/gml2geojson.min.js
  13. // @require https://cdn.jsdelivr.net/npm/@turf/turf@7.2.0/turf.min.js
  14. // @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
  15. // @require https://cdn.jsdelivr.net/npm/@placemarkio/geojson-rewind@1.0.2/dist/rewind.umd.min.js
  16. // @grant none
  17. // @author Timbones
  18. // @contributor wlodek76
  19. // @contributor Twister-UK
  20. // @contributor Karlsosha
  21. // @namespace https://greasyfork.org/users/3339
  22. // @run-at document-idle
  23. // ==/UserScript==
  24. /* global WazeWrap */
  25. // import type { WmeSDK } from "wme-sdk-typings";
  26. // import * as toGeoJSON from "@tmcw/togeojson";
  27. // import * as Terraformer from "@terraformer/wkt";
  28. // import * as turf from "@turf/turf";
  29. // import type { Feature, LineString, Point, Polygon, Position } from "geojson";
  30. // import WazeWrap from "https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js";
  31. // import * as rewind from "@placemarkio/geojson-rewind";
  32. window.SDK_INITIALIZED.then(geometries);
  33. function geometries() {
  34. const GF_LINK = "https://greasyfork.org/en/scripts/8129-wme-geometries";
  35. const FORUM_LINK = "https://www.waze.com/discuss/t/script-wme-geometries-v1-7-june-2021/291428/8";
  36. const GEOMETRIES_UPDATE_NOTES = `<b>NEW:</b><br>
  37. - Updated Require Script to use Latest version of toGeoJson and turf<br>
  38. <b>KNOWN ISSUES:</b><br>
  39. - Label Property is a radio Button vs ability to select multiple properties.<br>
  40. - Draw State Boundary is no longer available<br>
  41. - 3D Points are not Supported. (LAT, LON, ALT)<br><br>
  42. `;
  43. // show labels using first attribute that starts or ends with 'name' (case insensitive regexp)
  44. const defaultLabelName = /^name|name$/;
  45. // each loaded file will be rendered with one of these colours in ascending order
  46. const colorList = new Set(["deepskyblue", "magenta", "limegreen", "orange", "teal", "navy", "maroon"]);
  47. const usedColors = new Set();
  48. // Id of div element for Checkboxes:
  49. const checkboxListID = "geometries-cb-list-id";
  50. // -------------------------------------------------------------
  51. const geometryLayers = new Set();
  52. let parser;
  53. let Formats;
  54. (function (Formats) {
  55. Formats[Formats["GEOJSON"] = 0] = "GEOJSON";
  56. Formats[Formats["KML"] = 1] = "KML";
  57. Formats[Formats["WKT"] = 2] = "WKT";
  58. Formats[Formats["GML"] = 3] = "GML";
  59. Formats[Formats["GMX"] = 4] = "GMX";
  60. })(Formats || (Formats = {}));
  61. const formathelp = "GeoJSON, KML, WKT, GPX, GML";
  62. let layerindex = 0;
  63. let selectedAttrib = "";
  64. if (!window.getWmeSdk) {
  65. throw new Error("SDK is not installed");
  66. }
  67. const sdk = window.getWmeSdk({
  68. scriptId: "wme-geometries",
  69. scriptName: "WME Geometries",
  70. });
  71. console.log(`SDK v ${sdk.getSDKVersion()} on ${sdk.getWMEVersion()} initialized`);
  72. // delayed initialisation
  73. sdk.Events.once({ eventName: "wme-map-data-loaded" }).then(() => {
  74. init();
  75. });
  76. // function processMapUpdateEvent() {
  77. // if (Object.keys(geometryLayers).length === 0) return;
  78. // for (const l in geometryLayers) {
  79. // sdk.Map.removeLayer({ layerName: l });
  80. // sdk.LayerSwitcher.removeLayerCheckbox({ name: l });
  81. // }
  82. // geometryLayers = {};
  83. // loadLayers();
  84. // }
  85. // sdk.Events.on({ eventName: "wme-map-move-end", eventHandler: processMapUpdateEvent });
  86. // sdk.Events.on({ eventName: "wme-map-zoom-changed", eventHandler: processMapUpdateEvent });
  87. sdk.Events.on({
  88. eventName: "wme-layer-checkbox-toggled",
  89. eventHandler(payload) {
  90. sdk.Map.setLayerVisibility({ layerName: payload.name, visibility: payload.checked });
  91. },
  92. });
  93. class LayerStoreObj {
  94. fileContent;
  95. color;
  96. fileExt;
  97. fileName;
  98. formatType;
  99. constructor(fileContent, color, fileext, filename) {
  100. this.fileContent = fileContent;
  101. this.color = color;
  102. this.fileExt = fileext;
  103. this.fileName = filename;
  104. this.formatType = fileext.toUpperCase();
  105. }
  106. }
  107. let geolist;
  108. function loadLayers() {
  109. // Parse any locally stored layer objects
  110. const files = JSON.parse(localStorage.getItem("WMEGeoLayers") || "[]");
  111. for (const f in files)
  112. processGeometryFile(files[f]);
  113. }
  114. // add interface to Settings tab
  115. function init() {
  116. if (!WazeWrap.Ready) {
  117. setTimeout(() => {
  118. init();
  119. }, 100);
  120. return;
  121. }
  122. const geobox = document.createElement("div");
  123. geobox.style.paddingTop = "6px";
  124. console.group();
  125. const sidepanelAreas = $("#sidepanel-areas");
  126. sidepanelAreas.append(geobox);
  127. const geotitle = document.createElement("h4");
  128. geotitle.innerHTML = "Import Geometry File";
  129. geobox.appendChild(geotitle);
  130. geolist = document.createElement("ul");
  131. geobox.appendChild(geolist);
  132. const geoform = document.createElement("form");
  133. geobox.appendChild(geoform);
  134. const inputfile = document.createElement("input");
  135. inputfile.type = "file";
  136. inputfile.id = "GeometryFile";
  137. inputfile.title = ".geojson, .gml or .wkt";
  138. inputfile.addEventListener("change", addGeometryLayer, false);
  139. geoform.appendChild(inputfile);
  140. const notes = document.createElement("p");
  141. notes.style.marginTop = "12px";
  142. notes.innerHTML = `<b>Formats:</b> <span id="formathelp">${formathelp}</span><br> <b>Coords:</b> EPSG:4326, EPSG:4269, EPSG:3857`;
  143. geoform.appendChild(notes);
  144. // var inputstate = document.createElement("input");
  145. // inputstate.type = "button";
  146. // inputstate.value = "Draw State Boundary";
  147. // inputstate.title = "Draw the boundary for the topmost state";
  148. // inputstate.onclick = drawStateBoundary;
  149. // geoform.appendChild(inputstate);
  150. const inputclear = document.createElement("input");
  151. inputclear.type = "button";
  152. inputclear.value = "Clear All";
  153. inputclear.style.marginLeft = "8px";
  154. inputclear.onclick = removeGeometryLayers;
  155. geoform.appendChild(inputclear);
  156. loadLayers();
  157. WazeWrap.Interface.ShowScriptUpdate(GM_info.script.name, GM_info.script.version, GEOMETRIES_UPDATE_NOTES, GF_LINK, FORUM_LINK);
  158. console.log("WME Geometries is now available....");
  159. console.groupEnd();
  160. }
  161. function addFormat(format) {
  162. $("#formathelp")[0].innerText += `, ${format}`;
  163. }
  164. // function drawStateBoundary() {
  165. // let topState: State | null = sdk.DataModel.States.getTopState();
  166. // if (!topState) {
  167. // console.info("WME Geometries: no state or geometry available, sorry");
  168. // return;
  169. // }
  170. // var layerName = `(${topState.name})`;
  171. // var layers = W.map.getLayersBy("layerGroup", "wme_geometry");
  172. // for (var i = 0; i < layers.length; i++) {
  173. // if (layers[i].name === "Geometry: " + layerName) {
  174. // console.info("WME Geometries: current state already loaded");
  175. // return;
  176. // }
  177. // }
  178. // var geo = formats.GEOJSON.parseGeometry(topState.name);
  179. // var json = formats.GEOJSON.write(geo);
  180. // var obj = new layerStoreObj(json, "grey", "GEOJSON", layerName);
  181. // parseFile(obj);
  182. // }
  183. // import selected file as a vector layer
  184. function addGeometryLayer() {
  185. // get the selected file from user
  186. const fileListElement = document.getElementById("GeometryFile");
  187. const fileList = fileListElement.files;
  188. if (!fileList)
  189. return;
  190. const file = fileList[0];
  191. fileListElement.value = "";
  192. processGeometryFile(file);
  193. }
  194. function processGeometryFile(file) {
  195. if (colorList.size === 0) {
  196. console.error("Cannot add Any more Layers at this point");
  197. }
  198. let fileext = file?.name?.split(".").pop();
  199. const filename = file?.name?.replace(`.${fileext}`, "");
  200. if (!file || !file?.name || !fileext || !filename)
  201. return;
  202. fileext = fileext ? fileext.toUpperCase() : "";
  203. // add list item
  204. const color = colorList.values().next().value;
  205. if (!color) {
  206. console.error("Cannot add Any more Layers at this point");
  207. return;
  208. }
  209. colorList.delete(color);
  210. usedColors.add(color);
  211. const fileitem = document.createElement("li");
  212. fileitem.id = file.name.toLowerCase();
  213. fileitem.style.color = color;
  214. fileitem.innerHTML = "Loading...";
  215. geolist.appendChild(fileitem);
  216. // check if format is supported
  217. const parser = {
  218. read: null,
  219. internalProjection: null,
  220. externalProjection: null,
  221. };
  222. if (!parser) {
  223. fileitem.innerHTML = `${fileext.toUpperCase()} format not supported :(`;
  224. fileitem.style.color = "red";
  225. return;
  226. }
  227. // read the file into the new layer, and update the localStorage layer cache
  228. const reader = new FileReader();
  229. reader.onload = ((theFile) => (ev) => {
  230. if (!color) {
  231. const msg = "Color is Undefined. Cannot Load File";
  232. console.error(msg);
  233. throw new Error(msg);
  234. }
  235. const tObj = new LayerStoreObj(ev.target?.result, color, fileext, filename);
  236. parseFile(tObj);
  237. const filenames = JSON.parse(localStorage.getItem("WMEGeoLayers") || "[]");
  238. filenames[color] = theFile;
  239. localStorage.setItem("WMEGeoLayers", JSON.stringify(filenames));
  240. })(file);
  241. reader.readAsText(file);
  242. }
  243. const layerConfig = {
  244. defaultRule: {
  245. styleContext: {
  246. strokeColor: (context) => {
  247. const style = context?.feature?.properties?.style;
  248. if (!style)
  249. return style;
  250. return style?.strokeColor;
  251. },
  252. fillColor: (context) => {
  253. const style = context?.feature?.properties?.style;
  254. if (!style)
  255. return style;
  256. return style?.fillColor;
  257. },
  258. labelOutlineColor: (context) => {
  259. const style = context?.feature?.properties?.style;
  260. if (!style)
  261. return style;
  262. return style?.labelOutlineColor;
  263. },
  264. label: (context) => {
  265. const style = context?.feature?.properties?.style;
  266. if (!style)
  267. return style;
  268. return style?.label;
  269. },
  270. },
  271. styleRules: [
  272. {
  273. predicate: () => {
  274. return true;
  275. },
  276. style: {
  277. strokeColor: "${strokeColor}",
  278. strokeOpacity: 0.75,
  279. strokeWidth: 3,
  280. fillColor: "${fillColor}",
  281. fillOpacity: 0.1,
  282. pointRadius: 6,
  283. fontColor: "white",
  284. labelOutlineColor: "${labelOutlineColor}",
  285. labelOutlineWidth: 4,
  286. labelAlign: "center",
  287. label: "${label}",
  288. },
  289. },
  290. ],
  291. },
  292. };
  293. function polygonSanitization(f) {
  294. // Rewind first:
  295. f = rewind.rewindFeature(f, "RFC7946");
  296. const resPolygonCoordinates = [];
  297. for (const poly of f.geometry.coordinates) {
  298. const resSubPolyCoordinates = [];
  299. const positionMap = new Map();
  300. for (let ix = 0; ix < poly.length - 1; ++ix) {
  301. const key = poly[ix].toString();
  302. if (positionMap.has(key)) {
  303. positionMap.get(key)?.push(ix);
  304. }
  305. else {
  306. positionMap.set(key, [ix]);
  307. }
  308. resSubPolyCoordinates.push(poly[ix]);
  309. }
  310. resSubPolyCoordinates.push(resSubPolyCoordinates[0]);
  311. let removeSpliced = false;
  312. positionMap.forEach((value, key, map) => {
  313. if (value.length > 1) {
  314. if (value.length % 2 !== 0) {
  315. const message = `Currently Polygons with multiple intersects of the same vertex are not supported.
  316. They have to be splittable into distinct Polygons.
  317. Please contact script maintainers for bug fix with original Data Source`;
  318. console.error(message);
  319. throw new Error(message);
  320. }
  321. for (let vix = 0; vix < value.length; vix += 2) {
  322. if (value[vix + 1] - value[vix] > 1) {
  323. resPolygonCoordinates.push(resSubPolyCoordinates.slice(value[vix], value[vix + 1] + 1));
  324. }
  325. resSubPolyCoordinates.fill([], value[vix] + 1, value[vix + 1] + 1);
  326. removeSpliced = true;
  327. }
  328. }
  329. });
  330. if (removeSpliced) {
  331. for (let i = 0; i < resSubPolyCoordinates.length; ++i) {
  332. if (resSubPolyCoordinates[i].length === 0) {
  333. resSubPolyCoordinates.splice(i, 1);
  334. --i;
  335. }
  336. }
  337. }
  338. if (resSubPolyCoordinates.length > 3) {
  339. resPolygonCoordinates.push(resSubPolyCoordinates);
  340. }
  341. }
  342. return turf.polygon(resPolygonCoordinates, f.properties, { id: f.id });
  343. }
  344. function remove3DPoints(feature) {
  345. let resFeature = undefined;
  346. switch (feature.geometry.type) {
  347. case "Point":
  348. feature.geometry.coordinates.splice(2);
  349. resFeature = feature;
  350. break;
  351. case "LineString": {
  352. const lsPos = feature.geometry.coordinates.map((pos) => pos.splice(2) === undefined ? pos : pos);
  353. resFeature = turf.lineString(lsPos, feature.properties, { bbox: feature.bbox, id: feature.id });
  354. break;
  355. }
  356. case "Polygon": {
  357. const polyPos = feature.geometry.coordinates.map((poly) => poly.map((pos) => (pos.splice(2) ? pos : pos)) ? poly : poly);
  358. resFeature = turf.polygon(polyPos, feature.properties, { bbox: feature.bbox, id: feature.id });
  359. break;
  360. }
  361. default: {
  362. const msg = "Unsupported Type of Feature for 3D Points Removal";
  363. console.log(msg);
  364. }
  365. }
  366. return resFeature;
  367. }
  368. const sanityChecker = {
  369. polygon: polygonSanitization,
  370. };
  371. function sanityCheck(source) {
  372. const resFeatures = [];
  373. for (let f of source) {
  374. f = remove3DPoints(f);
  375. switch (f.geometry.type) {
  376. case "Polygon":
  377. resFeatures.push(sanityChecker.polygon(f));
  378. break;
  379. default:
  380. resFeatures.push(f);
  381. break;
  382. }
  383. }
  384. return resFeatures.length === 0 ? undefined : resFeatures;
  385. }
  386. // Renders a layer object
  387. function parseFile(layerObj) {
  388. // add a new layer for the geometry
  389. let labelWith = "(no labels)";
  390. const layerid = `wme_geometry_${++layerindex}`;
  391. sdk.Map.addLayer({
  392. layerName: layerid,
  393. styleRules: layerConfig.defaultRule.styleRules,
  394. styleContext: layerConfig.defaultRule.styleContext,
  395. });
  396. sdk.Map.setLayerVisibility({ layerName: layerid, visibility: true });
  397. sdk.LayerSwitcher.addLayerCheckbox({ name: layerid });
  398. let features = [];
  399. if (layerObj.fileContent) {
  400. switch (layerObj.formatType) {
  401. case "GEOJSON": {
  402. let jsonObject = JSON.parse(layerObj.fileContent);
  403. jsonObject = turf.flatten(jsonObject);
  404. features = sanityCheck(jsonObject.features);
  405. // geometryLayers[layerid] = features;
  406. break;
  407. }
  408. case "KML": {
  409. const kmlData = new DOMParser().parseFromString(layerObj.fileContent, "application/xml");
  410. let geoJson = toGeoJSON.kml(kmlData);
  411. geoJson = turf.flatten(geoJson);
  412. features = sanityCheck(geoJson.features);
  413. // geometryLayers[layerid] = features;
  414. break;
  415. }
  416. case "GPX": {
  417. const gpxData = new DOMParser().parseFromString(layerObj.fileContent, "application/xml");
  418. let gpxGeoGson = toGeoJSON.gpx(gpxData);
  419. gpxGeoGson = turf.flatten(gpxGeoGson);
  420. features = sanityCheck(gpxGeoGson.features);
  421. // geometryLayers[layerid] = features;
  422. break;
  423. }
  424. case "WKT": {
  425. const wktGeoJson = Terraformer.wktToGeoJSON(layerObj.fileContent);
  426. switch (wktGeoJson.type) {
  427. case "Polygon":
  428. features = sanityCheck([
  429. {
  430. type: "Feature",
  431. properties: { name: layerObj.fileName },
  432. geometry: wktGeoJson,
  433. },
  434. ]);
  435. break;
  436. case "GeometryCollection": {
  437. features = [];
  438. for (const g in wktGeoJson.geometries) {
  439. features.push({
  440. type: "Feature",
  441. properties: { name: layerObj.fileName },
  442. geometry: wktGeoJson.geometries[g],
  443. });
  444. }
  445. let featureCollection = turf.featureCollection(features);
  446. featureCollection = turf.flatten(featureCollection);
  447. features = sanityCheck(featureCollection.features);
  448. break;
  449. }
  450. default: {
  451. const errorMessage = "Unknown Type has been Encountered";
  452. console.error(errorMessage);
  453. throw Error(errorMessage);
  454. }
  455. }
  456. break;
  457. }
  458. case "GML": {
  459. // let gmlData = new DOMParser().parseFromString(layerObj.fileContent, "application/xml");
  460. let gmlGeoJSON = gml2geojson.parseGML(layerObj.fileContent);
  461. gmlGeoJSON = turf.flatten(gmlGeoJSON);
  462. features = sanityCheck(gmlGeoJSON.features);
  463. // geometryLayers[layerid] = features;
  464. break;
  465. }
  466. default:
  467. throw new Error(`Format Type: ${layerObj.formatType} is not implemented`);
  468. }
  469. }
  470. else {
  471. throw new Error("File Content is Empty");
  472. }
  473. geometryLayers.add(layerid);
  474. // hack in translation:
  475. // I18n.translations[sdk.Settings.getLocale()].layers.name[layerid] = "WME Geometries: " + layerObj.filename;
  476. // if (/"EPSG:3857"|:EPSG::3857"/.test(layerObj.fileContent)) {
  477. // parser.externalProjection = EPSG_3857;
  478. // }
  479. // else if (/"EPSG:4269"|:EPSG::4269"/.test(layerObj.fileContent)) {
  480. // parser.externalProjection = EPSG_4269;
  481. // }
  482. // else default to EPSG:4326
  483. // load geometry files
  484. // var features = parser.read(layerObj.fileContent);
  485. // Append Div for Future Use for picking the Layer with Name
  486. const layersList = document.createElement("ul");
  487. layersList.className = "geometries-cb-list";
  488. layersList.id = checkboxListID;
  489. let trigger = null;
  490. // check we have features to render
  491. if (!features)
  492. return;
  493. if (features.length > 0) {
  494. // check which attribute can be used for labels
  495. for (const attrib in features[0].properties) {
  496. const attribLC = attrib.toLowerCase();
  497. const attribClassName = `geometries-${layerindex}-${attribLC}`;
  498. const attribIdName = `geometries-${layerindex}-${attribLC}`;
  499. const listElement = document.createElement("li");
  500. const inputElement = document.createElement("input");
  501. inputElement.className = attribClassName;
  502. inputElement.id = attribIdName;
  503. inputElement.setAttribute("type", "radio");
  504. inputElement.setAttribute("name", `geometries-name-label-${layerindex}`);
  505. inputElement.textContent = attrib;
  506. listElement.appendChild(inputElement);
  507. const labelElement = document.createElement("label");
  508. labelElement.textContent = attrib;
  509. labelElement.className = "geometries-cb-label";
  510. labelElement.setAttribute("for", attribIdName);
  511. labelElement.style.color = "black";
  512. listElement.appendChild(labelElement);
  513. layersList.appendChild(listElement);
  514. $(inputElement).on("change", (event) => {
  515. addFeatures(features, event);
  516. });
  517. if (selectedAttrib && selectedAttrib === attrib) {
  518. trigger = $(inputElement);
  519. }
  520. else if (!selectedAttrib && defaultLabelName.test(attribLC) === true) {
  521. trigger = $(inputElement);
  522. }
  523. }
  524. }
  525. if (trigger) {
  526. trigger[0].checked = true;
  527. trigger.trigger("change");
  528. }
  529. function createClearButton(layerObj, layerid) {
  530. const clearButtonObject = document.createElement("button");
  531. clearButtonObject.textContent = "Clear Layer";
  532. clearButtonObject.name = `clear-${(`${layerObj.fileName}.${layerObj.fileExt}`).toLowerCase()}`;
  533. clearButtonObject.id = `clear-${layerid}`;
  534. clearButtonObject.className = "clear-layer-button";
  535. clearButtonObject.style.backgroundColor = layerObj.color;
  536. return clearButtonObject;
  537. }
  538. // When called as part of loading a new file, the list object will already have been created,
  539. // whereas if called as part of reloding cached data we need to create it here...
  540. let liObj = document.getElementById((`${layerObj.fileName}.${layerObj.fileExt}`).toLowerCase());
  541. if (liObj === null) {
  542. liObj = document.createElement("li");
  543. liObj.id = (`${layerObj.fileName}.${layerObj.fileExt}`).toLowerCase();
  544. liObj.style.color = layerObj.color;
  545. geolist.appendChild(liObj);
  546. }
  547. if (features.length === 0) {
  548. liObj.innerHTML = "No features loaded :(";
  549. liObj.style.color = "red";
  550. }
  551. else {
  552. liObj.innerHTML = layerObj.fileName;
  553. liObj.title =
  554. `${layerObj.fileExt.toUpperCase()}: ${features.length} features loaded\n${labelWith}`;
  555. liObj.appendChild(layersList);
  556. const clearButtonObject = createClearButton(layerObj, layerid);
  557. liObj.appendChild(clearButtonObject);
  558. console.info(`WME Geometries: Loaded ${liObj.title}`);
  559. $(".clear-layer-button").on("click", function () {
  560. let clearLayerId = this.id;
  561. clearLayerId = clearLayerId.replace("clear-", "");
  562. let clearListId = "";
  563. if (this.hasAttribute("name")) {
  564. clearListId = this.getAttribute("name");
  565. clearListId = clearListId?.replace("clear-", "");
  566. if (clearListId) {
  567. const elem = document.getElementById(clearListId);
  568. elem?.remove();
  569. }
  570. }
  571. sdk.Map.removeLayer({ layerName: clearLayerId });
  572. geometryLayers.delete(clearLayerId);
  573. sdk.LayerSwitcher.removeLayerCheckbox({ name: clearLayerId });
  574. const listId = this.textContent?.replace("Clear ", "");
  575. if (!listId)
  576. return;
  577. const elementToRemove = document.getElementById(listId);
  578. elementToRemove?.remove();
  579. const files = JSON.parse(localStorage.getItem("WMEGeoLayers") || "[]");
  580. delete files[this.style.backgroundColor];
  581. localStorage.setItem("WMEGeoLayers", JSON.stringify(files));
  582. usedColors.delete(this.style.backgroundColor);
  583. colorList.add(this.style.backgroundColor);
  584. this.remove();
  585. });
  586. }
  587. function addFeatures(features, event) {
  588. sdk.Map.removeAllFeaturesFromLayer({ layerName: layerid });
  589. selectedAttrib = event.target.textContent;
  590. for (const f of features) {
  591. if (f.properties) {
  592. labelWith = `Labels: ${selectedAttrib}`;
  593. const layerStyle = {
  594. strokeColor: layerObj.color,
  595. fillColor: layerObj.color,
  596. labelOutlineColor: layerObj.color,
  597. label: typeof f.properties[selectedAttrib] === "string"
  598. ? `${f.properties[selectedAttrib]}`
  599. : "undefined",
  600. };
  601. if (!f.properties?.style)
  602. f.properties.style = {};
  603. Object.assign(f.properties.style, layerStyle);
  604. }
  605. if (!f.id) {
  606. f.id = `${layerid}_${layerindex.toString()}`;
  607. }
  608. try {
  609. sdk.Map.addFeatureToLayer({ feature: f, layerName: layerid });
  610. }
  611. catch (err) {
  612. console.error(err);
  613. }
  614. }
  615. }
  616. }
  617. // clear all
  618. function removeGeometryLayers() {
  619. for (const l of geometryLayers) {
  620. sdk.Map.removeLayer({ layerName: l });
  621. sdk.LayerSwitcher.removeLayerCheckbox({ name: l });
  622. }
  623. geometryLayers.clear();
  624. geolist.innerHTML = "";
  625. layerindex = 0;
  626. // Clear the cached layers
  627. localStorage.removeItem("WMEGeoLayers");
  628. for (const c in usedColors) {
  629. colorList.add(c);
  630. }
  631. usedColors.clear();
  632. return false;
  633. }
  634. }
  635. // // ------------------------------------------------------------------------------------