Shows road closures from Waze Live map in WME
// ==UserScript==
// @name WME LiveMap closures
// @description Shows road closures from Waze Live map in WME
// @match https://www.waze.com/*editor*
// @match https://beta.waze.com/*editor*
// @grant unsafeWindow
// @version 2.0.0
// @copyright 2014-2026, pvo11
// @namespace https://greasyfork.org/scripts/5144-wme-road-closures
// ==/UserScript==
const LAYER_NAME = "LiveMap closures";
var sdk; // WME SDK instance
var featureSeq = 0; // unique feature id counter
var lineWidth = [
[4, 5],
[5, 6],
[6, 7],
[7, 8],
[8, 9],
[10, 12],
[12, 14],
[14, 16],
[15, 17],
[16, 18],
[17, 19]
];
// Add a styled LineString feature to the closures layer. The style values are read
// from the feature properties via the layer's styleRules (${...} interpolation).
function addLineFeature(coordinates, strokeColor, strokeWidth, strokeDashstyle, strokeLinecap)
{
sdk.Map.addFeatureToLayer({
layerName: LAYER_NAME,
feature: {
type: "Feature",
id: "closure_" + (featureSeq++),
geometry: { type: "LineString", coordinates: coordinates },
properties: {
style: {
strokeColor: strokeColor,
strokeWidth: strokeWidth,
strokeDashstyle: strokeDashstyle,
strokeLinecap: strokeLinecap
}
}
}
});
}
function drawLine(line)
{
var coordinates = [];
var zoom = sdk.Map.getZoomLevel() - 12;
if (zoom >= lineWidth.length) {
zoom = lineWidth.length - 1;
}
// Coordinates are plain WGS84 lon/lat (GeoJSON) - no projection transform.
coordinates.push([line[0].x, line[0].y]);
for (var i = 1; i < line.length - 1; i++) {
var lp1 = line[i];
var lp2 = line[i + 1];
var dif_lon = Math.abs(lp1.x - lp2.x);
var dif_lat = Math.abs(lp1.y - lp2.y);
if (dif_lon < 0.0000001 && dif_lat < 0.0000001) continue;
coordinates.push([lp1.x, lp1.y]);
}
coordinates.push([line[line.length - 1].x, line[line.length - 1].y]);
// Three overlaid lines (drawn in add order): black base, red core, white dotted on top.
addLineFeature(coordinates, '#000000', lineWidth[zoom][1], 'solid', 'round');
addLineFeature(coordinates, '#FF0000', lineWidth[zoom][0], 'solid', 'round');
addLineFeature(coordinates, '#FFFFFF', lineWidth[zoom][0], 'dot', 'square');
}
function getRoutingURL()
{
var routingURL = 'https://www.waze.com';
if (~document.URL.indexOf('https://beta.waze.com')) {
routingURL = 'https://beta.waze.com';
}
routingURL += '/live-map/api/georss';
return routingURL;
}
function requestClosures()
{
var zoom = sdk.Map.getZoomLevel() - 12;
var env = (typeof sdk.Settings.getRegionCode === 'function')
? sdk.Settings.getRegionCode()
: sdk.DataModel.Countries.getTopCountry()?.regionCode;
if (env == 'usa') {
env = 'na';
}
if (zoom < 0) {
return;
}
if (!sdk.Map.isLayerVisible({ layerName: LAYER_NAME })) {
return;
}
// getMapExtent() returns WGS84 [minLon, minLat, maxLon, maxLat]; the georss API
// expects WGS84 lon/lat bounds, mapped positionally as the original script did.
var extent = sdk.Map.getMapExtent();
var params = {
env: env,
ma: "600",
mj: "100",
mu: "100",
types: "traffic",
left: extent[0],
top: extent[1],
right: extent[2],
bottom: extent[3]
};
var query = Object.keys(params)
.map(function(k) { return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); })
.join('&');
var url = getRoutingURL() + '?' + query;
fetch(url)
.then(function(response) { return response.json(); })
.then(function(json) {
if (json.error != undefined) {
return;
}
sdk.Map.removeAllFeaturesFromLayer({ layerName: LAYER_NAME });
if ("undefined" !== typeof(json.jams)) {
var numjams = json.jams.length;
for (var i = 0; i < numjams; i++) {
var jam = json.jams[i];
if (typeof jam.causeAlert !== 'undefined' && jam.causeAlert.type == 'ROAD_CLOSED') {
drawLine(jam.line);
}
}
}
})
.catch(function(error) {
console.error('WME LiveMap closures: request failed', error);
});
}
function liveMapClosures_init()
{
sdk.Map.addLayer({
layerName: LAYER_NAME,
styleRules: [{
predicate: function() { return true; },
style: {
strokeColor: "${strokeColor}",
strokeWidth: "${strokeWidth}",
strokeDashstyle: "${strokeDashstyle}",
strokeLinecap: "${strokeLinecap}",
strokeOpacity: 1
}
}],
styleContext: {
strokeColor: function(ctx) { return ctx?.feature?.properties?.style?.strokeColor; },
strokeWidth: function(ctx) { return ctx?.feature?.properties?.style?.strokeWidth; },
strokeDashstyle: function(ctx) { return ctx?.feature?.properties?.style?.strokeDashstyle; },
strokeLinecap: function(ctx) { return ctx?.feature?.properties?.style?.strokeLinecap; }
}
});
var visible;
if (localStorage.DrawLiveMapClosures) {
visible = (localStorage.DrawLiveMapClosures == "true");
} else {
visible = true;
}
sdk.Map.setLayerVisibility({ layerName: LAYER_NAME, visibility: visible });
sdk.LayerSwitcher.addLayerCheckbox({ name: LAYER_NAME, isChecked: visible });
sdk.Events.on({ eventName: "wme-map-move-end", eventHandler: requestClosures });
sdk.Events.on({ eventName: "wme-map-zoom-changed", eventHandler: requestClosures });
sdk.Events.on({
eventName: "wme-layer-checkbox-toggled",
eventHandler: function(payload) {
if (payload.name !== LAYER_NAME) {
return;
}
sdk.Map.setLayerVisibility({ layerName: LAYER_NAME, visibility: payload.checked });
localStorage.DrawLiveMapClosures = payload.checked;
requestClosures();
}
});
requestClosures();
}
function liveMapClosures_bootstrap()
{
sdk = unsafeWindow.getWmeSdk({ scriptId: "wme-road-closures", scriptName: "WME LiveMap closures" });
if (sdk.State.isReady()) {
liveMapClosures_init();
} else {
sdk.Events.once({ eventName: "wme-ready" }).then(liveMapClosures_init);
}
}
if (unsafeWindow.SDK_INITIALIZED) {
unsafeWindow.SDK_INITIALIZED.then(liveMapClosures_bootstrap);
} else {
document.addEventListener("wme-ready", liveMapClosures_bootstrap, { once: true });
}