WME overlay for HR, based on WME Map Overlay script
// ==UserScript==
// @name WME Overlay HR maps
// @namespace https://waze.com
// @version 1.8
// @description WME overlay for HR, based on WME Map Overlay script
// @match https://www.waze.com/*editor*
// @grant none
// @license MIT
// @author makunasis
// ==/UserScript==
(function () {
'use strict';
let sliderContainer, isSliderVisible = false;
let wazeLiveLayer, googleBaseLayer, osmLayer, trafficLayerRef, bingTrafficLayer;
// ─── Coordinate helpers ───────────────────────────────────────────────────
function wgs84ToHTRS96(lon, lat) {
const deg2rad = Math.PI / 180;
const a = 6378137.0;
const f = 1 / 298.257222101;
const k0 = 0.9999;
const lon0 = 16.5 * deg2rad;
const x0 = 500000.0;
const y0 = 0.0;
const phi = lat * deg2rad;
const lam = lon * deg2rad;
const e2 = f * (2 - f);
const n = f / (2 - f);
const n2 = n * n;
const n4 = n2 * n2;
const A = (a / (1 + n)) * (1 + n2 / 4 + n4 / 64);
const alpha = [
0,
(1/2)*n - (2/3)*n2 + (5/16)*Math.pow(n, 3),
(13/48)*n2 - (3/5)*Math.pow(n, 3),
(61/240)*Math.pow(n, 3)
];
const t = Math.sinh(
Math.atanh(Math.sin(phi)) -
(2 * Math.sqrt(n) / (1 + n)) * Math.atanh(2 * Math.sqrt(n) / (1 + n) * Math.sin(phi))
);
const xi_p = Math.atan(t / Math.cos(lam - lon0));
const eta_p = Math.atanh(Math.sin(lam - lon0) / Math.sqrt(1 + t * t));
let xi = xi_p, eta = eta_p;
for (let j = 1; j <= 3; j++) {
xi += alpha[j] * Math.sin(2 * j * xi_p) * Math.cosh(2 * j * eta_p);
eta += alpha[j] * Math.cos(2 * j * xi_p) * Math.sinh(2 * j * eta_p);
}
return {
east: (x0 + k0 * A * eta).toFixed(8),
north: (y0 + k0 * A * xi ).toFixed(8)
};
}
function getMapCoords() {
const center = W.map.getCenter();
const zoom = W.map.getZoom();
const lonlat = new OpenLayers.LonLat(center.lon, center.lat).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));
return { lat, lon, zoom, htrs: wgs84ToHTRS96(lon, lat) };
}
// ─── URL builders ─────────────────────────────────────────────────────────
const urlBuilders = {
Google: ({ lat, lon, zoom }) =>
`https://www.google.com/maps/@${lat},${lon},${zoom}z`,
OSM: ({ lat, lon, zoom }) =>
`https://www.openstreetmap.org/#map=${zoom}/${lat}/${lon}`,
Mapillary: ({ lat, lon, zoom }) =>
`https://www.mapillary.com/app/?lat=${lat}&lng=${lon}&z=${zoom}`,
AppleMaps: ({ lat, lon, zoom }) =>
`https://lookmap.eu.pythonanywhere.com/#c=${zoom}/${lat}/${lon}`,
HAK: ({ lat, lon, zoom }) =>
`https://map.hak.hr/?lang=hr&z=${zoom}&c=${lat},${lon}`,
HC: ({ zoom, htrs }) => {
const hcZoom = (zoom - 3.4).toFixed(1);
return `https://geoportal.hrvatske-ceste.hr/gis?c=${htrs.east}%2C${htrs.north}&so&z=${hcZoom}`;
},
DGU: ({ zoom, htrs }) => {
const dguZoom = Math.min(Math.max(zoom + 1, 1), 21);
const layers = "DOF5_2023_2024,DKP_CESTICE,DKP_KATASTARSKE_OPCINE,zupanija,ulica,kucni_broj";
return `https://oss.uredjenazemlja.hr/map?center=${htrs.east},${htrs.north}&zoom=${dguZoom}&layers=${layers}`;
},
Kamere: ({ lat, lon, zoom }) =>
`https://www.google.com/maps/d/u/0/viewer?mid=1W8NNPa3GwVfPZnGRlu-ZNlTJUrJYbmDi&ll=${lat},${lon}&z=${zoom}`,
"Waze Live": ({ lat, lon, zoom }) =>
`https://www.waze.com/live-map/directions?latlng=${lat}%2C${lon}&zoom=${zoom}`,
BingTraffic: ({ lat, lon, zoom }) =>
`https://www.bing.com/maps/traffic?v=2&FORM=Trafi2&cp=${lat}~${lon}&lvl=${zoom}&sty=h&form=LMLTEW&style=h`,
};
// ─── Layer factory ────────────────────────────────────────────────────────
const makeLayerOptions = (extra = {}) => ({
isBaseLayer: false,
opacity: 0.0,
visibility: false,
displayInLayerSwitcher: false,
transitionEffect: 'resize',
buffer: 0,
...extra
});
// ─── Init ─────────────────────────────────────────────────────────────────
function initOverlay() {
if (document.getElementById('wme-overlay-hr-btn')) return;
const mapElement = document.getElementById('map');
if (!mapElement) {
console.warn("WME Overlay HR: #map element not found.");
return;
}
const map = W.map;
// Build layers
googleBaseLayer = new OpenLayers.Layer.XYZ("Google Maps",
"https://mt${s}.google.com/vt/lyrs=m&x=${x}&y=${y}&z=${z}",
makeLayerOptions({
serverResolutions: [156543.0339, 78271.51695, 39135.758475, 19567.8792375, 9783.93961875, 4891.969809375, 2445.9849046875, 1222.99245234375, 611.496226171875, 305.7481130859375, 152.87405654296875, 76.43702827148438, 38.21851413574219, 19.109257067871094, 9.554628533935547, 4.777314266967773, 2.3886571334838865, 1.1943285667419433, 0.5971642833709716, 0.2985821416854858],
getURL: function (bounds) {
const res = this.getServerResolution();
const x = Math.round((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
const y = Math.round((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
const z = this.getServerZoom();
const s = Math.abs(x + y) % 4;
return this.url.replace("${s}", s).replace("${x}", x).replace("${y}", y).replace("${z}", z);
}
})
);
osmLayer = new OpenLayers.Layer.XYZ("OpenStreetMap", "https://tile.openstreetmap.org/${z}/${x}/${y}.png", makeLayerOptions());
wazeLiveLayer = new OpenLayers.Layer.XYZ("Waze Live Map", "https://worldtiles1.waze.com/tiles/${z}/${x}/${y}.png", makeLayerOptions());
trafficLayerRef = new OpenLayers.Layer.XYZ("Google Traffic", "https://mt1.google.com/vt?lyrs=h@159000000,traffic&hl=en&x=${x}&y=${y}&z=${z}", makeLayerOptions());
bingTrafficLayer = new OpenLayers.Layer.XYZ("Bing Satellite",
"https://ecn.t${s}.tiles.virtualearth.net/tiles/h${quadkey}.jpeg?g=12361&mkt=en-hr&shading=hill&stl=h&trfc=1&it=G,L,TR&n=z",
makeLayerOptions({
sphericalMercator: true,
getURL: function (bounds) {
const res = this.getServerResolution();
const x = Math.round((bounds.left - this.maxExtent.left) / (res * this.tileSize.w));
const y = Math.round((this.maxExtent.top - bounds.top) / (res * this.tileSize.h));
const z = this.getServerZoom();
let quadKey = "";
for (let i = z; i > 0; i--) {
let digit = 0;
const mask = 1 << (i - 1);
if ((x & mask) !== 0) digit++;
if ((y & mask) !== 0) digit += 2;
quadKey += digit;
}
const s = Math.abs(x + y) % 8;
return this.url.replace("${s}", s).replace("${quadkey}", quadKey);
}
})
);
map.addLayers([wazeLiveLayer, osmLayer, googleBaseLayer, trafficLayerRef, bingTrafficLayer]);
// ─── UI ───────────────────────────────────────────────────────────────
sliderContainer = document.createElement("div");
sliderContainer.id = 'wme-overlay-hr-container';
Object.assign(sliderContainer.style, {
position: "absolute", top: "70px", left: "50%", transform: "translateX(-50%)",
zIndex: "9999", padding: "8px", background: "rgba(10, 25, 50, 0.95)",
borderRadius: "10px", border: "1px solid #444", display: "none",
flexDirection: "row", gap: "8px", boxShadow: "0 4px 15px rgba(0,0,0,0.5)",
maxWidth: "98vw", overflowX: "auto", whiteSpace: "nowrap"
});
const items = [
{ name: "Waze Live", label: "Waze Live", icon: "https://www.waze.com/favicon.ico", layer: wazeLiveLayer },
{ name: "OSM", label: "OSM", icon: "https://www.openstreetmap.org/favicon.ico", layer: osmLayer },
{ name: "Google", label: "GoogleMaps", icon: "https://www.google.com/favicon.ico", layer: googleBaseLayer },
{ name: "Traffic", label: "Promet/Mjesta", icon: "https://i.ibb.co/rK09xy0d/traffic-layer.jpg", layer: trafficLayerRef },
{ name: "BingTraffic", label: "Bing", icon: "https://www.bing.com/sa/simg/favicon-2x.ico", layer: bingTrafficLayer },
{ name: "HC", label: "HC Geoportal", icon: "https://hrvatske-ceste.hr/assets/icons/logo-fb6868f9e3d22fc99c8493898fe96538d22a818a92bfa96f76e9c0d12f1a256e.png", isJumpOnly: true },
{ name: "DGU", label: "Uredjenazemlja", icon: "https://oss.uredjenazemlja.hr/assets/images/coat.png", isJumpOnly: true },
{ name: "Mapillary", label: "Mapillary", icon: "https://play-lh.googleusercontent.com/z3qzEc13E2sDWky9LgqADojcdy8hrX_szuAAeX21k_dFe7GNXLIYXJtOu5RcE3_5Jz8", isJumpOnly: true },
{ name: "AppleMaps", label: "AppleMaps", icon: "https://www.apple.com/favicon.ico", isJumpOnly: true },
{ name: "HAK", label: "HAK", icon: "https://map.hak.hr/favicon.ico", isJumpOnly: true },
{ name: "Kamere", label: "Kamere", icon: "https://web-assets.waze.com/discuss/prod/original/3X/d/4/d4a27d15ea2e5cbbac8666cb46c82d83cd6fa8bd.svg", isJumpOnly: true }
];
items.forEach(item => {
const wrapper = document.createElement("div");
Object.assign(wrapper.style, {
display: "inline-flex", flexDirection: "column", alignItems: "center",
gap: "3px", width: "70px", flexShrink: "0"
});
const img = document.createElement("img");
img.src = item.icon;
img.onerror = function () { this.src = "https://www.waze.com/favicon.ico"; this.onerror = null; };
Object.assign(img.style, {
width: "40px", height: "40px", borderRadius: "6px", border: "1px solid white",
cursor: "pointer", backgroundColor: "white", objectFit: "contain",
flexShrink: "0", padding: "4px"
});
img.onclick = () => {
const builder = urlBuilders[item.name];
if (builder) window.open(builder(getMapCoords()), '_blank');
};
const textLabel = document.createElement("div");
textLabel.textContent = item.label;
Object.assign(textLabel.style, {
fontSize: "9px", color: "white", fontWeight: "bold",
textAlign: "center", width: "100%", overflow: "hidden"
});
wrapper.appendChild(img);
wrapper.appendChild(textLabel);
if (!item.isJumpOnly) {
const slider = document.createElement("input");
slider.type = "range"; slider.min = "0"; slider.max = "1";
slider.step = "0.05"; slider.value = "0";
slider.style.width = "60px";
slider.style.height = "4px";
slider.oninput = () => {
const val = parseFloat(slider.value);
item.layer.setVisibility(val > 0);
item.layer.setOpacity(val);
};
wrapper.appendChild(slider);
} else {
const spacer = document.createElement("div");
spacer.style.height = "16px";
wrapper.appendChild(spacer);
}
sliderContainer.appendChild(wrapper);
});
const toggleBtn = document.createElement("button");
toggleBtn.id = 'wme-overlay-hr-btn';
toggleBtn.textContent = "Overlay HR";
Object.assign(toggleBtn.style, {
position: "absolute", top: "10px", left: "50%", transform: "translateX(-50%)",
zIndex: "9999", padding: "5px 12px", backgroundColor: "#0074D9",
color: "white", border: "1px solid white", borderRadius: "4px",
cursor: "pointer", fontWeight: "bold", fontSize: "12px"
});
toggleBtn.onclick = () => {
isSliderVisible = !isSliderVisible;
sliderContainer.style.display = isSliderVisible ? "flex" : "none";
};
mapElement.appendChild(toggleBtn);
mapElement.appendChild(sliderContainer);
console.log("WME Overlay HR: initialized successfully.");
}
// ─── Modern WME ready detection ───────────────────────────────────────────
if (W?.userscripts?.state.isReady) {
initOverlay();
} else {
document.addEventListener("wme-ready", initOverlay, { once: true });
}
})();