Affiche les limites des villes et départements français
// ==UserScript==
// @name WME Draw Borders France
// @version 2026.04.23.001
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAABCJJREFUWIWtl09oHFUcx7/fN7vJ1jSlrUp2c2oCPRjddGcGoV5sqBSk0IKCh4JF0RwC2grVIP6B1pOIIJWIlygIWkEsUvVYWrGnXmYmzcqqKK0HkdWW7aFKd5PZ9/WQ3WWzyW4nyf5gYd7v/d77fub33vzePiKheZ53GsCJRnMuDMN3ko7tZU6SINd1jxtjPiR5X+M3lc1mr5fL5cWtApgkQSQPreN70ff93FYBEmUgm80WSB7oANgj6ZVcLrc/l8vFQ0NDv1cqlfpGAVJJgkjebm9LugbgNwBHSB4GcHjHjh0V3/e/qtfr3xhjTgI4BOCGpBNRFF3qOve9xPP5/Hg6nb4GYIjkrKQoDMMrAOKJiYndmUzmGIDnAfgk18wn6Q7J8SAIbm0GgJ7nXSY5Za09F0XRs90CJycnH3Ec5zmSp0iu2lvW2qNRFH2/3riem9B13ZdITkkq12q1k71iFxcXf4qiaBbAr+1+SarX6ze6jesKkM/nx0m+K0nW2plSqVTpBdAmeELSnTbXv3Ec/7VRAKbT6U9Jbpf05cLCwrdJxAEgiqJLJMettUcl/UhyOJPJvN8tft094Lruy8aYOUnlarX6cNK377RCobDXGLMIYFDSVBRFVzpj1tSBfD4/7jjOeQBpa+3xYrG4sBlxACiXy5XR0VFD8iCA/bt27Zq/efPmqlrRWgLP8057nndrYGDgl82kvpvdvXv3PUk/k3xo27Ztr3f2G2Cl1pM8Q/J+AOlG3+WtigNAqVRakjQjyUp6s1Ao7F0D0KXWT/UDAAAaa/8ZyYwxZt513SO+7z/QApC05jtdz7cVq1ars42qeMAY852k667rPmEAII7js5Jam03SQhzHZ/sJkEqlRgFsb7ZJDpOcMwBQLBZvA3gVAKy1V8MwfLTh65s5jjO2zlkx1voKrLUjDbI/AMT9FAeApaWlBUnqcF9sHcckswAg6e9+iwPA4ODgGZKUVG2Kx3E83Q4wAgDGmHK/xX3fnwbwgqT/SD4WBEGx2dcCkDRCEtbavmbA8zxf0tyKhGbCMCy297f2QDMD/VyCiYmJ3QDOk8xI+jiKoi86Y9YAGGP6BWAymcw5knustVdrtdqpdYOaD5KyALC8vNwXANd1T5N8UtI/cRw/UyqVlnoBkOSDkjQwMLBlgEKhcJjk25JiAMeKxeKf3WJTAJDP58ewcghVgiBY3qRuyvO8xyXtJDnf+F/4VhAEPQ81ep53EMAFksOSRPKDIAhe24jyvn37djqO8wPJQpv7QhAETwPoLD6rqQF8RHIYWFkHSadc1x0HkDgTJMc6xGGtXbyXeBNgrGMyknwqqXgvqCRxKQAXARxpOhpH5gw2kAFrrWuMeaPdJ+liIoA4jqdTqdQnaLtKhWHY9SrVxb72PK+Gtut7FEWfJxn4P1GVx29d/9ycAAAAAElFTkSuQmCC
// @description Affiche les limites des villes et départements français
// @author Sebiseba & Electrochock1974
// @copyright Sebiseba 2017-2026 (Inspired by Draw Border - ©giovanni-cortinovis)
// @include https://www.waze.com/*/editor*
// @include https://beta.waze.com/*/editor*
// @match https://www.waze.com/*/editor*
// @match https://beta.waze.com/*/editor*
// @exclude https://www.waze.com/*/user*
// @grant GM_xmlhttpRequest
// @connect api.wazefrance.com
// @connect radars.securite-routiere.gouv.fr
// @connect signalement.adresse.data.gouv.fr
// @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @namespace Sebiseba
// ==/UserScript==
(function () {
const SCRIPT_NAME = "WME Draw Borders France";
var DBFhandleClass, DBFhandleClass2, CitiesOld = [], StatesOld = [], StatesPROld = [], Cty_Layer = [], Dpt_Layer = [], PR_Layer = [], Cam_Layer = [], Ban_Layer = [], Equip_Layer = [], Etabl_Layer = [], RS_Layer = [], Bump_Layer = [], Signals_Layer = [], debug = false, wmeSDK;
var icon_DrB = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAOxAAADsQBlSsOGwAABCJJREFUWIWtl09oHFUcx7/fN7vJ1jSlrUp2c2oCPRjddGcGoV5sqBSk0IKCh4JF0RwC2grVIP6B1pOIIJWIlygIWkEsUvVYWrGnXmYmzcqqKK0HkdWW7aFKd5PZ9/WQ3WWzyW4nyf5gYd7v/d77fub33vzePiKheZ53GsCJRnMuDMN3ko7tZU6SINd1jxtjPiR5X+M3lc1mr5fL5cWtApgkQSQPreN70ff93FYBEmUgm80WSB7oANgj6ZVcLrc/l8vFQ0NDv1cqlfpGAVJJgkjebm9LugbgNwBHSB4GcHjHjh0V3/e/qtfr3xhjTgI4BOCGpBNRFF3qOve9xPP5/Hg6nb4GYIjkrKQoDMMrAOKJiYndmUzmGIDnAfgk18wn6Q7J8SAIbm0GgJ7nXSY5Za09F0XRs90CJycnH3Ec5zmSp0iu2lvW2qNRFH2/3riem9B13ZdITkkq12q1k71iFxcXf4qiaBbAr+1+SarX6ze6jesKkM/nx0m+K0nW2plSqVTpBdAmeELSnTbXv3Ec/7VRAKbT6U9Jbpf05cLCwrdJxAEgiqJLJMettUcl/UhyOJPJvN8tft094Lruy8aYOUnlarX6cNK377RCobDXGLMIYFDSVBRFVzpj1tSBfD4/7jjOeQBpa+3xYrG4sBlxACiXy5XR0VFD8iCA/bt27Zq/efPmqlrRWgLP8057nndrYGDgl82kvpvdvXv3PUk/k3xo27Ztr3f2G2Cl1pM8Q/J+AOlG3+WtigNAqVRakjQjyUp6s1Ao7F0D0KXWT/UDAAAaa/8ZyYwxZt513SO+7z/QApC05jtdz7cVq1ars42qeMAY852k667rPmEAII7js5Jam03SQhzHZ/sJkEqlRgFsb7ZJDpOcMwBQLBZvA3gVAKy1V8MwfLTh65s5jjO2zlkx1voKrLUjDbI/AMT9FAeApaWlBUnqcF9sHcckswAg6e9+iwPA4ODgGZKUVG2Kx3E83Q4wAgDGmHK/xX3fnwbwgqT/SD4WBEGx2dcCkDRCEtbavmbA8zxf0tyKhGbCMCy297f2QDMD/VyCiYmJ3QDOk8xI+jiKoi86Y9YAGGP6BWAymcw5knustVdrtdqpdYOaD5KyALC8vNwXANd1T5N8UtI/cRw/UyqVlnoBkOSDkjQwMLBlgEKhcJjk25JiAMeKxeKf3WJTAJDP58ewcghVgiBY3qRuyvO8xyXtJDnf+F/4VhAEPQ81ep53EMAFksOSRPKDIAhe24jyvn37djqO8wPJQpv7QhAETwPoLD6rqQF8RHIYWFkHSadc1x0HkDgTJMc6xGGtXbyXeBNgrGMyknwqqXgvqCRxKQAXARxpOhpH5gw2kAFrrWuMeaPdJ+liIoA4jqdTqdQnaLtKhWHY9SrVxb72PK+Gtut7FEWfJxn4P1GVx29d/9ycAAAAAElFTkSuQmCC";
console.log('WME Draw Borders France : ' + GM_info.script.version + ' starting');
// --- CLUSTER PANNEAUX (globals) ---
var RS_pending = [];
var RS_CLUSTER_THRESH_M = 5;
var Cty_LastZoom = null;
var Cty_BboxOld = null;
var lastMouseLL = null, lastMouseTS = 0;
// --- Debounce utility (P1 audit: reduce redundant API calls) ---
function DBF_debounce(func, wait) {
let timeout;
return function () {
const context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
const debounced_show_border = DBF_debounce(function () { show_border(); }, 400);
const debounced_load_ban = DBF_debounce(function () { load_ban(); }, 400);
const debounced_load_bump = DBF_debounce(function () { load_bump(); }, 400);
const debounced_load_signals = DBF_debounce(function () { load_signals(); }, 400);
// --- Wrapper universel pour l'étendue de la carte (SDK-first) ---
function DBF_getMapExtent() {
// Tente le SDK officiel WME (wmeSDK initialisé dans DBFstep1)
try {
if (wmeSDK && wmeSDK.Map && typeof wmeSDK.Map.getMapExtent === 'function') {
return wmeSDK.Map.getMapExtent();
}
} catch (e) { }
// Fallback legacy
if (typeof W !== 'undefined' && W.map && typeof W.map.getExtent === 'function') {
return W.map.getExtent();
}
return null;
}
// --- Smart Diffing: Spatial Garbage Collector ---
// Supprime uniquement les features hors du viewport actuel
var _featureIndex = new WeakMap();
function _getFeatureSet(layer) {
var s = _featureIndex.get(layer);
if (!s) { s = new Set(); _featureIndex.set(layer, s); }
return s;
}
function DBF_cleanupLayer(layer) {
if (!layer || !layer.features || layer.features.length === 0) return;
const extent = DBF_getMapExtent();
if (!extent) return;
const featuresToRemove = [];
for (let i = 0; i < layer.features.length; i++) {
const feat = layer.features[i];
if (feat.geometry) {
const b = feat.geometry.getBounds();
if (b) {
const isOutside = (b.left > extent.right || b.right < extent.left || b.bottom > extent.top || b.top < extent.bottom);
if (isOutside) {
featuresToRemove.push(feat);
}
}
}
}
if (featuresToRemove.length > 0) {
var set = _getFeatureSet(layer);
for (var i = 0; i < featuresToRemove.length; i++) {
if (featuresToRemove[i].id) set.delete(featuresToRemove[i].id);
}
layer.destroyFeatures(featuresToRemove);
}
}
function DBF_clearFeatureIndex(layer) {
if (!layer) return;
var s = _featureIndex.get(layer);
if (s) s.clear();
}
// Vérifie si une feature avec cet ID existe déjà dans le layer — O(1)
function DBF_hasFeature(layer, id) {
if (!layer || !id) return false;
return _getFeatureSet(layer).has(id);
}
function DBF_registerFeature(layer, id) {
if (!layer || !id) return;
_getFeatureSet(layer).add(id);
}
// === Config API updates ===
const API_BASE = 'https://api.wazefrance.com';
const UPDATES_ENDPOINT = API_BASE + '/updates';
window.adresses_list = [];
function getId(node) { return document.getElementById(node); }
function getElementsByClassName(classname, node) {
node || (node = document.getElementsByTagName('body')[0]);
for (var a = [], re = new RegExp('\\b' + classname + '\\b'), els = node.getElementsByTagName('*'), i = 0, j = els.length; i < j; i++) { re.test(els[i].className) && a.push(els[i]); }
return a;
}
function isJsonString(str) {
try { JSON.parse(str); }
catch (e) { return false; }
return true;
}
function deltaDate(adresseDate) {
let diffTime = Math.abs(new Date().valueOf() - new Date(adresseDate).valueOf());
let days = diffTime / (24 * 60 * 60 * 1000);
let hours = (days % 1) * 24;
let minutes = (hours % 1) * 60;
[days, hours, minutes] = [Math.floor(days), Math.floor(hours), Math.floor(minutes)]
var delta = days + 'j ' + hours + 'h ' + minutes + 'm';
return delta;
}
function addScriptsMenu() {
if (typeof getElementsByClassName('collapsible-GROUP__SCRIPTS', getId('layer-switcher-region'))[0] != 'object') {
if ('undefined' === typeof localStorage.posScriptMenu) { localStorage.setItem('posScriptMenu', '["top"]'); }
var menuParent = getElementsByClassName('togglers', getId('layer-switcher-region'))[0];
var scriptMenu = document.createElement('li');
scriptMenu.className = "group";
var scriptMenuContent = document.createElement('div');
scriptMenuContent.className = 'layer-switcher-toggler-tree-category';
scriptMenuContent.innerHTML = '<wz-button id="developScript" color="clear-icon" size="xs"><i class="toggle-category w-icon w-icon-caret-down"></i></wz-button>' +
'<wz-toggle-switch disabled="false" checked id="layer-switcher-group__scripts" class="layer-switcher-group__scripts" tabindex="0" name="" value=""></wz-toggle-switch>' +
'<label class="label-text" for="layer-switcher-group__scripts">Scripts</label>';
scriptMenu.appendChild(scriptMenuContent);
var groupScripts = document.createElement('ul');
groupScripts.className = "collapsible-GROUP__SCRIPTS";
scriptMenu.appendChild(groupScripts);
if (JSON.parse(localStorage.posScriptMenu)[0] == 'top') { menuParent.insertBefore(scriptMenu, menuParent.firstChild); }
else { menuParent.appendChild(scriptMenu); }
getId('developScript').addEventListener('click', function (e) {
if (groupScripts.className == 'collapsible-GROUP__SCRIPTS') {
groupScripts.className = 'collapsible-GROUP__SCRIPTS collapse-layer-switcher-group';
this.innerHTML = '<i class="toggle-category w-icon w-icon-caret-down upside-down"></i>';
} else {
groupScripts.className = 'collapsible-GROUP__SCRIPTS';
this.innerHTML = '<i class="toggle-category w-icon w-icon-caret-down"></i>';
}
});
getId('layer-switcher-group__scripts').addEventListener('click', function (e) {
if (groupScripts.className == 'collapsible-GROUP__SCRIPTS') {
groupScripts.className = 'collapsible-GROUP__SCRIPTS collapse-layer-switcher-group';
getId('developScript').innerHTML = '<i class="toggle-category w-icon w-icon-caret-down upside-down"></i>';
} else {
groupScripts.className = 'collapsible-GROUP__SCRIPTS';
getId('developScript').innerHTML = '<i class="toggle-category w-icon w-icon-caret-down"></i>';
}
});
var lng = I18n.locale;
if (lng == 'fr') { var title = "Position menu des scripts", top = "En haut", bottom = "En bas"; }
else if (lng == 'es') { var title = "Posición del menú de script", top = "En alto", bottom = "Abajo"; }
else { var title = "Scripts menu position", top = "On top", bottom = "On bottom"; }
var optionPosMenu = document.createElement('div');
optionPosMenu.className = 'settings__form-group';
optionPosMenu.innerHTML = '<wz-label html-for="">' + title + '</wz-label><span style="padding-right:15px;">' + bottom + ' </span><wz-toggle-switch name="posScriptMenu" id="posScriptMenu" checked=' + (JSON.parse(localStorage.posScriptMenu)[0] == 'top' ? "true" : "false") + ' class="alert-settings-visibility-toggle" tabindex="0" value=""> ' + top + '<input type="checkbox" name="posScriptMenu" value="" style="display: none; visibility: hidden;"></wz-toggle-switch>';
if ('undefined' === typeof localStorage.posScriptMenu || !isJsonString(localStorage.posScriptMenu)) { localStorage.setItem('posScriptMenu', '[]'); }
}
}
function addTooltipToCheckbox(checkboxId, tooltipText) {
var checkbox = document.getElementById(checkboxId);
if (!checkbox) return;
try {
// Conteneur flex pour aligner texte + icône
var container = document.createElement('div');
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.justifyContent = 'space-between';
var textSpan = document.createElement('span');
textSpan.textContent = checkbox.textContent;
checkbox.textContent = '';
// WME Beta exige : <wz-tooltip> → <wz-tooltip-source> + <wz-tooltip-content>
var tooltipEl = document.createElement('wz-tooltip');
tooltipEl.setAttribute('position', 'bottom');
var tooltipSource = document.createElement('wz-tooltip-source');
var infoIcon = document.createElement('i');
infoIcon.className = 'w-icon w-icon-info layer-selector-info-icon';
infoIcon.title = tooltipText; // fallback natif si le web component échoue
tooltipSource.appendChild(infoIcon);
var tooltipContent = document.createElement('wz-tooltip-content');
tooltipContent.textContent = tooltipText;
tooltipEl.appendChild(tooltipSource);
tooltipEl.appendChild(tooltipContent);
container.appendChild(textSpan);
container.appendChild(tooltipEl);
checkbox.appendChild(container);
} catch (e) {
// Fallback ultra-simple : juste le title natif
console.warn("DBF: Tooltip web component failed, using native title.", e);
checkbox.title = tooltipText;
}
}
function DBFstep1() {
if (typeof (W.map) === 'undefined') { window.setTimeout(DBFstep1, 500); return; }
if (typeof (W.model) === 'undefined') { window.setTimeout(DBFstep1, 500); return; }
if (typeof (OpenLayers) === 'undefined') { window.setTimeout(DBFstep1, 500); return; }
if (document.querySelector('.togglers') === null) { window.setTimeout(DBFstep1, 500); return; }
var DBFhandle = getId("user-info"); if (typeof (DBFhandle) === 'undefined') { window.setTimeout(DBFstep1, 500); return; }
DBFhandleClass = getElementsByClassName("nav-tabs", DBFhandle)[0]; if (typeof (DBFhandleClass) === 'undefined') { window.setTimeout(DBFstep1, 500); return; }
DBFhandleClass2 = getElementsByClassName("tab-content", DBFhandle)[0]; if (typeof (DBFhandleClass2) === 'undefined') { window.setTimeout(DBFstep1, 500); return; }
wmeSDK = getWmeSdk({ scriptId: "dbf_sdk", scriptName: "Draw Borders France" });
try {
wmeSDK.Shortcuts.createShortcut({
callback: () => {
document.getElementById('layerBanVisib').click();
},
description: "Activer/Désactiver le calque BAN",
shortcutId: "ban_switch",
shortcutKeys: "C+B",
});
} catch (e) {
console.warn("DBF - Raccourci 'ban_switch' (C+B) ignoré :", e.message);
}
try {
wmeSDK.Shortcuts.createShortcut({
callback: () => {
},
description: "Autofill BAN : Ctrl + M avec la souris sur le point du numéro souhaité (LVL 5)",
shortcutId: "ban_autofill",
shortcutKeys: "C+M",
});
} catch (e) {
console.warn("DBF - Raccourci 'ban_autofill' (C+M) ignoré :", e.message);
}
try {
wmeSDK.Shortcuts.createShortcut({
callback: () => {
DBF_autofillAddress();
},
description: "Autofill adresse BAN (rue/ville) : Ctrl+Shift+M avec la souris sur l'adresse souhaitée",
shortcutId: "ban_address_autofill",
shortcutKeys: "CS+M",
});
} catch (e) {
console.warn("DBF - Raccourci 'ban_address_autofill' (CS+M) ignoré :", e.message);
}
try {
wmeSDK.Shortcuts.createShortcut({
callback: () => {
wmeSDK.Map.setZoomLevel({ zoomLevel: 19 });
},
description: "Zoom niveau 19",
shortcutId: "ban_zoom_level",
shortcutKeys: "A+97",
});
} catch (e) {
console.warn("DBF - Raccourci 'ban_zoom_level' (A+97) ignoré :", e.message);
}
window.setTimeout(DBFstep2, 500);
}
// --- Injecteur de checkbox layer résilient (WazeWrap + fallback natif WME Beta) ---
function DBF_addWazeCheckbox(layerName, layerId, isChecked, callback) {
// Tentative WazeWrap (si disponible et fonctionnel)
try {
if (typeof WazeWrap !== 'undefined' && WazeWrap.Interface && typeof WazeWrap.Interface.AddLayerCheckbox === 'function') {
WazeWrap.Interface.AddLayerCheckbox("_scripts", layerName, isChecked, callback);
return;
}
} catch (e) { console.warn("DBF: WazeWrap.Interface.AddLayerCheckbox indisponible, fallback natif."); }
// Fallback natif WME Beta
var scriptsGroup = document.querySelector('.collapsible-GROUP__SCRIPTS');
if (!scriptsGroup) {
console.warn("DBF: Groupe Scripts introuvable dans le menu calques.");
return;
}
// Vérifie si la checkbox n'existe pas déjà
if (document.getElementById(layerId)) return;
var li = document.createElement('li');
li.innerHTML =
'<div class="layer-selector">' +
' <wz-checkbox id="' + layerId + '" name="" value="on"' + (isChecked ? ' checked="true"' : '') + '>' +
' <div class="layer-selector-container">' + layerName + '</div>' +
' </wz-checkbox>' +
'</div>';
scriptsGroup.appendChild(li);
var checkbox = li.querySelector('wz-checkbox');
if (checkbox) {
// Écoute le web component natif Waze
checkbox.addEventListener('change', function () {
if (typeof callback === 'function') callback(checkbox.checked);
});
// Sécurité : certains web components émettent 'click' au lieu de 'change'
checkbox.addEventListener('click', function () {
setTimeout(function () {
if (typeof callback === 'function') callback(checkbox.checked);
}, 0);
});
}
}
function DBFstep2() {
if ('undefined' === typeof localStorage.speedCamList || !isJsonString(localStorage.speedCamList)) { localStorage.setItem('speedCamList', '[]'); }
if ('undefined' === typeof localStorage.DBFsettings || !isJsonString(localStorage.DBFsettings)) { localStorage.setItem('DBFsettings', '{"optionDBF0":false,"optionDBF1":true,"optionDBF2":false,"optionDBF3":false,"optionDBF4":true,"optionDBF5":false,"optionDBF6":false}'); }
// WME Layers check
addScriptsMenu();
DBF_addWazeCheckbox("Limites des communes", "layer-switcher-item_dbf_communes", false, LayerVilToggled);
checklayer("__WME_Draw_Border_Cty");
DBF_addWazeCheckbox("Limites des départements", "layer-switcher-item_dbf_departements", false, LayerDepToggled);
checklayer("__WME_Draw_Border_Dpt");
DBF_addWazeCheckbox("Points Routiers", "layer-switcher-item_dbf_pr", false, LayerPRToggled);
checklayer("__WME_Draw_Border_PR");
DBF_addWazeCheckbox("Radars", "layer-switcher-item_dbf_radars", false, LayerCamToggled);
checklayer("__WME_Draw_Border_Cam");
DBF_addWazeCheckbox("Adresses BAN", "layer-switcher-item_dbf_ban", false, LayerBanToggled);
checklayer("__WME_Draw_Border_Ban");
addTooltipToCheckbox("layer-switcher-item_dbf_ban", "Affiche les adresses tirées de la BAN à partir du zoom 19.");
DBF_addWazeCheckbox("Équipements", "layer-switcher-item_dbf_equip", false, LayerEquipToggled);
checklayer("__WME_Draw_Border_Equip");
DBF_addWazeCheckbox("Établissements", "layer-switcher-item_dbf_etabl", false, LayerEtablToggled);
checklayer("__WME_Draw_Border_Etabl");
DBF_addWazeCheckbox("Panneaux routiers Icones", "layer-switcher-item_dbf_rs", false, LayerRSToggled);
checklayer("__WME_Draw_Border_RS");
DBF_addWazeCheckbox("Ralentisseurs OSM", "layer-switcher-item_dbf_bump", false, LayerBumpToggled);
checklayer("__WME_Draw_Border_Bump");
DBF_addWazeCheckbox("Feux tricolores OSM", "layer-switcher-item_dbf_signals", false, LayerSignalsToggled);
checklayer("__WME_Draw_Border_Signals");
// --- Enregistrement des événements (SDK-first avec fallback legacy) ---
function DBF_onMapEvent(sdkEventName, legacyEventName, callback) {
try {
if (wmeSDK && wmeSDK.Events && wmeSDK.Events.on) {
wmeSDK.Events.on({ eventName: sdkEventName, eventHandler: callback });
return;
}
} catch (e) { }
// Fallback legacy
try { W.map.events.register(legacyEventName, null, callback); } catch (e) { }
}
// moveend → MAP_MOVE_END (SDK)
DBF_onMapEvent("moveend", "moveend", debounced_load_bump);
DBF_onMapEvent("moveend", "moveend", debounced_load_signals);
DBF_onMapEvent("moveend", "moveend", debounced_show_border);
DBF_onMapEvent("moveend", "moveend", debounced_load_ban);
// zoomend → MAP_ZOOM_CHANGED (SDK)
DBF_onMapEvent("zoomend", "zoomend", debounced_load_bump);
DBF_onMapEvent("zoomend", "zoomend", debounced_load_signals);
DBF_onMapEvent("zoomend", "zoomend", debounced_show_border);
DBF_onMapEvent("zoomend", "zoomend", debounced_load_ban);
// mergeend — pas encore dans le SDK, legacy seulement
try { W.map.events.register("mergeend", null, debounced_load_bump); } catch (e) { }
try { W.map.events.register("mergeend", null, debounced_load_signals); } catch (e) { }
try { W.map.events.register("mergeend", null, debounced_show_border); } catch (e) { }
try { W.map.events.register("mergeend", null, debounced_load_ban); } catch (e) { }
// selectionchanged → SDK Events
try {
if (wmeSDK && wmeSDK.Events && wmeSDK.Events.on) {
wmeSDK.Events.on({ eventName: "selectionchanged", eventHandler: debounced_load_ban });
} else if (W && W.selectionManager && W.selectionManager.events) {
W.selectionManager.events.register('selectionchanged', null, debounced_load_ban);
}
} catch (e) { }
// actionManager — pas encore dans le SDK, legacy seulement
try { W.model.actionManager.events.register("afterclearactions", null, debounced_load_ban); } catch (e) { }
try { W.model.actionManager.events.register("afterundoaction", null, debounced_load_ban); } catch (e) { }
const banCheckboxEl = getId('layer-switcher-item_adresses_ban') || getId('layer-switcher-item_dbf_ban');
if (banCheckboxEl) {
banCheckboxEl.addEventListener('click', function (e) {
var _banCb = getId('layer-switcher-item_adresses_ban') || getId('layer-switcher-item_dbf_ban');
if (_banCb && _banCb.checked && $('#layer-switcher-item_house_numbers').prop('checked') === false) { $('#layer-switcher-item_house_numbers').click(); }
var _lbv = getId('layerBanVisib');
if (_lbv) _lbv.style.backgroundColor = (Ban_Layer?.visibility === true) ? '#bbffcc' : '#fac3c3';
load_ban();
});
}
insertScriptHTML();
load_radar(); // Just one time on launch
}
const DBF_DEFAULT_STYLES = {
city: {
strokeColor: "#ce00ce",
strokeWidth: 2,
fontSize: 12,
fontColor: "#000000",
showLabel: false,
fontBold: false,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 4
},
dep: {
strokeColor: "#b20000",
strokeWidth: 5,
fontSize: 24,
fontColor: "#b20000",
fontBold: true,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 4
},
ban: {
verifiedStroke: "#00dd00",
unverifiedStroke: "#ff8000",
textMissing: "#ff0000",
textExisting: "#0000ff",
strokeWidth: 10,
fontSize: 12,
fontBold: false,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 4
},
equip: {
strokeColor: "#2d792d",
strokeWidth: 10,
fontSize: 12,
fontColor: "#33ff33",
fontBold: false,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 3
},
etabl: {
strokeColor: "#690069",
strokeWidth: 10,
fontSize: 12,
fontColor: "#ff77ff",
fontBold: false,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 3
},
radar: {
strokeColor: "#ff0000",
strokeWidth: 10,
fontSize: 12,
fontColor: "#ffff00",
fontBold: false,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 3
},
pr: {
strokeColor: "#3380ff",
strokeWidth: 10,
fontSize: 12,
fontColor: "#33ffee",
fontBold: false,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 3
},
rs: { scale: 1.0 },
bump: {
fillColor: "#ff00ff",
strokeColor: "#ff00ff",
fillOpacity: 0.35,
strokeWidth: 2,
pointRadius: 6,
fontColor: "#000000",
fontSize: 10,
fontBold: false,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 3
},
signals: {
fillColor: "#00ccff",
strokeColor: "#00ccff",
fillOpacity: 0.35,
strokeWidth: 2,
pointRadius: 6,
fontColor: "#000000",
fontSize: 10,
fontBold: false,
labelOutlineColor: "#ffffff",
labelOutlineWidth: 3
}
};
var _dbfCachedStyles = null;
function DBF_getStyles() {
if (_dbfCachedStyles) return _dbfCachedStyles;
let stored = {};
try {
stored = JSON.parse(localStorage.DBFstyles || '{}');
} catch (e) {
stored = {};
}
// merge simple
const base = JSON.parse(JSON.stringify(DBF_DEFAULT_STYLES));
Object.keys(stored).forEach(layerKey => {
if (!base[layerKey]) base[layerKey] = {};
Object.assign(base[layerKey], stored[layerKey]);
});
_dbfCachedStyles = base;
return base;
}
function DBF_saveStyles(styles) {
localStorage.DBFstyles = JSON.stringify(styles);
_dbfCachedStyles = null;
}
function insertScriptHTML() {
// CSP-safe injection: textContent + head (WME Beta blocks innerHTML on <style>)
const DBF_STYLE_ID = "WME-DBF-Styles";
if (document.getElementById(DBF_STYLE_ID)) return; // Avoid duplicates
var Scss = document.createElement('style');
Scss.id = DBF_STYLE_ID;
Scss.type = 'text/css';
Scss.textContent = `
/* =============================================
DBF DESIGN SYSTEM — Premium Modern Theme
============================================= */
/* --- Design Tokens --- */
:root {
--dbf-bg: #ffffff;
--dbf-bg-subtle: #f8fafc;
--dbf-bg-hover: #f1f5f9;
--dbf-bg-active: #eef2ff;
--dbf-border: #e2e8f0;
--dbf-border-hover: #c7d2fe;
--dbf-text: #1e293b;
--dbf-text-secondary: #64748b;
--dbf-text-muted: #94a3b8;
--dbf-accent: #4f46e5;
--dbf-accent-light: #e0e7ff;
--dbf-accent-text: #3730a3;
--dbf-success: #059669;
--dbf-success-bg: #ecfdf5;
--dbf-success-border: #6ee7b7;
--dbf-warn: #c2410c;
--dbf-warn-bg: #fff7ed;
--dbf-warn-border: #fdba74;
--dbf-danger: #dc2626;
--dbf-danger-bg: #fef2f2;
--dbf-radius: 12px;
--dbf-radius-sm: 8px;
--dbf-shadow: 0 1px 3px rgba(15,23,42,.06), 0 1px 2px rgba(15,23,42,.04);
--dbf-shadow-hover: 0 4px 12px rgba(15,23,42,.08);
--dbf-transition: .2s cubic-bezier(.4,0,.2,1);
--dbf-font: 'Inter', 'Waze Sans', system-ui, -apple-system, sans-serif;
}
/* --- Global --- */
#dbf-panel {
padding: 8px;
font-family: var(--dbf-font);
color: var(--dbf-text);
line-height: 1.5;
width: 100%;
max-width: 100%;
overflow-x: hidden;
box-sizing: border-box;
word-break: break-word;
padding-top: 1px !important;
}
#dbf-panel * {
box-sizing: border-box;
}
.userscripts-api-docs-link-container {
display: none !important;
}
/* --- Header --- */
#dbf-header {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: var(--dbf-bg-subtle);
border-radius: var(--dbf-radius);
border: 1px solid var(--dbf-border);
margin-bottom: 14px;
transition: box-shadow var(--dbf-transition);
}
#dbf-header:hover {
box-shadow: var(--dbf-shadow);
}
#dbf-header .dbf-title {
font-size: 15px;
font-weight: 800;
letter-spacing: -0.01em;
}
#dbf-header .dbf-version {
font-size: 11px;
padding: 3px 10px;
border-radius: 999px;
background: var(--dbf-accent-light);
color: var(--dbf-accent-text);
font-weight: 600;
}
/* --- Cards --- */
.dbf-card {
background: var(--dbf-bg);
border-radius: var(--dbf-radius);
border: 1px solid var(--dbf-border);
margin: 10px 0;
overflow: visible !important;
box-shadow: var(--dbf-shadow);
transition: box-shadow var(--dbf-transition), border-color var(--dbf-transition);
}
.dbf-card:hover {
box-shadow: var(--dbf-shadow-hover);
border-color: var(--dbf-border-hover);
}
.dbf-card__head {
padding: 12px 16px;
border-bottom: 1px solid var(--dbf-border);
display: flex;
align-items: center;
justify-content: space-between;
background: var(--dbf-bg-subtle);
border-radius: var(--dbf-radius) var(--dbf-radius) 0 0;
}
.dbf-card__title {
font-size: 13px;
font-weight: 700;
display: flex;
align-items: center;
gap: 8px;
letter-spacing: -0.01em;
}
.dbf-card__body {
padding: 14px 16px;
animation: dbf-fadeIn .25s ease;
}
.dbf-sub {
font-size: 11px;
color: var(--dbf-text-secondary);
font-weight: 500;
}
/* --- Cards width control --- */
#dbf-panel .dbf-card,
#dbf-panel .dbf-card__head,
#dbf-panel .dbf-card__body {
width: 100%;
min-width: 0;
}
/* --- Chips / Badges --- */
.dbf-chip a {
background: var(--dbf-bg-subtle);
padding: 6px 12px;
border-radius: 999px;
border: 1px solid var(--dbf-border);
font-size: 12px;
text-decoration: none;
color: var(--dbf-text);
display: flex;
align-items: center;
gap: 6px;
transition: all var(--dbf-transition);
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.dbf-chip a:hover {
background: var(--dbf-bg-active);
border-color: var(--dbf-border-hover);
transform: translateY(-1px);
}
.dbf-chip { max-width: 100%; }
.dbf-chip.loading a {
background: var(--dbf-bg-active);
border-color: #93c5fd;
}
.dbf-chip.loading a .dbf-spinner {
width: 9px; height: 9px;
border-radius: 50%;
background: var(--dbf-accent);
animation: dbf-blink .75s infinite alternate;
}
/* --- Pills --- */
.dbf-pill {
background: var(--dbf-bg-subtle);
border: 1px solid var(--dbf-border);
padding: 7px 14px;
border-radius: var(--dbf-radius-sm);
font-size: 12px;
font-weight: 600;
cursor: pointer;
text-align: center;
transition: all var(--dbf-transition);
}
.dbf-pill:hover {
background: var(--dbf-bg-active);
transform: translateY(-1px);
}
.dbf-pill--ok {
background: var(--dbf-success-bg);
border-color: var(--dbf-success-border);
color: var(--dbf-success);
}
.dbf-pill--ko {
background: var(--dbf-warn-bg);
border-color: var(--dbf-warn-border);
color: var(--dbf-warn);
}
/* --- Legend dots --- */
.dbf-dot {
width: 10px; height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
box-shadow: 0 0 0 2px rgba(0,0,0,.08);
}
.dbf-dot--red { background: #ef4444; }
.dbf-dot--green{ background: #22c55e; }
.dbf-tag { color: #ea580c; font-weight: 500; }
.dbf-tag2 { color: #0284c7; font-weight: 500; }
/* --- Warning --- */
#dbf-warning {
background: var(--dbf-danger-bg);
border-left: 4px solid var(--dbf-danger);
padding: 10px 14px;
color: #7f1d1d;
border-radius: var(--dbf-radius-sm);
font-size: 12px;
line-height: 1.5;
margin-top: 10px;
}
/* --- Sub-tabs --- */
#dbf-subtabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 12px;
padding: 0 0 2px 0;
list-style: none;
border-bottom: 2px solid var(--dbf-border);
}
#dbf-subtabs .dbf-nav-item {
flex: 1;
min-width: 0;
}
#dbf-subtabs .dbf-nav-item a {
display: block;
padding: 6px 4px;
border-radius: var(--dbf-radius-sm) var(--dbf-radius-sm) 0 0;
background: var(--dbf-bg-subtle);
font-size: 11px;
font-weight: 600;
color: var(--dbf-text-secondary);
cursor: pointer;
user-select: none;
transition: all var(--dbf-transition);
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#dbf-subtabs .dbf-nav-item a:hover {
background: var(--dbf-bg-active);
color: var(--dbf-accent-text);
}
#dbf-subtabs .dbf-nav-item.active a {
background: var(--dbf-accent);
color: #fff;
}
/* --- Updates bar --- */
#dbf-updates-bar {
display: flex;
flex-wrap: wrap;
gap: 6px;
max-width: 100%;
}
/* --- KPIs / Legend grids --- */
.dbf-kpis {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
gap: 8px;
}
.dbf-kpis > *, .dbf-legend > * { min-width: 0; }
/* --- Options checkbox grid (R2) --- */
.dbf-options-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px 16px;
}
.dbf-options-grid wz-checkbox {
font-size: 12px;
line-height: 1.4;
}
.dbf-section-divider {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--dbf-text-muted);
margin: 12px 0 6px;
padding-bottom: 4px;
border-bottom: 1px solid var(--dbf-border);
grid-column: 1 / -1;
}
.dbf-section-divider:first-child { margin-top: 0; }
/* --- Icon Grid (panneaux) --- */
#rsLegendContainer > div {
border-radius: var(--dbf-radius-sm);
border: 1px solid var(--dbf-border);
background: var(--dbf-bg);
padding: 10px;
transition: all var(--dbf-transition);
}
#rsLegendContainer > div:hover {
background: var(--dbf-bg-subtle);
border-color: var(--dbf-border-hover);
box-shadow: var(--dbf-shadow);
}
/* --- WME integration --- */
#sidepanel-DrawBordersFR {
overflow: visible !important;
}
.dbf-tab-content .dbf-tab-pane { display: none; }
.dbf-tab-content .dbf-tab-pane.active { display: block; }
#sidepanel-DrawBordersFR .tab-content {
overflow: visible !important;
height: auto !important;
max-height: none !important;
}
#dbf-panel wz-tabs::part(container) {
background: transparent !important;
box-shadow: none !important;
border: none !important;
}
#dbf-panel wz-tab::part(button) {
background: transparent !important;
}
#dbf-panel wz-tab[selected]::part(button) {
background: var(--dbf-bg-hover) !important;
color: var(--dbf-text) !important;
font-weight: 600 !important;
}
#dbf-panel wz-tab:not([selected])::part(button) {
color: var(--dbf-text-secondary) !important;
}
/* --- Animations --- */
@keyframes dbf-fadeIn {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes dbf-blink {
from { opacity: .4; }
to { opacity: 1; }
}
/* --- Search results --- */
.dbf-results {
border: 1px solid var(--dbf-border);
background: var(--dbf-bg);
max-height: 200px;
overflow: auto;
display: none;
border-radius: 0 0 var(--dbf-radius-sm) var(--dbf-radius-sm);
box-shadow: var(--dbf-shadow-hover);
}
.dbf-item {
padding: 8px 12px;
cursor: pointer;
font-size: 12px;
transition: background var(--dbf-transition);
}
.dbf-item:hover { background: var(--dbf-bg-active); }
/* --- Search input --- */
.dbf-search input {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--dbf-border);
border-radius: var(--dbf-radius-sm);
font-size: 12px;
font-family: var(--dbf-font);
background: var(--dbf-bg-subtle);
transition: all var(--dbf-transition);
}
.dbf-search input:focus {
outline: none;
border-color: var(--dbf-accent);
background: var(--dbf-bg);
box-shadow: 0 0 0 3px rgba(79,70,229,.1);
}
/* =============================================
CUSTOM / PERSONNALISATION PANEL (R3)
============================================= */
#dbf-custom-panel {
padding: 4px 0;
}
#dbf-custom-panel details {
border: 1px solid var(--dbf-border);
border-radius: var(--dbf-radius);
margin: 8px 0;
background: var(--dbf-bg);
box-shadow: var(--dbf-shadow);
transition: all var(--dbf-transition);
overflow: hidden;
}
#dbf-custom-panel details:hover {
border-color: var(--dbf-border-hover);
box-shadow: var(--dbf-shadow-hover);
}
#dbf-custom-panel details[open] {
border-color: var(--dbf-border-hover);
}
#dbf-custom-panel summary {
padding: 12px 16px;
background: var(--dbf-bg-subtle);
cursor: pointer;
user-select: none;
font-size: 13px;
font-weight: 700;
display: flex;
align-items: center;
gap: 8px;
list-style: none;
transition: background var(--dbf-transition);
}
#dbf-custom-panel summary::-webkit-details-marker { display: none; }
#dbf-custom-panel summary::after {
content: '▸';
margin-left: auto;
font-size: 11px;
color: var(--dbf-text-muted);
transition: transform var(--dbf-transition);
}
#dbf-custom-panel details[open] summary::after {
transform: rotate(90deg);
}
#dbf-custom-panel summary:hover {
background: var(--dbf-bg-active);
}
#dbf-custom-panel .dbf-detail-body {
padding: 12px 16px;
animation: dbf-fadeIn .2s ease;
}
/* --- Form rows (Grid aligned) --- */
.dbf-form-row {
display: grid;
grid-template-columns: 1fr auto;
gap: 4px 12px;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid var(--dbf-bg-subtle);
}
.dbf-form-row:last-child { border-bottom: none; }
.dbf-form-row label {
font-size: 12px;
color: var(--dbf-text-secondary);
font-weight: 500;
white-space: nowrap;
}
.dbf-form-row input[type="color"] {
width: 34px; height: 28px;
padding: 2px;
border: 2px solid var(--dbf-border);
border-radius: var(--dbf-radius-sm);
cursor: pointer;
background: none;
transition: all var(--dbf-transition);
}
.dbf-form-row input[type="color"]:hover {
border-color: var(--dbf-accent);
transform: scale(1.08);
}
.dbf-form-row input[type="number"] {
width: 58px; height: 28px;
padding: 2px 6px;
border: 1px solid var(--dbf-border);
border-radius: var(--dbf-radius-sm);
font-size: 12px;
text-align: center;
background: var(--dbf-bg-subtle);
font-family: var(--dbf-font);
transition: all var(--dbf-transition);
}
.dbf-form-row input[type="number"]:focus {
border-color: var(--dbf-accent);
outline: none;
background: var(--dbf-bg);
box-shadow: 0 0 0 3px rgba(79,70,229,.1);
}
.dbf-form-row input[type="checkbox"] {
width: 16px; height: 16px;
accent-color: var(--dbf-accent);
cursor: pointer;
}
.dbf-form-row .dbf-unit {
font-size: 11px;
color: var(--dbf-text-muted);
margin-left: 2px;
}
.dbf-form-row .dbf-input-group {
display: flex;
align-items: center;
gap: 4px;
}
/* --- Action bar (Appliquer / Reset) --- */
#dbf-custom-actions {
position: sticky;
bottom: 0;
background: var(--dbf-bg);
padding: 12px 0 4px;
border-top: 1px solid var(--dbf-border);
display: flex;
gap: 8px;
margin-top: 14px;
}
#dbf-custom-actions wz-button {
transition: transform var(--dbf-transition);
}
#dbf-custom-actions wz-button:hover {
transform: translateY(-1px);
}
`;
document.head.appendChild(Scss);
// Onglet DrawBorders FR (icône dans la barre du sidepanel)
var DBFnewtab = document.createElement('li');
DBFnewtab.innerHTML = "<a href='#sidepanel-DrawBordersFR' data-toggle='tab'><img src=" + icon_DrB + " style='height:16px;'></a>";
DBFhandleClass.appendChild(DBFnewtab);
// ----- contenu Paramètres (comme avant) -----
var DBFcontent =
`<div id="dbf-panel">
<div class="dbf-card">
<div class="dbf-card__head">
<div class="dbf-card__title">📡 Sources & mises à jour <span class="dbf-sub">(survol = date)</span></div>
<wz-button id="dbf-refresh-updates" size="sm" color="secondary">Rafraîchir</wz-button>
</div>
<div class="dbf-card__body">
<div id="dbf-updates-bar"></div>
<div id="dbf-warning">⚠️ Données issues de sources officielles (BAN, panneaux, etc.). Elles peuvent évoluer, être mal positionnées ou incomplètes : vérifiez toujours sur le terrain avant toute édition.</div>
</div>
</div>
<div class="dbf-card">
<div class="dbf-card__head">
<div class="dbf-card__title">📍 Adresses BAN</div>
<div class="dbf-sub">Contrôles rapides</div>
</div>
<div class="dbf-card__body">
<div class="dbf-kpis" style="margin-bottom:10px">
<div id="layerBanVisib" class="dbf-pill dbf-pill--ok" title="CTRL+B">Calque BAN</div>
<div id="layerHNVisib" class="dbf-pill dbf-pill--ok">Calque HN</div>
<div id="zoomBanValue" class="dbf-pill">Zoom: —</div>
</div>
<div class="dbf-sub">Nombre de numéros BAN chargés : <b id="banQty">0</b></div>
</div>
</div>
<div class="dbf-card">
<div class="dbf-card__head">
<div class="dbf-card__title">🔎 Signalement BAN</div>
<div class="dbf-sub">Vérification commune</div>
</div>
<div class="dbf-card__body">
<div class="dbf-search">
<input id="dbf-ban-search" placeholder="Rechercher une adresse BAN…" />
<div id="dbf-ban-results" class="dbf-results"></div>
</div>
<div id="dbfSignalementResult" style="margin-top:10px"></div>
</div>
<div id="dbfSignalementActions" style="margin-top:10px;display:none">
<wz-button id="dbfCopyMail" size="sm" color="secondary">
📋 Copier le modèle de mail
</wz-button>
</div>
</div>
</div>
<div class="dbf-card">
<div class="dbf-card__head">
<div class="dbf-card__title">🗺️ Limites, PR, Radars, Équipements</div>
</div>
<div class="dbf-card__body">
<div id="zoomMiscValue" class="dbf-pill">Zoom: —</div>
<div class="dbf-legend" style="margin-top:12px">
<div><span class="dbf-dot dbf-dot--red"></span>Non certifié</div>
<div><span class="dbf-dot dbf-dot--green"></span>Certifié</div>
<div class="dbf-tag">Non présent sur WME</div>
<div class="dbf-tag2">Présent sur WME</div>
</div>
</div>
</div>
<div class="dbf-card">
<div class="dbf-card__head"><div class="dbf-card__title">⚙️ Options</div></div>
<div class="dbf-card__body">
<div class="dbf-options-grid">
<div class="dbf-section-divider">👁️ Affichage</div>
<wz-checkbox name="optionDBF0" id="optionDBF0">Afficher uniquement les n° manquants sur WME<input type="checkbox" style="display:none"></wz-checkbox>
<wz-checkbox name="optionDBF1" id="optionDBF1">Afficher uniquement les données certifiées<input type="checkbox" style="display:none"></wz-checkbox>
<wz-checkbox name="optionDBF2" id="optionDBF2">Afficher les anciens noms de communes<input type="checkbox" style="display:none"></wz-checkbox>
<wz-checkbox name="optionDBF3" id="optionDBF3">Afficher la date de mise à jour des données<input type="checkbox" style="display:none"></wz-checkbox>
<div class="dbf-section-divider">🔍 Filtrage</div>
<wz-checkbox name="optionDBF6" id="optionDBF6">Masquer le nom de la commune<input type="checkbox" style="display:none"></wz-checkbox>
<wz-checkbox name="optionDBF8" id="optionDBF8">Masquer les noms de rue (n° uniquement)<input type="checkbox" style="display:none"></wz-checkbox>
<wz-checkbox name="optionDBF9" id="optionDBF9">Masquer les lieux-dits dans les noms de rue<input type="checkbox" style="display:none"></wz-checkbox>
<wz-checkbox name="optionDBF7" id="optionDBF7">Afficher uniquement les lieux-dits<input type="checkbox" style="display:none"></wz-checkbox>
<div class="dbf-section-divider">🛠️ Avancé</div>
<wz-checkbox name="optionDBF4" id="optionDBF4">Effacer les données au déplacement de la carte<input type="checkbox" style="display:none"></wz-checkbox>
<wz-checkbox name="optionDBF5" id="optionDBF5">Filtrer les adresses pour la rue sélectionnée<input type="checkbox" style="display:none"></wz-checkbox>
</div>
</div>
</div>
<div class="dbf-card">
<div class="dbf-card__head"><div class="dbf-card__title">📌 Rue sélectionnée</div></div>
<div class="dbf-card__body">
<wz-body2 class="alert-settings-period-label" id="selectedRoad"></wz-body2>
<div class="region-switcher" style="margin-top:10px;">
<wz-select id="DBFselectRoad" name="selectRue" label="Rues disponibles" value="">
<wz-option value="">---</wz-option>
<input name="selectRue" style="display:none">
</wz-select>
</div>
</div>
</div>
<div id="dbf-footer" style="margin-top:25px; padding-top:15px; border-top:1px solid #e5e7eb;">
<div id="dbf-header" style="margin-bottom:0;">
<div class="dbf-title">WME Draw Borders France</div>
<div class="dbf-version">v${GM_info.script.version}</div>
<div class="dbf-right">
<a href="https://greasyfork.org/fr/scripts/8138-wme-draw-borders-france" target="_blank">Documentation</a>
</div>
</div>
<div id="loadBanData" style="display:none"></div>
<div id="loadMiscData" style="display:none"></div>
</div>`;
// ----- contenu Panneaux -----
var RScontent =
"<div style='margin:8px 0 10px;'>"
+ "<wz-label>Informations Panneaux</wz-label>"
+ "<div id='rsLegendContainer' style='display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;'></div>"
+ "<div style='margin-top:10px;display:flex;gap:8px;align-items:center'>"
+ "<wz-button id='rsLegendRefresh' size='sm'>Rafraîchir</wz-button>"
+ "<wz-body2 id='rsLegendStatus' style='color:#666'></wz-body2>"
+ "</div>"
+ "</div>";
// ----- contenu Personnalisation -----
var CustomContent =
`<div id="dbf-custom-panel">
<details>
<summary>🏘️ Villes</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Couleur du trait</label><input id="dbfCityStroke" type="color" value="#ce00ce"></div>
<div class="dbf-form-row"><label>Épaisseur</label><input id="dbfCityStrokeWidth" type="number" min="1" max="20" value="2"></div>
<div class="dbf-form-row"><label>Couleur du texte</label><input id="dbfCityFontColor" type="color" value="#000000"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfCityFontSize" type="number" min="8" max="40" value="12"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Afficher le nom</label><input id="dbfCityShowLabel" type="checkbox" checked></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfCityFontBold" type="checkbox"></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfCityOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfCityOutlineWidth" type="number" min="0" max="12" value="4"></div>
</div>
</details>
<details>
<summary>🗺️ Départements</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Couleur du trait</label><input id="dbfDepStroke" type="color" value="#b20000"></div>
<div class="dbf-form-row"><label>Épaisseur</label><input id="dbfDepStrokeWidth" type="number" min="1" max="20" value="5"></div>
<div class="dbf-form-row"><label>Couleur du texte</label><input id="dbfDepFontColor" type="color" value="#b20000"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfDepFontSize" type="number" min="8" max="40" value="24"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfDepFontBold" type="checkbox" checked></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfDepOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfDepOutlineWidth" type="number" min="0" max="12" value="4"></div>
</div>
</details>
<details>
<summary>📍 Adresses BAN</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Trait vérifié</label><input id="dbfBanVerifiedStroke" type="color" value="#00dd00"></div>
<div class="dbf-form-row"><label>Trait non vérifié</label><input id="dbfBanUnverifiedStroke" type="color" value="#ff8000"></div>
<div class="dbf-form-row"><label>Texte manquant</label><input id="dbfBanTextMissing" type="color" value="#ff0000"></div>
<div class="dbf-form-row"><label>Texte existant</label><input id="dbfBanTextExisting" type="color" value="#0000ff"></div>
<div class="dbf-form-row"><label>Épaisseur du point</label><input id="dbfBanStrokeWidth" type="number" min="1" max="25" value="10"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfBanFontSize" type="number" min="8" max="30" value="12"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfBanFontBold" type="checkbox"></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfBanOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfBanOutlineWidth" type="number" min="0" max="12" value="4"></div>
</div>
</details>
<details>
<summary>🏗️ Équipements</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Couleur (point/trait)</label><input id="dbfEquipStroke" type="color" value="#2d792d"></div>
<div class="dbf-form-row"><label>Épaisseur</label><input id="dbfEquipStrokeWidth" type="number" min="1" max="25" value="10"></div>
<div class="dbf-form-row"><label>Couleur texte</label><input id="dbfEquipFontColor" type="color" value="#33ff33"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfEquipFontSize" type="number" min="8" max="30" value="12"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfEquipFontBold" type="checkbox"></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfEquipOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfEquipOutlineWidth" type="number" min="0" max="12" value="3"></div>
</div>
</details>
<details>
<summary>🏢 Établissements</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Couleur (point/trait)</label><input id="dbfEtablStroke" type="color" value="#690069"></div>
<div class="dbf-form-row"><label>Épaisseur</label><input id="dbfEtablStrokeWidth" type="number" min="1" max="25" value="10"></div>
<div class="dbf-form-row"><label>Couleur texte</label><input id="dbfEtablFontColor" type="color" value="#ff77ff"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfEtablFontSize" type="number" min="8" max="30" value="12"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfEtablFontBold" type="checkbox"></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfEtablOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfEtablOutlineWidth" type="number" min="0" max="12" value="3"></div>
</div>
</details>
<details>
<summary>📸 Radars</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Couleur (point/trait)</label><input id="dbfRadarStroke" type="color" value="#ff0000"></div>
<div class="dbf-form-row"><label>Épaisseur du point</label><input id="dbfRadarStrokeWidth" type="number" min="1" max="25" value="10"></div>
<div class="dbf-form-row"><label>Couleur texte</label><input id="dbfRadarFontColor" type="color" value="#ffff00"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfRadarFontSize" type="number" min="8" max="30" value="12"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfRadarFontBold" type="checkbox"></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfRadarOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfRadarOutlineWidth" type="number" min="0" max="12" value="3"></div>
</div>
</details>
<details>
<summary>🛣️ Points routiers</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Couleur (point/trait)</label><input id="dbfPrStroke" type="color" value="#3380ff"></div>
<div class="dbf-form-row"><label>Épaisseur du point</label><input id="dbfPrStrokeWidth" type="number" min="1" max="25" value="10"></div>
<div class="dbf-form-row"><label>Couleur texte</label><input id="dbfPrFontColor" type="color" value="#33ffee"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfPrFontSize" type="number" min="8" max="30" value="12"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfPrFontBold" type="checkbox"></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfPrOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfPrOutlineWidth" type="number" min="0" max="12" value="3"></div>
</div>
</details>
<details>
<summary>🪧 Panneaux routiers</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Échelle panneaux</label><div class="dbf-input-group"><input id="dbfRsScale" type="number" min="50" max="200" value="100"><span class="dbf-unit">%</span></div></div>
</div>
</details>
<details>
<summary>⏬ Ralentisseurs OSM</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Couleur remplissage</label><input id="dbfBumpFillColor" type="color" value="#ff00ff"></div>
<div class="dbf-form-row"><label>Couleur trait</label><input id="dbfBumpStrokeColor" type="color" value="#ff00ff"></div>
<div class="dbf-form-row"><label>Opacité remplissage</label><div class="dbf-input-group"><input id="dbfBumpFillOpacity" type="number" min="0" max="100" value="35"><span class="dbf-unit">%</span></div></div>
<div class="dbf-form-row"><label>Épaisseur trait</label><input id="dbfBumpStrokeWidth" type="number" min="1" max="20" value="2"></div>
<div class="dbf-form-row"><label>Taille point</label><input id="dbfBumpPointRadius" type="number" min="2" max="30" value="6"></div>
<div class="dbf-form-row"><label>Couleur texte</label><input id="dbfBumpFontColor" type="color" value="#000000"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfBumpFontSize" type="number" min="6" max="30" value="10"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfBumpFontBold" type="checkbox"></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfBumpOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfBumpOutlineWidth" type="number" min="0" max="12" value="3"></div>
</div>
</details>
<details>
<summary>🚦 Feux tricolores (OSM)</summary>
<div class="dbf-detail-body">
<div class="dbf-form-row"><label>Couleur remplissage</label><input id="dbfSigFillColor" type="color" value="#00ccff"></div>
<div class="dbf-form-row"><label>Couleur trait</label><input id="dbfSigStrokeColor" type="color" value="#00ccff"></div>
<div class="dbf-form-row"><label>Opacité remplissage</label><div class="dbf-input-group"><input id="dbfSigFillOpacity" type="number" min="0" max="100" value="35"><span class="dbf-unit">%</span></div></div>
<div class="dbf-form-row"><label>Épaisseur trait</label><input id="dbfSigStrokeWidth" type="number" min="1" max="20" value="2"></div>
<div class="dbf-form-row"><label>Taille point</label><input id="dbfSigPointRadius" type="number" min="2" max="30" value="6"></div>
<div class="dbf-form-row"><label>Couleur texte</label><input id="dbfSigFontColor" type="color" value="#000000"></div>
<div class="dbf-form-row"><label>Taille du texte</label><div class="dbf-input-group"><input id="dbfSigFontSize" type="number" min="6" max="30" value="10"><span class="dbf-unit">px</span></div></div>
<div class="dbf-form-row"><label>Texte en gras</label><input id="dbfSigFontBold" type="checkbox"></div>
<div class="dbf-form-row"><label>Contour du texte</label><input id="dbfSigOutlineColor" type="color" value="#ffffff"></div>
<div class="dbf-form-row"><label>Épaisseur contour</label><input id="dbfSigOutlineWidth" type="number" min="0" max="12" value="3"></div>
</div>
</details>
<details>
<summary>📖 Dictionnaire d'abréviations</summary>
<div class="dbf-detail-body">
<p style="margin-bottom: 8px; font-size: 11px; color: #4b5563;">Définissez vos propres règles de correction pour la BAN :</p>
<div id="dbfBanDictContainer"></div>
<button type="button" id="dbfAddDictRow" style="margin-top: 8px; padding: 4px 10px; cursor: pointer; border-radius: 6px; border: 1px solid var(--dbf-border, #d1d5db); background: var(--dbf-card-bg, #f9fafb); font-size: 12px; transition: background 0.2s;">+ Ajouter une règle</button>
</div>
</details>
<div id="dbf-custom-actions">
<wz-button id="dbfCustomApply" color="secondary">✅ Appliquer</wz-button>
<wz-button id="dbfCustomReset" color="secondary">🔄 Réinitialiser</wz-button>
</div>
`;
// ----- section unique + sous-onglets -----
var DBFaddon = document.createElement('section');
DBFaddon.id = "sidepanel-DrawBordersFR";
DBFaddon.className = 'tab-pane';
DBFaddon.innerHTML =
"<ul class='dbf-nav' id='dbf-subtabs' style='margin:0 0 10px 0;'>"
+ "<li class='dbf-nav-item active'><a data-pane='dbf-pane-settings'>Paramètres</a></li>"
+ "<li class='dbf-nav-item'><a data-pane='dbf-pane-signs'>Panneaux</a></li>"
+ "<li class='dbf-nav-item'><a data-pane='dbf-pane-custom'>Personnalisation</a></li>"
+ "</ul>"
+ "<div class='dbf-tab-content'>"
+ "<div class='dbf-tab-pane active' id='dbf-pane-settings'>" + DBFcontent + "</div>"
+ "<div class='dbf-tab-pane' id='dbf-pane-signs'>" + RScontent + "</div>"
+ "<div class='dbf-tab-pane' id='dbf-pane-custom'>" + CustomContent + "</div>"
+ "</div>";
DBFhandleClass2.appendChild(DBFaddon);
// switch sous-onglets
(function () {
var tabs = DBFaddon.querySelector('#dbf-subtabs');
tabs.addEventListener('click', function (ev) {
var a = ev.target.closest('a[data-pane]');
if (!a) return;
ev.preventDefault();
ev.stopPropagation();
var paneId = a.getAttribute('data-pane');
// Si on quitte "Panneaux", on vide la légende
if (paneId !== 'dbf-pane-signs') {
var cont = DBFaddon.querySelector('#rsLegendContainer');
var status = DBFaddon.querySelector('#rsLegendStatus');
if (cont) cont.innerHTML = '';
if (status) status.textContent = '';
}
// Si on quitte "Personnalisation", on masque le footer sticky
var customActions = DBFaddon.querySelector('#dbf-custom-actions');
if (paneId !== 'dbf-pane-custom') {
if (customActions) customActions.style.display = 'none';
}
// Désactiver tous les onglets
Array.from(tabs.querySelectorAll('.dbf-nav-item')).forEach(function (li) {
li.classList.remove('active');
});
// Masquer tous les panneaux
DBFaddon.querySelectorAll('.dbf-tab-content .dbf-tab-pane').forEach(function (p) {
p.classList.remove('active');
p.style.display = 'none';
});
// Activer l'onglet cliqué et afficher son panneau
a.parentElement.classList.add('active');
var pane = DBFaddon.querySelector('#' + paneId);
if (pane) {
pane.classList.add('active');
pane.style.display = 'block';
}
// Réafficher le footer custom si on est sur Personnalisation
if (paneId === 'dbf-pane-custom' && customActions) {
customActions.style.display = 'flex';
}
// Rendu panneaux
if (paneId === 'dbf-pane-signs') RS_renderLegend();
}, false);
})();
// rendu panneaux (utilise RS_buildIcon(code,value))
function RS_renderLegend() {
var cont = document.getElementById("rsLegendContainer");
var status = document.getElementById("rsLegendStatus");
if (!cont) return;
cont.innerHTML = "";
if (status) status.textContent = "Génération…";
const items = [
{ code: "EB10", label: "EB10 (entrée agglo – vierge)" },
{ code: "EB20", label: "EB20 (sortie agglo – vierge)" },
{ code: "B14", value: "30", label: "B14-30" },
{ code: "B14", value: "50", label: "B14-50" },
{ code: "B14", value: "70", label: "B14-70" },
{ code: "B31", label: "B31 (fin d’interdictions)" },
{ code: "B33", value: "70", label: "B33-70 (fin de limitation)" },
{ code: "C107", label: "C107 (route réglementée – début)" },
{ code: "C108", label: "C108 (route réglementée – fin)" },
{ code: "C207", label: "C207 (début autoroute)" },
{ code: "C208", label: "C208 (fin autoroute)" },
// Distances / étendue
{ code: "M1", value: "350", label: "M1 – 350 m" },
{ code: "M1", value: "1200", label: "M1 – 1,2 km" },
{ code: "M2", value: "500", label: "M2 – étendue 500 m" },
{ code: "M2", value: "2000", label: "M2 – étendue 2 km" },
// Directions
{ code: "M3A", value: "droite", label: "M3a – flèche à droite" },
{ code: "M3A", value: "gauche", label: "M3a – flèche à gauche" },
{ code: "M3D", label: "M3d – flèche vers le bas (voie)" },
// Catégories de véhicules / matières
{ code: "M4A", label: "M4a – voiture (≤ 3,5 t)" },
{ code: "M4B", label: "M4b – bus / TC" },
{ code: "M4C", label: "M4c – moto" },
{ code: "M4F", value: "3.5", label: "M4f – masse 3,5 t" },
{ code: "M4F", value: "≤ 7.5", label: "M4f – masse ≤ 7,5 t" },
{ code: "M4G", label: "M4g – marchandises (camion)" },
{ code: "M4K", label: "M4k – matières inflammables" },
{ code: "M4L", label: "M4l – matières polluantes" },
{ code: "M4M", label: "M4m – matières dangereuses (ADR)" },
{ code: "M4X", label: "M4x – remorque > 250 kg" },
// Rappel
{ code: "M9Z", label: "M9z – RAPPEL" }
];
items.forEach(function (it) {
var card = document.createElement("div");
card.style.cssText = "border:1px solid #e2e4e7;border-radius:8px;padding:8px;background:#fff;";
var title = document.createElement("div");
title.style.cssText = "font-weight:600;font-size:12px;margin-bottom:6px;word-break:break-word;";
title.textContent = it.label || (it.code + (it.value ? (" " + it.value) : ""));
card.appendChild(title);
var prev = document.createElement("div");
prev.style.cssText = "display:flex;align-items:center;justify-content:center;min-height:84px;margin-bottom:6px;";
var icon = null;
try {
// si code M*, passe par le générateur panonceau
if (/^M/i.test(it.code)) icon = RS_buildPanonceau(it.code, it.value, 120);
else icon = RS_buildIcon(it.code, it.value);
} catch (e) { console.error("[RS] RS_buildIcon error", it, e); }
if (icon && icon.uri) {
var img = document.createElement("img");
img.src = icon.uri;
img.width = Math.min(icon.w || 80, 100);
img.height = Math.min(icon.h || 80, 100);
img.alt = title.textContent;
prev.appendChild(img);
} else {
prev.innerHTML = "<span style='color:#c00;font-size:12px;'>Erreur rendu</span>";
}
card.appendChild(prev);
var meta = document.createElement("div");
meta.style.cssText = "font-family:monospace;font-size:11px;color:#444;margin-bottom:2px;";
meta.textContent = "code: " + it.code + (it.value ? (" | value: " + it.value) : "");
card.appendChild(meta);
// petit footer neutre pour garder une structure stable (remplace l'ancien <details>)
var spacer = document.createElement("div");
spacer.style.cssText = "height:0;";
card.appendChild(spacer);
cont.appendChild(card);
});
if (status) status.textContent = "OK (" + items.length + " panneaux)";
}
document.addEventListener('click', function (e) {
if (e.target && e.target.id === 'rsLegendRefresh') RS_renderLegend();
}, true);
document.addEventListener('click', function (e) {
if (e.target && e.target.id === 'dbfCopyMail') {
if (!window.DBF_currentSignalementData) return;
const text = DBF_buildMailTemplate(window.DBF_currentSignalementData);
navigator.clipboard.writeText(text).then(() => {
e.target.textContent = "✅ Modèle copié";
setTimeout(() => {
e.target.textContent = "📋 Copier le modèle de mail";
}, 2000);
}).catch(() => {
alert("Impossible de copier le texte automatiquement.");
});
}
}, true);
document.addEventListener('change', function (e) {
if (e.target && e.target.id === 'dbfSignalementSelect') {
const val = e.target.value;
const box = document.getElementById('dbfSignalementResult');
if (!val || !box) return;
box.innerHTML = `<div class="dbf-chip loading"><a>Vérification… <span class="dbf-spinner"></span></a></div>`;
checkSignalement(val, result => {
renderResult(box, result);
});
}
}, true);
// si l’onglet Panneaux est déjà actif (reload)
setTimeout(function () {
var pane = DBFaddon.querySelector('#dbf-pane-signs');
if (pane && pane.classList.contains('active')) RS_renderLegend();
}, 300);
var settings = JSON.parse(localStorage.DBFsettings);
var _el;
_el = getId('optionDBF0'); if (_el) _el.checked = (settings.optionDBF0 === true);
_el = getId('optionDBF1'); if (_el) _el.checked = (settings.optionDBF1 === true);
_el = getId('optionDBF2'); if (_el) _el.checked = (settings.optionDBF2 === true);
_el = getId('optionDBF3'); if (_el) _el.checked = (settings.optionDBF3 === true);
_el = getId('optionDBF4'); if (_el) _el.checked = (settings.optionDBF4 === true);
_el = getId('optionDBF5'); if (_el) _el.checked = (settings.optionDBF5 === true);
_el = getId('optionDBF6'); if (_el) _el.checked = (settings.optionDBF6 === true);
_el = getId('optionDBF7'); if (_el) _el.checked = (settings.optionDBF7 === true);
_el = getId('optionDBF8'); if (_el) _el.checked = (settings.optionDBF8 === true);
_el = getId('optionDBF9'); if (_el) _el.checked = (settings.optionDBF9 === true);
// --- Option checkboxes (factored) ---
['optionDBF0', 'optionDBF1', 'optionDBF2', 'optionDBF3', 'optionDBF4',
'optionDBF5', 'optionDBF6', 'optionDBF7', 'optionDBF8', 'optionDBF9'].forEach(function (id) {
var el = getId(id);
if (!el) return;
el.addEventListener('click', function () {
var a = JSON.parse(localStorage.DBFsettings);
a[id] = !!getId(id)?.checked;
// Mutual exclusion: optionDBF7 <-> optionDBF8
if (id === 'optionDBF7' && a.optionDBF7) { a.optionDBF8 = false; var tmp8 = getId('optionDBF8'); if (tmp8) tmp8.checked = false; }
if (id === 'optionDBF8' && a.optionDBF8) { a.optionDBF7 = false; var tmp7 = getId('optionDBF7'); if (tmp7) tmp7.checked = false; }
localStorage.setItem('DBFsettings', JSON.stringify(a));
load_ban();
});
});
var _elBanVisib = getId('layerBanVisib');
if (_elBanVisib) _elBanVisib.addEventListener('click', function (e) {
(getId('layer-switcher-item_adresses_ban') || getId('layer-switcher-item_dbf_ban'))?.click();
var _lbv2 = getId('layerBanVisib');
if (_lbv2) _lbv2.style.backgroundColor = (Ban_Layer?.visibility === true) ? '#bbffcc' : '#fac3c3';
load_ban();
});
var _elHNVisib = getId('layerHNVisib');
if (_elHNVisib) _elHNVisib.addEventListener('click', function (e) {
getId('layer-switcher-item_house_numbers')?.click();
var _lhv = getId('layerHNVisib');
if (_lhv) _lhv.style.backgroundColor = ($('#layer-switcher-item_house_numbers').prop('checked') === true) ? '#bbffcc' : '#fac3c3';
load_ban();
});
var _elZoomBan = getId('zoomBanValue');
if (_elZoomBan) _elZoomBan.addEventListener('click', function (e) {
W.map.getOLMap().zoomTo("19");
var _zbv = getId('zoomBanValue');
if (_zbv) _zbv.style.backgroundColor = W.map.getZoom() < 19 ? '#fac3c3' : '#bbffcc';
load_ban();
});
var _elSelectRoad = getId('DBFselectRoad');
if (_elSelectRoad) _elSelectRoad.addEventListener('change', function (e) { load_ban(); });
var zoomBanInit = getId('zoomBanValue');
if (zoomBanInit) {
zoomBanInit.innerHTML = "Zoom: " + W.map.getZoom();
zoomBanInit.style.backgroundColor = W.map.getZoom() < 19 ? '#fac3c3' : '#bbffcc';
}
var zoomMiscInit = getId('zoomMiscValue');
if (zoomMiscInit) {
zoomMiscInit.innerHTML = "Zoom: " + W.map.getZoom();
zoomMiscInit.style.backgroundColor = W.map.getZoom() < 13 ? '#fac3c3' : '#bbffcc';
}
var _lbvInit = getId('layerBanVisib');
if (_lbvInit) _lbvInit.style.backgroundColor = (Ban_Layer?.visibility === true) ? '#bbffcc' : '#fac3c3';
var _lhvInit = getId('layerHNVisib');
if (_lhvInit) _lhvInit.style.backgroundColor = ($('#layer-switcher-item_house_numbers').prop('checked') === true) ? '#bbffcc' : '#fac3c3';
// Charger /updates au chargement
try {
fetchUpdatesGM()
.then(renderUpdatesBar)
.catch(err => {
console.error('[DBF] /updates error:', err);
renderUpdatesBar([]);
});
} catch (e) {
console.error('[DBF] updates init failed:', e);
}
var _refreshBtn = document.getElementById('dbf-refresh-updates');
if (_refreshBtn) _refreshBtn.addEventListener('click', function () {
const btn = this;
btn.disabled = true;
btn.textContent = "Chargement…";
fetchUpdatesGM()
.then(updates => {
renderUpdatesBar(updates);
btn.textContent = "Rafraîchir";
btn.disabled = false;
})
.catch(err => {
console.error("[DBF] refresh error:", err);
btn.textContent = "Erreur";
setTimeout(() => {
btn.textContent = "Rafraîchir";
btn.disabled = false;
}, 1500);
});
});
DBF_initCustomPanel();
}
function setCheck(id, value) {
var el = document.getElementById(id);
if (el) el.checked = !!value;
}
function DBF_initCustomPanel() {
var styles = DBF_getStyles();
// --- Mapping : { htmlId: { section, key, type, transform? } } ---
// type: "val" = setVal, "chk" = setCheck, "pct" = pourcentage (val * 100)
var FIELDS = [
// CITY
{ id: "dbfCityStroke", sec: "city", key: "strokeColor", type: "val" },
{ id: "dbfCityStrokeWidth", sec: "city", key: "strokeWidth", type: "val" },
{ id: "dbfCityFontColor", sec: "city", key: "fontColor", type: "val" },
{ id: "dbfCityFontSize", sec: "city", key: "fontSize", type: "val" },
{ id: "dbfCityShowLabel", sec: "city", key: "showLabel", type: "chk" },
{ id: "dbfCityFontBold", sec: "city", key: "fontBold", type: "chk" },
{ id: "dbfCityOutlineColor", sec: "city", key: "labelOutlineColor", type: "val" },
{ id: "dbfCityOutlineWidth", sec: "city", key: "labelOutlineWidth", type: "val" },
// DEP
{ id: "dbfDepStroke", sec: "dep", key: "strokeColor", type: "val" },
{ id: "dbfDepStrokeWidth", sec: "dep", key: "strokeWidth", type: "val" },
{ id: "dbfDepFontColor", sec: "dep", key: "fontColor", type: "val" },
{ id: "dbfDepFontSize", sec: "dep", key: "fontSize", type: "val" },
{ id: "dbfDepFontBold", sec: "dep", key: "fontBold", type: "chk" },
{ id: "dbfDepOutlineColor", sec: "dep", key: "labelOutlineColor", type: "val" },
{ id: "dbfDepOutlineWidth", sec: "dep", key: "labelOutlineWidth", type: "val" },
// BAN
{ id: "dbfBanVerifiedStroke", sec: "ban", key: "verifiedStroke", type: "val" },
{ id: "dbfBanUnverifiedStroke", sec: "ban", key: "unverifiedStroke", type: "val" },
{ id: "dbfBanTextMissing", sec: "ban", key: "textMissing", type: "val" },
{ id: "dbfBanTextExisting", sec: "ban", key: "textExisting", type: "val" },
{ id: "dbfBanStrokeWidth", sec: "ban", key: "strokeWidth", type: "val" },
{ id: "dbfBanFontSize", sec: "ban", key: "fontSize", type: "val" },
{ id: "dbfBanFontBold", sec: "ban", key: "fontBold", type: "chk" },
{ id: "dbfBanOutlineColor", sec: "ban", key: "labelOutlineColor", type: "val" },
{ id: "dbfBanOutlineWidth", sec: "ban", key: "labelOutlineWidth", type: "val" },
// EQUIP
{ id: "dbfEquipStroke", sec: "equip", key: "strokeColor", type: "val" },
{ id: "dbfEquipStrokeWidth", sec: "equip", key: "strokeWidth", type: "val" },
{ id: "dbfEquipFontColor", sec: "equip", key: "fontColor", type: "val" },
{ id: "dbfEquipFontSize", sec: "equip", key: "fontSize", type: "val" },
{ id: "dbfEquipFontBold", sec: "equip", key: "fontBold", type: "chk" },
{ id: "dbfEquipOutlineColor", sec: "equip", key: "labelOutlineColor", type: "val" },
{ id: "dbfEquipOutlineWidth", sec: "equip", key: "labelOutlineWidth", type: "val" },
// ETABL
{ id: "dbfEtablStroke", sec: "etabl", key: "strokeColor", type: "val" },
{ id: "dbfEtablStrokeWidth", sec: "etabl", key: "strokeWidth", type: "val" },
{ id: "dbfEtablFontColor", sec: "etabl", key: "fontColor", type: "val" },
{ id: "dbfEtablFontSize", sec: "etabl", key: "fontSize", type: "val" },
{ id: "dbfEtablFontBold", sec: "etabl", key: "fontBold", type: "chk" },
{ id: "dbfEtablOutlineColor", sec: "etabl", key: "labelOutlineColor", type: "val" },
{ id: "dbfEtablOutlineWidth", sec: "etabl", key: "labelOutlineWidth", type: "val" },
// RADAR
{ id: "dbfRadarStroke", sec: "radar", key: "strokeColor", type: "val" },
{ id: "dbfRadarStrokeWidth", sec: "radar", key: "strokeWidth", type: "val" },
{ id: "dbfRadarFontColor", sec: "radar", key: "fontColor", type: "val" },
{ id: "dbfRadarFontSize", sec: "radar", key: "fontSize", type: "val" },
{ id: "dbfRadarFontBold", sec: "radar", key: "fontBold", type: "chk" },
{ id: "dbfRadarOutlineColor", sec: "radar", key: "labelOutlineColor", type: "val" },
{ id: "dbfRadarOutlineWidth", sec: "radar", key: "labelOutlineWidth", type: "val" },
// PR
{ id: "dbfPrStroke", sec: "pr", key: "strokeColor", type: "val" },
{ id: "dbfPrStrokeWidth", sec: "pr", key: "strokeWidth", type: "val" },
{ id: "dbfPrFontColor", sec: "pr", key: "fontColor", type: "val" },
{ id: "dbfPrFontSize", sec: "pr", key: "fontSize", type: "val" },
{ id: "dbfPrFontBold", sec: "pr", key: "fontBold", type: "chk" },
{ id: "dbfPrOutlineColor", sec: "pr", key: "labelOutlineColor", type: "val" },
{ id: "dbfPrOutlineWidth", sec: "pr", key: "labelOutlineWidth", type: "val" },
// PANNEAUX (cas spécial : pourcentage)
{ id: "dbfRsScale", sec: "rs", key: "scale", type: "pct", fallback: 1 },
// BUMP
{ id: "dbfBumpFillColor", sec: "bump", key: "fillColor", type: "val" },
{ id: "dbfBumpStrokeColor", sec: "bump", key: "strokeColor", type: "val" },
{ id: "dbfBumpFillOpacity", sec: "bump", key: "fillOpacity", type: "pct", fallback: 0.35 },
{ id: "dbfBumpStrokeWidth", sec: "bump", key: "strokeWidth", type: "val" },
{ id: "dbfBumpPointRadius", sec: "bump", key: "pointRadius", type: "val" },
{ id: "dbfBumpFontColor", sec: "bump", key: "fontColor", type: "val" },
{ id: "dbfBumpFontSize", sec: "bump", key: "fontSize", type: "val" },
{ id: "dbfBumpFontBold", sec: "bump", key: "fontBold", type: "chk" },
{ id: "dbfBumpOutlineColor", sec: "bump", key: "labelOutlineColor", type: "val" },
{ id: "dbfBumpOutlineWidth", sec: "bump", key: "labelOutlineWidth", type: "val" },
// SIGNALS
{ id: "dbfSigFillColor", sec: "signals", key: "fillColor", type: "val" },
{ id: "dbfSigStrokeColor", sec: "signals", key: "strokeColor", type: "val" },
{ id: "dbfSigFillOpacity", sec: "signals", key: "fillOpacity", type: "pct", fallback: 0.35 },
{ id: "dbfSigStrokeWidth", sec: "signals", key: "strokeWidth", type: "val" },
{ id: "dbfSigPointRadius", sec: "signals", key: "pointRadius", type: "val" },
{ id: "dbfSigFontColor", sec: "signals", key: "fontColor", type: "val" },
{ id: "dbfSigFontSize", sec: "signals", key: "fontSize", type: "val" },
{ id: "dbfSigFontBold", sec: "signals", key: "fontBold", type: "chk" },
{ id: "dbfSigOutlineColor", sec: "signals", key: "labelOutlineColor", type: "val" },
{ id: "dbfSigOutlineWidth", sec: "signals", key: "labelOutlineWidth", type: "val" }
];
// --- Initialisation : remplir les champs HTML depuis les styles ---
FIELDS.forEach(function (f) {
var val = styles[f.sec] ? styles[f.sec][f.key] : undefined;
if (f.type === "chk") {
setCheck(f.id, val);
} else if (f.type === "pct") {
setVal(f.id, Math.round((val || f.fallback) * 100));
} else {
setVal(f.id, val);
}
});
// --- Helpers DOM ---
function setVal(id, value) {
var el = document.getElementById(id);
if (el && value !== undefined && value !== null) el.value = value;
}
function num(id, def) {
var el = document.getElementById(id);
var v = el ? Number(el.value) : NaN;
return isNaN(v) ? def : v;
}
function col(id, def) {
var el = document.getElementById(id);
return el && el.value ? el.value : def;
}
function chk(id, def) {
var el = document.getElementById(id);
return el ? !!el.checked : !!def;
}
// --- Dictionnaire d'abréviations dynamique ---
var dictContainer = document.getElementById('dbfBanDictContainer');
var btnAddRow = document.getElementById('dbfAddDictRow');
function addDictRow(abbr, full) {
abbr = abbr || ''; full = full || '';
if (!dictContainer) return;
var row = document.createElement('div');
row.style.cssText = 'display:flex;gap:4px;margin-bottom:4px;align-items:center;';
row.innerHTML =
'<input type="text" class="dbf-dict-abbr" value="' + abbr.replace(/"/g, '"') + '" placeholder="Ex: bd" style="width:55px;padding:2px 4px;border:1px solid var(--dbf-border,#d1d5db);border-radius:4px;font-size:12px;">'
+ '<span style="color:#6b7280;">=</span>'
+ '<input type="text" class="dbf-dict-full" value="' + full.replace(/"/g, '"') + '" placeholder="Ex: boulevard" style="flex:1;padding:2px 4px;border:1px solid var(--dbf-border,#d1d5db);border-radius:4px;font-size:12px;">'
+ '<button type="button" class="dbf-dict-del" style="cursor:pointer;color:#ef4444;background:none;border:none;font-weight:bold;font-size:14px;padding:0 4px;" title="Supprimer cette r\u00e8gle">×</button>';
row.querySelector('.dbf-dict-del').addEventListener('click', function () { row.remove(); });
dictContainer.appendChild(row);
}
// Charger les règles existantes (depuis styles ou valeurs par défaut)
var defaultDict = "mal =mar\u00e9chal \nav =avenue \nbd =boulevard \nr =rue \npl =place \nch =chemin \nimp =impasse \nrte =route \n st = saint-\n sainte = sainte-\nza =zone artisanale \nzi =zone industrielle ";
var dictString = (typeof styles.banDict === 'string') ? styles.banDict : defaultDict;
dictString.split('\n').forEach(function (line) {
if (line.includes('=')) {
var parts = line.split('=');
addDictRow(parts[0].trim(), parts.slice(1).join('=').trim());
}
});
if (btnAddRow) {
btnAddRow.addEventListener('click', function () { addDictRow(); });
}
// --- Bouton Appliquer : lire les champs HTML et sauvegarder ---
var btnApply = document.getElementById("dbfCustomApply");
if (btnApply) {
btnApply.addEventListener("click", function () {
var s = DBF_getStyles();
FIELDS.forEach(function (f) {
if (!s[f.sec]) s[f.sec] = {};
if (f.type === "chk") {
s[f.sec][f.key] = chk(f.id, s[f.sec][f.key]);
} else if (f.type === "pct") {
s[f.sec][f.key] = num(f.id, (f.fallback || 1) * 100) / 100.0;
} else if (f.key === "strokeWidth" || f.key === "fontSize" || f.key === "labelOutlineWidth" || f.key === "pointRadius") {
s[f.sec][f.key] = num(f.id, s[f.sec][f.key]);
} else {
s[f.sec][f.key] = col(f.id, s[f.sec][f.key]);
}
});
// Sauvegarder le dictionnaire d'abréviations
var newDictLines = [];
document.querySelectorAll('#dbfBanDictContainer > div').forEach(function (row) {
var a = row.querySelector('.dbf-dict-abbr');
var f = row.querySelector('.dbf-dict-full');
if (a && f && a.value.trim() && f.value.trim()) {
newDictLines.push(a.value.trim() + '=' + f.value.trim());
}
});
s.banDict = newDictLines.join('\n');
DBF_saveStyles(s);
DBF_applyLiveUpdates();
});
}
// --- Bouton Reset ---
var btnReset = document.getElementById("dbfCustomReset");
if (btnReset) {
btnReset.addEventListener("click", function () {
if (confirm("Réinitialiser tous les styles aux valeurs par défaut ?")) {
localStorage.removeItem("DBFstyles");
location.reload();
}
});
}
}
function DBF_applyLiveUpdates() {
console.log("[DBF] Mise à jour des styles");
// 1. Forcer OpenLayers à recalculer les styles
var layers = W.map.getOLMap().layers;
layers.forEach(function (layer) {
if (!layer || !layer.uniqueName) return;
if (
layer.uniqueName == "__WME_Draw_Border_Cty" ||
layer.uniqueName == "__WME_Draw_Border_Dpt" ||
layer.uniqueName == "__WME_Draw_Border_Ban" ||
layer.uniqueName == "__WME_Draw_Border_Equip" ||
layer.uniqueName == "__WME_Draw_Border_Etabl" ||
layer.uniqueName == "__WME_Draw_Border_Cam" ||
layer.uniqueName == "__WME_Draw_Border_PR" ||
layer.uniqueName.includes("__WME_DrawSigns")
) {
try {
layer.redraw();
} catch (e) {
console.error("Erreur redraw:", layer.uniqueName, e);
}
}
});
console.log("[DBF] Styles mis à jour immédiatement !");
}
let DBF_poll_delay = 60000;
let DBF_poll_timer = null;
function pollUpdates() {
if (document.hidden) {
DBF_poll_timer = setTimeout(pollUpdates, DBF_poll_delay);
return;
}
GM_xmlhttpRequest({
method: "GET",
url: "https://api.wazefrance.com/updates",
timeout: 10000,
onload: function (res) {
try {
const data = JSON.parse(res.responseText);
const updates = data.updates || [];
renderUpdatesBar(updates);
const anyRunning = updates.some(u => u.en_cours == 1);
if (anyRunning) {
DBF_poll_delay = 15000;
} else {
DBF_poll_delay = 60000;
}
} catch (err) {
console.error("[DBF] update polling error:", err);
}
DBF_poll_timer = setTimeout(pollUpdates, DBF_poll_delay);
},
onerror: function () {
console.warn("[DBF] Erreur GM_xmlhttpRequest, retry dans 30s");
DBF_poll_delay = 30000;
DBF_poll_timer = setTimeout(pollUpdates, DBF_poll_delay);
}
});
}
setTimeout(pollUpdates, 5000); // P2-5: defer to avoid racing DOM creation
function fetchUpdatesGM() {
return new Promise(function (resolve, reject) {
GM_xmlhttpRequest({
method: 'GET',
url: UPDATES_ENDPOINT,
headers: { 'Accept': 'application/json' },
onload: function (res) {
try {
if (res.status < 200 || res.status >= 300) return reject(new Error('HTTP ' + res.status));
const json = JSON.parse(res.responseText || '{}');
resolve(Array.isArray(json.updates) ? json.updates : []);
} catch (e) { reject(e); }
},
onerror: function (err) { reject(err); }
});
});
}
function renderUpdatesBar(updates) {
const el = document.getElementById('dbf-updates-bar');
if (!el) return;
el.style.display = 'none';
el.innerHTML = '';
if (!updates || !updates.length) return;
// Ordre souhaité
const pref = ['ban', 'panneaux', 'sante', 'sirene', 'dep', 'commune', 'equip / etab'];
// Normalisation des noms dans une map
const byName = new Map();
updates.forEach(u =>
byName.set(String((u.nom || '').trim().toLowerCase()), u)
);
// Tri ordonné
const ordered = [];
pref.forEach(n => {
const u = byName.get(n);
if (u) ordered.push(u);
});
updates.forEach(u => {
const n = String((u.nom || '').trim().toLowerCase());
if (!pref.includes(n)) ordered.push(u);
});
// Construction UI
ordered.forEach((u, i) => {
const chip = document.createElement('span');
chip.className = 'dbf-chip' + (u.en_cours == 1 ? ' loading' : '');
const a = document.createElement('a');
a.href = u.lien || '#';
a.target = '_blank';
// --- TEXTE DANS LE CHIP -------------------------------------
if (u.en_cours == 1) {
// Affichage : nom + pourcentage + point bleu animé
a.innerHTML = `
[${u.nom}]
<strong style="color:#2563eb">${u.progress || 0}%</strong>
<span class="dbf-spinner"></span>
`;
} else {
// Si pas en cours → nom seulement
a.textContent = `[${u.nom}]`;
a.title = 'Dernière mise à jour : ' + (u.date || '—');
}
chip.appendChild(a);
el.appendChild(chip);
// Séparateur " | "
if (i < ordered.length - 1) {
const pipe = document.createElement('span');
pipe.className = 'dbf-pipe';
pipe.textContent = '|';
el.appendChild(pipe);
}
});
// Affichage final
el.style.display = 'flex';
}
function LayerVilToggled(checked) { if (Cty_Layer && Cty_Layer.setVisibility) Cty_Layer.setVisibility(checked); }
function LayerDepToggled(checked) { if (Dpt_Layer && Dpt_Layer.setVisibility) Dpt_Layer.setVisibility(checked); }
function LayerPRToggled(checked) { if (PR_Layer && PR_Layer.setVisibility) PR_Layer.setVisibility(checked); }
function LayerCamToggled(checked) { if (Cam_Layer && Cam_Layer.setVisibility) Cam_Layer.setVisibility(checked); }
function LayerBanToggled(checked) { if (Ban_Layer && Ban_Layer.setVisibility) Ban_Layer.setVisibility(checked); }
function LayerEquipToggled(checked) { if (Equip_Layer && Equip_Layer.setVisibility) Equip_Layer.setVisibility(checked); }
function LayerEtablToggled(checked) { if (Etabl_Layer && Etabl_Layer.setVisibility) Etabl_Layer.setVisibility(checked); }
function LayerRSToggled(checked) { if (RS_Layer && RS_Layer.setVisibility) RS_Layer.setVisibility(checked); }
function LayerBumpToggled(checked) { if (Bump_Layer && Bump_Layer.setVisibility) Bump_Layer.setVisibility(checked); }
function LayerSignalsToggled(checked) { if (Signals_Layer && Signals_Layer.setVisibility) Signals_Layer.setVisibility(checked); }
function show_border() {
var zoomMiscEl = getId('zoomMiscValue');
if (zoomMiscEl) {
zoomMiscEl.innerHTML = "Zoom: " + W.map.getZoom();
zoomMiscEl.style.backgroundColor = W.map.getZoom() < 13 ? '#fac3c3' : '#bbffcc';
}
if (W.map.getZoom() < 13) return;
var Cty_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Cty")[0];
var Dpt_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Dpt")[0];
var PR_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_PR")[0];
var Cam_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Cam")[0];
var Equip_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Equip")[0];
var Etabl_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Etabl")[0];
var RS_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_RS")[0];
//On récupère les infos de la carte et vérification de la présence de toutes les données nécessaires
let lonlat = new OpenLayers.LonLat(W.map.getProjectedCenter().lon, W.map.getProjectedCenter().lat);
lonlat.transform(W.Config.map.projection.local, W.Config.map.projection.remote);
let pt = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
var clat = pt.y;
var clon = pt.x;
var zoomLevel = W.map.getZoom();
if (!clat || !clon || !zoomLevel) {
console.error("Latitude, longitude ou zoomLevel manquant dans l'URL");
return;
}
// RS_Layer.visibility - Panneaux routiers (icônes)
if (RS_Layer && RS_Layer.visibility === true) {
// zoom mini pour lisibilité
var zoomMiscRS = getId('zoomMiscValue');
if (zoomMiscRS) {
zoomMiscRS.innerHTML = "Zoom: " + W.map.getZoom();
zoomMiscRS.style.backgroundColor = W.map.getZoom() < 16 ? '#fac3c3' : '#bbffcc';
}
// coordonnées centre
let lonlat = new OpenLayers.LonLat(W.map.getProjectedCenter().lon, W.map.getProjectedCenter().lat);
lonlat.transform(W.Config.map.projection.local, W.Config.map.projection.remote);
let clat = lonlat.lat, clon = lonlat.lon, zoomLevel = W.map.getZoom();
if (!clat || !clon || !zoomLevel) return;
try {
var _lmd = getId('loadMiscData'); if (_lmd) _lmd.style.backgroundColor = '#bbffcc';
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.wazefrance.com/rs?lat=' + clat + '&lon=' + clon + '&zoom=' + zoomLevel,
onload: function (response) {
if (response.status === 200) {
var data = JSON.parse(response.responseText);
var list = data.rs || [];
// reset file + calque
RS_pending = [];
DBF_cleanupLayer(RS_Layer);
// on met en file
for (var i = 0; i < list.length; i++) {
var it = list[i];
RS_queueSign(
{ code: it.panneau_code, value: it.panneau_value },
it.pannonceau_code ? { code: it.pannonceau_code, value: it.pannonceau_value } : null,
it.longitude, it.latitude
);
}
// on rend les clusters (un seul SVG par groupe à ≤ 5 m)
RS_renderClusters();
var _lmd2 = getId('loadMiscData'); if (_lmd2) _lmd2.style.backgroundColor = '#ffffff';
} else {
var _lmd3 = getId('loadMiscData'); if (_lmd3) _lmd3.style.backgroundColor = '#fac3c3';
}
},
onerror: function (err) {
console.error("RS fetch error:", err);
var _lmd4 = getId('loadMiscData'); if (_lmd4) _lmd4.style.backgroundColor = '#fac3c3';
}
});
} catch (ex) {
console.error(ex);
var _lmd5 = getId('loadMiscData'); if (_lmd5) _lmd5.style.backgroundColor = '#fac3c3';
}
}
// Cty_Layer.visibility - Limites villes
if (Cty_Layer && Cty_Layer.visibility === true) {
var extent = DBF_getMapExtent();
var minLon = extent.left !== undefined ? extent.left : extent[0];
var minLat = extent.bottom !== undefined ? extent.bottom : extent[1];
var maxLon = extent.right !== undefined ? extent.right : extent[2];
var maxLat = extent.top !== undefined ? extent.top : extent[3];
var bbox = [
Number(minLon.toFixed(6)),
Number(minLat.toFixed(6)),
Number(maxLon.toFixed(6)),
Number(maxLat.toFixed(6))
];
var zoomLevel = W.map.getZoom();
// Détecter si c'est un zoom ou un pan
var isZoomChange = (Cty_LastZoom !== null && zoomLevel !== Cty_LastZoom);
Cty_LastZoom = zoomLevel;
if (debug) {
console.log("[COMMUNES DEBUG] Zoom:", zoomLevel, "isZoomChange:", isZoomChange);
console.log("[COMMUNES DEBUG] BBOX:", bbox);
}
// Option : ne pas charger si trop dézoomé
if (zoomLevel < 10) {
if (debug) console.log("[COMMUNES DEBUG] Zoom trop faible, pas de requête communes.");
return;
}
// Si la BBOX n'a pas changé, on ne fait rien
if (JSON.stringify(Cty_BboxOld) === JSON.stringify(bbox)) {
if (debug) console.log("WME Draw Borders France: BBOX inchangée, pas de nouvelle requête communes");
return;
}
Cty_BboxOld = bbox.slice();
try {
var _lmdc1 = getId('loadMiscData'); if (_lmdc1) _lmdc1.style.backgroundColor = '#bbffcc';
var datas = JSON.stringify({ bbox: bbox });
if (debug) {
console.log("[COMMUNES DEBUG] XHR SEND /communes", datas);
}
GM_xmlhttpRequest({
method: 'POST',
headers: { "Content-Type": "application/json" },
url: 'https://api.wazefrance.com/communes',
data: datas,
onload: function (response) {
if (response.status === 200) {
var data = JSON.parse(response.responseText);
var temp_city = "", c = 0;
// 🧹 PAN : on nettoie TOUT et on repart uniquement avec cette BBOX
if (!isZoomChange) {
if (debug) console.log("[COMMUNES DEBUG] PAN détecté → cleanupLayer()");
DBF_cleanupLayer(Cty_Layer);
} else {
if (debug) console.log("[COMMUNES DEBUG] ZOOM détecté → on conserve les communes existantes");
}
// Maintenant on dessine toutes les communes renvoyées par l'API
for (var i = 0; i < data.length; i++) {
let name = data[i].name;
let coords = data[i].coord;
Cty_Borders_DrawBorder(name, coords);
c++;
if (debug) temp_city = temp_city + name + ", ";
}
if (debug) {
console.log("WME Draw Borders France: " + c + " communes dessinées pour la BBOX", temp_city);
}
var _lmdc2 = getId('loadMiscData'); if (_lmdc2) _lmdc2.style.backgroundColor = '#ffffff';
} else {
console.error("Erreur API /communes :", response.status, response.statusText);
var _lmdc3 = getId('loadMiscData'); if (_lmdc3) _lmdc3.style.backgroundColor = '#fac3c3';
}
},
onerror: function (error) {
console.error("Erreur réseau /communes :", error);
var _lmdc4 = getId('loadMiscData'); if (_lmdc4) _lmdc4.style.backgroundColor = '#fac3c3';
}
});
} catch (ex) {
console.error(`${SCRIPT_NAME} /communes bbox:`, ex);
}
}
//Dpt_Layer.visibility - Limites départements
if (Dpt_Layer && Dpt_Layer.visibility === true) {
if (W.model.states.objects.length === 0) return;
// Collect list if unique states from the segments
var States = [], temp_state = "";
for (var sid in W.model.states.objects) {
var state = W.model.states.getObjectById(sid).attributes;
if (state.countryID == "73") {
state = state.name.replace(/[èé]/g, "e").replace(/[ô]/g, 'o').replace(/\'/g, '_').replace(/ /g, '_');
States.push({ name: state });
if (debug) { temp_state = temp_state + state + ", "; }
}
}
if (debug) { console.log("Départements demandés : ", temp_state); }
// Get Data
try {
if (JSON.stringify(StatesOld) != JSON.stringify(States)) {
getId('loadMiscData') && (getId('loadMiscData').style.backgroundColor = '#bbffcc');
if (debug) { console.log("XHR SEND ", States); }
var datas = JSON.stringify(States);
GM_xmlhttpRequest({
method: 'POST',
headers: { "Content-Type": "application/JSON" },
url: 'https://api.wazefrance.com/dep',
data: datas,
onload: function (response) {
if (response.status === 200) {
var data = JSON.parse(response.responseText);
var temp_city = "", c = 0;
if (data.length > 0) {
for (var i = 0; i < data.length; i++) {
Dpt_Borders_DrawBorder(data[i].name, data[i].coord); c++;
if (debug) { temp_state = temp_state + data[i].name + ", "; }
}
}
if (debug) { console.log("WME Draw Borders France: " + c + " départements reçus ", temp_state); }
var _lmdd1 = getId('loadMiscData'); if (_lmdd1) _lmdd1.style.backgroundColor = '#ffffff';
} else {
var _lmdd2 = getId('loadMiscData'); if (_lmdd2) _lmdd2.style.backgroundColor = '#fac3c3';
}
},
onerror: function (error) {
console.error("Erreur réseau :", error);
}
});
StatesOld = States;
} else {
if (debug) { console.log("WME Draw Borders France: Pas de nouveaux Départements détectés"); }
}
} catch (ex) { console.error(`${SCRIPT_NAME}:`, ex); }
}
//PR_Layer.visibility - Points routiers
if (PR_Layer && PR_Layer.visibility === true) {
//Get Data
try {
var _lmdpr1 = getId('loadMiscData'); if (_lmdpr1) _lmdpr1.style.backgroundColor = '#bbffcc';
var ret = GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.wazefrance.com/pr?lat=' + clat + '&lon=' + clon + '&zoom=' + zoomLevel,
onload: function (response) {
if (response.status === 200) {
try {
var r = JSON.parse(response.responseText);
r = r.pr;
if (r && r.length > 0) {
for (var i = 0; r[i]; i++) {
PR_Borders_DrawBorder(r[i].numero, r[i].longitude, r[i].latitude);
}
}
if (debug) { console.log("WME Draw Borders France: Points Routiers chargés", r); }
var _lmdpr2 = getId('loadMiscData'); if (_lmdpr2) _lmdpr2.style.backgroundColor = '#ffffff';
} catch (e) { console.warn("Erreur parsing Points Routiers", e); }
} else {
var _lmdpr3 = getId('loadMiscData'); if (_lmdpr3) _lmdpr3.style.backgroundColor = '#fac3c3';
}
},
onerror: function (error) {
console.error("Erreur réseau :", error);
}
});
} catch (ex) { console.error(`${SCRIPT_NAME}:`, ex); }
}
//Equip_Layer.visibility - Rond-Poin, Parking
if (Equip_Layer && Equip_Layer.visibility === true) {
//Get Data
try {
var _lmdeq1 = getId('loadMiscData'); if (_lmdeq1) _lmdeq1.style.backgroundColor = '#bbffcc';
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.wazefrance.com/equipement_data?lat=' + clat + '&lon=' + clon + '&zoom=' + zoomLevel,
onload: function (response) {
if (response.status === 200) {
try {
var r = JSON.parse(response.responseText);
r = r.equipements;
if (debug) { console.log("WME Draw Borders France: Équipements chargés", r); }
if (r && r.length > 0) {
for (var i = 0; r[i]; i++) {
Equip_Borders_DrawBorder("(" + r[i].nature + ")\n" + r[i].toponyme, r[i].lon, r[i].lat);
}
}
var _lmdeq2 = getId('loadMiscData'); if (_lmdeq2) _lmdeq2.style.backgroundColor = '#ffffff';
} catch (e) { console.warn("Erreur parsing Équipements", e); }
} else {
var _lmdeq3 = getId('loadMiscData'); if (_lmdeq3) _lmdeq3.style.backgroundColor = '#fac3c3';
}
},
onerror: function (error) {
console.error("Erreur réseau :", error);
}
});
} catch (ex) { console.error(`${SCRIPT_NAME}:`, ex); }
}
//Etabl_Layer.visibility - Établissement, École
if (Etabl_Layer && Etabl_Layer.visibility === true) {
//Get Data
try {
var _lmdet1 = getId('loadMiscData'); if (_lmdet1) _lmdet1.style.backgroundColor = '#bbffcc';
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.wazefrance.com/combined_data?lat=' + clat + '&lon=' + clon + '&zoom=' + zoomLevel,
onload: function (response) {
if (response.status === 200) {
try {
var r = JSON.parse(response.responseText);
r = r.établissements;
if (debug) { console.log("WME Draw Borders France: Établissements chargés", r); }
if (r && r.length > 0) {
for (var i = 0; r[i]; i++) {
Etabl_Borders_DrawBorder(r[i].appellation_officielle + "\n(" + r[i].adresse_uai + ")", r[i].longitude, r[i].latitude);
}
}
var _lmdet2 = getId('loadMiscData'); if (_lmdet2) _lmdet2.style.backgroundColor = '#ffffff';
} catch (e) { console.warn("Erreur parsing Établissements", e); }
} else {
var _lmdet3 = getId('loadMiscData'); if (_lmdet3) _lmdet3.style.backgroundColor = '#fac3c3';
}
},
onerror: function (error) {
console.error("Erreur réseau :", error);
}
});
} catch (ex) { console.error(`${SCRIPT_NAME}:`, ex); }
}
//Cam_Layer.visibility
if (Cam_Layer && Cam_Layer.visibility === true) {
var listCam = localStorage.speedCamList;
if (listCam) {
var a = JSON.parse(listCam);
if (debug) { console.log(a); }
for (var i = 0; i < a.length; i++) { Cam_Borders_DrawBorder(a[i].typeLabel, a[i].lng, a[i].lat); }
}
}
}
function load_radar() {
var zoomMiscEl = getId('zoomMiscValue');
if (zoomMiscEl) {
zoomMiscEl.innerHTML = "Zoom: " + W.map.getZoom();
zoomMiscEl.style.backgroundColor = W.map.getZoom() < 13 ? '#fac3c3' : '#bbffcc';
}
if (W.map.getZoom() < 13) return;
var listCam = localStorage.speedCamList;
//Get Data
var _lmdr = getId('loadMiscData'); if (_lmdr) _lmdr.style.backgroundColor = '#bbffcc';
try {
var ret = GM_xmlhttpRequest({
method: 'GET',
url: 'https://radars.securite-routiere.gouv.fr/radars/all?_format=json',
onload: function (response) {
if (response.status === 200) {
try {
if (_.isEqual(JSON.parse(listCam), JSON.parse(response.responseText)) !== true && response.responseText) {
console.log("WME Draw Borders France: Mise à jour de la liste des radars");
localStorage.setItem('speedCamList', response.responseText);
}
var _lmdr2 = getId('loadMiscData'); if (_lmdr2) _lmdr2.style.backgroundColor = '#ffffff';
} catch (e) { console.warn("Erreur parsing Radars", e); }
} else {
var _lmdr3 = getId('loadMiscData'); if (_lmdr3) _lmdr3.style.backgroundColor = '#fac3c3';
}
},
onerror: function (error) {
console.error("Erreur réseau :", error);
}
});
} catch (ex) { console.error(`${SCRIPT_NAME}:`, ex); }
}
function load_ban() {
var Ban_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Ban")[0];
var _layerBan = getId('layerBanVisib');
if (_layerBan) _layerBan.style.backgroundColor = (Ban_Layer && Ban_Layer.visibility === true) ? '#bbffcc' : '#fac3c3';
var _layerHN = getId('layerHNVisib');
if (_layerHN) _layerHN.style.backgroundColor = ($('#layer-switcher-item_house_numbers').prop('checked') === true) ? '#bbffcc' : '#fac3c3';
var zoomBanEl = getId('zoomBanValue');
if (zoomBanEl) {
zoomBanEl.innerHTML = "Zoom: " + W.map.getZoom();
zoomBanEl.style.backgroundColor = W.map.getZoom() < 19 ? '#fac3c3' : '#bbffcc';
}
if (W.map.getZoom() < 19) return;
if (Ban_Layer && Ban_Layer.visibility === true) {
if (getId('optionDBF4')?.checked) { DBF_cleanupLayer(Ban_Layer); checklayer("__WME_Draw_Border_Ban"); }
else { Ban_Layer.redraw(); }
//try {
var _lbd1 = getId('loadBanData'); if (_lbd1) _lbd1.style.backgroundColor = '#bbffcc';
//Liste les noms de rue à l'écran
var streetList = [];
for (var i in W.model.streets.objects) {
let streetObj = W.model.streets.objects[i];
if (!streetObj || !streetObj.attributes) continue;
var streetName = streetObj.attributes.name;
if (streetName != "") {
if (streetList.indexOf(streetName) == - 1) {
streetList.push(streetName);
}
}
}
streetList.sort();
//Création du menu déroulant après l'avoir vidé (actualisation), mis l'option par défaut et, éventuellement, la rue sélectionnée
var selectRoadEl = getId('DBFselectRoad');
var selectedRoadEl = getId('selectedRoad');
var tmp = selectRoadEl ? selectRoadEl.value : "";
if (selectRoadEl) selectRoadEl.innerHTML = "";
if (selectedRoadEl) selectedRoadEl.innerHTML = "";
var cList = document.createElement('wz-option');
cList.value = "";
cList.innerHTML = "---";
if (selectRoadEl) selectRoadEl.appendChild(cList);
if (tmp != "") {
cList = document.createElement('wz-option');
cList.value = tmp;
cList.innerHTML = tmp;
if (selectRoadEl) selectRoadEl.appendChild(cList);
}
//On complète avec les autres rues
for (var k = 0; streetList[k]; k++) {
if (tmp != streetList[k]) {
cList = document.createElement('wz-option');
cList.value = streetList[k];
cList.innerHTML = streetList[k];
if (selectRoadEl) selectRoadEl.appendChild(cList);
}
}
//Segment sélectionné à l'écran
let sel = W.selectionManager.getSelectedWMEFeatures();
if (sel && sel[0] && sel[0].featureType === 'segment') {
let seg = W.model.segments.objects[sel[0].id];
if (seg && seg.attributes) {
let streetObj = W.model.streets.objects[seg.attributes.primaryStreetID];
if (streetObj && streetObj.attributes) {
if (selectedRoadEl) selectedRoadEl.innerHTML = streetObj.attributes.name;
} else {
if (selectedRoadEl) selectedRoadEl.innerHTML = "";
}
}
}
//Liste des n° de rue déjà existants sur la carte
var HNdone = [];
for (var i in W.model.segmentHouseNumbers.objects) {
if (W.model.segmentHouseNumbers.objects[i].attributes.segID != null
&& W.model.segments.objects[W.model.segmentHouseNumbers.objects[i].attributes.segID] != null) {
// Mise en mémoire des noms de rue et ville
let segId = W.model.segmentHouseNumbers.objects[i].attributes.segID;
let segment = W.model.segments.objects[segId];
if (!segment || !segment.attributes) continue;
let streetId = segment.attributes.primaryStreetID;
let streetObj = W.model.streets.objects[streetId];
if (!streetObj || !streetObj.attributes) continue;
let streetName = streetObj.attributes.name;
streetName = streetName.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
let cityObj = W.model.cities.objects[streetObj.attributes.cityID];
if (!cityObj || !cityObj.attributes) continue;
let cityName = cityObj.attributes.name;
cityName = cityName.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
if (cityName.indexOf("(") > 0) { cityName = cityName.substring(cityName.indexOf("(") + 1).replace(")", ""); } // On garde le nom de la commune principale si sous-commune
HNdone.push(W.model.segmentHouseNumbers.objects[i].attributes.number + " " + streetName + " " + cityName);
// Mise en mémoire des noms de rue et ville alt
if (W.model.segments.objects[segId].attributes.streetIDs[0] != null) {
for (var j = 0; j < W.model.segments.objects[segId].attributes.streetIDs.length; j++) {
let altStreetId = segment.attributes.streetIDs[j];
let altStreetObj = W.model.streets.objects[altStreetId];
if (!altStreetObj || !altStreetObj.attributes) continue;
let streetAltName = altStreetObj.attributes.name;
streetAltName = streetAltName.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
let cityObj = W.model.cities.objects[streetObj.attributes.cityID];
if (!cityObj || !cityObj.attributes) continue;
let cityName = cityObj.attributes.name;
cityName = cityName.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
if (cityName.indexOf("(") > 0) { cityName = cityName.substring(cityName.indexOf("(") + 1).replace(")", ""); } // On garde le nom de la commune principale si sous-commune
HNdone.push(W.model.segmentHouseNumbers.objects[i].attributes.number + " " + streetAltName + " " + cityName);
}
}
}
}
//Liste des POI résidentiels déjà existants sur la carte
for (var i in W.model.venues.objects) {
if (W.model.venues.objects[i].attributes.residential != null
&& W.model.venues.objects[i].attributes.streetID != null
&& W.model.streets.objects[W.model.venues.objects[i].attributes.streetID] != null) {
let venue = W.model.venues.objects[i];
if (!venue || !venue.attributes) continue;
let streetObj = W.model.streets.objects[venue.attributes.streetID];
if (!streetObj || !streetObj.attributes) continue;
let streetName = streetObj.attributes.name;
streetName = streetName.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
let cityObjAlt = W.model.cities.objects[streetObj.attributes.cityID];
if (!cityObjAlt || !cityObjAlt.attributes) continue;
let cityName = cityObjAlt.attributes.name;
cityName = cityName.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
if (cityName.indexOf("(") > 0) { cityName = cityName.substring(cityName.indexOf("(") + 1).replace(")", ""); } // On garde le nom de la commune principale si sous-commune
HNdone.push(W.model.venues.objects[i].attributes.houseNumber + " " + streetName + " " + cityName);
}
}
//On récupère les infos de la carte et vérification de la présence de toutes les données nécessaires
let lonlat = new OpenLayers.LonLat(W.map.getProjectedCenter().lon, W.map.getProjectedCenter().lat);
lonlat.transform(W.Config.map.projection.local, W.Config.map.projection.remote);
let pt = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
var clat = pt.y;
var clon = pt.x;
var extent = DBF_getMapExtent();
var minLon = extent.left !== undefined ? extent.left : extent[0];
var minLat = extent.bottom !== undefined ? extent.bottom : extent[1];
var maxLon = extent.right !== undefined ? extent.right : extent[2];
var maxLat = extent.top !== undefined ? extent.top : extent[3];
var bbox = [minLon, minLat, maxLon, maxLat];
var zoomLevel = W.map.getZoom();
//console.log("[BAN DEBUG] Zoom:", zoomLevel);
//console.log("[BAN DEBUG] BBOX:", bbox);
if (!clat || !clon || !zoomLevel) {
console.error("Latitude, longitude ou zoomLevel manquant dans l'URL");
return;
}
// Requête à l'API Flask
var bboxParam = bbox.map(function (n) { return Number(n).toFixed(6); }).join(',');
if (debug) console.log("[BAN DEBUG] URL API:", 'https://api.wazefrance.com/combined_data?bbox=' + bboxParam + '&zoom=' + zoomLevel);
try {
var ret = GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.wazefrance.com/combined_data?bbox=' + bboxParam + '&zoom=' + zoomLevel,
onload: function (response) {
if (response.status === 200) {
// Règles hardcodées (unicode / ligatures, non-éditables)
const correction = {
"oe": "œ", "œ": "oe",
"ae": "æ", "æ": "ae"
};
// Merge avec le dictionnaire utilisateur (depuis styles)
try {
var userStyles = DBF_getStyles();
var dictStr = (typeof userStyles.banDict === 'string') ? userStyles.banDict : "";
dictStr.split('\n').forEach(function (line) {
if (line.includes('=')) {
var parts = line.split('=');
var k = parts[0].trim();
var v = parts.slice(1).join('=').trim();
if (k && v) correction[k] = v;
}
});
} catch (e) { /* fallback: on utilise juste les règles unicode */ }
var adresses = JSON.parse(response.responseText);
// --- Fresh redraw: vider les features BAN existantes ---
// Le Smart Diffing empêchait la mise à jour des couleurs
// (rouge → bleu) quand un HN est ajouté sur WME, car le
// featId ne contient pas le state. On vide et on redessine
// intégralement puisque l'API est rappelée à chaque fois.
Ban_Layer.destroyFeatures();
DBF_clearFeatureIndex(Ban_Layer);
//Numéros de rue
if (debug) { console.log("N° BAN", adresses.info_ban); }
var a = adresses.info_ban;
var _bq = getId('banQty'); if (_bq) _bq.innerHTML = a.length;
adresses_list = a.map(x => ({
id_raw: x.id,
id_signalement: x.id.split('_').slice(0, 3).join('_'),
numero: x.numero + (x.rep ?? ""),
voie: x.nom_voie,
commune: x.nom_commune,
lon: x.lon,
lat: x.lat
}));
setTimeout(DBF_initBanSearch, 0);
var _bq2 = getId('banQty'); if (_bq2) _bq2.style.color = a.length > 199 ? "red" : "black";
for (var i = 0; i < a.length; i++) {
var adresse = a[i], nomRueCorr;
var nomRueBan = adresse.numero + (adresse.rep != null ? adresse.rep : "") + " " + adresse.nom_voie;
var NumeroRue = adresse.numero + (adresse.rep != null ? adresse.rep : "");
var nomCommuneBAN = adresse.nom_commune;
nomRueCorr = nomRueBan.toLowerCase();
Object.keys(correction).forEach((key) => {
nomRueCorr = nomRueCorr.replaceAll(key, correction[key]);
nomCommuneBAN = nomCommuneBAN.replaceAll(key, correction[key]);
});
nomRueCorr = nomRueCorr.normalize('NFD').replace(/\p{Diacritic}/gu, '').replace("’", "'");
var nomCommuneCorr = nomCommuneBAN.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
if (getId('optionDBF9')?.checked == true) {
if (nomRueCorr.search(" - ") != "-1") { nomRueCorr = nomRueCorr.substring(0, nomRueCorr.search(" - ")); }
}
// Appel à Ban_Borders_DrawBorder avec le format attendu après avoir testé si le n° existe déjà sur WME
var state, Ville = nomCommuneBAN;
((HNdone.indexOf(nomRueCorr + " " + nomCommuneCorr) === -1) ? state = 'no' : state = 'yes')
if (getId('optionDBF0')?.checked == true && state == 'yes') { continue; } // On bloque l'affichage des n° renseignés sur WME
if (getId('optionDBF1')?.checked == true && adresse.certification_commune == 0) { continue; } // On bloque l'affichage des n° non certifiés
if (getId('optionDBF2')?.checked == true && adresse.nom_ancienne_commune != null) { Ville = "\n" + adresse.nom_ancienne_commune + ' (' + nomCommuneBAN + ')'; } // On affiche l'ancien nom de commune
if (getId('optionDBF3')?.checked == true && adresse.date_ajout_modif != null) { Ville = Ville + '\n' + deltaDate(adresse.date_ajout_modif); } // On affiche l'age de l'info
if (getId('optionDBF5')?.checked == true && ((selectedRoadEl && selectedRoadEl.innerHTML != "") || (selectRoadEl && selectRoadEl.value != ""))) {
if (selectedRoadEl && selectedRoadEl.innerHTML != "") { var selectedRoad = selectedRoadEl.innerHTML; } // Rue sélectionnée
else if (selectRoadEl && selectRoadEl.value != "") { var selectedRoad = selectRoadEl.value; } // Sinon rue dans le menu déroulant
var adres = adresse.nom_voie;
adres = adres.normalize('NFD').replace(/\p{Diacritic}/gu, '').replace("’", "'").toLowerCase();
var selectRoad = selectedRoad.normalize('NFD').replace(/\p{Diacritic}/gu, '').replace("’", "'").toLowerCase();
if (selectRoad != adres) { continue; } // On bloque si ce n'est pas la rue sélectionnée
}
if (getId('optionDBF7')?.checked !== true) {
if (getId('optionDBF8')?.checked == true) { Ban_Borders_DrawBorder(NumeroRue, adresse.lon, adresse.lat, state, adresse.certification_commune); }
else {
if (getId('optionDBF6')?.checked == true) { Ban_Borders_DrawBorder(nomRueBan, adresse.lon, adresse.lat, state, adresse.certification_commune); }
else { Ban_Borders_DrawBorder(nomRueBan + "\n" + Ville, adresse.lon, adresse.lat, state, adresse.certification_commune); }
}
}
}
//Lieux-dits
if (debug) { console.log("Lieux-dits", adresses.lieux_dits); }
var b = adresses.lieux_dits;
for (var i = 0; i < b.length; i++) {
var lieux = b[i], lieudit2;
var lieudit = lieux.nom_lieu_dit;
Object.keys(correction).forEach((key) => {
lieudit = lieudit.replaceAll(key, correction[key]);
});
lieudit2 = lieudit.normalize('NFD').replace(/\p{Diacritic}/gu, '').replace("’", "'").toLowerCase();
if (getId('optionDBF6')?.checked == true) { var Ville = ""; } else { var Ville = "\n" + (lieux.nom_commune || ""); }
// Appel à Ban_Borders_DrawBorder avec le format attendu après avoir testé si le n° existe déjà sur WME
var state;
((HNdone.indexOf(lieudit2) === -1) ? state = 'no' : state = 'yes')
if (getId('optionDBF0')?.checked == true && state == 'yes') { continue; } // On bloque l'affichage des lieux-dits renseignés sur WME
if (getId('optionDBF8')?.checked !== true) { Ban_Borders_DrawBorder(lieudit + Ville, lieux.lon, lieux.lat, "", ""); }
}
var _lbd2 = getId('loadBanData'); if (_lbd2) _lbd2.style.backgroundColor = '#ffffff';
} else {
var _lbd3 = getId('loadBanData'); if (_lbd3) _lbd3.style.backgroundColor = '#fac3c3';
}
},
onerror: function (error) { console.error("Erreur réseau :", error); }
});
} catch (ex) { console.error(`${SCRIPT_NAME}:`, ex); }
}
}
function load_bump() {
var Bump_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Bump")[0];
if (!Bump_Layer) return;
// zoom mini (tu ajustes)
if (W.map.getZoom() < 16) {
if (Bump_Layer.visibility === true) { DBF_cleanupLayer(Bump_Layer); DBF_clearFeatureIndex(Bump_Layer); }
return;
}
if (Bump_Layer.visibility === true) {
Bump_Layer.redraw();
// bbox comme load_ban()
var extent = DBF_getMapExtent();
var minLon = extent.left !== undefined ? extent.left : extent[0];
var minLat = extent.bottom !== undefined ? extent.bottom : extent[1];
var maxLon = extent.right !== undefined ? extent.right : extent[2];
var maxLat = extent.top !== undefined ? extent.top : extent[3];
var bbox = [minLon, minLat, maxLon, maxLat];
var bboxParam = bbox.map(function (n) { return Number(n).toFixed(6); }).join(',');
var zoomLevel = W.map.getZoom();
if (debug) console.log("[BUMP DEBUG] URL:", 'https://api.wazefrance.com/bump?bbox=' + bboxParam + '&zoom=' + zoomLevel);
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.wazefrance.com/bump?bbox=' + bboxParam + '&zoom=' + zoomLevel,
onload: function (response) {
if (response.status == 200) {
try {
var data = JSON.parse(response.responseText);
// 🔥 on repart propre
DBF_cleanupLayer(Bump_Layer);
// Option A : ton API renvoie un tableau : { bumps: [ {geom_type, coordinates, osm_id...}, ... ] }
var list = data.bump || data.bumps || data.ralentisseurs || data.items || [];
for (var i = 0; i < list.length; i++) {
var it = list[i];
var geomType = it.geom_type || it.type; // ex: "Point" / "Polygon" / "LineString"
var coords = it.coordinates;
// si en DB tu stockes coordinates en string JSON => on parse
if (typeof coords === "string") {
try { coords = JSON.parse(coords); } catch (e) { continue; }
}
var label = it.traffic_calming ? String(it.traffic_calming) : "";
var bs = DBF_getStyles().bump;
var bumpInlineStyle = {
pointRadius: bs.pointRadius || 6,
fill: true,
fillColor: bs.fillColor || "#ff00ff",
fillOpacity: bs.fillOpacity !== undefined ? bs.fillOpacity : 0.35,
stroke: true,
strokeColor: bs.strokeColor || "#ff00ff",
strokeWidth: bs.strokeWidth || 2,
label: label,
fontColor: bs.fontColor || "#000",
fontSize: (bs.fontSize || 10) + "px",
labelOutlineColor: bs.labelOutlineColor || "#fff",
labelOutlineWidth: (typeof bs.labelOutlineWidth === "number") ? bs.labelOutlineWidth : 2,
fontWeight: bs.fontBold ? "bold" : "normal",
};
OSM_DrawGeometry(Bump_Layer, geomType, coords, label, bumpInlineStyle);
}
if (debug) console.log("[BUMP DEBUG] items:", list.length);
} catch (e) {
console.error("[BUMP DEBUG] parse error:", e);
}
} else {
console.warn("[BUMP DEBUG] HTTP", response.status);
}
},
onerror: function (err) {
console.error("[BUMP DEBUG] network error:", err);
}
});
}
}
function load_signals() {
var Signals_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Signals")[0];
if (!Signals_Layer) return;
if (W.map.getZoom() < 10) {
if (Signals_Layer.visibility === true) { DBF_cleanupLayer(Signals_Layer); DBF_clearFeatureIndex(Signals_Layer); }
return;
}
if (Signals_Layer.visibility === true) {
if (getId('optionDBF4')?.checked) { DBF_cleanupLayer(Signals_Layer); checklayer("__WME_Draw_Border_Signals"); }
else { Signals_Layer.redraw(); }
// bbox comme load_ban()
var extent = DBF_getMapExtent();
var minLon = extent.left !== undefined ? extent.left : extent[0];
var minLat = extent.bottom !== undefined ? extent.bottom : extent[1];
var maxLon = extent.right !== undefined ? extent.right : extent[2];
var maxLat = extent.top !== undefined ? extent.top : extent[3];
var bbox = [minLon, minLat, maxLon, maxLat];
var bboxParam = bbox.map(function (n) { return Number(n).toFixed(6); }).join(',');
var zoomLevel = W.map.getZoom();
if (debug) console.log("[SIGNALS DEBUG] URL:", 'https://api.wazefrance.com/traffic_signals_data?bbox=' + bboxParam + '&zoom=' + zoomLevel);
GM_xmlhttpRequest({
method: 'GET',
url: 'https://api.wazefrance.com/traffic_signals_data?bbox=' + bboxParam + '&zoom=' + zoomLevel,
onload: function (response) {
if (response.status == 200) {
try {
var data = JSON.parse(response.responseText);
DBF_cleanupLayer(Signals_Layer);
// API tableau : { signals: [...] }
var list = data.features || data.signals || data.feux || data.items || [];
for (var i = 0; i < list.length; i++) {
var it = list[i];
var geomType = (it.geometry && it.geometry.type) || it.geom_type || it.type;
var coords = (it.geometry && it.geometry.coordinates) || it.coordinates;
if (typeof coords === "string") {
try { coords = JSON.parse(coords); } catch (e) { continue; }
}
var label = (it.properties && it.properties.highway) || it.highway || "";
label = label ? String(label) : "";
var ss = DBF_getStyles().signals;
var sigInlineStyle = {
pointRadius: ss.pointRadius || 6,
fill: true,
fillColor: ss.fillColor || "#00ccff",
fillOpacity: ss.fillOpacity !== undefined ? ss.fillOpacity : 0.35,
stroke: true,
strokeColor: ss.strokeColor || "#00ccff",
strokeWidth: ss.strokeWidth || 2,
label: label,
fontColor: ss.fontColor || "#000",
fontSize: (ss.fontSize || 10) + "px",
labelOutlineColor: ss.labelOutlineColor || "#fff",
labelOutlineWidth: (typeof ss.labelOutlineWidth === "number") ? ss.labelOutlineWidth : 2
};
OSM_DrawGeometry(Signals_Layer, geomType, coords, label, sigInlineStyle);
}
if (debug) console.log("[SIGNALS DEBUG] items:", list.length);
} catch (e) {
console.error("[SIGNALS DEBUG] parse error:", e);
}
} else {
console.warn("[SIGNALS DEBUG] HTTP", response.status);
}
},
onerror: function (err) {
console.error("[SIGNALS DEBUG] network error:", err);
}
});
}
}
function OSM_DrawGeometry(layer, geomType, coords, labelText, style) {
if (!layer || !geomType || coords == null) return;
var featureStyle = style || null;
// Smart Diffing: g\u00e9n\u00e8re un ID unique bas\u00e9 sur le type de g\u00e9om\u00e9trie et les premi\u00e8res coordonn\u00e9es
function makeOsmFeatId(prefix, c) {
if (Array.isArray(c) && c.length >= 2) {
if (Array.isArray(c[0])) return (layer.name || "") + "_" + prefix + "_" + c[0][0] + "_" + c[0][1];
return (layer.name || "") + "_" + prefix + "_" + c[0] + "_" + c[1];
}
return null;
}
function addWithId(feat, id) {
if (id) {
if (DBF_hasFeature(layer, id)) return;
feat.id = id;
}
layer.addFeatures([feat]);
if (id) DBF_registerFeature(layer, id);
}
function toPointXY(lon, lat) {
return new OpenLayers.Geometry.Point(lon, lat)
.transform(new OpenLayers.Projection("EPSG:4326"), W.map.getProjectionObject());
}
function makeLineString(lineCoords) {
var pts = [];
for (var i = 0; i < lineCoords.length; i++) {
pts.push(toPointXY(lineCoords[i][0], lineCoords[i][1]));
}
return new OpenLayers.Geometry.LineString(pts);
}
function makePolygon(polyCoords) {
// polyCoords = [ [ [lon,lat], ... ] ] (rings)
var rings = [];
for (var r = 0; r < polyCoords.length; r++) {
var ringPts = [];
for (var i = 0; i < polyCoords[r].length; i++) {
ringPts.push(toPointXY(polyCoords[r][i][0], polyCoords[r][i][1]));
}
rings.push(new OpenLayers.Geometry.LinearRing(ringPts));
}
return new OpenLayers.Geometry.Polygon(rings);
}
var geom = null;
if (geomType === "Point") {
geom = toPointXY(coords[0], coords[1]);
} else if (geomType === "MultiPoint") {
// on ajoute 1 feature par point
for (var i = 0; i < coords.length; i++) {
var g = toPointXY(coords[i][0], coords[i][1]);
var fid = (layer.name || "") + "_MP_" + coords[i][0] + "_" + coords[i][1];
addWithId(new OpenLayers.Feature.Vector(g, { labelText: labelText || "" }, featureStyle), fid);
}
return;
} else if (geomType === "LineString") {
geom = makeLineString(coords);
} else if (geomType === "MultiLineString") {
for (var i = 0; i < coords.length; i++) {
var g = makeLineString(coords[i]);
var fid = (layer.name || "") + "_MLS_" + coords[i][0][0] + "_" + coords[i][0][1];
addWithId(new OpenLayers.Feature.Vector(g, { labelText: labelText || "" }, featureStyle), fid);
}
return;
} else if (geomType === "Polygon") {
geom = makePolygon(coords);
} else if (geomType === "MultiPolygon") {
for (var i = 0; i < coords.length; i++) {
var g = makePolygon(coords[i]);
var fid = (layer.name || "") + "_MPG_" + coords[i][0][0][0] + "_" + coords[i][0][0][1];
addWithId(new OpenLayers.Feature.Vector(g, { labelText: labelText || "" }, featureStyle), fid);
}
return;
}
if (geom) {
var featId = makeOsmFeatId(geomType, coords);
addWithId(new OpenLayers.Feature.Vector(geom, { labelText: labelText || "" }, featureStyle), featId);
}
}
function checklayer(layer) {
var layers = W.map.getLayersBy("uniqueName", layer);
if (layers.length === 0) {
var DBF_style = new OpenLayers.Style({
pointRadius: 2,
fontWeight: "normal",
label: "${labelText}",
fontFamily: "Tahoma, Courier New",
labelOutlineColor: "#FFFFFF",
labelOutlineWidth: 2,
fontColor: '#000000',
fontSize: "10px"
});
if (layer === "__WME_Draw_Border_RS") {
// style par défaut (sera écrasé par externalGraphic)
var RS_style = new OpenLayers.Style({
pointRadius: 16,
fill: true, fillColor: "#ffffff", fillOpacity: 0.0,
stroke: false,
label: "${labelText}",
fontColor: "#000000",
fontSize: "12px",
labelOutlineColor: "#ffffff",
labelOutlineWidth: 3
});
RS_Layer = new OpenLayers.Layer.Vector("Panneaux routiers Icones", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(RS_style)
});
RS_Layer.setVisibility(false);
W.map.addLayer(RS_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "Panneaux routiers (icônes)";
}
else if (layer === "__WME_Draw_Border_Cty") {
Cty_Layer = new OpenLayers.Layer.Vector("Limites des communes", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(DBF_style)
});
Cty_Layer.setVisibility(false);
W.map.addLayer(Cty_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "Villes";
}
else if (layer === "__WME_Draw_Border_Dpt") {
Dpt_Layer = new OpenLayers.Layer.Vector("Limites Départements", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(DBF_style)
});
Dpt_Layer.setVisibility(false);
W.map.addLayer(Dpt_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "Départements";
}
else if (layer === "__WME_Draw_Border_PR") {
PR_Layer = new OpenLayers.Layer.Vector("Points Routiers", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(DBF_style)
});
PR_Layer.setVisibility(false);
W.map.addLayer(PR_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "PR";
}
else if (layer === "__WME_Draw_Border_Cam") {
Cam_Layer = new OpenLayers.Layer.Vector("Radars", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(DBF_style)
});
Cam_Layer.setVisibility(false);
W.map.addLayer(Cam_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "Radars";
}
else if (layer === "__WME_Draw_Border_Ban") {
Ban_Layer = new OpenLayers.Layer.Vector("Adresses BAN", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(DBF_style)
});
Ban_Layer.setVisibility(false);
W.map.addLayer(Ban_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "BAN";
}
else if (layer === "__WME_Draw_Border_Equip") {
Equip_Layer = new OpenLayers.Layer.Vector("Équipements", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(DBF_style)
});
Equip_Layer.setVisibility(false);
W.map.addLayer(Equip_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "Équipements";
}
else if (layer === "__WME_Draw_Border_Etabl") {
Etabl_Layer = new OpenLayers.Layer.Vector("Établissements", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(DBF_style)
});
Etabl_Layer.setVisibility(false);
W.map.addLayer(Etabl_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "Établissements";
}
else if (layer === "__WME_Draw_Border_Bump") {
var bumpStyle = new OpenLayers.Style({
pointRadius: 6,
fill: true,
fillColor: "#ff00ff",
fillOpacity: 0.35,
stroke: true,
strokeColor: "#ff00ff",
strokeWidth: 2
});
Bump_Layer = new OpenLayers.Layer.Vector("Ralentisseurs (OSM)", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(bumpStyle)
});
Bump_Layer.setVisibility(false);
W.map.addLayer(Bump_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "Ralentisseurs (OSM)";
}
else if (layer === "__WME_Draw_Border_Signals") {
var sigStyle = new OpenLayers.Style({
pointRadius: 6,
fill: true,
fillColor: "#00ccff",
fillOpacity: 0.35,
stroke: true,
strokeColor: "#00ccff",
strokeWidth: 2
});
Signals_Layer = new OpenLayers.Layer.Vector("Feux tricolores (OSM)", {
displayInLayerSwitcher: true,
uniqueName: layer,
styleMap: new OpenLayers.StyleMap(sigStyle)
});
Signals_Layer.setVisibility(false);
W.map.addLayer(Signals_Layer);
I18n.translations[I18n.locale].layers.name[layer] = "Feux tricolores (OSM)";
}
}
}
function Cty_Borders_DrawBorder(Name, coordinateString) {
console.log("🏙️ [VILLES] Tentative de tracé pour :", Name || "Nom inconnu");
var Cty_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Cty")[0];
if (!Cty_Layer) { console.warn(" ❌ Cty_Layer introuvable"); return; }
if (!coordinateString || !coordinateString.trim()) { console.warn(" ❌ coordinateString vide pour", Name); return; }
// Smart Diffing: skip si cette commune est déjà dessinée
var featId = "CTY_" + Name;
if (DBF_hasFeature(Cty_Layer, featId)) { console.log(" ⏭️ Skip (déjà dessinée) :", Name); return; }
var parts = coordinateString.split('|');
console.log(" 📐 Parties |", parts.length, "| Taille données:", coordinateString.length, "chars");
if (parts.length > 1) {
for (var i = 0; i < parts.length; i++) {
var coords = parts[i].trim();
if (!coords) continue;
var geom = Geometrize(Name, coords);
if (geom) {
var poly = new OpenLayers.Feature.Vector(geom, null, new Cty_Borders_Style(Name));
poly.id = featId + "_" + i;
Cty_Layer.addFeatures(poly);
DBF_registerFeature(Cty_Layer, poly.id);
}
}
} else {
var geom = Geometrize(Name, coordinateString.trim());
if (geom) {
var poly = new OpenLayers.Feature.Vector(geom, null, new Cty_Borders_Style(Name));
poly.id = featId;
Cty_Layer.addFeatures(poly);
DBF_registerFeature(Cty_Layer, featId);
}
}
}
function Dpt_Borders_DrawBorder(Name, coordinateString) {
var Dpt_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Dpt")[0];
if (!Dpt_Layer) {
return;
}
// Smart Diffing: skip si ce d\u00e9partement est d\u00e9j\u00e0 dessin\u00e9
var featId = "DPT_" + Name;
if (DBF_hasFeature(Dpt_Layer, featId)) return;
var parts = coordinateString.split('|');
if (parts.length > 1) {
for (var i = 0; i < parts.length; i++) {
var coords = parts[i].trim();
var geom = Geometrize(Name, coords);
if (geom) {
var poly = new OpenLayers.Feature.Vector(geom, null, new Dpt_Borders_Style(Name));
poly.id = featId + "_" + i;
Dpt_Layer.addFeatures(poly);
DBF_registerFeature(Dpt_Layer, poly.id);
}
}
} else {
var geom = Geometrize(Name, coordinateString.trim());
if (geom) {
var poly = new OpenLayers.Feature.Vector(geom, null, new Dpt_Borders_Style(Name));
poly.id = featId;
Dpt_Layer.addFeatures(poly);
DBF_registerFeature(Dpt_Layer, featId);
}
}
}
function PR_Borders_DrawBorder(Name, lon, lat) {
var PR_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_PR")[0];
if (!PR_Layer) return;
var featId = "PR_" + lon + "_" + lat;
if (DBF_hasFeature(PR_Layer, featId)) return;
var poly = new OpenLayers.Feature.Vector(Geometrize(Name, lon + ";" + lat), null, new PR_Borders_Style(Name));
poly.id = featId;
PR_Layer.addFeatures(poly);
DBF_registerFeature(PR_Layer, featId);
}
function Cam_Borders_DrawBorder(Name, lon, lat) {
var Cam_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Cam")[0];
if (!Cam_Layer) return;
var featId = "CAM_" + lon + "_" + lat;
if (DBF_hasFeature(Cam_Layer, featId)) return;
var poly = new OpenLayers.Feature.Vector(Geometrize(Name, lon + ";" + lat), null, new Cam_Borders_Style(Name));
poly.id = featId;
Cam_Layer.addFeatures(poly);
DBF_registerFeature(Cam_Layer, featId);
}
function Ban_Borders_DrawBorder(Name, lon, lat, status, verif) {
var Ban_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Ban")[0];
if (!Ban_Layer) return;
// Smart Diffing: ID unique = coordonnées + label (BAN peut avoir plusieurs entrées au même point)
var featId = "BAN_" + lon + "_" + lat + "_" + Name;
if (DBF_hasFeature(Ban_Layer, featId)) return;
const s = DBF_getStyles().ban;
var labelColor;
if (status === "yes") {
// déjà présent sur WME
labelColor = s.textExisting || "#0000ff";
} else {
// manquant
labelColor = s.textMissing || "#ff0000";
}
var strokeColor;
if (verif == "1") {
strokeColor = s.verifiedStroke || "#00dd00";
} else {
strokeColor = s.unverifiedStroke || "#ff8000";
}
var strokeWidth = s.strokeWidth || 10;
var fontSize = s.fontSize || 12;
var poly = new OpenLayers.Feature.Vector(
Geometrize(Name, lon + ";" + lat),
null,
new Ban_Borders_Style(Name, labelColor, strokeColor, strokeWidth, fontSize)
);
poly.id = featId;
Ban_Layer.addFeatures(poly);
DBF_registerFeature(Ban_Layer, featId);
// On garde la logique z-index (nouveaux en haut)
if (status !== "yes") {
setTimeout(function () {
Ban_Layer.removeFeatures([poly]);
Ban_Layer.addFeatures([poly]);
}, 0);
}
}
function Equip_Borders_DrawBorder(Name, lon, lat) {
var Equip_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Equip")[0];
if (!Equip_Layer) return;
var featId = "EQ_" + lon + "_" + lat;
if (DBF_hasFeature(Equip_Layer, featId)) return;
var poly = new OpenLayers.Feature.Vector(Geometrize(Name, lon + ";" + lat), null, new Equip_Borders_Style(Name));
poly.id = featId;
Equip_Layer.addFeatures(poly);
DBF_registerFeature(Equip_Layer, featId);
}
function Etabl_Borders_DrawBorder(Name, lon, lat) {
var Etabl_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_Etabl")[0];
if (!Etabl_Layer) return;
var featId = "ET_" + lon + "_" + lat;
if (DBF_hasFeature(Etabl_Layer, featId)) return;
var poly = new OpenLayers.Feature.Vector(Geometrize(Name, lon + ";" + lat), null, new Etabl_Borders_Style(Name));
poly.id = featId;
Etabl_Layer.addFeatures(poly);
DBF_registerFeature(Etabl_Layer, featId);
}
function RS_DrawSignWithPanonceau(main, pano, lon, lat) {
// panneau principal
var mainIcon = RS_buildIcon(main.code, main.value);
var pt = new OpenLayers.Geometry.Point(lon, lat)
.transform(new OpenLayers.Projection("EPSG:4326"), W.map.getProjectionObject());
var mainStyle = {
externalGraphic: mainIcon.uri,
graphicWidth: mainIcon.w,
graphicHeight: mainIcon.h,
graphicXOffset: -mainIcon.w / 2,
graphicYOffset: -mainIcon.h / 2,
graphicOpacity: 1.0
};
RS_Layer.addFeatures([new OpenLayers.Feature.Vector(pt.clone(), null, mainStyle)]);
// panonceau (optionnel) : on le met juste sous le panneau
if (pano && pano.code) {
var mIcon = RS_buildPanonceau(pano.code, pano.value, mainIcon.w);
var mStyle = {
externalGraphic: mIcon.uri,
graphicWidth: mIcon.w,
graphicHeight: mIcon.h,
graphicXOffset: -mIcon.w / 2,
graphicYOffset: (-mainIcon.h / 2) + (mainIcon.h / 2) + 6, // sous le panneau
graphicOpacity: 1.0
};
RS_Layer.addFeatures([new OpenLayers.Feature.Vector(pt.clone(), null, mStyle)]);
}
}
function Cty_Borders_Style(Name) {
const s = DBF_getStyles().city;
this.fill = false;
this.stroke = true;
this.strokeColor = s.strokeColor;
this.strokeWidth = s.strokeWidth;
this.strokeDashstyle = "solid";
if (s.showLabel) {
this.label = Name;
this.fontSize = (s.fontSize || 12) + "px";
this.fontColor = s.fontColor;
this.fontWeight = s.fontBold ? "bold" : "normal";
this.labelOutlineColor = s.labelOutlineColor || "#ffffff";
this.labelOutlineWidth = (typeof s.labelOutlineWidth === "number") ? s.labelOutlineWidth : 4;
} else {
this.label = "";
}
}
function Dpt_Borders_Style(Name) {
const s = DBF_getStyles().dep;
this.fill = false;
this.stroke = true;
this.strokeColor = s.strokeColor;
this.strokeWidth = s.strokeWidth;
this.strokeDashstyle = "solid";
this.label = Name.replace(/_/g, "'");
this.fontSize = (s.fontSize || 24) + "px";
this.fontColor = s.fontColor;
this.fontWeight = s.fontBold ? "bold" : "normal";
this.labelOutlineColor = s.labelOutlineColor || "#ffffff";
this.labelOutlineWidth = (typeof s.labelOutlineWidth === "number") ? s.labelOutlineWidth : 4;
}
function PR_Borders_Style(Name) {
const s = DBF_getStyles().pr;
this.fill = false;
this.stroke = true;
this.strokeColor = s.strokeColor;
this.strokeWidth = s.strokeWidth;
this.strokeDashstyle = "solid";
this.label = "PR" + Name;
this.labelYOffset = 10;
this.fontSize = (s.fontSize || 12) + "px";
this.fontColor = s.fontColor;
this.fontWeight = s.fontBold ? "bold" : "normal";
this.labelOutlineColor = s.labelOutlineColor || s.strokeColor;
this.labelOutlineWidth = (typeof s.labelOutlineWidth === "number") ? s.labelOutlineWidth : 3;
}
function Cam_Borders_Style(Name) {
const s = DBF_getStyles().radar;
this.stroke = true;
this.strokeColor = s.strokeColor;
this.strokeWidth = s.strokeWidth;
this.strokeDashstyle = "solid";
this.label = Name;
this.labelYOffset = 13;
this.fontSize = (s.fontSize || 12) + "px";
this.fontColor = s.fontColor;
this.fontWeight = s.fontBold ? "bold" : "normal";
this.labelOutlineColor = s.labelOutlineColor || s.strokeColor;
this.labelOutlineWidth = (typeof s.labelOutlineWidth === "number") ? s.labelOutlineWidth : 3;
}
function Ban_Borders_Style(Name, labelColor, strokeColor, strokeWidth, fontSize) {
const s = DBF_getStyles().ban;
this.fill = false;
this.stroke = true;
this.strokeColor = strokeColor;
this.strokeWidth = strokeWidth;
this.strokeDashstyle = "solid";
this.label = Name;
this.labelYOffset = 22;
this.fontSize = fontSize + "px";
this.fontColor = labelColor;
this.fontWeight = s.fontBold ? "bold" : "normal";
this.labelOutlineColor = s.labelOutlineColor || "#ffffff";
this.labelOutlineWidth = (typeof s.labelOutlineWidth === "number") ? s.labelOutlineWidth : 4;
}
function Equip_Borders_Style(Name) {
const s = DBF_getStyles().equip;
this.fill = false;
this.stroke = true;
this.strokeColor = s.strokeColor;
this.strokeWidth = s.strokeWidth;
this.strokeDashstyle = "solid";
this.label = Name;
this.labelYOffset = 10;
this.fontSize = (s.fontSize || 12) + "px";
this.fontColor = s.fontColor;
this.fontWeight = s.fontBold ? "bold" : "normal";
this.labelOutlineColor = s.labelOutlineColor || s.strokeColor;
this.labelOutlineWidth = (typeof s.labelOutlineWidth === "number") ? s.labelOutlineWidth : 3;
}
function Etabl_Borders_Style(Name) {
const s = DBF_getStyles().etabl;
this.fill = false;
this.stroke = true;
this.strokeColor = s.strokeColor;
this.strokeWidth = s.strokeWidth;
this.strokeDashstyle = "solid";
this.label = Name;
this.labelYOffset = 10;
this.fontSize = (s.fontSize || 12) + "px";
this.fontColor = s.fontColor;
this.fontWeight = s.fontBold ? "bold" : "normal";
this.labelOutlineColor = s.labelOutlineColor || s.strokeColor;
this.labelOutlineWidth = (typeof s.labelOutlineWidth === "number") ? s.labelOutlineWidth : 3;
}
function RS_getScale() {
try {
var s = DBF_getStyles().rs;
return s.scale || 1.0;
} catch (e) {
return 1.0;
}
}
function Geometrize(Name, coordinateString) {
console.log("📍 [Geometrize] Entrée pour :", Name);
console.log(" -> Données brutes reçues :", (coordinateString ? coordinateString.substring(0, 80) + "..." : "VIDE OU NULL"));
var tempVector = coordinateString.split(" ");
var polyPoints = new Array(tempVector.length);
for (var i = 0; i < tempVector.length; i++) {
var coordinateVector = tempVector[i].split(";");
var lon = parseFloat(coordinateVector[0]);
var lat = parseFloat(coordinateVector[1]);
// --- DÉBUT DU MOUCHARD DE DEBUG ---
if (isNaN(lon) || isNaN(lat)) {
console.error("🛑 ALERTE CRASH SILENCIEUX dans Geometrize !");
console.warn("Nom de la zone :", Name);
console.warn("Donnée brute analysée :", tempVector[i]);
console.warn("Résultat Longitude :", lon, "| Résultat Latitude :", lat);
console.warn("coordinateString complète :", coordinateString.substring(0, 200) + (coordinateString.length > 200 ? "…" : ""));
}
// --- FIN DU MOUCHARD ---
polyPoints[i] = new OpenLayers.Geometry.Point(lon, lat).transform(new OpenLayers.Projection("EPSG:4326"), W.map.getProjectionObject());
}
var polygon = new OpenLayers.Geometry.Polygon(new OpenLayers.Geometry.LinearRing(polyPoints));
return polygon;
}
function RS_buildIcon(code, value) {
code = (code || "").toUpperCase();
function uri(svg) { return "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svg).replace(/'/g, "%27").replace(/"/g, "%22"); }
if (code === "B14" || code.startsWith("B14")) {
var v = (value && value !== "NULL") ? String(value) : "";
if (!v) {
var m = code.match(/^B14[\s\-_]?(\d{1,3})$/i);
if (m) v = m[1];
}
v = (v || "").replace(/[^\d]/g, "");
var s = 32;
var svg =
'<svg xmlns="http://www.w3.org/2000/svg" width="' + s + '" height="' + s + '" viewBox="0 0 100 100">'
+ '<circle cx="50" cy="50" r="46" fill="#fff" stroke="#d81e05" stroke-width="12"/>'
+ (v ? '<text x="50" y="63" font-family="Arial,Helvetica" font-size="44" font-weight="700" text-anchor="middle" fill="#000">' + v + '</text>' : '')
+ '</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
if (code === "B33" || code.startsWith("B33")) {
let v = (value && value !== "NULL") ? String(value) : "";
if (!v) {
const m = code.match(/^B33[\s\-_]?(\d{1,3})$/i);
if (m) v = m[1];
}
v = (v || "").replace(/[^\d]/g, "");
const s = 32;
const bandAngle = -35;
const bandWidth = 18;
const halo = 8;
const svg =
'<svg xmlns="http://www.w3.org/2000/svg" width="' + s + '" height="' + s + '" viewBox="0 0 100 100">'
+ '<circle cx="50" cy="50" r="46" fill="#fff" stroke="#000" stroke-width="4"/>'
+ '<defs><clipPath id="b33clip"><circle cx="50" cy="50" r="42"/></clipPath></defs>'
+ '<g clip-path="url(#b33clip)">'
+ '<g transform="translate(50,50) rotate(' + bandAngle + ') translate(-50,-50)">'
+ '<line x1="-20" y1="50" x2="120" y2="50" stroke="#000" stroke-width="' + bandWidth + '" stroke-linecap="butt"/>'
+ '</g>'
+ '</g>'
+ (v ? '<text x="50" y="63" text-anchor="middle" font-family="Arial Narrow, Arial, Helvetica, sans-serif" font-weight="700" font-size="44" fill="none" stroke="#fff" stroke-width="' + halo + '" stroke-linejoin="round" paint-order="stroke"><tspan>' + v + '</tspan></text>' : '')
+ (v ? '<text x="50" y="63" text-anchor="middle" font-family="Arial Narrow, Arial, Helvetica, sans-serif" font-weight="700" font-size="44" fill="#000"><tspan>' + v + '</tspan></text>' : '')
+ '</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
if (code === "B31") {
var s = 32;
var svg = ''
+ '<svg xmlns="http://www.w3.org/2000/svg" width="576" height="576" viewBox="-0.944 -0.944 576 576">'
+ ' <circle stroke="#000000" stroke-width="0.1764" stroke-linecap="round" stroke-linejoin="round" cx="287.056" cy="287.056" r="286.968"/>'
+ ' <path fill="#FFFFFF" stroke="#000000" stroke-width="0.1764" stroke-linecap="round" stroke-linejoin="round"'
+ ' d="M125.736,378.429 l218.521-218.53 l98.938-97.348 C334.725,-12.889,187.828,0.014,94.169,93.208 C0.51,186.403,-13.123,333.233,61.777,442.078 L125.736,378.429z"/>'
+ ' <path fill="#FFFFFF" stroke="#000000" stroke-width="0.1764" stroke-linecap="round" stroke-linejoin="round"'
+ ' d="M511.886,131.385 l-71.274,71.385 L228.789,414.754 l-97.521,97.053 c108.646,75.308,255.606,62.118,349.105,-31.333 C573.873,387.023,587.139,240.069,511.886,131.385z"/>'
+ '</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
if (code === "EB10") {
var s = 60;
var svg = ''
+ '<svg xmlns="http://www.w3.org/2000/svg" width="500" height="177.07678" viewBox="0 0 500 177.07678">'
+ ' <path fill="#ffffff" stroke="#000000" stroke-width="0.85" d="M29.004,0.425c-15.681,0-28.579,12.898-28.579,28.579v119.069c0,15.681,12.898,28.579,28.579,28.579h441.992c15.681,0,28.579-12.898,28.579-28.579V29.004c0-15.681-12.898-28.579-28.579-28.579H29.004z"/>'
+ ' <path fill="#ff0000" d="M29.004,9.212h441.992c10.965,0,19.792,8.827,19.792,19.792v119.069c0,10.965-8.827,19.792-19.792,19.792H29.004c-10.965,0-19.792-8.827-19.792-19.792V29.004c0-10.965,8.827-19.792,19.792-19.792z"/>'
+ ' <rect x="31" y="29.85" width="438" height="117.38" fill="#ffffff"/>'
+ '</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
if (code === "EB20") {
var s = 60;
var svg = ''
+ '<svg xmlns="http://www.w3.org/2000/svg" width="500" height="177.07678" viewBox="0 0 500 177.07678">'
+ ' <path fill="#ffffff" stroke="#000000" stroke-width="0.85" d="M29.004,0.425c-15.681,0-28.579,12.898-28.579,28.579v119.069c0,15.681,12.898,28.579,28.579,28.579h441.992c15.681,0,28.579-12.898,28.579-28.579V29.004c0-15.681-12.898-28.579-28.579-28.579H29.004z"/>'
+ ' <path fill="#000000" d="M29.004,9.213c-10.965,0-19.792,8.827-19.792,19.792v119.068c0,10.965,8.827,19.792,19.792,19.792h441.992c10.965,0,19.792-8.827,19.792-19.792V29.004c0-10.965-8.827-19.792-19.792-19.792H29.004z"/>'
+ ' <rect x="30.1" y="23.8" width="440" height="124" fill="#ffffff"/>'
+ ' <path fill="#ff0000" d="M410.97,23.8L24.39,149.62c0.93,2.24,3.14,3.81,5.71,3.81h58.48L475.66,27.45c-0.97-2.14-3.1-3.65-5.61-3.65H410.97z"/>'
+ '</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
if (code === "C107") {
var s = 32;
var svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="574.93799" height="574.93799" viewBox="0 0 574.93799 574.93799">
<path d="m 41.496,574.438 h 491.947 c 10.872,0 21.3,-4.318 28.988,-12.007 7.688,-7.688 12.007,-18.115 12.007,-28.988 V 41.496 c 0,-10.873 -4.319,-21.3 -12.007,-28.988 C 554.742,4.819 544.314,0.5 533.442,0.5 H 41.496 C 30.623,0.5 20.196,4.819 12.508,12.507 4.819,20.195 0.5,30.623 0.5,41.496 v 491.947 c 0,10.873 4.319,21.3 12.007,28.988 7.688,7.688 18.116,12.007 28.989,12.007 z"
style="fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round"/>
<path d="m 544.919,529.72 c 0,6.744 -5.468,12.214 -12.214,12.214 H 42.659 c -6.746,0 -12.214,-5.47 -12.214,-12.214 V 41.173 c 0,-6.746 5.468,-12.214 12.214,-12.214 h 490.046 c 6.746,0 12.214,5.469 12.214,12.214 V 529.72 z"
style="fill:#0000ff;stroke:#000000"/>
<path d="m 438.34344,248.84928 -28.56671,-101.00762 -9.81224,-5.31619 -217.50475,-1.77206 -13.083,7.08825 -34.95775,101.00762 c -14.48636,2.2568 -19.97153,9.01419 -23.91899,16.54043 l 0,96.87161 27.80135,0 0,72.65461 49.06123,0 0,-72.65461 201.15102,0 0,74.42667 49.06122,0 0,-74.42667 22.89524,0 0,-95.69141 c -4.43579,-11.03152 -12.36985,-16.71902 -22.12662,-17.72063 z m -270.66095,0 24.59276,-81.51492 196.2449,3.54412 19.37264,77.9708 -240.2103,0 z"
style="fill:#ffffff;stroke:#ffffff;stroke-width:6;stroke-linejoin:round;"/>
<circle cx="164.735" cy="301.609" r="18.57" fill="#0000ff"/>
<circle cx="366.497" cy="301.609" r="18.57" fill="#0000ff"/>
</svg>
`;
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
if (code === "C108") {
var s = 32;
var svg = ''
+ '<svg xmlns="http://www.w3.org/2000/svg" width="574.938" height="574.938" viewBox="0 0 574.938 574.938">'
+ ' <path fill="#FFFFFF" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"'
+ ' d="M41.496,574.438h491.945c10.872,0,21.3-4.318,28.988-12.007c7.688-7.688,12.007-18.115,12.007-28.988V41.496'
+ ' c0-10.873-4.319-21.302-12.007-28.988C554.74,4.819,544.314,0.5,533.442,0.5H41.496c-10.873,0-21.302,4.317-28.988,12.007'
+ ' C4.819,20.195,0.5,30.623,0.5,41.496v491.945c0,10.873,4.317,21.3,12.007,28.988C20.195,570.117,30.623,574.438,41.496,574.438z"/>'
+ ' <path fill="#0000FF" d="M42.659,28.959c-6.746,0-12.216,5.468-12.216,12.214v456.786l477.582-469H42.659z"/>'
+ ' <path fill="#0000FF" stroke="#000000" d="M80.775,541.934h451.929c6.746,0,12.216-5.47,12.216-12.214V79.202L80.775,541.934z"/>'
+ ' <path fill="#FFFFFF" stroke="#FFFFFF" stroke-width="6" stroke-linejoin="round"'
+ ' d="M438.343,248.849l-28.566-101.007l-9.813-5.316l-217.504-1.771l-13.083,7.089L134.419,248.85'
+ ' c-14.486,2.257-19.973,9.015-23.919,16.541v96.871h27.801v72.653h49.062v-72.653h201.15v74.427h49.062v-74.427h22.896V266.57'
+ ' C456.033,255.538,448.1,249.851,438.343,248.849L438.343,248.849z M167.683,248.849l24.593-81.515l196.244,3.544'
+ ' l19.373,77.971H167.683z"/>'
+ ' <circle fill="#0000FF" cx="165.063" cy="300.662" r="18.56"/>'
+ ' <circle fill="#0000FF" cx="410.595" cy="300.131" r="18.56"/>'
+ ' <rect x="180.636" y="427.969" fill="#0000FF" width="18" height="16.667"/>'
+ ' <path fill="#FF0000" d="M544.919,41.173c0-6.745-5.47-12.214-12.216-12.214h-24.678l-477.582,469v31.761'
+ ' c0,6.744,5.47,12.214,12.216,12.214h38.115L544.919,79.202V41.173z"/>'
+ '</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
if (code === "C207") {
var s = 32;
var svg = ''
+ '<svg xmlns="http://www.w3.org/2000/svg" width="575.1" height="575.1" viewBox="-0.322 -0.549 575.09998 575.09998">'
+ ' <rect x="0.728" y="0.501" width="573" height="573" rx="40.973" ry="40.973"'
+ ' fill="#ffffff" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
+ ' <rect x="28.177" y="27.95" width="518.102" height="518.102" rx="13.524" ry="13.524" fill="#0056a4"/>'
+ ' <g transform="matrix(1.1017437,0,0,1.1017437,-1225.6245,-409.94628)" fill="#ffffff">'
+ ' <path d="m1512.8729,845.14553-38.8177-234.10417h-74.1146l4.6875,234.10417z" />'
+ ' <path d="m1461.3208,532.80699-18.4479-112.78124h-46.4844l2.1927,112.78124z" />'
+ ' <path d="m1233.1854,845.14553 38.7812-234.10417h74.151l-4.7239,234.10417z" />'
+ ' <path d="m1284.7374,532.80699 18.4063-112.78124h46.526l-2.2291,112.78124z" />'
+ ' <path d="m1373.102,590.97366h114.6719c8.6563,0.11453,19.2031,5.55729,20.6771,14.58854l5.1406,44.90104h32.125V605.7497'
+ ' c-0.3021-8.92187,3.3229-14.77604,18.5937-14.85416l24.2292,0.0781v-29.63022h-430.7916v29.63021l24.1875-0.0781'
+ ' c15.2708,0.0781,18.8958,5.9323,18.6354,14.85416v44.71355h32.125l5.1041-44.90104'
+ ' c1.5105-9.03125,12.0157-14.47396,20.6719-14.58855z" />'
+ ' </g>'
+ '</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
if (code === "C208") {
var s = 32;
var svg = ''
+ '<svg xmlns="http://www.w3.org/2000/svg" width="575.1" height="575.1" viewBox="-0.322 -0.549 575.09998 575.09998">'
+ ' <rect x="0.728" y="0.501" width="573" height="573" rx="40.973" ry="40.973"'
+ ' fill="#ffffff" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
+ ' <rect x="28.177" y="27.95" width="518.102" height="518.102" rx="13.524" ry="13.524" fill="#0056a4"/>'
+ ' <g transform="matrix(1.1017437,0,0,1.1017437,-1225.6245,-409.94628)" fill="#ffffff">'
+ ' <path d="m1512.8729,845.14553-38.8177-234.10417h-74.1146l4.6875,234.10417z" />'
+ ' <path d="m1461.3208,532.80699-18.4479-112.78124h-46.4844l2.1927,112.78124z" />'
+ ' <path d="m1233.1854,845.14553 38.7812-234.10417h74.151l-4.7239,234.10417z" />'
+ ' <path d="m1284.7374,532.80699 18.4063-112.78124h46.526l-2.2291,112.78124z" />'
+ ' <path d="m1373.102,590.97366h114.6719c8.6563,0.11453,19.2031,5.55729,20.6771,14.58854l5.1406,44.90104h32.125V605.7497'
+ ' c-0.3021-8.92187,3.3229-14.77604,18.5937-14.85416l24.2292,0.0781v-29.63022h-430.7916v29.63021l24.1875-0.0781'
+ ' c15.2708,0.0781,18.8958,5.9323,18.6354,14.85416v44.71355h32.125l5.1041-44.90104'
+ ' c1.5105-9.03125,12.0157-14.47396,20.6719-14.58855z" />'
+ ' </g>'
+ ' <path fill="#d40000" d="M -374.64375 -335.11506 L -374.64375 334.79404 L -396.47843 356.62872 C -401.77646 361.92675 -410.30681 361.92813 -415.60484 356.6301 L -437.43814 334.7968 L -437.43814 -335.11782 L -415.60484 -356.95113 C -410.30681 -362.24916 -401.77646 -362.24778 -396.47843 -356.94975 L -374.64375 -335.11506 z "'
+ ' transform="matrix(-0.70710678,-0.70710678,-0.70710678,0.70710678,0,0)"/>'
+ '</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: s * scale, h: s * scale };
}
}
function RS_buildPanonceau(code, value, refWidth) {
code = (code || "").toUpperCase().trim();
value = (value || "").trim();
function uri(svg) {
return "data:image/svg+xml;charset=UTF-8," +
encodeURIComponent(svg).replace(/'/g, "%27").replace(/"/g, "%22");
}
function fmtDistance(v) {
if (!v) return "";
const s = String(v).replace(",", ".").replace(/\s+/g, "");
const m = s.match(/^([\d.]+)\s*(km|m)?$/i);
if (!m) return v;
let num = parseFloat(m[1]);
const unit = (m[2] || "m").toLowerCase();
if (unit === "km") return (num + "").replace(".", ",") + " km";
if (num >= 1000) return (Math.round(num) / 1000 + "").replace(".", ",") + " km";
return Math.round(num) + " m";
}
const baseW = Math.max(84, Math.min(refWidth || 120, 140));
const baseH = 26;
let w = baseW, h = baseH, txt = "";
if (code === "M9Z") txt = "RAPPEL";
else if (code === "M1") {
txt = fmtDistance(value || "");
h = 32;
const fs = 18, pad = 14, ff = "Arial Narrow, Arial, Helvetica, sans-serif";
const emW = fs * 0.62;
w = Math.max(84, Math.round(pad * 2 + emW * (txt.length || 4)));
const r = 6;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">` +
`<rect x="1.5" y="1.5" rx="${r}" ry="${r}" width="${w - 3}" height="${h - 3}" fill="#fff" stroke="#000" stroke-width="3"/>` +
`<text x="${w / 2}" y="${h / 2 + fs * 0.38}" font-family="${ff}" font-size="${fs}" font-weight="700" text-anchor="middle" fill="#000">${txt}</text>` +
`</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M3A") {
const dir = String(value || "droite").trim().toLowerCase();
const isLeft = ["gauche", "2", "left", "l"].includes(dir);
const w = 32, h = 32;
const r = 6, stroke = 3;
const officialPathD =
"m -38.631499,-5.8489465 12.908216,0.125613 -0.125661,-12.9081605 -3.139567,-3.139567 0.03919,9.625895 -14.84153,-14.84153 -3.195849,3.195848 14.841531,14.8415213 -9.625897,-0.03918 z";
const margin = 4;
const innerW = w - margin * 2;
const innerH = h - margin * 2;
const vbW = 27.289993, vbH = 27.289966;
const s = Math.min(innerW / vbW, innerH / vbH);
const preTranslateX = 50;
const preTranslateY = 30;
const arrowGroupRight =
`<g transform="translate(${margin},${margin}) scale(${s}) translate(${preTranslateX},${preTranslateY})">` +
`<path d="${officialPathD}" fill="#000" stroke="none"/>` +
`</g>`;
const arrowGroup = isLeft
? `<g transform="translate(${w},0) scale(-1,1)">${arrowGroupRight}</g>`
: arrowGroupRight;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">` +
`<rect x="1.5" y="1.5" rx="${r}" ry="${r}" width="${w - 3}" height="${h - 3}" fill="#fff" stroke="#000" stroke-width="${stroke}"/>` +
arrowGroup +
`</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M2") {
const txt = fmtDistance(value || "");
const h = 32, fs = 18, ff = "Arial Narrow, Arial, Helvetica, sans-serif";
const pad = 14, emW = fs * 0.62, arrowSpace = 26;
let w = Math.max(100, Math.round(pad * 2 + Math.max(4, txt.length) * emW + arrowSpace * 2));
const r = 6, leftX = 18, rightX = w - 18, shaftTop = 9, shaftBot = h - 6, headW = 7, headY = shaftTop;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">` +
`<rect x="1.5" y="1.5" rx="${r}" ry="${r}" width="${w - 3}" height="${h - 3}" fill="#fff" stroke="#000" stroke-width="3"/>` +
`<line x1="${leftX}" y1="${shaftBot}" x2="${leftX}" y2="${shaftTop}" stroke="#000" stroke-width="3" stroke-linecap="round"/>` +
`<path d="M ${leftX - headW},${shaftTop + 8} L ${leftX},${headY} L ${leftX + headW},${shaftTop + 8} Z" fill="#000"/>` +
`<line x1="${rightX}" y1="${shaftBot}" x2="${rightX}" y2="${shaftTop}" stroke="#000" stroke-width="3" stroke-linecap="round"/>` +
`<path d="M ${rightX - headW},${shaftTop + 8} L ${rightX},${headY} L ${rightX + headW},${shaftTop + 8} Z" fill="#000"/>` +
`<text x="${w / 2}" y="${h / 2 + fs * 0.38}" font-family="${ff}" font-size="${fs}" font-weight="700" text-anchor="middle" fill="#000">${txt}</text>` +
`</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M3D") {
const w = 32;
const h = 32;
const r = 6;
const stroke = 3;
const centerX = w / 2;
const shaftTop = 6;
const shaftBot = h - 10;
const headLen = 7;
const headHalf = 6;
const yHead = h - 6;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">` +
`<rect x="1.5" y="1.5" rx="${r}" ry="${r}" width="${w - 3}" height="${h - 3}" fill="#fff" stroke="#000" stroke-width="${stroke}"/>` +
`<line x1="${centerX}" y1="${shaftTop}" x2="${centerX}" y2="${shaftBot}" stroke="#000" stroke-width="${stroke}" stroke-linecap="round"/>` +
`<polygon points="
${centerX - headHalf},${yHead - headLen}
${centerX},${yHead}
${centerX + headHalf},${yHead - headLen}
" fill="#000"/>` +
`</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4A") {
const w = 56, h = 32;
const r = 6, stroke = 3;
const srcW = 739, srcH = 300;
const layerShiftY = -752.36218;
const margin = 4;
const innerW = w - margin * 2;
const innerH = h - margin * 2;
const s = Math.min(innerW / srcW, innerH / srcH);
const carBody = `M 117.77367,953.22223
c -5.15811,-0.32025 -10.06219,-1.08759 -10.58836,-9.56006
v -29.85149
c 0.53272,-8.53723 6.95609,-13.44232 14.0008,-13.97577
l 122.69341,-11.06439 80.17858,-80.17858 h 200.07085
l 76.34743,76.34744 h 20.70822
c 7.09313,0.25401 9.58524,2.5844 10.57946,10.57947
v 57.75511 H 117.77367 Z`;
const window1 = `M 412.68588,884.31495 H 289.74518
c -6.72863,-0.28913 -8.14501,-4.1394 -5.65759,-8.45926
l 49.06428,-49.06428 h 79.53401 z`;
const window2 = `M 428.55051,884.31495 H 536.46337
c 5.90615,-0.28913 7.1494,-10.08864 4.96602,-14.4085
l -43.06683,-43.11504 h -69.81205 z`;
const wheelsGroup =
`<g transform="matrix(1.2606884,0,0,1.2606884,-9.4267,524.78681)">
<path d="m 186.54661,371.66315
c 0,16.41058 -13.3034,29.71398 -29.71398,29.71398
-16.41058,0 -29.71399,-13.3034 -29.71399,-29.71398
0,-16.41058 13.30341,-29.71399 29.71399,-29.71399
16.41058,0 29.71398,13.30341 29.71398,29.71399 z"
transform="matrix(1.1818181,0,0,1.1818181,-24.860346,-98.719168)"
fill="#000" stroke="#ffffff" stroke-width="2.53846169"/>
<path d="m 186.54661,371.66315
c 0,16.41058 -13.3034,29.71398 -29.71398,29.71398
-16.41058,0 -29.71399,-13.3034 -29.71399,-29.71398
0,-16.41058 13.30341,-29.71399 29.71399,-29.71399
16.41058,0 29.71398,13.30341 29.71398,29.71399 z"
transform="matrix(1.1818181,0,0,1.1818181,261.38805,-97.289084)"
fill="#000" stroke="#ffffff" stroke-width="2.53846169"/>
</g>`;
const pictogram =
`<g transform="translate(${margin},${margin})
scale(${s})
translate(0,${layerShiftY})">
<path d="${carBody}" fill="#000" stroke="#000" stroke-width="1.26068842"/>
<path d="${window1}" fill="#ffffff" stroke="none"/>
<path d="${window2}" fill="#ffffff" stroke="none"/>
${wheelsGroup}
</g>`;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg"
width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<rect x="1.5" y="1.5" rx="${r}" ry="${r}"
width="${w - 3}" height="${h - 3}"
fill="#fff" stroke="#000" stroke-width="${stroke}"/>
${pictogram}
</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4B") {
const w = 56, h = 32;
const r = 6, stroke = 3;
const srcW = 739, srcH = 300;
const layerShiftY = -752.36218;
const margin = 4;
const innerW = w - margin * 2;
const innerH = h - margin * 2;
const s = Math.min(innerW / srcW, innerH / srcH);
const busBody = `
m 65.006101,994.17227
c -13.360978,-1.705 -27.614236,-6.09796 -26.665427,-26.64158
l 0,-52.43286
c 2.117675,-15.55105 12.575772,-17.836 19.573551,-20.40632
l 18.004892,-67.13505
c 0.503673,-1.75637 -1.051869,-2.91108 -4.993629,-2.8805
l -5.919387,0 0,-39.10151
609.356929,0
c 16.27003,1.27046 15.11737,11.16552 15.29477,15.2811
l 0,193.31672 z`;
const frontWhite = `
m 86.58393,823.54872
33.267,0
c 3.86906,-0.18291 5.41985,1.55363 5.2863,5.384
l 0,58.58648
c -0.0773,4.91784 -3.24827,6.66062 -6.74699,6.87169
l -45.49008,0
c -4.581395,-0.10657 -6.502362,-0.60194 -6.051431,-6.16327
6.701842,-21.63965 17.089221,-63.46302 19.735201,-64.6789 z`;
const winRects = [
{ x: 137.76866, y: 823.8363, w: 124.95871, h: 70.855232, ry: 6.1679206 },
{ x: 275.42181, y: 823.8363, w: 124.95871, h: 70.855232, ry: 6.1679206 },
{ x: 411.58569, y: 823.8363, w: 124.95871, h: 70.855232, ry: 6.1679206 },
{ x: 548.0332, y: 823.8363, w: 124.95871, h: 70.855232, ry: 6.1679206 },
];
const wheelsGroup = `
<g transform="matrix(1.2623691,0,0,1.2612398,-13.855469,526.51775)">
<path
d="m 186.54661,371.66315
c 0,16.41058 -13.3034,29.71398 -29.71398,29.71398
-16.41058,0 -29.71399,-13.3034 -29.71399,-29.71398
0,-16.41058 13.30341,-29.71399 29.71399,-29.71399
16.41058,0 29.71398,13.30341 29.71398,29.71399 z"
transform="matrix(1.4211617,0,0,1.4211617,-58.328645,-166.65405)"
fill="#000" stroke="#ffffff" stroke-width="2.53846169"/>
<path
d="m 186.54661,371.66315
c 0,16.41058 -13.3034,29.71398 -29.71398,29.71398
-16.41058,0 -29.71399,-13.3034 -29.71399,-29.71398
0,-16.41058 13.30341,-29.71399 29.71399,-29.71399
16.41058,0 29.71398,13.30341 29.71398,29.71399 z"
transform="matrix(1.4211617,0,0,1.4211617,204.52558,-168.45783)"
fill="#000" stroke="#ffffff" stroke-width="2.53846169"/>
</g>`;
let windowsSvg = winRects.map(r =>
`<rect x="${r.x}" y="${r.y}" width="${r.w}" height="${r.h}" ry="${r.ry}" fill="#ffffff" stroke="none"/>`
).join("");
const pictogram =
`<g transform="translate(${margin},${margin}) scale(${s}) translate(0,${layerShiftY})">
<path d="${busBody}" fill="#000" stroke="none"/>
${windowsSvg}
<path d="${frontWhite}" fill="#ffffff" stroke="none"/>
${wheelsGroup}
</g>`;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<rect x="1.5" y="1.5" rx="${r}" ry="${r}"
width="${w - 3}" height="${h - 3}"
fill="#fff" stroke="#000" stroke-width="${stroke}"/>
${pictogram}
</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4C") {
const w = 56, h = 32;
const r = 6, stroke = 3;
const srcW = 430, srcH = 175;
const layerShiftY = -877.36219;
const margin = 4;
const innerW = w - margin * 2;
const innerH = h - margin * 2;
const s = Math.min(innerW / srcW, innerH / srcH);
const motoGroup = `
<g transform="matrix(0.70621469,0,0,0.70621469,276.36309,364.04201)">
<path d="m -205.64013,832.76137 c -29.961,0 -54.25,23.617 -54.25,52.75 0,29.133 24.289,52.75 54.25,52.75 29.961,0 54.25,-23.617 54.25,-52.75 0,-29.133 -24.289,-52.75 -54.25,-52.75 z m 0,89 c -20.297,0 -36.75,-16.175 -36.75,-36.13 0,-19.954 16.453,-36.13 36.75,-36.13 20.297,0 36.75,16.176 36.75,36.13 0,19.955 -16.453,36.13 -36.75,36.13 z"
fill="#000"/>
<path d="m 24.35987,833.76137 c -29.961,0 -54.25,23.617 -54.25,52.75 0,29.133 24.289,52.75 54.25,52.75 29.961,0 54.25,-23.617 54.25,-52.75 0,-29.133 -24.289,-52.75 -54.25,-52.75 z m 0,89 c -20.297,0 -36.75,-16.175 -36.75,-36.13 0,-19.954 16.453,-36.13 36.75,-36.13 20.297,0 36.75,16.176 36.75,36.13 0,19.955 -16.453,36.13 -36.75,36.13 z"
fill="#000"/>
<!-- (les deux ellipses ci-dessous ont un stroke rouge dans le fichier ; on les supprime ou on met noir pour cohérence) -->
<!-- Elles ne sont pas indispensables au pictogramme ; on peut les omettre. -->
<polygon transform="translate(-374.35913,563.79237)"
points="217.469,234.969 167.469,311.969 177.969,318.469 234.969,242.469"
fill="#000"/>
<path d="m -108.70513,893.75737 c 3.17,1.29 19.07,0.916 22.788,-0.996 l 60.027,0 59.5,-91 -129.5,0 0,43.116 c -0.823,-0.074 -1.656,-0.116 -2.5,-0.116 -14.636,0 -26.5,11.417 -26.5,25.5 0,10.562 6.674,19.625 16.185,23.496"
fill="#000"/>
<rect x="-17.390137" y="880.76135" width="104" height="13.5" fill="#000"/>
<polygon transform="translate(-374.35913,563.79237)"
points="389.969,274.969 375.969,281.969 389.469,310.469 401.469,307.469"
fill="#000"/>
<polygon transform="translate(-374.35913,563.79237)"
points="192.469,225.969 194.469,219.469 198.469,215.969 209.969,216.469 217.969,220.469 224.469,225.969 227.969,221.469 232.969,209.469 236.969,204.469 240.969,202.969 246.969,201.469 253.469,200.469 264.469,198.469 267.969,200.969 269.469,204.969 242.969,211.969 238.469,219.469 249.969,224.469 276.969,226.469 286.469,227.469 294.969,230.469 303.969,235.969 309.969,244.969 265.469,290.969 255.969,287.469 255.469,279.969 249.469,278.969 241.969,278.469 234.469,275.469 228.969,270.469 221.969,258.469 217.469,251.969 212.469,249.469 198.469,249.969 193.969,244.469 191.969,237.969 191.969,231.469"
fill="#000"/>
<polygon transform="translate(-374.35913,563.79237)"
points="407.219,237.969 412.719,262.969 423.219,266.469 430.219,270.469 436.719,274.969 443.719,281.969 448.219,287.969 452.719,298.469 453.719,303.969 439.219,305.969 402.719,277.969 378.719,279.469 367.219,248.469 399.219,239.469"
fill="#000"/>
<rect x="-101.89014" y="836.26135" width="19.5" height="15" fill="#000"/>
<rect x="-105.64014" y="881.26135" width="95" height="12.75" fill="#000"/>
</g>`;
const pictogram =
`<g transform="translate(${margin},${margin}) scale(${s}) translate(0,${layerShiftY})">
${motoGroup}
</g>`;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<rect x="1.5" y="1.5" rx="${r}" ry="${r}"
width="${w - 3}" height="${h - 3}"
fill="#fff" stroke="#000" stroke-width="${stroke}"/>
${pictogram}
</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4F") {
const raw = String(value || "").trim();
function fmtTonnage(s) {
if (!s) return "> 3,5 t";
const m = s.replace(/\s+/g, "")
.match(/^([<>]=?|≤|≥)?\s*([\d.,]+)\s*(t|tonnes?)?$/i);
if (!m) {
const withT = /t\b/i.test(s) ? s : (s + " t");
return withT.replace(/\./g, ",").replace(/\s*t\b/i, " t");
}
let op = m[1] || "";
let num = m[2] || "";
if (op === "<=") op = "≤";
if (op === ">=") op = "≥";
num = num.replace(",", ".");
const n = isNaN(parseFloat(num)) ? num : String(parseFloat(num));
const withComma = n.replace(".", ",");
return (op ? op + " " : "") + withComma + " t";
}
const txt = fmtTonnage(raw);
const h = 32;
const fs = 18;
const ff = "Arial Narrow, Arial, Helvetica, sans-serif";
const pad = 14;
const emW = fs * 0.62;
let w = Math.max(84, Math.round(pad * 2 + emW * Math.max(4, txt.length)));
const r = 6;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">` +
`<rect x="1.5" y="1.5" rx="${r}" ry="${r}" width="${w - 3}" height="${h - 3}" fill="#fff" stroke="#000" stroke-width="3"/>` +
`<text x="${w / 2}" y="${h / 2 + fs * 0.38}" font-family="${ff}" font-size="${fs}" font-weight="700" text-anchor="middle" fill="#000">${txt}</text>` +
`</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4G") {
const w = 56, h = 32;
const r = 6, stroke = 3;
const srcW = 430.79999, srcH = 300;
const layerShiftY = -752.36218;
const margin = 3;
const innerW = w - margin * 2;
const innerH = h - margin * 2;
const s = Math.min(innerW / srcW, innerH / srcH);
const truckGroup = `
<g transform="matrix(1.2622928,0,0,1.2622928,291.45569,962.24437)">
<path
id="path3854"
d="m 73.980186,7.1260033 -269.173716,0 0,-13.92117 4.89407,0 0,-34.2130303 c -1.01195,-5.5112 8.30559,-8.29403 9.34322,-6.84261 l 3.91487,-37.595009 c 0.15177,-4.079433 1.30083,-7.765569 8.78241,-8.074555 l 32.23916,0 0,-27.003639 c -0.31061,-7.22221 4.15984,-8.92065 8.58958,-8.58958 l 194.205856,0 c 7.20455,0 7.37569,5.73912 7.20455,7.20455 z"
style="fill:#000000;stroke:none"/>
<path
id="path3862"
d="m -133.35497,-46.395227 0,-36.246535 c -0.15336,-1.375633 0.31576,-2.952489 -2.75822,-2.764563 l -27.73547,0 c -1.80037,-0.117556 -5.33706,1.004371 -5.2866,5.29875 l -2.98807,33.712348 z"
style="fill:#ffffff;stroke:none"/>
<g transform="translate(-232.92973,-137.90311)" id="g3850">
<path
d="m 235.36017,128.13559 c 0,15.72607 -12.74851,28.47458 -28.47458,28.47458 -15.72608,0 -28.47458,-12.74851 -28.47458,-28.47458 0,-15.72608 12.7485,-28.474577 28.47458,-28.474577 15.72607,0 28.47458,12.748497 28.47458,28.474577 z"
transform="matrix(1.0966518,0,0,1.0966518,-135.87723,4.4701928)"
style="fill:#000000;stroke:#ffffff;stroke-width:3"/>
<path
d="m 235.36017,128.13559 c 0,15.72607 -12.74851,28.47458 -28.47458,28.47458 -15.72608,0 -28.47458,-12.74851 -28.47458,-28.47458 0,-15.72608 12.7485,-28.474577 28.47458,-28.474577 15.72607,0 28.47458,12.748497 28.47458,28.474577 z"
transform="matrix(1.0966518,0,0,1.0966518,23.227608,4.4701928)"
style="fill:#000000;stroke:#ffffff;stroke-width:3"/>
</g>
</g>`;
const pictogram =
`<g transform="translate(${margin},${margin}) scale(${s}) translate(0,${layerShiftY})">
${truckGroup}
</g>`;
const svg =
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<rect x="1.5" y="1.5" rx="${r}" ry="${r}"
width="${w - 3}" height="${h - 3}"
fill="#fff" stroke="#000" stroke-width="${stroke}"/>
${pictogram}
</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4K") {
const w = 64, h = 24;
const r = 6, stroke = 3;
const srcW = 300, srcH = 304.54999;
const layerShiftY = -747.81217;
const margin = 4;
const innerW = w - margin * 2;
const innerH = h - margin * 2;
const s = Math.min(innerW / srcW, innerH / srcH);
const pictogram = `
<g transform="translate(${margin},${margin}) scale(${s}) translate(0,${layerShiftY})">
<g transform="matrix(0.54883723,0,0,0.54883723,-507.16318,1051.4543)">
<g transform="translate(449.10268,-577.25898)">
<path style="fill:#000000;stroke:none"
d="m 606.35593,366.10169 47.80225,12.80882 -24.54791,23.3145 77.60084,-19.04195 15.54478,43.92573 17.23397,-25.76397 12.4964,30.9989 10.95914,-33.50898 19.31855,27.65167 6.05407,-42.68482 80.04472,22.07894 -36.89062,-26.55914 64.21432,-13.2197 0,90.88984 c 0.77768,11.64719 -7.77725,18.79831 -16.84322,21.61016 l 0,49.25848 c -0.0479,9.74322 -2.28104,10.63087 -6.05484,10.48729 l -22.22906,0 c -2.75466,-0.0245 -8.24106,-0.0433 -8.26271,-8.26271 l 0,-51.48306 -180.82627,0 0,50.21187 c 0.0938,7.11842 -2.54828,9.24216 -5.5044,9.5339 l -25.79857,0 c -3.09648,-0.21747 -4.00256,0.82064 -5.56144,-5.56144 l 0,-54.18433 c -11.15662,-0.56558 -17.9871,-10.1699 -18.75,-18.75 z" />
<g transform="translate(451.66094,0.00652086)">
<circle fill="#ffffff" r="19.9" cx="191.9" cy="431.7"/>
<circle fill="#ffffff" r="19.9" cx="402.8" cy="431.7"/>
</g>
</g>
<path fill="#ff0000" stroke="none"
d="m 1175.927,-406.93856 26.2976,-105.66737 27.9661,109.87817 19.3856,-24.70868 2.2245,30.19068 53.7077,-60.69916 -23.517,60.06356 63.8771,-19.06779 -43.5381,47.66949 30.5085,-1.27119 -49.5763,40.04238 79.1314,-7.62712 -68.1674,52.91313 67.5318,-20.49788 -77.5424,62.92373 84.5339,17.16102 -107.733,24.47033 17.7966,11.75848 -47.6695,-14.30085 -5.0848,32.41526 -15.2542,-21.61017 -9.8517,22.88135 -12.3941,-20.97457 -14.6186,22.24576 -13.9831,-35.27543 -51.8008,12.71187 13.3474,-8.58051 -97.5635,-28.2839 76.7592,-9.70409 -57.3736,-24.61794 50.2118,-6.99153 -33.3686,-28.91949 39.7246,6.99153 -61.3348,-62.28814 74.2361,22.04033 -66.609,-100.21829 85.1695,38.13559 -18.1144,-28.6017 36.2288,19.70339 -5.0847,-58.15678 z"/>
</g>
</g>`;
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<rect x="1.5" y="1.5" rx="${r}" ry="${r}"
width="${w - 3}" height="${h - 3}"
fill="#fff" stroke="#000" stroke-width="${stroke}"/>
${pictogram}
</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4L") {
const w = 64, h = 24;
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="304.55" viewBox="0 0 300 304.55">
<g transform="translate(0,-747.81217)">
<g transform="matrix(0.50372367,0,0,0.50372367,-427.73823,976.95041)">
<rect ry="65.451103" y="-452.08929" x="851.85663" height="599.09375" width="590.15643"
style="fill:#ffffff;stroke:#000000;stroke-width:5.40820599;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
<g transform="translate(847.16351,-429.66106)">
<g>
<path d="m 78.425932,402.36217 c 5.965599,-0.45215 18.557406,-3.56983 29.315738,-2.18131 32.54649,4.20062 62.42662,15.14849 88.02104,15.23024 33.82733,2.06358 63.57905,-17.52909 95.00108,-16.13215 46.10819,-0.0985 50.15768,17.07323 102.68963,16.73849 21.94807,0.943 54.65605,-9.06431 82.43011,-15.06238 13.1364,-2.83693 23.22253,-1.42985 37.29645,0.11064 3.68767,0.40364 10.91423,0.34143 10.91423,0.34143"
style="fill:none;stroke:#000000;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
<path d="m 77.801453,449.16774 c 5.965599,-0.45215 18.557406,-3.56983 29.315737,-2.18131 32.54649,4.20062 62.42662,15.14849 88.02104,15.23024 33.82733,2.06358 63.57905,-17.52909 95.00108,-16.13215 46.10819,-0.0985 50.15768,17.07323 102.68963,16.73849 21.94807,0.943 54.65605,-9.06431 82.43011,-15.06238 13.1364,-2.83693 23.22253,-1.42985 37.29645,0.11064 3.68767,0.40364 10.91423,0.34143 10.91423,0.34143"
style="fill:none;stroke:#000000;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
<path d="m 142.69068,348.30508 315.73093,0"
style="fill:none;stroke:#000000;stroke-width:7;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
<path d="m 154.13136,338.76749 c 0.19183,3.67259 1.52419,6.3136 3.58825,6.21504 l 20.59265,0 c 1.59917,0.16968 4.19409,-1.37571 5.05638,-5.05639 l 0,-24.03631 97.41418,0 c 12.92078,22.43128 32.34204,12.43435 38.87589,0 l 96.01926,0 0,23.55181 c 0.28625,2.73868 2.83775,5.87619 5.7537,5.7537 l 18.1131,0 c 2.05269,0.15662 6.32225,-1.37103 6.95947,-6.95947 l 0,-66.88322 c 0.13288,-1.95421 -1.38941,-5.05082 -5.28083,-5.28083 l -20.77951,0 c -1.8143,-0.0933 -4.9887,1.55761 -4.76593,4.76593 l 0,21.85293 -48.04237,0 0,-15.57204 -135.72854,0 0,15.57204 -48.53842,0 0,-21.22567 -5.39319,-5.39319 -17.52813,0 -6.31596,6.31596 z"
style="fill:#000000;stroke:none"/>
</g>
<!-- ellipse rouge officielle (chemin elliptique transformé) -->
<path d="m 446.50426,249.494 c 0,23.85687 -65.4499,43.19668 -146.18645,43.19668 -80.73655,0 -146.18645,-19.33981 -146.18645,-43.19668 0,-23.85686 65.4499,-43.19667 146.18645,-43.19667 80.73655,0 146.18645,19.33981 146.18645,43.19667 z"
style="fill:#ff0000;stroke:none"
transform="matrix(0.99999995,0,0,1.9676022,4.6167523e-6,-283.20814)"/>
</g>
</g>
</g>
</svg>`.trim();
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4M") {
const w = 64, h = 24;
const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="430" height="366" id="svg3864" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="France_road_sign_M4m.svg">
<defs id="defs3866"/>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.6871719" inkscape:cx="-7.8266119" inkscape:cy="57.852457" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" inkscape:snap-bbox="true" inkscape:bbox-paths="true" inkscape:bbox-nodes="true" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-bbox-midpoints="true" inkscape:object-paths="true" inkscape:snap-intersection-paths="true" inkscape:object-nodes="true" inkscape:snap-smooth-nodes="true" inkscape:snap-midpoints="true" inkscape:snap-object-midpoints="true" inkscape:snap-center="true" inkscape:snap-page="true" showguides="true" inkscape:guide-bbox="true" inkscape:window-width="1440" inkscape:window-height="788" inkscape:window-x="0" inkscape:window-y="1" inkscape:window-maximized="1"/>
<metadata id="metadata3869">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g inkscape:label="Calque 1" inkscape:groupmode="layer" id="layer1" transform="translate(0,-686.36219)">
<rect style="fill:#ffffff;stroke:#000000;stroke-width:2.209692;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" id="rect2995" width="427.79013" height="363.79031" x="1.1049371" y="687.46704" ry="64.742134"/>
<g id="g4285" transform="matrix(1.384742,0,0,1.384742,965.48278,2.0987819)">
<polygon transform="translate(-829.3453,340.32404)" id="polygon4244" points="328.114,357.475 335.096,356.991 335.096,351.176 340.085,345.361 354.383,345.361 359.371,351.176 359.371,393.329 354.716,399.143 340.085,399.627 335.428,394.298 335.096,387.999 328.114,387.999 328.114,376.371 293.531,378.309 285.883,382.186 277.237,378.793 243.32,375.886 243.32,387.999 236.004,387.999 236.004,393.813 232.347,398.659 215.721,399.143 212.396,393.813 212.728,351.661 216.385,345.848 231.682,345.848 236.004,350.691 236.336,356.991 243.652,357.475 243.652,368.619 277.57,366.682 284.885,362.805 293.531,366.195 328.114,368.619 " style="stroke:#000000;stroke-width:0.17640001;stroke-linecap:round;stroke-linejoin:round"/>
<rect id="rect4246" height="4.8449998" width="167.925" y="667.27502" x="-625.92828" style="stroke:#000000;stroke-width:5;stroke-linecap:round;stroke-linejoin:round"/>
<polygon transform="translate(-829.3453,340.32404)" id="polygon4248" points="341.393,298.614 234.724,299.831 234.724,189.119 341.393,189.119 " style="fill:none;stroke:#ffff00;stroke-width:0.17640001;stroke-linecap:round;stroke-linejoin:round"/>
<polygon transform="translate(-829.3453,340.32404)" id="polygon4250" points="363.061,314.43 212.223,313.213 224.724,175.736 350.56,175.736 " style="stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:round"/>
<rect id="rect4252" height="121" width="112" y="524.38104" x="-598.28827" style="fill:#f17e01"/>
</g>
</g>
</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else if (code === "M4X") {
const w = 64, h = 24;
const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="430" height="160" version="1.1">
<g transform="translate(0,-892.36219)">
<rect style="fill:#ffffff;stroke:#000000;stroke-width:1.46001;stroke-miterlimit:4;stroke-dasharray:none"
width="428.53983" height="158.53999" x="0.73009604" y="893.0921" ry="28.214649"/>
<g transform="matrix(0.73971716,0,0,0.73971716,228.63796,270.88608)">
<path d="m 251.93691,931.63933 c 0,-37.371 -49.1,-67.667 -109.667,-67.667 -60.567,0 -109.667,30.295 -109.667,67.667 0,0.223 0.01,0.444 0.014,0.667 l -0.013,0 0,72.66697 84.358,0 c 0.536,-13.34297 11.732,-23.99997 25.476,-23.99997 13.741,0 24.938,10.657 25.474,23.99997 l 84.025,0 0,-72.66697 -0.014,0 c 0.004,-0.222 0.014,-0.444 0.014,-0.667 z m -126.334,13.082 0,0.085 -54,0 0,-0.214 c -9.042,-1.309 -16,-9.134 -16,-18.62 0,-9.486 6.958,-17.311 16,-18.62 l 0,-0.214 54,0 0,0.085 c 9.527,0.852 17.001,8.915 17.001,18.749 0,9.834 -7.474,17.898 -17.001,18.749 z m 102.667,30.419 c 0,9.572 -8.581,17.333 -19.166,17.333 -10.523,0 -19.059,-7.672 -19.158,-17.167 l -0.009,0 0,-53.333 c 0,-9.573 8.581,-17.333 19.167,-17.333 10.585,0 19.166,7.76 19.166,17.333 0,0.736 -0.067,1.458 -0.166,2.169 l 0,48.827 c 0.098,0.714 0.166,1.434 0.166,2.171 z"
style="fill:#000000;stroke:none"/>
<ellipse ry="13.667" rx="14" cy="924.47333" cx="209.10394" style="fill:#000000;stroke:none"/>
<ellipse ry="23" rx="22.875" cy="1006.9733" cx="142.5199" style="fill:#000000;stroke:none"/>
<rect height="10" width="30" y="994.97333" x="9.7698994" style="fill:#000000;stroke:none"/>
<path d="m -282.33616,1004.6011 c -2.97667,-0.1849 -5.80674,-0.6277 -6.11039,-5.51703 l 0,-17.22687 c 0.30743,-4.92671 4.01426,-7.75737 8.07967,-8.06522 l 70.80463,-6.3851 46.26993,-46.26993 115.458067,0 44.0590258,44.05903 11.9504218,0 c 4.0933454,0.14659 5.5315064,1.49142 6.1052574,6.10526 l 0,33.32966 -280.661362,0 z"
style="fill:#000000;stroke:#000000;stroke-width:0.72752553px;stroke-linecap:butt;stroke-linejoin:miter"/>
<circle cx="-238.98311" cy="1005.0933" r="26.5" style="fill:#ffffff;stroke:none"/>
<circle cx="-30.730089" cy="1006.1337" r="26.5" style="fill:#ffffff;stroke:none"/>
<circle r="23.5" cy="1005.0933" cx="-238.98311" style="fill:#000000;stroke:none"/>
<circle r="23.5" cy="1006.1337" cx="-30.730089" style="fill:#000000;stroke:none"/>
<path d="m -112.14648,964.83563 -70.94734,0 c -3.883,-0.16685 -4.70037,-2.38879 -3.26492,-4.88172 l 28.31431,-28.3143 45.89795,0 z"
style="fill:#ffffff;stroke:none"/>
<path d="m -102.99122,964.83563 62.274987,0 c 3.408356,-0.16685 4.125818,-5.82201 2.86582,-8.31494 l -24.85326,-24.88108 -40.287547,0 z"
style="fill:#ffffff;stroke:none"/>
</g>
</g>
</svg>`;
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
else txt = (code + (value && value !== "NULL" ? " " + value : ""));
const svg =
'<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="' + h + '" viewBox="0 0 ' + w + ' ' + h + '">' +
'<rect x="1.5" y="1.5" rx="4" ry="4" width="' + (w - 3) + '" height="' + (h - 3) + '" fill="#fff" stroke="#000" stroke-width="3"/>' +
'<text x="' + (w / 2) + '" y="' + (h / 2 + 6) + '" font-family="Arial,Helvetica" font-size="14" font-weight="700" text-anchor="middle" fill="#000">' + (txt || "") + '</text>' +
'</svg>';
var scale = RS_getScale();
return { uri: uri(svg), w: w * scale, h: h * scale };
}
function RS_queueSign(main, pano, lon, lat) {
var mainIcon = RS_buildIcon(main.code, main.value);
var panoIcon = (pano && pano.code) ? RS_buildPanonceau(pano.code, pano.value, mainIcon.w) : null;
var pt = new OpenLayers.Geometry.Point(lon, lat)
.transform(new OpenLayers.Projection("EPSG:4326"), W.map.getProjectionObject());
RS_pending.push({
pt3857: { x: pt.x, y: pt.y },
pt4326: { lon: lon, lat: lat },
mainIcon: mainIcon,
panoIcon: panoIcon
});
}
function RS_renderClusters() {
if (!RS_Layer) RS_Layer = W.map.getLayersBy("uniqueName", "__WME_Draw_Border_RS")[0];
if (!RS_Layer) return;
DBF_cleanupLayer(RS_Layer);
DBF_clearFeatureIndex(RS_Layer);
var clusters = [];
RS_pending.forEach(item => {
var found = null;
for (var c of clusters) {
var dx = item.pt3857.x - c.anchor.x;
var dy = item.pt3857.y - c.anchor.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d <= RS_CLUSTER_THRESH_M) { found = c; break; }
}
if (!found) {
clusters.push({ anchor: { x: item.pt3857.x, y: item.pt3857.y }, items: [item] });
} else {
found.items.push(item);
var n = found.items.length;
found.anchor.x = (found.anchor.x * (n - 1) + item.pt3857.x) / n;
found.anchor.y = (found.anchor.y * (n - 1) + item.pt3857.y) / n;
}
});
clusters.forEach(c => {
var composite = RS_buildCompositeSVG(c.items);
var geom = new OpenLayers.Geometry.Point(c.anchor.x, c.anchor.y);
var feat = new OpenLayers.Feature.Vector(geom, null, {
externalGraphic: composite.uri,
graphicWidth: composite.w,
graphicHeight: composite.h,
graphicXOffset: -composite.w / 2,
graphicYOffset: -composite.h / 2,
graphicOpacity: 1.0
});
RS_Layer.addFeatures([feat]);
});
RS_pending = [];
}
function RS_buildCompositeSVG(items) {
var pad = 6;
var vpad = 4;
var parts = [];
var x = 0, maxH = 0;
items.forEach(it => {
var m = it.mainIcon;
var p = it.panoIcon;
var cellW = Math.max(m.w, p ? p.w : 0);
var cellH = m.h + (p ? (vpad + p.h) : 0);
var mX = x + (cellW - m.w) / 2;
var mY = 0;
var pTag = "";
if (p) {
var pX = x + (cellW - p.w) / 2;
var pY = m.h + vpad;
pTag = '<image x="' + pX + '" y="' + pY + '" width="' + p.w + '" height="' + p.h + '" href="' + p.uri + '"/>';
}
parts.push(
'<image x="' + mX + '" y="' + mY + '" width="' + m.w + '" height="' + m.h + '" href="' + m.uri + '"/>' + pTag
);
x += cellW + pad;
if (cellH > maxH) maxH = cellH;
});
var totalW = Math.max(1, x - pad);
var totalH = Math.max(1, maxH);
var svg =
'<svg xmlns="http://www.w3.org/2000/svg" width="' + totalW + '" height="' + totalH + '" viewBox="0 0 ' + totalW + ' ' + totalH + '">'
+ parts.join('')
+ '</svg>';
return {
uri: "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(svg),
w: totalW,
h: totalH
};
}
function getSignalementId(banId) {
if (!banId) return null;
return banId.split('_').slice(0, 3).join('_');
}
function checkSignalement(banId, callback) {
const id = getSignalementId(banId);
if (!id) return;
GM_xmlhttpRequest({
method: "GET",
url: `https://api.wazefrance.com/api/signalement_status/${id}`,
onload: res => {
try {
const data = JSON.parse(res.responseText);
data.id = id;
callback(data);
} catch {
callback({ accepted: false, error: true });
}
},
onerror: () => callback({ accepted: false, error: true })
});
}
function renderResult(container, data) {
if (data.error) {
container.innerHTML = "❌ Erreur lors de la vérification";
return;
}
window.DBF_currentSignalementData = {
id: data.id,
numero: data.numero,
voie: data.voie,
commune: data.commune
};
document.getElementById('dbfSignalementActions').style.display = 'block';
if (data.accepted) {
container.innerHTML = `
✅ Commune participante<br>
<a target="_blank"
href="https://signalement.adresse.data.gouv.fr/#/${data.id}">
✏️ Modifier / Demander suppression
</a>
`;
} else {
container.innerHTML = `
❌ Signalements désactivés<br>
${data.emails?.length
? `<a href="mailto:${data.emails[0]}">📧 Contacter la mairie</a>`
: "📧 Contact mairie indisponible"}
`;
}
}
function DBF_initBanSearch() {
const input = document.getElementById('dbf-ban-search');
const box = document.getElementById('dbf-ban-results');
if (!input || !box || input._dbfBound) return;
input._dbfBound = true;
input.addEventListener('input', () => {
const q = input.value.toLowerCase();
box.innerHTML = '';
if (!q) {
box.style.display = 'none';
return;
}
const hits = adresses_list
.filter(a => `${a.numero} ${a.voie} ${a.commune}`.toLowerCase().includes(q))
.slice(0, 30);
hits.forEach(a => {
const d = document.createElement('div');
d.className = 'dbf-item';
d.textContent = `${a.numero} – ${a.voie} (${a.commune})`;
d.onclick = () => {
input.value = d.textContent;
box.style.display = 'none';
var adresseInfo = { numero: a.numero, voie: a.voie, commune: a.commune };
checkSignalement(a.id_raw, r => {
r.numero = adresseInfo.numero;
r.voie = adresseInfo.voie;
r.commune = adresseInfo.commune;
renderResult(document.getElementById('dbfSignalementResult'), r);
});
};
box.appendChild(d);
});
box.style.display = 'block';
});
}
function DBF_buildMailTemplate(data) {
return `Objet : Signalement d’une anomalie d’adresse – ${data.commune}
Madame, Monsieur,
Je me permets de vous contacter en tant qu’éditeur bénévole de la carte Waze,
utilisée quotidiennement par de nombreux usagers (automobilistes, services de secours,
livraisons, transports, etc.).
Dans le cadre d’un travail d’amélioration de la précision cartographique sur votre commune,
j’ai identifié une anomalie concernant une adresse référencée dans la Base Adresse Nationale (BAN).
Adresse concernée :
– Identifiant BAN : ${data.id}
– Adresse : ${data.numero} ${data.voie}
– Commune : ${data.commune}
Cette incohérence peut entraîner des erreurs de guidage ou de localisation pour les usagers.
N’ayant pas la possibilité de soumettre un signalement directement via la plateforme nationale
pour votre commune, je me permets donc de vous en informer par ce message.
Je reste bien entendu à votre disposition pour tout complément d’information utile
(coordonnées, capture d’écran, contexte d’usage, etc.).
Je vous remercie par avance pour l’attention portée à ce signalement.
Cordialement,
[Votre nom]
Éditeur bénévole Waze
`;
}
// =====================================================================
// AUTOFILL BAN — Raccourci Ctrl+M pour pré-remplir les numéros de rue
// =====================================================================
/**
* Récupération du rang de l'utilisateur WME connecté.
* Retourne null si l'objet loginManager n'est pas encore disponible.
*/
function getUserRankDBF() {
try {
return W?.loginManager?.user?.attributes?.rank ?? null;
} catch (err) {
return null;
}
}
/**
* Attend que le rang utilisateur soit disponible (polling 300ms).
* Active l'autofill uniquement pour les rangs > 1.
*/
function waitRank() {
const rank = getUserRankDBF();
if (null === rank) return setTimeout(waitRank, 300);
window.DBF_USER_RANK = rank;
window.DBF_USER_CAN_AUTOFILL = !(rank <= 1);
}
/**
* Parse le contenu du span de position souris WME (ex: "48.8566, 2.3522").
* Retourne un objet { lon, lat } ou null si le format est invalide.
*/
function parseWmeMouseSpan(text) {
if (!window.DBF_USER_CAN_AUTOFILL) return;
if (!text) return null;
const coordRegex = /^\s*([+-]?\d+(?:\.\d+)?)\s*[ ,;]\s*([+-]?\d+(?:\.\d+)?)\s*$/;
const regexMatch = String(text).trim().match(coordRegex);
if (!regexMatch) return null;
const firstNum = parseFloat(regexMatch[1]);
const secondNum = parseFloat(regexMatch[2]);
if (!isFinite(firstNum) || !isFinite(secondNum)) return null;
// Heuristique : déterminer quel nombre est lat vs lon
let latitude, longitude;
if (Math.abs(firstNum) <= 90 && Math.abs(secondNum) > 90) {
latitude = firstNum;
longitude = secondNum;
} else if (Math.abs(firstNum) > 90 && Math.abs(secondNum) <= 90) {
longitude = firstNum;
latitude = secondNum;
} else {
latitude = firstNum;
longitude = secondNum;
}
return { lon: longitude, lat: latitude };
}
/**
* Vérifie si les coordonnées sont dans la bounding box France métro.
* Si ce n'est pas le cas, tente un swap lon/lat pour corriger.
*/
function sanitySwapIfWeird(coords) {
if (!window.DBF_USER_CAN_AUTOFILL) return;
if (!coords) return coords;
// Bounding box France métropolitaine : lon [-10, 15], lat [40, 55]
if (coords.lon >= -10 && coords.lon <= 15 && coords.lat >= 40 && coords.lat <= 55) {
return coords;
}
const swapped = { lon: coords.lat, lat: coords.lon };
if (swapped.lon >= -10 && swapped.lon <= 15 && swapped.lat >= 40 && swapped.lat <= 55) {
return swapped;
}
return coords; // Aucune correction possible
}
/**
* Lit la position souris depuis le span WME et met à jour les variables globales.
* Appelé par setInterval toutes les 200ms.
*/
function tickMouseSpan() {
if (!window.DBF_USER_CAN_AUTOFILL) return;
const mouseSpanEl = document.querySelector(".wz-map-ol-control-span-mouse-position");
if (!mouseSpanEl) return;
const parsed = parseWmeMouseSpan(mouseSpanEl.textContent || mouseSpanEl.innerText || "");
if (parsed) {
lastMouseLL = sanitySwapIfWeird(parsed);
lastMouseTS = Date.now();
}
}
/**
* Calcul de la distance Haversine entre deux points (lon1, lat1) et (lon2, lat2).
* Retourne la distance en mètres.
*/
function haversine(lon1, lat1, lon2, lat2) {
if (!window.DBF_USER_CAN_AUTOFILL) return;
const toRad = (deg) => deg * Math.PI / 180;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a = Math.sin(dLat / 2) ** 2
+ Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
return 12742e3 * Math.asin(Math.sqrt(a)); // Diamètre Terre * arcsin
}
/**
* Trouve l'adresse la plus proche de `mousePosition` dans `addressList`,
* dans un rayon maximum de `maxDistMeters` mètres.
* Retourne l'adresse trouvée (avec propriété .dist) ou null.
*/
function pickNearestFromList(addressList, mousePosition, maxDistMeters) {
if (!window.DBF_USER_CAN_AUTOFILL) return;
let nearest = null;
let shortestDistance = Infinity;
for (const address of addressList) {
const addrLon = Number(address.lon);
const addrLat = Number(address.lat);
if (!isFinite(addrLon) || !isFinite(addrLat)) continue;
const distance = haversine(mousePosition.lon, mousePosition.lat, addrLon, addrLat);
if (distance < shortestDistance) {
shortestDistance = distance;
nearest = { ...address, dist: distance };
}
}
return (!nearest || nearest.dist > maxDistMeters) ? null : nearest;
}
// --- Initialisation de l'autofill ---
window.DBF_USER_CAN_AUTOFILL = false;
window.DBF_USER_RANK = null;
waitRank();
// Polling position souris toutes les 200ms
setInterval(tickMouseSpan, 200);
// --- Écouteur du raccourci Ctrl+M ---
document.addEventListener("keydown", function (event) {
if (!window.DBF_USER_CAN_AUTOFILL) return;
// Vérifier que c'est bien Ctrl+M
if (!event.ctrlKey || "m" !== event.key.toLowerCase()) return;
// Vérifier que la liste d'adresses BAN est chargée
if (!adresses_list || !adresses_list.length) return;
// Vérifier que la position souris est récente (< 4 secondes)
if (!lastMouseLL || Date.now() - lastMouseTS > 4e3) return;
// Helper : vérifie qu'un élément est visible dans le DOM
const isVisible = (el) => el && null !== el.offsetParent;
// Trouver le champ input du numéro de maison actif
let targetInput = null;
const activeEl = document.activeElement;
if (activeEl && "INPUT" === activeEl.tagName && activeEl.classList.contains("number") && isVisible(activeEl)) {
targetInput = activeEl;
} else {
const houseNumberInput = document.querySelector(".house-number.is-active input.number, .content.active input.number");
if (isVisible(houseNumberInput)) {
targetInput = houseNumberInput;
}
}
if (!targetInput) return;
// Trouver l'adresse BAN la plus proche (rayon 50m)
const nearestAddress = pickNearestFromList(adresses_list, lastMouseLL, 50);
if (!nearestAddress) return;
// Injecter le numéro dans le champ input via le setter natif React/WME
const houseNumber = nearestAddress.numero;
const nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
if (nativeSetter) {
nativeSetter.call(targetInput, houseNumber);
} else {
targetInput.value = houseNumber;
}
// Déclencher les événements pour que WME détecte le changement
targetInput.dispatchEvent(new Event("input", { bubbles: true }));
targetInput.dispatchEvent(new Event("change", { bubbles: true }));
// Retirer le focus pour valider la saisie
if (document.activeElement && document.activeElement.blur) {
document.activeElement.blur();
}
});
// =====================================================================
// AUTOFILL ADRESSE BAN — Ctrl+Shift+M pour remplir rue, n° et/ou ville
// =====================================================================
/**
* Recherche récursive d'un input dans les shadow DOMs imbriqués.
* wz-autocomplete → shadowRoot → wz-text-input → shadowRoot → input
*
* @param {HTMLElement} root - L'élément racine à explorer
* @param {number} maxDepth - Profondeur maximale de recherche
* @returns {HTMLInputElement|null}
*/
function DBF_findDeepInput(root, maxDepth) {
if (!root || maxDepth <= 0) return null;
// Chercher un input directement dans cet élément
var inputs = root.querySelectorAll ? root.querySelectorAll('input') : [];
for (var i = 0; i < inputs.length; i++) {
var inp = inputs[i];
// Ignorer les inputs cachés (display:none / visibility:hidden / type=hidden)
if (inp.type === 'hidden') continue;
if (inp.style && inp.style.display === 'none') continue;
if (inp.style && inp.style.visibility === 'hidden') continue;
return inp;
}
// Explorer le shadow DOM de cet élément
if (root.shadowRoot) {
var fromShadow = DBF_findDeepInput(root.shadowRoot, maxDepth - 1);
if (fromShadow) return fromShadow;
}
// Explorer les shadow DOMs des enfants (web components imbriqués)
var children = root.querySelectorAll ? root.querySelectorAll('*') : [];
for (var j = 0; j < children.length; j++) {
if (children[j].shadowRoot) {
var fromChild = DBF_findDeepInput(children[j].shadowRoot, maxDepth - 1);
if (fromChild) return fromChild;
}
}
return null;
}
/**
* Injecte une valeur dans un wz-autocomplete WME.
* Utilise document.execCommand('insertText') pour simuler une saisie
* réelle, puis sélectionne la première suggestion via ArrowDown+Enter.
*
* IMPORTANT : NE PAS utiliser Enter seul sans ArrowDown car cela crée
* un "new item" avec un ID null, provoquant l'erreur WME :
* "city null does not exist" lors de la sauvegarde.
*
* Séquence :
* 1. Recherche récursive de l'input (shadow DOMs imbriqués)
* 2. Focus + sélection du contenu existant
* 3. execCommand('insertText') — trusted user input
* 4. Attente du dropdown fuzzy
* 5. ArrowDown pour sélectionner la 1ère suggestion + Enter pour confirmer
*
* @param {HTMLElement} wzAutoEl - Le composant wz-autocomplete
* @param {string} value - La valeur à injecter
* @returns {boolean} true si l'injection a démarré
*/
function DBF_setWzAutocompleteValue(wzAutoEl, value) {
if (!wzAutoEl || !value) return false;
// --- Recherche récursive de l'input (jusqu'à 4 niveaux de shadow DOM) ---
var innerInput = DBF_findDeepInput(wzAutoEl, 4);
// Debug : afficher l'état de la recherche
console.log("[DBF] wz-autocomplete debug:", {
component: wzAutoEl.className,
hasShadowRoot: !!wzAutoEl.shadowRoot,
inputFound: !!innerInput,
inputTag: innerInput ? innerInput.tagName : "N/A",
inputType: innerInput ? innerInput.type : "N/A"
});
if (innerInput) {
// Étape 1 : Focus
innerInput.focus();
// Étape 2 : Sélectionner tout le contenu
innerInput.select();
// Étape 3 : Supprimer le contenu sélectionné puis insérer le texte
var execOk = document.execCommand('insertText', false, value);
if (execOk) {
console.log("[DBF] execCommand('insertText') réussi pour:", value);
} else {
// Fallback : setter natif + InputEvent
console.warn("[DBF] execCommand échoué, fallback setter natif pour:", value);
var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
if (nativeSetter) {
nativeSetter.call(innerInput, value);
} else {
innerInput.value = value;
}
try {
innerInput.dispatchEvent(new InputEvent("input", {
bubbles: true, composed: true, inputType: "insertText", data: value
}));
} catch (e) {
innerInput.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
}
innerInput.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
}
// Étape 4 : Attendre le dropdown et sélectionner via ArrowDown + Enter
// On utilise une approche à 2 tentatives (le dropdown peut être lent)
DBF_trySelectSuggestion(wzAutoEl, innerInput, value, 0);
return true;
}
// Dernier recours : propriété value directe sur le web component
console.warn("[DBF] Aucun input trouvé dans wz-autocomplete, tentative directe");
try {
wzAutoEl.value = value;
wzAutoEl.dispatchEvent(new Event("input", { bubbles: true }));
wzAutoEl.dispatchEvent(new Event("change", { bubbles: true }));
return true;
} catch (e) {
console.warn("[DBF] Impossible d'injecter dans wz-autocomplete", e);
return false;
}
}
/**
* Tente de sélectionner une suggestion dans le dropdown du wz-autocomplete.
* Utilise ArrowDown + Enter, la méthode la plus fiable pour les composants WME.
*
* Recherche les suggestions dans :
* 1. Le shadow DOM du wz-autocomplete
* 2. Les enfants du wz-autocomplete (light DOM)
* 3. Le document body (overlays/popovers)
*
* @param {HTMLElement} wzAutoEl - Le composant wz-autocomplete
* @param {HTMLInputElement} innerInput - L'input interne du composant
* @param {string} value - La valeur recherchée
* @param {number} attempt - Numéro de tentative (0, 1, 2)
*/
function DBF_trySelectSuggestion(wzAutoEl, innerInput, value, attempt) {
var delays = [300, 600, 1000]; // Délais progressifs pour chaque tentative
var maxAttempts = delays.length;
if (attempt >= maxAttempts) {
console.warn("[DBF] ⚠️ Aucune suggestion trouvée après", maxAttempts, "tentatives pour:", value);
console.warn("[DBF] → Le champ contient le texte mais l'utilisateur devra sélectionner manuellement dans le dropdown.");
// NE PAS appuyer Enter ici — cela créerait un item avec ID null
// qui provoquerait l'erreur "city null does not exist"
return;
}
setTimeout(function () {
// --- Recherche de suggestions dans tous les emplacements possibles ---
var menuItems = [];
// 1. Shadow DOM du wz-autocomplete
if (wzAutoEl.shadowRoot) {
menuItems = wzAutoEl.shadowRoot.querySelectorAll(
'wz-menu-item, [role="option"], [role="listbox"] > *, .menu-item, .suggestion-item'
);
}
// 2. Light DOM du wz-autocomplete
if (!menuItems.length) {
menuItems = wzAutoEl.querySelectorAll(
'wz-menu-item, [role="option"], [role="listbox"] > *, .menu-item, .suggestion-item'
);
}
// 3. Overlays/popovers dans le document body (WME rend parfois les
// dropdowns dans des portals attachés au body)
if (!menuItems.length) {
menuItems = document.querySelectorAll(
'wz-menu-item[role="option"], wz-popover wz-menu-item, .wz-autocomplete-popover wz-menu-item'
);
}
console.log("[DBF] Tentative", attempt + 1 + "/" + maxAttempts, "- Suggestions trouvées:", menuItems.length);
if (menuItems.length > 0) {
// Chercher une suggestion qui correspond au texte
var matched = false;
var valueLower = value.toLowerCase();
for (var i = 0; i < menuItems.length; i++) {
var itemText = (menuItems[i].textContent || "").trim().toLowerCase();
if (itemText === valueLower || itemText.includes(valueLower) || valueLower.includes(itemText)) {
console.log("[DBF] ✅ Suggestion match, clic :", menuItems[i].textContent.trim());
menuItems[i].click();
matched = true;
break;
}
}
if (!matched && menuItems.length > 0) {
// Pas de match textuel exact, mais des suggestions existent
// → Utiliser ArrowDown + Enter pour sélectionner la première
console.log("[DBF] Pas de match exact, sélection de la 1ère suggestion via ArrowDown+Enter");
innerInput.dispatchEvent(new KeyboardEvent("keydown", {
key: "ArrowDown", code: "ArrowDown", keyCode: 40, which: 40,
bubbles: true, composed: true
}));
innerInput.dispatchEvent(new KeyboardEvent("keyup", {
key: "ArrowDown", code: "ArrowDown", keyCode: 40, which: 40,
bubbles: true, composed: true
}));
// Petit délai puis Enter pour confirmer la sélection
setTimeout(function () {
innerInput.dispatchEvent(new KeyboardEvent("keydown", {
key: "Enter", code: "Enter", keyCode: 13, which: 13,
bubbles: true, composed: true
}));
innerInput.dispatchEvent(new KeyboardEvent("keyup", {
key: "Enter", code: "Enter", keyCode: 13, which: 13,
bubbles: true, composed: true
}));
console.log("[DBF] ✅ ArrowDown+Enter envoyé");
}, 50);
}
} else {
// Aucune suggestion visible → réessayer avec un délai plus long
console.log("[DBF] Pas de suggestions visibles, nouvelle tentative dans", delays[attempt + 1] || "N/A", "ms...");
DBF_trySelectSuggestion(wzAutoEl, innerInput, value, attempt + 1);
}
}, delays[attempt]);
}
/**
* Injecte une valeur dans un wz-text-input WME (ex: numéro de rue).
* Même logique que wz-autocomplete mais adapté au composant wz-text-input.
*
* @param {HTMLElement} wzTextEl - Le composant wz-text-input
* @param {string} value - La valeur à injecter
* @returns {boolean} true si l'injection a réussi
*/
function DBF_setWzTextInputValue(wzTextEl, value) {
if (!wzTextEl || value === undefined || value === null) return false;
var strValue = String(value);
// Stratégie 1 : input dans le shadow DOM
var innerInput = null;
if (wzTextEl.shadowRoot) {
innerInput = wzTextEl.shadowRoot.querySelector('input');
}
// Stratégie 2 : input enfant direct
if (!innerInput) {
innerInput = wzTextEl.querySelector('input:not([style*="display: none"])');
}
// Stratégie 3 : tout input enfant (le hidden de WME a display:none)
if (!innerInput) {
var inputs = wzTextEl.querySelectorAll('input');
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].style.display !== 'none') { innerInput = inputs[i]; break; }
}
}
if (innerInput) {
var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
if (nativeSetter) {
nativeSetter.call(innerInput, strValue);
} else {
innerInput.value = strValue;
}
innerInput.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
innerInput.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
return true;
}
// Stratégie 4 : propriété value directe sur le web component
try {
wzTextEl.value = strValue;
wzTextEl.dispatchEvent(new Event("input", { bubbles: true }));
wzTextEl.dispatchEvent(new Event("change", { bubbles: true }));
return true;
} catch (e) {
console.warn("[DBF] Impossible d'injecter dans wz-text-input", e);
return false;
}
}
/**
* Détecte si un élément est (ou est contenu dans) un web component Waze
* (wz-autocomplete ou wz-text-input) avec la classe spécifiée.
*/
function DBF_isInsideWzComponent(el, tagName, className) {
if (!el) return false;
var current = el;
while (current) {
if (current.tagName && current.tagName.toLowerCase() === tagName && current.classList.contains(className)) {
return true;
}
// Traverser les shadow DOM boundaries
current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
}
return false;
}
// Alias rétro-compatible
function DBF_isInsideWzAutocomplete(el, className) {
return DBF_isInsideWzComponent(el, 'wz-autocomplete', className);
}
/**
* Trouve le wz-autocomplete parent d'un élément.
*/
function DBF_findParentWzAutocomplete(el) {
if (!el) return null;
var current = el;
while (current) {
if (current.tagName && current.tagName.toLowerCase() === 'wz-autocomplete') {
return current;
}
current = current.parentElement || (current.getRootNode && current.getRootNode().host) || null;
}
return null;
}
/**
* Vérifie si le focus est dans le shadow DOM d'un web component.
*/
function DBF_isFocusInsideShadow(wzEl) {
if (!wzEl || !wzEl.shadowRoot) return false;
var shadowActive = wzEl.shadowRoot.activeElement;
return !!(shadowActive && shadowActive.tagName === 'INPUT');
}
/**
* Handler principal : Autofill adresse BAN (rue + n° + ville).
* Détecte automatiquement le champ actif dans le formulaire d'adresse WME.
* Fonctionne à la fois pour les segments et les lieux (venues).
*
* Comportement :
* - Si le focus est dans wz-autocomplete.street-name → remplit la rue
* - Si le focus est dans wz-text-input.house-number → remplit le n°
* - Si le focus est dans wz-autocomplete.city-name → remplit la ville
* - Si le formulaire d'adresse est visible mais aucun champ spécifique
* n'a le focus → remplit tous les champs disponibles (séquentiellement)
*
* IMPORTANT : les wz-autocomplete annulent la saisie au blur si la
* valeur n'a pas été confirmée. On remplit donc les champs un par un
* avec un délai de 800ms entre chaque pour laisser la confirmation
* (sélection de suggestion ou Enter) se terminer avant le focus suivant.
*/
function DBF_autofillAddress() {
if (!window.DBF_USER_CAN_AUTOFILL) {
console.warn("[DBF] Autofill adresse : rang insuffisant.");
return;
}
// Vérifier que la liste d'adresses BAN est chargée
if (!adresses_list || !adresses_list.length) {
console.warn("[DBF] Autofill adresse : aucune adresse BAN chargée. Zoom 19 requis.");
return;
}
// Vérifier que la position souris est récente (< 4 secondes)
if (!lastMouseLL || Date.now() - lastMouseTS > 4000) {
console.warn("[DBF] Autofill adresse : position souris trop ancienne ou absente.");
return;
}
// Trouver l'adresse BAN la plus proche (rayon 100m pour les adresses)
var nearestAddress = pickNearestFromList(adresses_list, lastMouseLL, 100);
if (!nearestAddress) {
console.warn("[DBF] Autofill adresse : aucune adresse BAN trouvée dans un rayon de 100m.");
return;
}
var streetValue = nearestAddress.voie || "";
var houseNumValue = nearestAddress.numero || "";
var cityValue = nearestAddress.commune || "";
if (!streetValue && !cityValue && !houseNumValue) {
console.warn("[DBF] Autofill adresse : l'adresse BAN trouvée est vide.");
return;
}
console.log("[DBF] Autofill adresse BAN →", houseNumValue, streetValue, "|", cityValue, "| Distance:", Math.round(nearestAddress.dist), "m");
// Trouver le formulaire d'adresse WME (segments OU lieux/venues)
var addressForm = document.querySelector('.address-edit-view .address-form, .address-form');
if (!addressForm) {
console.warn("[DBF] Autofill adresse : formulaire d'adresse non trouvé. Ouvrez le panneau d'édition d'un segment ou d'un lieu.");
return;
}
var streetAutoEl = addressForm.querySelector('wz-autocomplete.street-name');
var cityAutoEl = addressForm.querySelector('wz-autocomplete.city-name');
var houseNumEl = addressForm.querySelector('wz-text-input.house-number');
// --- Détecter quel champ a le focus ---
var activeEl = document.activeElement;
var focusInStreet = false;
var focusInCity = false;
var focusInHouseNum = false;
if (activeEl) {
focusInStreet = DBF_isInsideWzAutocomplete(activeEl, 'street-name');
focusInCity = DBF_isInsideWzAutocomplete(activeEl, 'city-name');
focusInHouseNum = DBF_isInsideWzComponent(activeEl, 'wz-text-input', 'house-number');
}
// Vérifier aussi via le shadowRoot actif de chaque composant
if (!focusInStreet && streetAutoEl) focusInStreet = DBF_isFocusInsideShadow(streetAutoEl);
if (!focusInCity && cityAutoEl) focusInCity = DBF_isFocusInsideShadow(cityAutoEl);
if (!focusInHouseNum && houseNumEl) focusInHouseNum = DBF_isFocusInsideShadow(houseNumEl);
// --- Remplissage d'un seul champ (focus détecté) ---
if (focusInStreet) {
if (streetAutoEl && streetValue) {
DBF_setWzAutocompleteValue(streetAutoEl, streetValue);
console.log("[DBF] ✅ Autofill rue :", streetValue);
}
return;
}
if (focusInHouseNum) {
if (houseNumEl && houseNumValue) {
DBF_setWzTextInputValue(houseNumEl, houseNumValue);
console.log("[DBF] ✅ Autofill n° :", houseNumValue);
}
return;
}
if (focusInCity) {
if (cityAutoEl && cityValue) {
DBF_setWzAutocompleteValue(cityAutoEl, cityValue);
console.log("[DBF] ✅ Autofill ville :", cityValue);
}
return;
}
// --- Pas de focus spécifique → remplir TOUS les champs séquentiellement ---
// On utilise une file d'attente : chaque champ attend que le précédent
// ait eu le temps de confirmer sa valeur (suggestion sélectionnée / Enter)
// avant de recevoir le focus et la saisie.
var fillQueue = [];
// 1. Rue (wz-autocomplete — le délai doit couvrir les 3 tentatives
// de suggestion: 300ms + 600ms + 1000ms max, plus marge)
if (streetAutoEl && streetValue) {
fillQueue.push({ type: 'autocomplete', el: streetAutoEl, value: streetValue, label: 'Rue', delay: 1500 });
}
// 2. N° de rue (wz-text-input, synchrone, pas besoin de long délai)
if (houseNumEl && houseNumValue) {
fillQueue.push({ type: 'textinput', el: houseNumEl, value: houseNumValue, label: 'N°', delay: 200 });
}
// 3. Ville (wz-autocomplete, même logique que la rue)
if (cityAutoEl && cityValue) {
fillQueue.push({ type: 'autocomplete', el: cityAutoEl, value: cityValue, label: 'Ville', delay: 1500 });
}
if (!fillQueue.length) {
console.warn("[DBF] Autofill adresse : aucun champ à remplir.");
return;
}
console.log("[DBF] Remplissage séquentiel de", fillQueue.length, "champ(s)...");
// Exécuter la file séquentiellement
function processNext(index) {
if (index >= fillQueue.length) {
console.log("[DBF] ✅ Autofill adresse terminé.");
return;
}
var task = fillQueue[index];
console.log("[DBF] →", task.label + ":", task.value);
if (task.type === 'autocomplete') {
DBF_setWzAutocompleteValue(task.el, task.value);
} else {
DBF_setWzTextInputValue(task.el, task.value);
}
// Attendre que ce champ ait confirmé sa valeur avant de passer au suivant
setTimeout(function () {
processNext(index + 1);
}, task.delay);
}
processNext(0);
}
// --- Écouteur clavier fallback (si le SDK shortcut échoue) ---
document.addEventListener("keydown", function (event) {
// Ctrl+Shift+M → Autofill adresse
if (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === "m") {
event.preventDefault();
event.stopPropagation();
DBF_autofillAddress();
}
});
DBFstep1();
})();