Kreise im WME erstellen
目前為
// ==UserScript==
// @name WME Circles
// @namespace http://tampermonkey.net/
// @version 2025.01.17
// @description Kreise im WME erstellen
// @namespace https://greasyfork.org/de/users/863740-horst-wittlich
// @author Hiwi234
// @match https://*.waze.com/editor*
// @match https://*.waze.com/*/editor*
// @match https://beta.waze.com/editor*
// @match https://beta.waze.com/*/editor*
// @icon https://www.google.com/s2/favicons?sz=64&domain=waze.com
// @grant none
// @license MIT
// @run-at document-idle
// ==/UserScript==
// Versions Format
// yyyy.mm.dd
(function() {
'use strict';
let wmeSDK;
let uOpenLayers;
let uWaze;
let radiusLayer, infoLayer;
let polygonControl, freehandControl;
function addSidePanel() {
wmeSDK.Sidebar.registerScriptTab('wme-circles').then(({ tabLabel, tabPane }) => {
// Set tab label
tabLabel.innerText = 'Circles';
tabLabel.title = 'WME Circles';
// Create the content of the side-panel tab
tabPane.innerHTML = `
<div style="padding: 15px;">
<h3>WME Circles v2025</h3>
<p id="circles-current-radius"></p>
<div style="margin: 15px 0;">
<label style="display: flex; align-items: center; cursor: pointer; margin-bottom: 8px;">
<input type="checkbox" id="wme-circles-edit-mode" style="margin-right: 8px;">
Enable Circle Drawing
</label>
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="wme-freehand-edit-mode" style="margin-right: 8px;">
Enable Free Hand Drawing
</label>
</div>
<div style="display: flex; flex-direction: column; gap: 10px; margin-top: 20px;">
<button id="wme-circles-select" style="width: 100%; padding: 8px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">
Select Streets within
</button>
<button id="wme-circles-clear" style="width: 100%; padding: 8px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer;">
Clear Circles
</button>
</div>
<div style="margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
<strong>Display Options:</strong>
<div style="margin-top: 8px;">
<label style="display: flex; align-items: center; cursor: pointer; margin-bottom: 5px;">
<input type="checkbox" id="show-radius" checked style="margin-right: 8px;">
Show Radius
</label>
<label style="display: flex; align-items: center; cursor: pointer; margin-bottom: 5px;">
<input type="checkbox" id="show-diameter" style="margin-right: 8px;">
Show Diameter
</label>
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="show-area" style="margin-right: 8px;">
Show Area
</label>
</div>
</div>
<div style="margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
<strong>Filter Options:</strong>
<div style="margin-top: 8px;">
<label style="display: flex; align-items: center; cursor: pointer; margin-bottom: 5px;">
<input type="checkbox" id="filter-drivable-only" checked style="margin-right: 8px;">
Only drivable segments
</label>
</div>
</div>
</div>
`;
let checkbox = tabPane.querySelector('#wme-circles-edit-mode');
let freehandCheckbox = tabPane.querySelector('#wme-freehand-edit-mode');
let clearButton = tabPane.querySelector('#wme-circles-clear');
let selectButton = tabPane.querySelector('#wme-circles-select');
let showRadiusCheckbox = tabPane.querySelector('#show-radius');
let showDiameterCheckbox = tabPane.querySelector('#show-diameter');
let showAreaCheckbox = tabPane.querySelector('#show-area');
let filterDrivableCheckbox = tabPane.querySelector('#filter-drivable-only');
// Update annotations when display options change
function updateAllAnnotations() {
// Clear current annotations
infoLayer.destroyFeatures();
// Re-add annotations for all features
for (let feature of radiusLayer.features) {
if (feature.geometry.CLASS_NAME === "OpenLayers.Geometry.Polygon") {
// Check if it's a circle (regular polygon with ~100 sides) or freehand
let components = feature.geometry.components[0].components;
if (components.length > 90) {
// Likely a circle
addCircleAnnotations(feature);
} else {
// Likely freehand
addFreehandAnnotations(feature);
}
}
}
}
showRadiusCheckbox.addEventListener('change', updateAllAnnotations);
showDiameterCheckbox.addEventListener('change', updateAllAnnotations);
showAreaCheckbox.addEventListener('change', updateAllAnnotations);
function addCircleAnnotations(f) {
let minX = f.geometry.bounds.left;
let minY = f.geometry.bounds.bottom;
let maxX = f.geometry.bounds.right;
let maxY = f.geometry.bounds.top;
let startX = (minX + maxX) / 2;
let startY = (minY + maxY) / 2;
let startPoint = new uOpenLayers.Geometry.Point(startX, startY);
let endPoint = new uOpenLayers.Geometry.Point(maxX, startY);
let radius = new uOpenLayers.Geometry.LineString([startPoint, endPoint]);
let len = radius.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913"));
// Calculate area (circle area = π * r²)
let area = Math.PI * len * len;
let showRadius = showRadiusCheckbox?.checked;
let showDiameter = showDiameterCheckbox?.checked;
let showArea = showAreaCheckbox?.checked;
let centerStyle = {
strokeColor: "#c40606",
strokeWidth: 2,
pointRadius: 5,
fillOpacity: 0.2
};
let labelText = [];
// Add radius info
if (showRadius) {
let unit = 'm';
let displayLen = len;
if (len > 1000) {
displayLen = Math.round((len / 1000) * 100) / 100;
unit = "km";
} else {
displayLen = Math.round(len);
}
labelText.push('R: ' + displayLen + ' ' + unit);
}
// Add diameter info
if (showDiameter) {
let diameter = len * 2;
let unit = 'm';
let displayDiam = diameter;
if (diameter > 1000) {
displayDiam = Math.round((diameter / 1000) * 100) / 100;
unit = "km";
} else {
displayDiam = Math.round(diameter);
}
labelText.push('Ø: ' + displayDiam + ' ' + unit);
}
// Add area info
if (showArea) {
let unit = 'm²';
let displayArea = area;
if (area > 1000000) {
displayArea = Math.round((area / 1000000) * 100) / 100;
unit = "km²";
} else {
displayArea = Math.round(area);
}
labelText.push('A: ' + displayArea + ' ' + unit);
}
let lineStyle = {
strokeColor: "#c40606",
strokeWidth: 3,
label: labelText.join(' | '),
labelAlign: "left",
labelXOffset: "20",
labelYOffset: "10",
labelOutlineColor: "white",
labelOutlineWidth: 3
};
let center = new uOpenLayers.Feature.Vector(startPoint, {}, centerStyle);
if (labelText.length > 0) {
let radiusLine = new uOpenLayers.Feature.Vector(radius, { 'length': len }, lineStyle);
infoLayer.addFeatures([center, radiusLine]);
} else {
infoLayer.addFeatures([center]);
}
}
function addFreehandAnnotations(f) {
let bounds = f.geometry.getBounds();
let minX = bounds.left;
let minY = bounds.bottom;
let maxX = bounds.right;
let maxY = bounds.top;
let centerX = (minX + maxX) / 2;
let centerY = (minY + maxY) / 2;
let width = maxX - minX;
let height = maxY - minY;
let diameter = Math.max(width, height);
let centerPoint = new uOpenLayers.Geometry.Point(centerX, centerY);
let endPoint = new uOpenLayers.Geometry.Point(centerX + diameter/2, centerY);
let diameterLine = new uOpenLayers.Geometry.LineString([centerPoint, endPoint]);
let len = diameterLine.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913"));
let area = f.geometry.getArea();
let showRadius = showRadiusCheckbox?.checked;
let showDiameter = showDiameterCheckbox?.checked;
let showArea = showAreaCheckbox?.checked;
let centerStyle = {
strokeColor: "#c40606",
strokeWidth: 2,
pointRadius: 5,
fillOpacity: 0.2
};
let labelText = [];
if (showRadius) {
let radius = len / 2;
let unit = 'm';
let displayRadius = radius;
if (radius > 1000) {
displayRadius = Math.round((radius / 1000) * 100) / 100;
unit = "km";
} else {
displayRadius = Math.round(radius);
}
labelText.push('R: ' + displayRadius + ' ' + unit);
}
if (showDiameter) {
let unit = 'm';
let displayLen = len;
if (len > 1000) {
displayLen = Math.round((len / 1000) * 100) / 100;
unit = "km";
} else {
displayLen = Math.round(len);
}
labelText.push('Ø: ' + displayLen + ' ' + unit);
}
if (showArea) {
let unit = 'm²';
let displayArea = area;
if (area > 1000000) {
displayArea = Math.round((area / 1000000) * 100) / 100;
unit = "km²";
} else {
displayArea = Math.round(area);
}
labelText.push('A: ' + displayArea + ' ' + unit);
}
let lineStyle = {
strokeColor: "#c40606",
strokeWidth: 3,
label: labelText.join(' | '),
labelAlign: "left",
labelXOffset: "20",
labelYOffset: "10",
labelOutlineColor: "white",
labelOutlineWidth: 3
};
let center = new uOpenLayers.Feature.Vector(centerPoint, {}, centerStyle);
if (labelText.length > 0) {
let diameterLineFeature = new uOpenLayers.Feature.Vector(diameterLine, { 'diameter': len }, lineStyle);
infoLayer.addFeatures([center, diameterLineFeature]);
} else {
infoLayer.addFeatures([center]);
}
}
checkbox.addEventListener('click', (e) => {
if (e.target.checked) {
freehandCheckbox.checked = false;
freehandControl.deactivate();
polygonControl.activate();
} else {
polygonControl.deactivate();
}
});
freehandCheckbox.addEventListener('click', (e) => {
if (e.target.checked) {
checkbox.checked = false;
polygonControl.deactivate();
freehandControl.activate();
} else {
freehandControl.deactivate();
}
});
clearButton.addEventListener('click', (e) => {
infoLayer.destroyFeatures();
radiusLayer.destroyFeatures();
checkbox.checked = false;
freehandCheckbox.checked = false;
polygonControl.deactivate();
freehandControl.deactivate();
});
selectButton.addEventListener('click', (e) => {
const toSelect = [];
const segments = uWaze.model.segments.getObjectArray();
for (const segment of segments) {
if (!segment.attributes?.roadType) continue;
if (filterDrivableCheckbox.checked) {
const type = segment.attributes.roadType;
if (type < 1 || type > 6) continue; // only street to freeway
}
const segGeom = segment.getOLGeometry ? segment.getOLGeometry() : segment.geometry;
if (!segGeom) continue;
for (const drawnFeature of radiusLayer.features) {
if (drawnFeature.geometry.intersects(segGeom)) {
toSelect.push(segment);
break;
}
}
}
console.log(`[WME Circles] Selected ${toSelect.length} segments`);
if (toSelect.length > 0) {
uWaze.selectionManager.setSelectedModels(toSelect);
}
checkbox.checked = false;
freehandCheckbox.checked = false;
polygonControl.deactivate();
freehandControl.deactivate();
});
});
}
function radiusInit() {
radiusLayer = new uOpenLayers.Layer.Vector("WME Circle Control Layer", {
displayInLayerSwitcher: true,
uniqueName: "__CircleControlLayer",
visibility: true,
style: {
"fillColor": "#c40606",
"fillOpacity": 0.2,
"strokeColor": "#c40606",
"strokeOpacity": 1,
"strokeWidth": 1,
"strokeLinecap": "round",
"strokeDashstyle": "solid",
"pointRadius": 6,
"pointerEvents": "visiblePainted",
"labelAlign": "cm",
"labelOutlineColor": "white",
"labelOutlineWidth": 3
}
});
infoLayer = new uOpenLayers.Layer.Vector("WME Circle Visual Layer", {
displayInLayerSwitcher: true,
uniqueName: "__DrawCircleDisplayLayer",
visibility: true
});
let polygonHandler = uOpenLayers.Handler.RegularPolygon;
polygonControl = new uOpenLayers.Control.DrawFeature(radiusLayer, polygonHandler, {
handlerOptions: {
sides: 100
}
});
// Add free hand drawing control with Polygon handler for closed shapes
let polygonHandlerFreehand = uOpenLayers.Handler.Polygon;
freehandControl = new uOpenLayers.Control.DrawFeature(radiusLayer, polygonHandlerFreehand, {
handlerOptions: {
freehand: true,
freehandToggle: null
}
});
uWaze.map.addLayer(radiusLayer);
uWaze.map.addLayer(infoLayer);
uWaze.map.addControl(polygonControl);
uWaze.map.addControl(freehandControl);
addSidePanel();
polygonControl.handler.callbacks.move = function (e) {
let linearRing = new uOpenLayers.Geometry.LinearRing(e.components[0].components);
let geometry = new uOpenLayers.Geometry.Polygon([linearRing]);
let polygonFeature = new uOpenLayers.Feature.Vector(geometry, null);
let polybounds = polygonFeature.geometry.getBounds();
let minX = polybounds.left;
let minY = polybounds.bottom;
let maxX = polybounds.right;
let maxY = polybounds.top;
let startX = (minX + maxX) / 2;
let startY = (minY + maxY) / 2;
let startPoint = new uOpenLayers.Geometry.Point(startX, startY);
let endPoint = new uOpenLayers.Geometry.Point(maxX, startY);
let radius = new uOpenLayers.Geometry.LineString([startPoint, endPoint]);
let len = radius.getGeodesicLength(new uOpenLayers.Projection("EPSG:900913"));
let unit = 'm';
if (len > 1000) {
len = Math.round((len / 1000) * 100) / 100;
unit = "km";
} else {
len = Math.round(len);
}
let rad = document.getElementById('circles-current-radius');
if (rad) {
rad.innerHTML = 'Current Radius: ' + len + ' ' + unit;
}
// Show radius on the map while drawing - clear ALL annotations first
infoLayer.destroyFeatures();
let centerStyle = {
strokeColor: "#ff9800",
strokeWidth: 2,
pointRadius: 3,
fillOpacity: 0.3
};
let lineStyle = {
strokeColor: "#ff9800",
strokeWidth: 2,
label: len + ' ' + unit,
labelAlign: "left",
labelXOffset: "15",
labelYOffset: "5",
labelOutlineColor: "white",
labelOutlineWidth: 2,
strokeDashstyle: "dash"
};
let center = new uOpenLayers.Feature.Vector(startPoint, {}, centerStyle);
let radiusLine = new uOpenLayers.Feature.Vector(radius, {}, lineStyle);
infoLayer.addFeatures([center, radiusLine]);
}
polygonControl.events.on({
'featureadded': function (e) {
let rad = document.getElementById('circles-current-radius');
if (rad) {
rad.innerHTML = '';
}
// Add a small delay to ensure the feature is fully added, then update annotations
setTimeout(() => {
updateAllAnnotations();
}, 100);
}
});
freehandControl.events.on({
'featureadded': function (e) {
let rad = document.getElementById('circles-current-radius');
if (rad) {
rad.innerHTML = '';
}
// Add a small delay to ensure the feature is fully added, then update annotations
setTimeout(() => {
updateAllAnnotations();
}, 100);
}
});
}
function radiusBootstrap() {
// Initialize SDK first
if (!window.getWmeSdk) {
setTimeout(radiusBootstrap, 500);
return;
}
wmeSDK = window.getWmeSdk({
scriptId: 'wme-circles',
scriptName: 'WME Circles'
});
// Wait for WME to be ready
wmeSDK.Events.once({ eventName: "wme-ready" }).then(() => {
// Now get the traditional objects
uWaze = window.W || unsafeWindow.W;
uOpenLayers = window.OpenLayers || unsafeWindow.OpenLayers;
if (typeof (uOpenLayers) === 'undefined' || typeof (uWaze) === 'undefined' || typeof (uWaze.map) === 'undefined') {
setTimeout(radiusBootstrap, 500);
} else {
radiusInit();
}
});
}
// Initialize when SDK is available
if (window.SDK_INITIALIZED) {
window.SDK_INITIALIZED.then(radiusBootstrap);
} else {
radiusBootstrap();
}
})();