<!-- Leaflet CSS -->
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
>
<style>
.meteo-france-extreme {
max-width: 1200px;
margin: 30px auto;
padding: 20px;
}
.meteo-france-extreme h2,
.meteo-france-extreme h3 {
color: #fff;
}
.meteo-france-actions,
.meteo-france-options {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 14px;
}
.meteo-france-actions button,
.meteo-france-actions a,
.meteo-france-options select,
.meteo-france-options input {
padding: 9px 12px;
border: 1px solid #ccc;
border-radius: 8px;
background: #fff;
color: #111;
font-weight: bold;
text-decoration: none;
}
.meteo-france-actions button {
cursor: pointer;
}
.meteo-france-check {
display: flex;
align-items: center;
gap: 6px;
color: #fff;
}
#meteo-france-status,
#meteo-france-radar-status {
margin: 8px 0;
color: #fff;
font-weight: bold;
}
#map-meteo-france-extreme {
width: 100%;
height: 560px;
position: relative;
overflow: hidden;
background: #aad3df;
border-radius: 14px;
margin-bottom: 20px;
border: 1px solid #ddd;
z-index: 1;
}
#map-meteo-france-extreme.leaflet-container {
width: 100%;
height: 560px;
}
#map-meteo-france-extreme .leaflet-tile,
#map-meteo-france-extreme .leaflet-marker-icon,
#map-meteo-france-extreme .leaflet-marker-shadow,
#map-meteo-france-extreme .leaflet-pane img {
max-width: none !important;
max-height: none !important;
margin: 0 !important;
padding: 0 !important;
border-radius: 0 !important;
box-shadow: none !important;
object-fit: initial !important;
}
#map-meteo-france-extreme .leaflet-tile {
width: 256px !important;
height: 256px !important;
}
.mf-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 24px;
}
.mf-card,
.mf-ligne {
border: 1px solid #ddd;
border-radius: 12px;
padding: 14px;
background: rgba(255,255,255,0.96);
color: #111;
}
.mf-card h3,
.mf-card p,
.mf-ligne h4,
.mf-ligne p,
.mf-card strong,
.mf-ligne strong {
color: #111 !important;
}
.mf-card h3,
.mf-ligne h4 {
margin: 0 0 8px;
}
.mf-card p,
.mf-ligne p {
margin: 5px 0;
}
.mf-liste {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.mf-badge {
display: inline-block;
padding: 4px 9px;
border-radius: 999px;
font-weight: bold;
margin: 3px 4px 3px 0;
border: 1px solid rgba(0,0,0,0.2);
}
.mf-vert {
background: #39b54a;
color: #fff;
}
.mf-jaune {
background: #f4c430;
color: #111;
}
.mf-orange {
background: #ff7b00;
color: #111;
}
.mf-rouge {
background: #d00000;
color: #fff;
}
.mf-marker {
background: transparent !important;
border: none !important;
}
.mf-pin {
width: 30px;
height: 30px;
border-radius: 50%;
border: 3px solid #fff;
box-shadow: 0 0 12px rgba(0,0,0,0.75);
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: bold;
}
.mf-pin.mf-vert {
background: #39b54a;
color: #fff;
}
.mf-pin.mf-jaune {
background: #f4c430;
color: #111;
}
.mf-pin.mf-orange {
background: #ff7b00;
color: #111;
animation: mfPulse 1.6s infinite;
}
.mf-pin.mf-rouge {
background: #d00000;
color: #fff;
animation: mfPulse 1.2s infinite;
}
@keyframes mfPulse {
0% {
box-shadow: 0 0 0 0 rgba(255,255,255,0.8);
}
70% {
box-shadow: 0 0 0 14px rgba(255,255,255,0);
}
100% {
box-shadow: 0 0 0 0 rgba(255,255,255,0);
}
}
@media (max-width: 800px) {
.mf-grid,
.mf-liste {
grid-template-columns: 1fr;
}
#map-meteo-france-extreme,
#map-meteo-france-extreme.leaflet-container {
height: 420px;
}
}
@media (max-width: 600px) {
#map-meteo-france-extreme,
#map-meteo-france-extreme.leaflet-container {
height: 360px;
}
}
</style>
<section class="meteo-france-extreme">
<h2>Météo extrême France : orages, tempêtes, pluie, cyclones et foudre</h2>
<div class="meteo-france-actions">
<button id="btn-mf-reload" type="button">Recharger vigilances</button>
<button id="btn-mf-radar" type="button">Afficher / masquer radar</button>
<button id="btn-mf-radar-play" type="button">Animer radar</button>
<button id="btn-mf-radar-stop" type="button">Stop radar</button>
<button id="btn-mf-france" type="button">Recentrer France</button>
<a href="https://map.blitzortung.org/" target="_blank" rel="noopener">
Carte foudre temps réel
</a>
<a href="https://vigilance.meteofrance.fr/fr" target="_blank" rel="noopener">
Vigilance officielle
</a>
</div>
<div class="meteo-france-options">
<select id="mf-echeance">
<option value="toutes">Aujourd’hui + demain</option>
<option value="J">Aujourd’hui</option>
<option value="J1">Demain</option>
</select>
<select id="mf-niveau-min">
<option value="1">Vert et plus</option>
<option value="2" selected>Jaune et plus</option>
<option value="3">Orange et rouge</option>
<option value="4">Rouge seulement</option>
</select>
<input id="mf-dep-search" type="text" placeholder="Département : 75, 13, 2A, 971...">
<label class="meteo-france-check">
<input id="mf-filtre-orage" type="checkbox" checked>
Orages
</label>
<label class="meteo-france-check">
<input id="mf-filtre-vent" type="checkbox" checked>
Vent / tempête
</label>
<label class="meteo-france-check">
<input id="mf-filtre-pluie" type="checkbox" checked>
Pluie / inondation
</label>
<label class="meteo-france-check">
<input id="mf-filtre-cyclone" type="checkbox" checked>
Cyclone / submersion
</label>
</div>
<div id="meteo-france-status">Chargement des vigilances...</div>
<div id="meteo-france-radar-status">Radar non chargé.</div>
<div id="map-meteo-france-extreme"></div>
<div class="mf-grid">
<article id="mf-resume" class="mf-card">
<h3>Résumé</h3>
<p>Chargement...</p>
</article>
<article class="mf-card">
<h3>Légende</h3>
<p><span class="mf-badge mf-jaune">Jaune</span> vigilance à surveiller</p>
<p><span class="mf-badge mf-orange">Orange</span> phénomène dangereux</p>
<p><span class="mf-badge mf-rouge">Rouge</span> danger exceptionnel</p>
<p><small>La foudre exacte n’est pas affichée ici sans API dédiée : le bouton ouvre une carte externe.</small></p>
</article>
</div>
<h3>Départements concernés</h3>
<div id="mf-liste" class="mf-liste"></div>
</section>
<!-- Leaflet JS -->
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin="">
</script>
<script>
(function () {
const API_RAINVIEWER = "https://api.rainviewer.com/public/weather-maps.json";
const API_VIGILANCE =
"https://public.opendatasoft.com/api/explore/v2.1/catalog/datasets/weatherref-france-vigilance-meteo-departement/records";
const DEPARTEMENTS = {
"01": ["Ain", 46.205, 5.225],
"02": ["Aisne", 49.565, 3.624],
"03": ["Allier", 46.568, 3.334],
"04": ["Alpes-de-Haute-Provence", 44.092, 6.235],
"05": ["Hautes-Alpes", 44.559, 6.079],
"06": ["Alpes-Maritimes", 43.710, 7.262],
"07": ["Ardèche", 44.735, 4.599],
"08": ["Ardennes", 49.762, 4.722],
"09": ["Ariège", 42.965, 1.607],
"10": ["Aube", 48.297, 4.074],
"11": ["Aude", 43.213, 2.352],
"12": ["Aveyron", 44.349, 2.575],
"13": ["Bouches-du-Rhône", 43.296, 5.370],
"14": ["Calvados", 49.182, -0.370],
"15": ["Cantal", 44.929, 2.444],
"16": ["Charente", 45.648, 0.156],
"17": ["Charente-Maritime", 46.160, -1.151],
"18": ["Cher", 47.081, 2.398],
"19": ["Corrèze", 45.267, 1.772],
"21": ["Côte-d’Or", 47.322, 5.041],
"22": ["Côtes-d’Armor", 48.514, -2.765],
"23": ["Creuse", 46.170, 1.872],
"24": ["Dordogne", 45.184, 0.721],
"25": ["Doubs", 47.238, 6.024],
"26": ["Drôme", 44.933, 4.892],
"27": ["Eure", 49.027, 1.151],
"28": ["Eure-et-Loir", 48.446, 1.489],
"29": ["Finistère", 47.996, -4.100],
"2A": ["Corse-du-Sud", 41.919, 8.738],
"2B": ["Haute-Corse", 42.697, 9.450],
"30": ["Gard", 43.837, 4.360],
"31": ["Haute-Garonne", 43.604, 1.444],
"32": ["Gers", 43.646, 0.586],
"33": ["Gironde", 44.837, -0.579],
"34": ["Hérault", 43.611, 3.877],
"35": ["Ille-et-Vilaine", 48.117, -1.677],
"36": ["Indre", 46.810, 1.691],
"37": ["Indre-et-Loire", 47.394, 0.684],
"38": ["Isère", 45.188, 5.724],
"39": ["Jura", 46.675, 5.555],
"40": ["Landes", 43.891, -0.500],
"41": ["Loir-et-Cher", 47.594, 1.329],
"42": ["Loire", 45.439, 4.387],
"43": ["Haute-Loire", 45.043, 3.885],
"44": ["Loire-Atlantique", 47.218, -1.553],
"45": ["Loiret", 47.903, 1.909],
"46": ["Lot", 44.447, 1.441],
"47": ["Lot-et-Garonne", 44.204, 0.617],
"48": ["Lozère", 44.518, 3.501],
"49": ["Maine-et-Loire", 47.478, -0.563],
"50": ["Manche", 49.116, -1.090],
"51": ["Marne", 48.956, 4.363],
"52": ["Haute-Marne", 48.111, 5.139],
"53": ["Mayenne", 48.070, -0.770],
"54": ["Meurthe-et-Moselle", 48.692, 6.184],
"55": ["Meuse", 48.772, 5.162],
"56": ["Morbihan", 47.658, -2.760],
"57": ["Moselle", 49.119, 6.175],
"58": ["Nièvre", 46.990, 3.159],
"59": ["Nord", 50.629, 3.057],
"60": ["Oise", 49.430, 2.083],
"61": ["Orne", 48.432, 0.091],
"62": ["Pas-de-Calais", 50.292, 2.778],
"63": ["Puy-de-Dôme", 45.777, 3.087],
"64": ["Pyrénées-Atlantiques", 43.296, -0.370],
"65": ["Hautes-Pyrénées", 43.232, 0.078],
"66": ["Pyrénées-Orientales", 42.688, 2.894],
"67": ["Bas-Rhin", 48.573, 7.752],
"68": ["Haut-Rhin", 48.079, 7.358],
"69": ["Rhône", 45.764, 4.835],
"70": ["Haute-Saône", 47.623, 6.155],
"71": ["Saône-et-Loire", 46.306, 4.828],
"72": ["Sarthe", 48.006, 0.199],
"73": ["Savoie", 45.565, 5.917],
"74": ["Haute-Savoie", 45.900, 6.129],
"75": ["Paris", 48.856, 2.352],
"76": ["Seine-Maritime", 49.443, 1.099],
"77": ["Seine-et-Marne", 48.540, 2.660],
"78": ["Yvelines", 48.804, 2.120],
"79": ["Deux-Sèvres", 46.323, -0.464],
"80": ["Somme", 49.895, 2.302],
"81": ["Tarn", 43.929, 2.148],
"82": ["Tarn-et-Garonne", 44.018, 1.355],
"83": ["Var", 43.124, 5.928],
"84": ["Vaucluse", 43.949, 4.805],
"85": ["Vendée", 46.670, -1.426],
"86": ["Vienne", 46.580, 0.340],
"87": ["Haute-Vienne", 45.833, 1.261],
"88": ["Vosges", 48.173, 6.449],
"89": ["Yonne", 47.798, 3.574],
"90": ["Territoire de Belfort", 47.638, 6.863],
"91": ["Essonne", 48.630, 2.443],
"92": ["Hauts-de-Seine", 48.892, 2.206],
"93": ["Seine-Saint-Denis", 48.907, 2.445],
"94": ["Val-de-Marne", 48.790, 2.455],
"95": ["Val-d’Oise", 49.036, 2.063],
"971": ["Guadeloupe", 16.000, -61.730],
"972": ["Martinique", 14.616, -61.058],
"973": ["Guyane", 4.933, -52.333],
"974": ["La Réunion", -20.879, 55.448],
"976": ["Mayotte", -12.781, 45.228]
};
const statusBox = document.getElementById("meteo-france-status");
const radarStatus = document.getElementById("meteo-france-radar-status");
const resumeBox = document.getElementById("mf-resume");
const listeBox = document.getElementById("mf-liste");
const btnReload = document.getElementById("btn-mf-reload");
const btnRadar = document.getElementById("btn-mf-radar");
const btnRadarPlay = document.getElementById("btn-mf-radar-play");
const btnRadarStop = document.getElementById("btn-mf-radar-stop");
const btnFrance = document.getElementById("btn-mf-france");
const echeanceInput = document.getElementById("mf-echeance");
const niveauMinInput = document.getElementById("mf-niveau-min");
const depSearchInput = document.getElementById("mf-dep-search");
const filtreOrage = document.getElementById("mf-filtre-orage");
const filtreVent = document.getElementById("mf-filtre-vent");
const filtrePluie = document.getElementById("mf-filtre-pluie");
const filtreCyclone = document.getElementById("mf-filtre-cyclone");
let vigilances = [];
let radarFrames = [];
let radarHost = "";
let radarIndex = 0;
let radarLayer = null;
let radarTimer = null;
let vigilanceLayer = null;
const map = L.map("map-meteo-france-extreme", {
worldCopyJump: true
}).setView([46.7, 2.5], 6);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 18,
attribution: '© OpenStreetMap'
}).addTo(map);
vigilanceLayer = L.layerGroup().addTo(map);
function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function refreshMapSize() {
setTimeout(function () {
map.invalidateSize();
}, 300);
}
function normalize(value) {
return String(value || "")
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase();
}
function normaliserDep(value) {
const v = String(value || "").trim().toUpperCase();
if (/^[0-9]$/.test(v)) {
return "0" + v;
}
return v;
}
function niveauDepuisTexte(value) {
const v = normalize(value);
if (v.includes("rouge")) return 4;
if (v.includes("orange")) return 3;
if (v.includes("jaune")) return 2;
if (v.includes("vert")) return 1;
return 0;
}
function couleurNiveau(niveau) {
const n = Number(niveau);
if (n >= 4) return "rouge";
if (n === 3) return "orange";
if (n === 2) return "jaune";
return "vert";
}
function labelNiveau(niveau) {
const c = couleurNiveau(niveau);
if (c === "rouge") return "Rouge";
if (c === "orange") return "Orange";
if (c === "jaune") return "Jaune";
return "Vert";
}
function badgeNiveau(niveau) {
const c = couleurNiveau(niveau);
return `<span class="mf-badge mf-${c}">${labelNiveau(niveau)}</span>`;
}
function formatDate(value) {
if (!value) return "—";
const d = new Date(value);
if (Number.isNaN(d.getTime())) {
return "—";
}
return d.toLocaleString("fr-FR", {
dateStyle: "short",
timeStyle: "short"
});
}
function normaliserRecord(record) {
const f = record.fields || record;
const dep = normaliserDep(
f.domain_id ??
f.code_departement ??
f.department_code ??
f.departement ??
f.dep ??
""
);
if (!dep) {
return null;
}
const phenomenon = String(
f.phenomenon ??
f.phenomene ??
f.phenomenon_name ??
f.libelle_phenomene ??
f.nom_phenomene ??
"Phénomène météo"
);
let colorId = Number(
f.color_id ??
f.couleur_id ??
f.phenomenon_color_id ??
f.max_color_id ??
0
);
if (!colorId) {
colorId = niveauDepuisTexte(
f.color ??
f.couleur ??
f.level ??
f.niveau ??
""
);
}
return {
dep: dep,
echeance: String(f.echeance ?? f.period ?? "").trim(),
phenomenon: phenomenon,
color_id: colorId || 1,
color: String(f.color ?? f.couleur ?? "").trim(),
begin_time: f.begin_time ?? f.debut_validite ?? f.start_time ?? "",
end_time: f.end_time ?? f.fin_validite ?? f.end_time ?? "",
product_datetime: f.product_datetime ?? f.date_production ?? f.updated_at ?? ""
};
}
function phenomeneSelectionne(record) {
const p = normalize(record.phenomenon);
const isOrage = p.includes("orage");
const isVent = p.includes("vent") || p.includes("tempete");
const isPluie = p.includes("pluie") || p.includes("inondation") || p.includes("crue");
const isCyclone = p.includes("cyclone") || p.includes("ouragan") || p.includes("submersion") || p.includes("vague");
return (
(filtreOrage.checked && isOrage) ||
(filtreVent.checked && isVent) ||
(filtrePluie.checked && isPluie) ||
(filtreCyclone.checked && isCyclone)
);
}
function filtrerVigilances() {
const echeance = echeanceInput.value;
const min = Number(niveauMinInput.value);
const depSearch = normaliserDep(depSearchInput.value);
return vigilances.filter(function (r) {
if (echeance !== "toutes" && r.echeance !== echeance) {
return false;
}
if (Number(r.color_id) < min) {
return false;
}
if (depSearch && r.dep !== depSearch) {
return false;
}
return phenomeneSelectionne(r);
});
}
function grouperParDepartement(records) {
const mapDep = new Map();
records.forEach(function (r) {
if (!mapDep.has(r.dep)) {
mapDep.set(r.dep, {
dep: r.dep,
niveauMax: Number(r.color_id),
records: []
});
}
const item = mapDep.get(r.dep);
item.records.push(r);
item.niveauMax = Math.max(item.niveauMax, Number(r.color_id));
});
return Array.from(mapDep.values()).sort(function (a, b) {
if (b.niveauMax !== a.niveauMax) {
return b.niveauMax - a.niveauMax;
}
return a.dep.localeCompare(b.dep);
});
}
async function chargerVigilances() {
statusBox.textContent = "Chargement des vigilances météo France...";
listeBox.innerHTML = "";
vigilanceLayer.clearLayers();
const limit = 100;
let offset = 0;
let total = Infinity;
let resultats = [];
while (offset < total) {
const params = new URLSearchParams();
params.set("limit", String(limit));
params.set("offset", String(offset));
const url = API_VIGILANCE + "?" + params.toString();
console.log("URL VIGILANCE FRANCE :", url);
const response = await fetch(url, {
cache: "no-store"
});
if (!response.ok) {
throw new Error("Erreur API vigilance : " + response.status);
}
const data = await response.json();
const lignes = Array.isArray(data.results)
? data.results
: Array.isArray(data.records)
? data.records
: [];
resultats = resultats.concat(
lignes.map(normaliserRecord).filter(Boolean)
);
total = Number(data.total_count || resultats.length);
if (!lignes.length) {
break;
}
offset += limit;
if (offset > 3000) {
break;
}
}
vigilances = resultats;
afficherVigilances();
}
function afficherVigilances() {
const filtrees = filtrerVigilances();
const groupes = grouperParDepartement(filtrees);
vigilanceLayer.clearLayers();
const bounds = L.latLngBounds();
groupes.forEach(function (groupe) {
const infoDep = DEPARTEMENTS[groupe.dep];
if (!infoDep) {
return;
}
const couleur = couleurNiveau(groupe.niveauMax);
const nomDep = infoDep[0];
const lat = infoDep[1];
const lon = infoDep[2];
const details = groupe.records.map(function (r) {
return `
${badgeNiveau(r.color_id)}
${escapeHtml(r.echeance || "—")}
—
${escapeHtml(r.phenomenon)}
<br>
<small>
Du ${escapeHtml(formatDate(r.begin_time))}
au ${escapeHtml(formatDate(r.end_time))}
</small>
`;
}).join("<hr>");
const popupHtml = `
<strong>${escapeHtml(groupe.dep)} — ${escapeHtml(nomDep)}</strong><br>
Niveau maximum : ${badgeNiveau(groupe.niveauMax)}
<hr>
${details}
`;
L.marker([lat, lon], {
icon: L.divIcon({
className: "mf-marker",
html: `
<div class="mf-pin mf-${couleur}">
<span>!</span>
</div>
`,
iconSize: [30, 30],
iconAnchor: [15, 15]
})
})
.bindPopup(popupHtml)
.addTo(vigilanceLayer);
bounds.extend([lat, lon]);
});
if (bounds.isValid()) {
map.fitBounds(bounds, {
padding: [30, 30],
maxZoom: 7
});
} else {
map.setView([46.7, 2.5], 6);
}
afficherListe(groupes);
afficherResume(groupes, filtrees);
statusBox.textContent =
groupes.length +
" département(s) affiché(s) — " +
filtrees.length +
" vigilance(s) correspondant aux filtres.";
refreshMapSize();
}
function afficherResume(groupes, records) {
const nbRouge = groupes.filter(function (g) { return g.niveauMax >= 4; }).length;
const nbOrange = groupes.filter(function (g) { return g.niveauMax === 3; }).length;
const nbJaune = groupes.filter(function (g) { return g.niveauMax === 2; }).length;
resumeBox.innerHTML = `
<h3>Résumé</h3>
<p>${badgeNiveau(4)} ${escapeHtml(nbRouge)} département(s)</p>
<p>${badgeNiveau(3)} ${escapeHtml(nbOrange)} département(s)</p>
<p>${badgeNiveau(2)} ${escapeHtml(nbJaune)} département(s)</p>
<p><strong>Total phénomènes filtrés :</strong> ${escapeHtml(records.length)}</p>
<p><small>Carte basée sur les vigilances départementales, pas sur des impacts foudre exacts.</small></p>
`;
}
function afficherListe(groupes) {
if (!groupes.length) {
listeBox.innerHTML = `
<article class="mf-ligne">
<h4>Aucun département concerné</h4>
<p>Aucune vigilance ne correspond aux filtres choisis.</p>
</article>
`;
return;
}
listeBox.innerHTML = groupes.map(function (groupe) {
const infoDep = DEPARTEMENTS[groupe.dep];
const nomDep = infoDep ? infoDep[0] : "Département";
const phenomenes = groupe.records.map(function (r) {
return `
<p>
${badgeNiveau(r.color_id)}
<strong>${escapeHtml(r.phenomenon)}</strong>
—
${escapeHtml(r.echeance || "—")}
<br>
<small>
Du ${escapeHtml(formatDate(r.begin_time))}
au ${escapeHtml(formatDate(r.end_time))}
</small>
</p>
`;
}).join("");
return `
<article class="mf-ligne">
<h4>${escapeHtml(groupe.dep)} — ${escapeHtml(nomDep)}</h4>
<p><strong>Niveau maximum :</strong> ${badgeNiveau(groupe.niveauMax)}</p>
${phenomenes}
</article>
`;
}).join("");
}
async function chargerRadar() {
radarStatus.textContent = "Chargement du radar pluie / orages...";
const response = await fetch(API_RAINVIEWER, {
cache: "no-store"
});
if (!response.ok) {
throw new Error("Erreur RainViewer : " + response.status);
}
const data = await response.json();
radarHost = data.host || "";
radarFrames = data.radar && Array.isArray(data.radar.past)
? data.radar.past
: [];
if (!radarFrames.length) {
throw new Error("Aucune image radar disponible.");
}
radarIndex = radarFrames.length - 1;
afficherFrameRadar(radarIndex);
}
function afficherFrameRadar(index) {
if (!radarFrames.length) {
return;
}
if (index < 0) {
index = radarFrames.length - 1;
}
if (index >= radarFrames.length) {
index = 0;
}
radarIndex = index;
const frame = radarFrames[radarIndex];
const url = radarHost + frame.path + "/512/{z}/{x}/{y}/2/1_1.png";
if (radarLayer) {
map.removeLayer(radarLayer);
radarLayer = null;
}
radarLayer = L.tileLayer(url, {
tileSize: 512,
zoomOffset: -1,
opacity: 0.65,
zIndex: 20,
maxNativeZoom: 7,
attribution: "RainViewer"
}).addTo(map);
const dateRadar = frame.time
? new Date(frame.time * 1000).toLocaleString("fr-FR", {
dateStyle: "short",
timeStyle: "short"
})
: "date inconnue";
radarStatus.textContent =
"Radar pluie / orages affiché — image : " + dateRadar;
}
function lancerAnimationRadar() {
if (!radarFrames.length) {
chargerRadar()
.then(function () {
lancerAnimationRadar();
})
.catch(function (error) {
radarStatus.textContent = error.message;
console.error(error);
});
return;
}
arreterAnimationRadar();
radarTimer = setInterval(function () {
afficherFrameRadar(radarIndex + 1);
}, 750);
radarStatus.textContent = "Animation radar en cours...";
}
function arreterAnimationRadar() {
if (radarTimer) {
clearInterval(radarTimer);
radarTimer = null;
}
}
function masquerRadar() {
arreterAnimationRadar();
if (radarLayer) {
map.removeLayer(radarLayer);
radarLayer = null;
}
radarStatus.textContent = "Radar masqué.";
}
btnReload.addEventListener("click", function () {
chargerVigilances().catch(function (error) {
statusBox.textContent = error.message;
console.error(error);
});
});
btnRadar.addEventListener("click", function () {
if (radarLayer) {
masquerRadar();
return;
}
chargerRadar().catch(function (error) {
radarStatus.textContent = error.message;
console.error(error);
});
});
btnRadarPlay.addEventListener("click", function () {
lancerAnimationRadar();
});
btnRadarStop.addEventListener("click", function () {
arreterAnimationRadar();
radarStatus.textContent = "Animation radar arrêtée.";
});
btnFrance.addEventListener("click", function () {
map.setView([46.7, 2.5], 6);
refreshMapSize();
});
echeanceInput.addEventListener("change", afficherVigilances);
niveauMinInput.addEventListener("change", afficherVigilances);
depSearchInput.addEventListener("input", afficherVigilances);
filtreOrage.addEventListener("change", afficherVigilances);
filtreVent.addEventListener("change", afficherVigilances);
filtrePluie.addEventListener("change", afficherVigilances);
filtreCyclone.addEventListener("change", afficherVigilances);
chargerVigilances().catch(function (error) {
statusBox.textContent = error.message;
console.error(error);
});
chargerRadar().catch(function (error) {
radarStatus.textContent = "Radar non chargé : " + error.message;
console.warn(error);
});
refreshMapSize();
})();
</script>