(function(){ "use strict"; var API = location.origin; var map = L.map("map",{zoomControl:false,attributionControl:false}).setView([48.85,2.35],13); var osm = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png",{maxZoom:19,attribution:"OSM"}); var esri = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",{maxZoom:19,attribution:"Esri"}); osm.addTo(map); L.control.attribution({position:"bottomleft",prefix:false}).addTo(map); var fromPt=null, toPt=null, routeLayer=null, stationLayer=null, gpsWatchId=null, gpsMarker=null; var selectingFrom=true; var $=function(id){return document.getElementById(id)}; var $from=$("from"), $to=$("to"), $btnRoute=$("btn-route"), $btnClear=$("btn-clear"); var $btnSat=$("btn-sat"), $btnGps=$("btn-gps"), $panel=$("panel"); var $routeInfo=$("route-info"), $legend=$("speed-legend"), $loading=$("loading"); var $searchIn=$("search-input"), $searchRes=$("search-results"); function esc(s){var d=document.createElement("div");d.textContent=s;return d.innerHTML} $btnSat.onclick=function(){ if(map.hasLayer(esri)){map.removeLayer(esri);osm.addTo(map);$btnSat.className="icon-btn"} else{map.removeLayer(osm);esri.addTo(map);$btnSat.className="icon-btn active"} }; $btnGps.onclick=function(){ if(gpsWatchId!=null){stopGps();return} if(!navigator.geolocation){alert("Géolocalisation non supportée");return} gpsWatchId=navigator.geolocation.watchPosition(function(pos){ var lat=pos.coords.latitude,lng=pos.coords.longitude; if(!gpsMarker){ var icon=L.divIcon({className:"",html:"
",iconSize:[14,14],iconAnchor:[7,7]}); gpsMarker=L.marker([lat,lng],{icon:icon,zIndexOffset:1000}).addTo(map); } else gpsMarker.setLatLng([lat,lng]); },function(e){alert("Erreur GPS: "+e.message)},{enableHighAccuracy:true,timeout:10000}); $btnGps.className="icon-btn active gps-active"; }; function stopGps(){ if(gpsWatchId!=null){navigator.geolocation.clearWatch(gpsWatchId);gpsWatchId=null} if(gpsMarker){map.removeLayer(gpsMarker);gpsMarker=null} $btnGps.className="icon-btn"; } var searchTimer=null; $searchIn.oninput=function(){ clearTimeout(searchTimer); var q=$searchIn.value.trim(); if(q.length<2){$searchRes.className="search-results";return} searchTimer=setTimeout(function(){doSearch(q)},300); }; $searchIn.onkeydown=function(e){if(e.key==="Escape"){$searchRes.className="search-results";$searchIn.blur()}}; document.addEventListener("click",function(e){if(!e.target.closest(".search-wrap"))$searchRes.className="search-results"}); function doSearch(q){ fetch(API+"/api/zmap/search?q="+encodeURIComponent(q)) .then(function(r){return r.json()}) .then(function(d){ if(!d.results||!d.results.length){$searchRes.innerHTML='
Aucun résultat
';return} $searchRes.innerHTML=""; for(var i=0;i
"+esc(r.type)+" · "+esc(r.category)+"
"; el.dataset.lat=r.lat;el.dataset.lng=r.lon; el.onclick=function(){ var lat=parseFloat(this.dataset.lat),lng=parseFloat(this.dataset.lng); map.setView([lat,lng],16); if(selectingFrom){setFrom(lat,lng);selectingFrom=false} else{setTo(lat,lng);selectingFrom=true} $searchRes.className="search-results";$searchIn.value=""; }; $searchRes.appendChild(el); } $searchRes.className="search-results open"; }).catch(function(){}); } map.on("click",function(e){ var lat=+e.latlng.lat.toFixed(5),lng=+e.latlng.lng.toFixed(5); if(selectingFrom){setFrom(lat,lng);selectingFrom=false} else{setTo(lat,lng);selectingFrom=true} }); function setFrom(lat,lng){fromPt={lat:lat,lng:lng};$from.value=lat+","+lng;if(fromPt&&toPt)$btnRoute.disabled=false} function setTo(lat,lng){toPt={lat:lat,lng:lng};$to.value=lat+","+lng;if(fromPt&&toPt)$btnRoute.disabled=false} $btnClear.onclick=function(){ fromPt=toPt=null;$from.value="";$to.value="";$btnRoute.disabled=true;selectingFrom=true; $routeInfo.className="route-info hidden";$legend.innerHTML=""; if(routeLayer){map.removeLayer(routeLayer);routeLayer=null} if(stationLayer){map.removeLayer(stationLayer);stationLayer=null} }; $btnRoute.onclick=function(){ if(!fromPt||!toPt)return; $loading.className="loading show"; fetch(API+"/api/zmap/route?from="+fromPt.lat+","+fromPt.lng+"&to="+toPt.lat+","+toPt.lng) .then(function(r){return r.json()}) .then(function(d){if(d.error){alert(d.error);return}renderRoute(d)}) .catch(function(e){alert("Erreur: "+e.message)}) .finally(function(){$loading.className="loading"}); }; function speedColor(k){ if(k==null)return"#555"; if(k<=20)return"#ef4444";if(k<=40)return"#f59e0b";if(k<=60)return"#eab308"; if(k<=80)return"#22c55e";if(k<=100)return"#06b6d4";if(k<=120)return"#3b82f6";return"#8b5cf6"; } function speedCls(k){ if(k==null)return"sl-unk"; if(k<=20)return"sl-20";if(k<=40)return"sl-40";if(k<=60)return"sl-60"; if(k<=80)return"sl-80";if(k<=100)return"sl-100";if(k<=120)return"sl-120";return"sl-max"; } function renderRoute(d){ if(routeLayer)map.removeLayer(routeLayer); var coords=d.geometry&&d.geometry.coordinates||[]; var speeds=d.speeds||[]; var segs=[]; for(var i=0;i"+kmh+" km/h"); segs.push(line); } routeLayer=L.layerGroup(segs).addTo(map); map.fitBounds(L.polyline(coords.map(function(c){return[c[1],c[0]]})).getBounds().pad(.1)); var covered=speeds.filter(function(s){return s!=null}).length; var total=speeds.length; var pct=total?Math.round(covered/total*100):0; $routeInfo.className="route-info"; var dist={}; for(var i=0;i120"; dist[k]=(dist[k]||0)+1; } var colors={"?":"#555","0-20":"#ef4444","21-40":"#f59e0b","41-60":"#eab308","61-80":"#22c55e","81-100":"#06b6d4","101-120":"#3b82f6",">120":"#8b5cf6"}; var order=["?","0-20","21-40","41-60","61-80","81-100","101-120",">120"]; var barHtml="
"; var distribHtml="
"; for(var j=0;j
"; distribHtml+=""+order[j]+" km/h: "+cnt+""; } barHtml+="
";distribHtml+=""; $routeInfo.innerHTML='
'+d.distance_km+" km
"+ '
'+d.duration_min+" min · "+covered+"/"+total+" segments · "+pct+"% couverts
"+barHtml+distribHtml; var leg=[["≤20","sl-20"],["21-40","sl-40"],["41-60","sl-60"], ["61-80","sl-80"],["81-100","sl-100"],["101-120","sl-120"],[">120","sl-max"],["?","sl-unk"]]; $legend.innerHTML=leg.map(function(l){return''+l[0]+" km/h"}).join(""); loadStations(coords); } function loadStations(coords){ if(stationLayer)map.removeLayer(stationLayer); if(!coords.length)return; var mnLa=90,mxLa=-90,mnLo=180,mxLo=-180; for(var i=0;imxLa)mxLa=coords[i][1]; if(coords[i][0]mxLo)mxLo=coords[i][0]; } var p=.02; fetch(API+"/api/zmap/stations?south="+(mnLa-p)+"&west="+(mnLo-p)+"&north="+(mxLa+p)+"&east="+(mxLo+p)) .then(function(r){return r.json()}) .then(function(d){ if(!d.stations||!d.stations.length)return; stationLayer=L.layerGroup().addTo(map); for(var i=0;i",iconSize:[10,10],iconAnchor:[5,5]}); var pw=0; if(s.connectors)for(var j=0;jpw)pw=v} L.marker([s.lat,s.lon],{icon:icon}).bindPopup( ""+esc((s.name||"").slice(0,40)||"Borne")+"
"+ (pw?pw+" kW":"")+" "+esc(s.network||"")+"
"+esc(s.status||"") ).addTo(stationLayer); } }).catch(function(){}); } })();