您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Overlay GPX, KML, WKT, GML or GeoJSON files onto Waze Map Editor
// ==UserScript== // @name WME GPX/KML/WKT/GML/GeoJSON Overlay // @namespace https://www.waze.com/ // @version 1.4 // @description Overlay GPX, KML, WKT, GML or GeoJSON files onto Waze Map Editor // @author Dosojintaizo // @license MIT/BSD/X11 // @include https://www.waze.com/editor* // @include https://www.waze.com/*/editor* // @include https://beta.waze.com/editor* // @include https://beta.waze.com/*/editor* // @require https://update.greasyfork.org/scripts/520574/1502033/togeojson.js // @grant none // ==/UserScript== (function () { "use strict"; if (W?.userscripts?.state.isReady) { initializeScript(); } else { document.addEventListener("wme-ready", initializeScript, { once: true }); } const overlays = []; async function initializeScript() { console.log("WME GPX/KML/WKT/GML/GeoJSON Overlay script initialized."); const EPSG_4326 = new OpenLayers.Projection("EPSG:4326"); // lat,lon const EPSG_4269 = new OpenLayers.Projection("EPSG:4269"); // NAD 83 const EPSG_3857 = new OpenLayers.Projection("EPSG:3857"); // WGS 84 const { tabLabel, tabPane } = W.userscripts.registerSidebarTab( "wme-strava-kml-overlay" ); tabLabel.innerText = "Geo Overlay"; tabLabel.title = "Import and manage GPX/KML/WKT/GML/GeoJSON overlays on the map"; tabPane.innerHTML = `<div> <h5>Geo Overlay</h5> <p>Import GPX, KML, WKT, GML or GeoJSON files to overlay them on the map.</p> <label for="fileInput" style=" display: inline-block; padding: 10px 20px; background-color: #0078d7; color: white; border: 1px solid #005bb5; border-radius: 8px; cursor: pointer; font-size: 14px; text-align: center; transition: background-color 0.3s ease;"> Select File </label> <input type="file" id="fileInput" accept=".gpx,.kml,.wkt,.gml,.geojson" style="display: none;" /> <div id="overlayList" style="margin-top: 20px;"></div> <div id="status" style="margin-top: 10px; color: green;"></div> </div>`; await W.userscripts.waitForElementConnected(tabPane); const fileInput = tabPane.querySelector("#fileInput"); const overlayList = tabPane.querySelector("#overlayList"); const status = tabPane.querySelector("#status"); fileInput.addEventListener("change", async (event) => { const file = event.target.files[0]; if (!file) { status.textContent = "No file selected."; return; } try { const text = await file.text(); let geoJSON; if (file.name.endsWith(".gpx")) { geoJSON = parseGPXToGeoJSON(text); } else if (file.name.endsWith(".kml")) { geoJSON = parseKMLToGeoJSON(text); } else if (file.name.endsWith(".geojson")) { geoJSON = JSON.parse(text); } else if (file.name.endsWith(".wkt")) { geoJSON = parseWKTToGeoJSON(text); } else if (file.name.endsWith(".gml")) { geoJSON = parseGMLToGeoJSON(text); } else { throw new Error( "Unsupported file format. Please upload a GPX, KML, GeoJSON, WKT, or GML file." ); } addOverlay(file.name, geoJSON); } catch (error) { console.error("Error processing file:", error); status.textContent = `Error: ${error.message}`; } }); } function parseGPXToGeoJSON(gpxText) { const parser = new DOMParser(); const gpxDoc = parser.parseFromString(gpxText, "application/xml"); return toGeoJSON.gpx(gpxDoc); } function parseKMLToGeoJSON(kmlText) { const parser = new DOMParser(); const kmlDoc = parser.parseFromString(kmlText, "application/xml"); return toGeoJSON.kml(kmlDoc); } function parseWKTToGeoJSON(wktText) { if (typeof Wkt !== "undefined") { const wkt = new Wkt.Wkt(); wkt.read(wktText); return wkt.toJson(); } throw new Error("WKT parsing requires Wicket.js library."); } function parseGMLToGeoJSON(gmlText) { const parser = new DOMParser(); const gmlDoc = parser.parseFromString(gmlText, "application/xml"); const format = new OpenLayers.Format.GML(); const features = format.read(gmlDoc); const geoJSON = new OpenLayers.Format.GeoJSON(); return geoJSON.write(features); } function addOverlay(fileName, geoJSON) { // Check if an overlay with the same name already exists if (overlays.some((overlay) => overlay.name === fileName)) { const status = document.getElementById("status"); status.textContent = `Error: The file "${fileName}" has already been added.`; status.style.color = "red"; return; } const layerName = fileName; const vectorLayer = new OpenLayers.Layer.Vector(layerName, { styleMap: new OpenLayers.StyleMap({ default: new OpenLayers.Style({ strokeColor: "#FFFF00", strokeWidth: 3, fillOpacity: 0.4, }), }), }); geoJSON.features.forEach((feature) => { if (feature.geometry && feature.geometry.coordinates) { feature.geometry.coordinates = removeZCoordinates( feature.geometry.coordinates ); } const olGeometry = W.userscripts.toOLGeometry(feature.geometry); const vectorFeature = new OpenLayers.Feature.Vector(olGeometry); vectorLayer.addFeatures([vectorFeature]); }); W.map.addLayer(vectorLayer); const overlay = { name: fileName, layer: vectorLayer, color: "#FFFF00", width: 3, }; overlays.push(overlay); renderOverlayList(); } function removeZCoordinates(coords) { if (Array.isArray(coords[0])) { return coords.map(removeZCoordinates); } else if (coords.length >= 2) { return coords.slice(0, 2); } return coords; } function renderOverlayList() { const overlayList = document.getElementById("overlayList"); overlayList.innerHTML = ""; overlays.forEach((overlay, index) => { const item = document.createElement("div"); item.style.marginBottom = "10px"; item.style.position = "relative"; item.style.border = "1px solid #ccc"; item.style.borderRadius = "8px"; item.style.padding = "10px"; item.style.boxShadow = "0 2px 4px rgba(0, 0, 0, 0.1)"; item.style.display = "flex"; item.style.flexDirection = "column"; // Top row container const topRow = document.createElement("div"); topRow.style.display = "flex"; topRow.style.alignItems = "center"; topRow.style.justifyContent = "space-between"; const leftContainer = document.createElement("div"); leftContainer.style.display = "flex"; leftContainer.style.alignItems = "center"; const title = document.createElement("span"); title.textContent = overlay.name; title.style.fontWeight = "bold"; title.style.marginLeft = "10px"; title.style.marginRight = "10px"; const checkboxContainer = document.createElement("label"); checkboxContainer.style.position = "relative"; checkboxContainer.style.display = "inline-block"; checkboxContainer.style.width = "24px"; checkboxContainer.style.height = "24px"; checkboxContainer.style.border = "2px solid #ccc"; checkboxContainer.style.borderRadius = "4px"; checkboxContainer.style.cursor = "pointer"; checkboxContainer.style.transition = "all 0.3s ease"; const toggle = document.createElement("input"); toggle.type = "checkbox"; toggle.style.display = "none"; toggle.checked = true; const customCheckbox = document.createElement("span"); customCheckbox.style.position = "absolute"; customCheckbox.style.top = "50%"; customCheckbox.style.left = "50%"; customCheckbox.style.transform = "translate(-50%, -50%)"; customCheckbox.style.width = "16px"; customCheckbox.style.height = "16px"; customCheckbox.style.backgroundColor = "#FFFF00"; customCheckbox.style.borderRadius = "2px"; customCheckbox.style.transition = "background-color 0.3s ease"; toggle.addEventListener("change", () => { overlay.layer.setVisibility(toggle.checked); customCheckbox.style.backgroundColor = toggle.checked ? overlay.color // Use overlay color when checked : "transparent"; // Transparent when unchecked }); checkboxContainer.appendChild(toggle); checkboxContainer.appendChild(customCheckbox); const iconsContainer = document.createElement("div"); iconsContainer.style.display = "flex"; iconsContainer.style.alignItems = "center"; const gearIcon = document.createElement("span"); gearIcon.textContent = "🎨"; gearIcon.style.cursor = "pointer"; gearIcon.style.fontSize = "20px"; gearIcon.style.marginLeft = "10px"; const trashIcon = document.createElement("span"); trashIcon.textContent = "❌"; trashIcon.style.cursor = "pointer"; trashIcon.style.marginLeft = "10px"; trashIcon.style.fontSize = "10px"; trashIcon.addEventListener("click", () => { W.map.removeLayer(overlay.layer); overlays.splice(index, 1); renderOverlayList(); }); leftContainer.appendChild(checkboxContainer); leftContainer.appendChild(title); iconsContainer.appendChild(gearIcon); iconsContainer.appendChild(trashIcon); topRow.appendChild(leftContainer); topRow.appendChild(iconsContainer); // Settings container const settings = document.createElement("div"); settings.style.display = "none"; settings.style.marginTop = "10px"; settings.style.padding = "10px"; settings.style.border = "1px solid #ccc"; settings.style.borderRadius = "8px"; settings.style.backgroundColor = "#f9f9f9"; const colorRow = document.createElement("div"); colorRow.style.display = "flex"; colorRow.style.alignItems = "center"; colorRow.style.marginBottom = "10px"; const colorLabel = document.createElement("label"); colorLabel.textContent = "Line Color:"; colorLabel.style.marginRight = "10px"; const colorInput = document.createElement("input"); colorInput.type = "color"; colorInput.value = overlay.color; colorInput.addEventListener("input", (event) => { overlay.color = event.target.value; overlay.layer.styleMap.styles.default.defaultStyle.strokeColor = overlay.color; overlay.layer.redraw(); customCheckbox.style.backgroundColor = overlay.color; }); colorRow.appendChild(colorLabel); colorRow.appendChild(colorInput); const widthRow = document.createElement("div"); widthRow.style.display = "flex"; widthRow.style.alignItems = "center"; const widthLabel = document.createElement("label"); widthLabel.textContent = "Line Width:"; widthLabel.style.marginRight = "10px"; const widthInput = document.createElement("input"); widthInput.type = "number"; widthInput.value = overlay.width; widthInput.min = 1; widthInput.max = 10; widthInput.style.width = "50px"; widthInput.addEventListener("input", (event) => { overlay.width = parseInt(event.target.value, 10) || 1; overlay.layer.styleMap.styles.default.defaultStyle.strokeWidth = overlay.width; overlay.layer.redraw(); }); widthRow.appendChild(widthLabel); widthRow.appendChild(widthInput); settings.appendChild(colorRow); settings.appendChild(widthRow); // Toggle settings visibility gearIcon.addEventListener("click", () => { settings.style.display = settings.style.display === "none" ? "block" : "none"; }); // Assemble item item.appendChild(topRow); item.appendChild(settings); overlayList.appendChild(item); }); } })();