Sobreposição de mapas no WME: Google, OSM, Trânsito, Apple Maps, Waze Live Map, Mapillary e ViaMichelin! Só baixa tiles em segundo plano quando os sliders estão visíveis.
// ==UserScript==
// @name WME Map Overlay (Optimized)
// @namespace https://greasyfork.org/en/users/1559074-xtryker
// @version 5.0
// @description Sobreposição de mapas no WME: Google, OSM, Trânsito, Apple Maps, Waze Live Map, Mapillary e ViaMichelin! Só baixa tiles em segundo plano quando os sliders estão visíveis.
// @author Xtryker e Jabc82
// @match https://www.waze.com/*editor*
// @grant none
// @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let trafficDiv, sliderContainer, isSliderVisible = false;
let wazeLiveLayer, gmap, mapillaryDiv, mapillaryIframe, trafficLayerRef;
// Keep track of each layer's current slider value so we know, when the
// panel is re-opened, which layers should actually resume fetching tiles.
const sliderValues = {
"Waze Live Map": 0,
"Google Maps": 0,
"Traffic": 0,
"OSM": 0,
"Mapillary": 0,
"Apple Maps": 0,
"ViaMichelin": 0
};
// All OpenLayers XYZ layers we manage (populated in initOverlay)
let tileLayers = {};
function initOverlay() {
if (typeof W === 'undefined' || typeof W.map === 'undefined') {
setTimeout(initOverlay, 1000);
return;
}
const map = W.map;
// NOTE: visibility starts as false for every layer. In OpenLayers,
// opacity is purely a rendering/CSS effect — a layer with opacity 0
// but visibility true will still issue tile requests in the
// background. Setting visibility:false is what actually stops the
// tile loader from firing requests, so we use it as our "should this
// layer be downloading right now" switch.
const googleBaseLayer = new OpenLayers.Layer.XYZ(
"Google Maps (Base)",
"https://mt.google.com/vt/lyrs=m&x=${x}&y=${y}&z=${z}",
{ isBaseLayer: false, opacity: 0.00, visibility: false }
);
const osmLayer = new OpenLayers.Layer.XYZ(
"OpenStreetMap",
"https://tile.openstreetmap.org/${z}/${x}/${y}.png",
{ isBaseLayer: false, opacity: 0.00, visibility: false }
);
const appleLayer = new OpenLayers.Layer.XYZ(
"Apple Maps",
"https://maps.apple.com/frame?map=explore¢er=${lat},${lon}&span=${spanX},${spanY}",
{ isBaseLayer: false, opacity: 0.00, visibility: false }
);
wazeLiveLayer = new OpenLayers.Layer.XYZ(
"Waze Live Map",
"https://worldtiles1.waze.com/tiles/${z}/${x}/${y}.png",
{ isBaseLayer: false, opacity: 0.00, visibility: false }
);
// NOTE: ViaMichelin has no public/official tile API (their real API
// is a paid B2B contract product - see api.viamichelin.com). This
// URL is a reverse-engineered internal endpoint their own web
// client calls, same category of risk as the Google layer above:
// undocumented, unauthenticated, and can change or get blocked
// without notice. The `version` query param is pinned to a build
// timestamp from ViaMichelin's client; if tiles start coming back
// empty/404, that's the first thing to suspect and update.
// Three round-robin subdomains (map1/2/3), matching how their own
// client spreads requests.
const viaMichelinLayer = new OpenLayers.Layer.XYZ(
"ViaMichelin",
[
"https://map1.viamichelin.com/map/mapdirect?map=viamichelin&z=${z}&x=${x}&y=${y}&version=201503191157&format=png&layer=background&locale=default",
"https://map2.viamichelin.com/map/mapdirect?map=viamichelin&z=${z}&x=${x}&y=${y}&version=201503191157&format=png&layer=background&locale=default",
"https://map3.viamichelin.com/map/mapdirect?map=viamichelin&z=${z}&x=${x}&y=${y}&version=201503191157&format=png&layer=background&locale=default"
],
{ isBaseLayer: false, opacity: 0.00, visibility: false }
);
map.addLayer(wazeLiveLayer);
map.addLayer(googleBaseLayer);
map.addLayer(osmLayer);
map.addLayer(appleLayer);
map.addLayer(viaMichelinLayer);
tileLayers = {
"Waze Live Map": wazeLiveLayer,
"Google Maps": googleBaseLayer,
"OSM": osmLayer,
"Apple Maps": appleLayer,
"ViaMichelin": viaMichelinLayer
// "Traffic" is added once initTrafficLayer() runs, below.
};
sliderContainer = document.createElement("div");
sliderContainer.style.position = "absolute";
sliderContainer.style.top = "80px";
sliderContainer.style.left = "50%";
sliderContainer.style.transform = "translateX(-50%)";
sliderContainer.style.zIndex = "1000";
sliderContainer.style.padding = "8px";
sliderContainer.style.background = "rgba(10, 25, 50, 0.95)";
sliderContainer.style.borderRadius = "10px";
sliderContainer.style.border = "1px solid white";
sliderContainer.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)";
sliderContainer.style.display = "none";
sliderContainer.style.flexDirection = "row";
sliderContainer.style.gap = "10px";
sliderContainer.style.fontFamily = "sans-serif";
sliderContainer.style.transition = "all 0.3s ease";
const layers = [
{
name: "Waze Live Map",
icon: "https://cdn-images-1.medium.com/max/1200/1*3kS1iOOTBrvtkecae3u2aA.png",
initial: 0.00,
onChange: value => wazeLiveLayer.setOpacity(value)
},
{
name: "Google Maps",
icon: "https://static.vecteezy.com/system/resources/previews/016/716/478/non_2x/google-maps-icon-free-png.png",
initial: 0.00,
onChange: value => googleBaseLayer.setOpacity(value)
},
{
name: "Traffic",
icon: "https://p7.hiclipart.com/preview/798/15/811/logo-font-traffic-jam-thumbnail.jpg",
initial: 0.00,
onChange: value => {
if (trafficLayerRef) trafficLayerRef.setOpacity(value);
}
},
{
name: "OSM",
icon: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQPC9m4tfa2uII2-_l3png4g2jIF1yJw8p1uXweLk7pYw&s=10",
initial: 0.00,
onChange: value => osmLayer.setOpacity(value)
},
{
name: "ViaMichelin",
icon: "https://i.imgur.com/Kyek7rE.png",
initial: 0.00,
onChange: value => tileLayers["ViaMichelin"].setOpacity(value)
},
{
name: "Mapillary",
icon: "https://pbs.twimg.com/profile_images/1097399669158825984/aXZ49j3I_400x400.png",
initial: 0.00,
onChange: value => {
if (mapillaryDiv) mapillaryDiv.style.opacity = value;
}
},
{
name: "Apple Maps",
icon: "https://th.bing.com/th/id/R.7bbe9deedfa162a68a224e18fbad2dfc?rik=nsbux2xtDIgyUw&pid=ImgRaw&r=0",
initial: 0.00,
onChange: value => appleLayer.setOpacity(value)
},
{
name: "Websig PT",
icon: "https://cdn-icons-png.flaticon.com/512/5379/5379902.png",
initial: 0.00,
onChange: () => {},
isButton: true
}
];
layers.forEach(layer => {
const wrapper = document.createElement("div");
wrapper.style.width = "60px";
wrapper.style.textAlign = "center";
const img = document.createElement("img");
img.src = layer.icon;
img.alt = layer.name;
img.title = layer.name;
img.style.width = "60px";
img.style.height = "60px";
img.style.borderRadius = "12px";
img.style.border = "2px solid white";
img.style.transition = "transform 0.3s ease, border 0.3s ease";
img.style.display = "block";
img.style.marginBottom = "4px";
img.style.cursor = "pointer";
img.addEventListener("mouseenter", () => {
img.style.transform = "scale(1.1)";
img.style.border = "2px solid gold";
});
img.addEventListener("mouseleave", () => {
img.style.transform = "scale(1)";
img.style.border = "2px solid white";
});
img.addEventListener("click", () => {
const center = W.map.getCenter();
const zoom = Math.round(W.map.getZoom() - 3);
const lonlat = new OpenLayers.LonLat(center.lon, center.lat);
lonlat.transform(
new OpenLayers.Projection("EPSG:900913"),
new OpenLayers.Projection("EPSG:4326")
);
const lat = parseFloat(lonlat.lat.toFixed(6));
const lon = parseFloat(lonlat.lon.toFixed(6));
const span = 360 / Math.pow(2, zoom);
if (layer.name === "Google Maps")
window.open(`https://www.google.com/maps/@${lat},${lon},${zoom + 3}z`, '_blank');
if (layer.name === "OSM")
window.open(`https://www.openstreetmap.org/#map=${zoom + 3}/${lat}/${lon}`, '_blank');
if (layer.name === "Websig PT")
window.open(`https://www.arcgis.com/apps/mapviewer/index.html?webmap=8c00d5d66ee24a0d94a7e923ea8f653e¢er=${lon},${lat}&level=${zoom + 3}`, '_blank');
if (layer.name === "Apple Maps")
window.open(`https://maps.apple.com/frame?map=hybrid¢er=${lat}%2C${lon}&zoom=${zoom + 3}%2C${span}`, '_blank');
if (layer.name === "Waze Live Map")
window.open(`https://www.waze.com/livemap?lat=${lat}&lng=${lon}&zoom=${zoom}`, '_blank');
if (layer.name === "Mapillary")
window.open(`https://www.mapillary.com/app/?lat=${lat}&lng=${lon}&z=${zoom + 2}`, '_blank');
// NOTE: ViaMichelin rebuilt their site (SvelteKit-based) and
// the old /web/Maps?latitude=...&longitude=... route is
// gone - it now 404s with "This page has been removed or
// modified". Their current URLs are slug-based
// (/maps/country/region/city/city-postcode), not raw
// lat/lon, and there's no documented way to deep-link to
// arbitrary coordinates anymore. Linking to the maps
// landing page instead of a URL that's guaranteed to break.
if (layer.name === "ViaMichelin")
window.open(`https://www.viamichelin.com/maps`, '_blank');
});
wrapper.appendChild(img);
if (!layer.isButton && layer.name !== "Apple Maps" && layer.name !== "Mapillary") {
const slider = document.createElement("input");
slider.type = "range";
slider.min = "0";
slider.max = "1";
slider.step = "0.01";
slider.value = layer.initial;
slider.style.width = "100%";
slider.addEventListener("input", () => {
const value = parseFloat(slider.value);
sliderValues[layer.name] = value;
layer.onChange(value);
// While the panel is open, keep the underlying tile
// layer's visibility in sync with whether it's actually
// dialed in (value > 0). No point downloading tiles for
// a layer that's set to fully transparent.
if (isSliderVisible) {
const olLayer = tileLayers[layer.name] || (layer.name === "Traffic" ? trafficLayerRef : null);
if (olLayer) olLayer.setVisibility(value > 0);
}
});
wrapper.appendChild(slider);
}
sliderContainer.appendChild(wrapper);
});
const toggleButton = document.createElement("button");
toggleButton.textContent = isSliderVisible ? "Hide Sliders" : "Show Sliders";
toggleButton.style.position = "absolute";
toggleButton.style.top = "37px";
toggleButton.style.left = "50%";
toggleButton.style.transform = "translateX(-50%)";
toggleButton.style.zIndex = "2000";
toggleButton.style.padding = "5px 10px";
toggleButton.style.backgroundColor = "rgba(10, 25, 50, 0.8)";
toggleButton.style.color = "white";
toggleButton.style.border = "1px solid white";
toggleButton.style.borderRadius = "5px";
toggleButton.style.cursor = "pointer";
toggleButton.addEventListener("click", () => {
isSliderVisible = !isSliderVisible;
sliderContainer.style.display = isSliderVisible ? "flex" : "none";
toggleButton.textContent = isSliderVisible ? "Hide Sliders" : "Show Sliders";
setBackgroundLoading(isSliderVisible);
});
document.body.appendChild(toggleButton);
document.body.appendChild(sliderContainer);
initTrafficLayer();
initMapillaryLayer();
}
// Turns background tile downloading on/off for every managed layer.
// - When switching ON: only layers whose slider is currently above 0
// resume fetching (no point turning on a layer nobody dialed in).
// - When switching OFF: everything is forced to visibility:false,
// which stops OpenLayers from issuing any further tile requests,
// and the Mapillary iframe is pointed at about:blank to stop its
// own network activity too.
function setBackgroundLoading(shouldLoad) {
Object.keys(tileLayers).forEach(name => {
const olLayer = tileLayers[name];
if (!olLayer) return;
olLayer.setVisibility(shouldLoad && sliderValues[name] > 0);
});
if (trafficLayerRef) {
trafficLayerRef.setVisibility(shouldLoad && sliderValues["Traffic"] > 0);
}
if (mapillaryDiv && mapillaryIframe) {
if (shouldLoad && sliderValues["Mapillary"] > 0) {
updateMapillaryIframe();
} else {
mapillaryIframe.src = "about:blank";
}
}
}
function initTrafficLayer() {
trafficLayerRef = new OpenLayers.Layer.XYZ(
"Google Traffic",
"https://mt1.google.com/vt?lyrs=h@159000000,traffic&hl=en&x=${x}&y=${y}&z=${z}",
{ isBaseLayer: false, opacity: 0.00, visibility: false }
);
W.map.addLayer(trafficLayerRef);
}
function updateMapillaryIframe() {
const center = W.map.getCenter();
const zoom = W.map.getZoom();
const lonlat = new OpenLayers.LonLat(center.lon, center.lat);
lonlat.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
const lat = parseFloat(lonlat.lat.toFixed(6));
const lon = parseFloat(lonlat.lon.toFixed(6));
mapillaryIframe.src = `https://www.mapillary.com/app/?lat=${lat}&lng=${lon}&z=${zoom}`;
}
function initMapillaryLayer() {
mapillaryDiv = document.createElement("div");
mapillaryDiv.style.position = 'absolute';
mapillaryDiv.style.top = '0';
mapillaryDiv.style.left = '0';
mapillaryDiv.style.right = '0';
mapillaryDiv.style.bottom = '0';
mapillaryDiv.style.zIndex = '400';
mapillaryDiv.style.opacity = '0';
mapillaryDiv.style.pointerEvents = 'none';
mapillaryIframe = document.createElement("iframe");
mapillaryIframe.style.width = '100%';
mapillaryIframe.style.height = '100%';
mapillaryIframe.style.border = 'none';
// Start blank — no request fires until the panel is opened and the
// Mapillary slider is actually moved above 0.
mapillaryIframe.src = "about:blank";
mapillaryDiv.appendChild(mapillaryIframe);
W.map.olMap.getViewport().appendChild(mapillaryDiv);
WazeWrap.Events.register('moveend', null, () => {
if (isSliderVisible && sliderValues["Mapillary"] > 0) updateMapillaryIframe();
});
WazeWrap.Events.register('zoomend', null, () => {
if (isSliderVisible && sliderValues["Mapillary"] > 0) updateMapillaryIframe();
});
}
setTimeout(initOverlay, 2000);
})();