// ==UserScript==
// @name WME Circles
// @namespace http://tampermonkey.net/
// @version 2025.07.19
// @description Kreise im WME erstellen - Updated for new Waze API
// @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==
(function() {
'use strict';
let uOpenLayers;
let uWaze;
let radiusLayer, infoLayer;
let polygonControl, freehandControl;
function initializeScript() {
uWaze = window.W;
uOpenLayers = window.OpenLayers;
if (!uWaze || !uOpenLayers || !uWaze.map) {
console.log('[WME Circles] WME objects not ready, retrying...');
setTimeout(initializeScript, 500);
return;
}
console.log('[WME Circles] Initializing...');
radiusInit();
}
function addSidePanel() {
try {
// Use new W.userscripts API
const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("wme-circles");
tabLabel.innerText = 'Circles';
tabLabel.title = 'WME Circles';
setupSidebarContent(tabPane);
} catch (error) {
console.log('[WME Circles] New API failed, using fallback:', error);
addSidePanelFallback();
}
}
function addSidePanelFallback() {
// Fallback to manual DOM manipulation
let userTabs = document.getElementById('user-info');
if (!userTabs) {
setTimeout(addSidePanelFallback, 1000);
return;
}
let navTabs = userTabs.getElementsByClassName('nav-tabs')[0];
let tabContent = userTabs.getElementsByClassName('tab-content')[0];
if (!navTabs || !tabContent) {
setTimeout(addSidePanelFallback, 1000);
return;
}
let tab = document.createElement('li');
tab.innerHTML = '<a href="#sidepanel-wme-circles" data-toggle="tab">Circles</a>';
navTabs.appendChild(tab);
let tabPane = document.createElement('section');
tabPane.id = "sidepanel-wme-circles";
tabPane.className = "tab-pane";
tabContent.appendChild(tabPane);
setupSidebarContent(tabPane);
}
function setupSidebarContent(tabPane) {
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: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<span>Show Radius</span>
<label class="toggle-switch">
<input type="checkbox" id="show-radius" checked>
<span class="slider"></span>
</label>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<span>Show Diameter</span>
<label class="toggle-switch">
<input type="checkbox" id="show-diameter">
<span class="slider"></span>
</label>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<span>Show Area</span>
<label class="toggle-switch">
<input type="checkbox" id="show-area">
<span class="slider"></span>
</label>
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>Imperial Units (ft/mi)</span>
<label class="toggle-switch">
<input type="checkbox" id="use-imperial">
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div style="margin-top: 20px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
<strong>Filter Options:</strong>
<div style="margin-top: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>Only drivable segments</span>
<label class="toggle-switch">
<input type="checkbox" id="filter-drivable-only" checked>
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div style="margin-top: 20px; padding: 10px; background: #e8f5e8; border-radius: 4px;">
<strong>Perimeter Blocking:</strong>
<div style="margin-top: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div>Block perimeter inbound only</div>
<div style="font-size: 11px; color: #666;">(hollow circle - exit allowed)</div>
</div>
<label class="toggle-switch">
<input type="checkbox" id="block-inbound-only">
<span class="slider"></span>
</label>
</div>
</div>
</div>
<style>
.toggle-switch {
position: relative;
display: inline-block;
width: 44px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .3s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .3s;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
input:checked + .slider {
background-color: #4CAF50;
}
input:checked + .slider:before {
transform: translateX(20px);
}
.slider:hover {
box-shadow: 0 0 8px rgba(76, 175, 80, 0.3);
}
</style>
</div>
`;
// Get UI elements
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 useImperialCheckbox = tabPane.querySelector('#use-imperial');
let filterDrivableCheckbox = tabPane.querySelector('#filter-drivable-only');
let blockInboundCheckbox = tabPane.querySelector('#block-inbound-only');
// Formatting functions
function formatLength(meters, useImperial = false) {
if (useImperial) {
let feet = meters * 3.28084;
if (feet >= 5280) {
let miles = feet / 5280;
return `${Math.round(miles * 100) / 100} mi`;
} else {
return `${Math.round(feet)} ft`;
}
} else {
if (meters > 1000) {
return `${Math.round((meters / 1000) * 100) / 100} km`;
} else {
return `${Math.round(meters)} m`;
}
}
}
function formatArea(squareMeters, useImperial = false) {
if (useImperial) {
let squareFeet = squareMeters * 10.7639;
if (squareFeet >= 27878400) { // 1 square mile
let squareMiles = squareFeet / 27878400;
return `${Math.round(squareMiles * 100) / 100} mi²`;
} else if (squareFeet >= 43560) { // 1 acre
let acres = squareFeet / 43560;
return `${Math.round(acres * 100) / 100} ac`;
} else {
return `${Math.round(squareFeet)} ft²`;
}
} else {
if (squareMeters > 1000000) {
return `${Math.round((squareMeters / 1000000) * 100) / 100} km²`;
} else {
return `${Math.round(squareMeters)} m²`;
}
}
}
function updateAllAnnotations() {
infoLayer.destroyFeatures();
for (let feature of radiusLayer.features) {
if (feature.geometry.CLASS_NAME === "OpenLayers.Geometry.Polygon") {
let components = feature.geometry.components[0].components;
if (components.length > 90) {
addCircleAnnotations(feature);
} else {
addFreehandAnnotations(feature);
}
}
}
}
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"));
let area = Math.PI * len * len;
let showRadius = showRadiusCheckbox?.checked;
let showDiameter = showDiameterCheckbox?.checked;
let showArea = showAreaCheckbox?.checked;
let useImperial = useImperialCheckbox?.checked;
let centerStyle = {
strokeColor: "#c40606",
strokeWidth: 2,
pointRadius: 5,
fillOpacity: 0.2
};
let labelText = [];
if (showRadius) {
labelText.push('R: ' + formatLength(len, useImperial));
}
if (showDiameter) {
labelText.push('Ø: ' + formatLength(len * 2, useImperial));
}
if (showArea) {
labelText.push('A: ' + formatArea(area, useImperial));
}
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 useImperial = useImperialCheckbox?.checked;
let centerStyle = {
strokeColor: "#c40606",
strokeWidth: 2,
pointRadius: 5,
fillOpacity: 0.2
};
let labelText = [];
if (showRadius) {
labelText.push('R: ' + formatLength(len / 2, useImperial));
}
if (showDiameter) {
labelText.push('Ø: ' + formatLength(len, useImperial));
}
if (showArea) {
labelText.push('A: ' + formatArea(area, useImperial));
}
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]);
}
}
// Event handlers
showRadiusCheckbox.addEventListener('change', updateAllAnnotations);
showDiameterCheckbox.addEventListener('change', updateAllAnnotations);
showAreaCheckbox.addEventListener('change', updateAllAnnotations);
useImperialCheckbox.addEventListener('change', updateAllAnnotations);
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();
const blockInboundOnly = blockInboundCheckbox?.checked;
if (blockInboundOnly) {
// Find perimeter segments only - create hollow circle
console.log('[WME Circles] Finding perimeter segments for hollow circle blocking...');
for (const drawnFeature of radiusLayer.features) {
const circleGeometry = drawnFeature.geometry;
const bounds = circleGeometry.getBounds();
const circleCenter = {
x: (bounds.left + bounds.right) / 2,
y: (bounds.bottom + bounds.top) / 2
};
// Calculate circle radius for perimeter detection
const radius = Math.abs(bounds.right - bounds.left) / 2;
const perimeterTolerance = radius * 0.15; // 15% tolerance for perimeter detection
for (const segment of segments) {
if (!segment.attributes?.roadType) continue;
if (filterDrivableCheckbox.checked) {
const type = segment.attributes.roadType;
if (type < 1 || type > 6) continue;
}
const segGeom = segment.getOLGeometry ? segment.getOLGeometry() : segment.geometry;
if (!segGeom) continue;
// Check if segment intersects with circle
if (circleGeometry.intersects(segGeom)) {
// Calculate segment's distance from circle center
const coords = segGeom.components || segGeom.getVertices();
if (!coords || coords.length < 2) continue;
// Check multiple points along segment
let isPerimeterSegment = false;
const checkPoints = Math.min(coords.length, 5);
for (let i = 0; i < checkPoints; i++) {
const point = coords[Math.floor(i * (coords.length - 1) / (checkPoints - 1))];
const distFromCenter = Math.sqrt(
Math.pow(point.x - circleCenter.x, 2) +
Math.pow(point.y - circleCenter.y, 2)
);
// Check if point is near the perimeter (within tolerance of radius)
if (Math.abs(distFromCenter - radius) <= perimeterTolerance) {
isPerimeterSegment = true;
break;
}
}
if (isPerimeterSegment) {
if (shouldBlockSegmentInbound(segment, segGeom, circleCenter)) {
toSelect.push(segment);
}
}
}
}
}
} else {
// Original logic - all segments within circle
for (const segment of segments) {
if (!segment.attributes?.roadType) continue;
if (filterDrivableCheckbox.checked) {
const type = segment.attributes.roadType;
if (type < 1 || type > 6) continue;
}
const segGeom = segment.getOLGeometry ? segment.getOLGeometry() : segment.geometry;
if (!segGeom) continue;
let circleCenter = null;
let isInCircle = false;
for (const drawnFeature of radiusLayer.features) {
if (drawnFeature.geometry.intersects(segGeom)) {
isInCircle = true;
const bounds = drawnFeature.geometry.getBounds();
circleCenter = {
x: (bounds.left + bounds.right) / 2,
y: (bounds.bottom + bounds.top) / 2
};
break;
}
}
if (isInCircle) {
toSelect.push(segment);
}
}
}
console.log(`[WME Circles] Selected ${toSelect.length} segments${blockInboundOnly ? ' (perimeter only)' : ''}`);
if (toSelect.length > 0) {
if (blockInboundOnly) {
// Apply direction restrictions to perimeter segments
applyDirectionRestrictions(toSelect);
} else {
// Regular selection
uWaze.selectionManager.setSelectedModels(toSelect);
}
}
checkbox.checked = false;
freehandCheckbox.checked = false;
polygonControl.deactivate();
freehandControl.deactivate();
});
function shouldBlockSegmentInbound(segment, segGeom, circleCenter) {
const coords = segGeom.components || segGeom.getVertices();
if (!coords || coords.length < 2) {
console.log(`[WME Circles] No valid coordinates for segment ${segment.getID()}`);
return false;
}
const startPoint = coords[0];
const endPoint = coords[coords.length - 1];
// Calculate which end is closer to circle center
const startDist = Math.sqrt(
Math.pow(startPoint.x - circleCenter.x, 2) +
Math.pow(startPoint.y - circleCenter.y, 2)
);
const endDist = Math.sqrt(
Math.pow(endPoint.x - circleCenter.x, 2) +
Math.pow(endPoint.y - circleCenter.y, 2)
);
segment._circleDirectionInfo = {
startPoint: startPoint,
endPoint: endPoint,
startDist: startDist,
endDist: endDist,
circleCenter: circleCenter
};
console.log(`[WME Circles] Set direction info for segment ${segment.getID()}: startDist=${Math.round(startDist)}, endDist=${Math.round(endDist)}`);
return true;
}
function applyDirectionRestrictions(segments) {
console.log(`[WME Circles] Applying direction restrictions to ${segments.length} segments`);
// First select the segments
uWaze.selectionManager.setSelectedModels(segments);
// Add delay to ensure selection is processed
setTimeout(() => {
let successCount = 0;
let modifiedCount = 0;
// Apply restrictions to each segment
for (const segment of segments) {
try {
const dirInfo = segment._circleDirectionInfo;
if (!dirInfo) {
console.log(`[WME Circles] No direction info for segment ${segment.getID()}, skipping`);
continue;
}
const currentFwd = segment.attributes.fwdDirection;
const currentRev = segment.attributes.revDirection;
// Only modify bidirectional segments
if (!currentFwd || !currentRev) {
console.log(`[WME Circles] Segment ${segment.getID()}: Already one-way, skipping`);
continue;
}
// FIXED: Simplified direction logic
// If start is closer to center: Block REVERSE (B->A), Keep FORWARD (A->B)
// If end is closer to center: Block FORWARD (A->B), Keep REVERSE (B->A)
let newFwd = currentFwd;
let newRev = currentRev;
if (dirInfo.startDist < dirInfo.endDist) {
// A is closer to center than B
// Traffic A->B goes TO center (BLOCK)
// Traffic B->A goes AWAY from center (KEEP)
newFwd = false; // Block A->B (inbound)
newRev = true; // Keep B->A (outbound)
console.log(`[WME Circles] Segment ${segment.getID()}: A closer to center - blocking A->B (inbound), keeping B->A (outbound)`);
} else {
// B is closer to center than A
// Traffic A->B goes AWAY from center (KEEP)
// Traffic B->A goes TO center (BLOCK)
newFwd = true; // Keep A->B (outbound)
newRev = false; // Block B->A (inbound)
console.log(`[WME Circles] Segment ${segment.getID()}: B closer to center - keeping A->B (outbound), blocking B->A (inbound)`);
}
// Only apply if there's a change needed
if (newFwd !== currentFwd || newRev !== currentRev) {
try {
// Check if user has edit permissions for this segment
if (!segment.arePropertiesEditable || !segment.arePropertiesEditable()) {
console.warn(`[WME Circles] No edit permissions for segment ${segment.getID()}`);
continue;
}
// Direct model update using WME's internal methods
const modelSegment = W.model.segments.getObjectById(segment.getID());
if (modelSegment) {
// Set attributes directly and mark as modified
modelSegment.attributes.fwdDirection = newFwd;
modelSegment.attributes.revDirection = newRev;
// Mark segment as modified for save system
modelSegment._hasUnsavedAttributes = true;
if (modelSegment.changed) {
modelSegment.changed.fwdDirection = true;
modelSegment.changed.revDirection = true;
} else {
modelSegment.changed = {
fwdDirection: true,
revDirection: true
};
}
// Trigger UI update
modelSegment.trigger('change:fwdDirection');
modelSegment.trigger('change:revDirection');
modelSegment.trigger('change');
// Let WME handle map updates naturally
modifiedCount++;
console.log(`[WME Circles] ✓ Modified segment ${segment.getID()}: fwd=${newFwd}, rev=${newRev}`);
} else {
console.warn(`[WME Circles] Model segment not found: ${segment.getID()}`);
}
} catch (error) {
console.warn(`[WME Circles] Failed to modify segment ${segment.getID()}:`, error);
}
}
successCount++;
// Clean up temporary data
delete segment._circleDirectionInfo;
} catch (error) {
console.warn('[WME Circles] Error processing segment:', error);
}
}
console.log(`[WME Circles] Processed ${successCount}/${segments.length} segments, modified ${modifiedCount} segments`);
// Show user feedback
const statusElement = document.getElementById('circles-current-radius');
if (statusElement) {
if (modifiedCount > 0) {
statusElement.innerHTML = `✓ Applied direction restrictions to ${modifiedCount} segments (outbound traffic preserved)`;
statusElement.style.color = '#4CAF50';
} else {
statusElement.innerHTML = `⚠ No segments needed modification (${successCount} processed)`;
statusElement.style.color = '#ff9800';
}
// Clear message after 4 seconds
setTimeout(() => {
statusElement.innerHTML = '';
statusElement.style.color = '';
}, 4000);
}
// Force map refresh without causing errors
try {
// Only trigger minimal updates
if (W.model && W.model.segments) {
W.model.segments.trigger('change');
}
} catch (e) {
// Ignore render errors
}
}, 500);
}
}
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
}
});
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;
}
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 = '';
}
setTimeout(() => {
// Trigger update from within tabPane scope
const event = new CustomEvent('updateAnnotations');
document.dispatchEvent(event);
}, 100);
}
});
freehandControl.events.on({
'featureadded': function (e) {
let rad = document.getElementById('circles-current-radius');
if (rad) {
rad.innerHTML = '';
}
setTimeout(() => {
// Trigger update from within tabPane scope
const event = new CustomEvent('updateAnnotations');
document.dispatchEvent(event);
}, 100);
}
});
// Listen for custom update events
document.addEventListener('updateAnnotations', () => {
// This will call the updateAllAnnotations function from the sidebar scope
const updateEvent = new CustomEvent('forceUpdate');
document.dispatchEvent(updateEvent);
});
}
// Use the new W.userscripts.state API
if (W?.userscripts?.state?.isReady) {
console.log('[WME Circles] WME already ready, initializing...');
initializeScript();
} else {
console.log('[WME Circles] Waiting for WME ready event...');
document.addEventListener("wme-ready", initializeScript, { once: true });
}
})();