La page utilise le contrôleur

<apex:page controller="CarRacerStatisticsCtrl" showHeader="false" sidebar="false" cache="false">

Ce panneau s’affiche s’il y a une erreur : pas de racer

<apex:outputPanel id="ErrorPage" rendered="{! !HasRacer}">
NO RACER FOUND
</apex:outputPanel>

Toute la suite s’affiche s’il y a bien un racer

<apex:outputPanel id="FullPage" rendered="{! HasRacer}">

Styles graphique

<style>
.chart-container {
    width: {!PageWidth}px;
    height:250px
}
</style>

La librairie qui permet d’afficher un graphe

<apex:includeScript value="{!$Resource.ChartJS}"/>

Les infos qui permettent de suivre l’état de la carte Google

<script type="text/javascript">
//<![CDATA[   
    // gloabal page vars
    var pageGoogleMap;
    var pageGoogleMapCreated = false;
    var pageGoogleMapMarkers = new Map();
    var pageGoogleMapMarkersInfoWindows = new Map();
    //]]>
</script> 

Le code qui va redemander toutes les 5 secondes de rafraichir des parties de la page

<apex:form >
    <apex:actionPoller action="{!reloadPageData}" rerender="rawdata,carchart,maprefresh" interval="5"/>
</apex:form>

La table qui affiche les données de la voiture (cela appelle les fonctions de renvoi d’information du contrôleur – décrites à la fin de la partie précédente)

<apex:outputPanel id="rawdata">
<br/>
<table border="1" style="width: {!PageWidth}px;">
<tr>
<td >
<font size="5">
    <b>Race : {!raceName}</b><br/>
Racer : {!racerName}<br/>
<i>Racer Car : {!racerCarName}</i><br/>
    </font>
</td>
<td >
    <font size="4">   
        Now : <apex:outputText value="{0,date,MM/dd/yy HH:mm a}" rendered="{!CurrentDisplayTime !=null}">
<apex:param value="{!CurrentDisplayTime}"/>
</apex:outputText> ( {!elapsedTime} s. )
    </font>
    <font size="4">
        <br/>Request : <apex:outputText value="{0,date,MM/dd/yy HH:mm a}"  style="{!if(LastRequestTooOld , 'color:red' , 'color:blue') };" rendered="{!LastRequestDate !=null}" >
            <apex:param value="{!LastRequestDate}"/>
</apex:outputText>
    </font>
</td>
</tr>
</table>
<table border="1" style="width: {!PageWidth}px;">
<tr>
<td>
<b>OBD</b>
<br/>
Measure : <apex:outputText value="{0,date,MM/dd/yy HH:mm a}"  style="{!if(ObdDataTooOld , 'color:red' , 'color:blue') };" rendered="{!LastOBDMeasure !=null}">
    <apex:param value="{!LastOBDMeasure}"/>
</apex:outputText>
<br/>
<font size="8">Speed: {! OBDSpeed }</font>
<br/>
RPM :{! OBDRPM }<br/>
Fuel Level: {! OBDFuelLevel }<br/>
</td>
<td>
<b>GPS</b><br/>
Measure : <apex:outputText value="{0,date,MM/dd/yy HH:mm a}"  style="{!if(GpsDataTooOld , 'color:red' , 'color:blue') };" rendered="{!LastGPSMeasure !=null}">
<apex:param value="{!LastGPSMeasure}"/>
</apex:outputText><br/>
<font size="8">Speed:{! GPSSpeed }</font><br/>
Elevation:{! GPSElevation }<br/>
Time:<apex:outputText value="{0,date,MM/dd/yy HH:mm a}"  style="{!if(TimeOutOfSync , 'color:red' , 'color:blue') };"  rendered="{!GPSTime !=null}">
<apex:param value="{!GPSTime}"/>
</apex:outputText>

</td>
<td>
    <font size="6">
        Req:&nbsp;<apex:outputText value="{!LastRequestAge} s."  style="{!if(LastRequestAge > 350 , 'color:red' , 'color:blue') };" /><br/> 
        GPS:&nbsp;<apex:outputText value="{!LastGPSMeasureAge-LastRequestAge} s."  style="{!if( (LastGPSMeasureAge-LastRequestAge) > 30 , 'color:red' , 'color:blue') };" /><br/> 
        OBD:&nbsp;<apex:outputText value="{!LastOBDMeasureAge-LastRequestAge} s."  style="{!if( (LastOBDMeasureAge-LastRequestAge) > 30 , 'color:red' , 'color:blue') };" />
    </font>
</td>
</tr>
</table>
<br/>    
</apex:outputPanel>

La zone pour afficher le graphe des vitesses

<div class="chart-container">
<canvas id="myChartSpeed" width="{!PageWidth}" height="250"></canvas>
</div>

Le script qui va mettre les données du graphe vitesse dans le chartSpeed

<apex:outputPanel id="carchart">
    <script>
    var theSpeedJsonContent = '{!SpeedChartData}'; 
    var speedJSON = JSON.parse(theSpeedJsonContent);
    var ctx = document.getElementById('myChartSpeed').getContext('2d');
    var myChartSpeed = new Chart(ctx, speedJSON );
    </script>
</apex:outputPanel>

La zone pour afficher la carte

<apex:outputPanel >
<div id="map" style="width: {!PageWidth}px; height: {!MapHeight}px;" ></div>
</apex:outputPanel>

Le script qui va mettre les marqueurs sur la carte commence ici

<script type="text/javascript">
//<![CDATA[

On récupère les infos depuis le contrôleur

var theJsonContent = '{!mapMarkersJSON}'; 
var mapCenterLatitude = '{!mapCenterLatitude}'; 
var mapCenterLongitude = '{!mapCenterLongitude}'; 
var mapCenterZoom = '{!mapCenterZoom}'; 

Ici c’est le code qui remplit la page :
Nota : on ne crée qu’une fois la carte (if (pageGoogleMapCreated == false))

var firstInitialisationDone = false;
function initMap() {
    
    if (pageGoogleMapCreated == false) {
        var mapDiv = document.getElementById('map');
        pageGoogleMap = new google.maps.Map(mapDiv, {
            center : {
                lat : mapCenterLatitude ,
                lng :  mapCenterLongitude 
            },
            zoom : mapCenterZoom
        });
        pageGoogleMapCreated = true;
    }
    
    try {
        var geoJSON = JSON.parse(theJsonContent);
        var markerBounds = new google.maps.LatLngBounds();

On boucle sur les participants, on récupére les infos, et on crée les marqueur (la première fois, ou on les déplace, s’ils existent déjà)   

for (var i = 0; i < geoJSON.features.length; i++) {
            
            var coords = geoJSON.features[i].geometry.coordinates;
            var theName = geoJSON.features[i].properties.name;
            var theRacerId = geoJSON.features[i].properties.racerId;
            var theCarName = geoJSON.features[i].properties.carName;
            var theStoredTime = geoJSON.features[i].properties.storedTime;
            var theMeasureTime = geoJSON.features[i].properties.measureTime;
            var theElapsedTime = geoJSON.features[i].properties.elapsedTime;
            var theMeasureAge = geoJSON.features[i].properties.measureAge
            
            var theMarkerPosition = new google.maps.LatLng(
coords[1],
coords[0]
    );
            
            // étend la carte pour afficher le marqueur ajouté.
            markerBounds.extend(theMarkerPosition);
           
		   // nom du marqueur
           var theTitle = "Name : "+theCarName+" "+theMeasureTime;

		   // popup d’information si on clique sur le marqueur de la voiture
           var theContentText = 
               "<b>"+theCarName+"</b><br/>"+
               ""+theMeasureTime+" ("+theElapsedTime+" sec after start)<br/>"+
                "<i>"+theMeasureAge+" sec old measure</i><br/>"+
                "<i>Read at "+theStoredTime+"</i><br/>"
           ;
		   // modification de l’icone en fonction du statut des infos sur la voiture 
           theMarkerIcon = "http://maps.google.com/mapfiles/ms/icons/blue-dot.png";
                if (geoJSON.features[i].properties.currentRacer) {
                    if (geoJSON.features[i].properties.finished) {
                            theMarkerIcon = "http://maps.google.com/mapfiles/ms/icons/purple-dot.png";
                    } else { 
                        if (theMeasureAge > 60) {
                            theMarkerIcon = "http://maps.google.com/mapfiles/ms/icons/orange-dot.png";
                        } else {
                            theMarkerIcon = "http://maps.google.com/mapfiles/ms/icons/red-dot.png";
                        }
                    }
                } else {
                    if (geoJSON.features[i].properties.finished) {
                            theMarkerIcon = "http://maps.google.com/mapfiles/ms/icons/green-dot.png";
                    } else {
                        if (theMeasureAge > 60) {
                            theMarkerIcon = "http://maps.google.com/mapfiles/ms/icons/yellow-dot.png";
                        } else {
                            theMarkerIcon = "http://maps.google.com/mapfiles/ms/icons/blue-dot.png";
                        }
                    }
                }
            
            // si le marqueur existait de la fois précedente, on l’utilise, en mettant à jour la position, changeant la couleur si nécessaire, et mettant à jour l’info window)

            if (pageGoogleMapMarkers.has(theRacerId) ) {  
                // get existing marker
                var existingMarker = pageGoogleMapMarkers.get(theRacerId);
                // move it
                existingMarker.setPosition( theMarkerPosition );
                existingMarker.setIcon(theMarkerIcon);
                
                var existingInfoWindow = pageGoogleMapMarkersInfoWindows.get(theRacerId);
                existingInfoWindow.setContent(theContentText);
            } else {
                // crée le marqueur
                var theNewMarker = new google.maps.Marker({
                    position: theMarkerPosition,
                    map: pageGoogleMap,
                    title: theTitle,
                    icon: {
                          url: theMarkerIcon
                    }                                    
                });
                // memorise le nouveau marqueur pour pouvoir le réutiliser
                pageGoogleMapMarkers.set(theRacerId,theNewMarker);
                // crée la fenetre d’info si on clique sur le marqueur
                var theNewInfoWindow = new google.maps.InfoWindow({
                    content: theContentText
                });
                pageGoogleMapMarkersInfoWindows.set(theRacerId, theNewInfoWindow); 
                google.maps.event.addListener(
                    theNewMarker,
                    'click',
                    (function(marker,contentText,infowindow){ 
                        return function() {
                            infowindow.setContent(contentText);
                            infowindow.open(pageGoogleMap,marker);
                            windows.push(infowindow)
                            google.maps.event.addListener(pageGoogleMap,'click', function(){ 
                                infowindow.close();
                            }); 
                        };
                    })(theNewMarker,theContentText,theNewInfoWindow)); 
                }
            }
        } catch(error) {
            console.error(error);
        }
        pageGoogleMap.fitBounds(markerBounds);
        firstInitialisationDone = true;        
    }
//]]>
</script> 

Le panneau qui va demander le rafraichissement périodique en redemandant les infos au contrôleur et redemander à remplir la carte (initMap)

<apex:outputPanel id="maprefresh">
        <script type="text/javascript">
        //<![CDATA[
        if (firstInitialisationDone) {
            theJsonContent      = '{!mapMarkersJSON}'; 
            mapCenterLatitude   = '{!mapCenterLatitude}'; 
            mapCenterLongitude  = '{!mapCenterLongitude}'; 
            mapCenterZoom       = '{!mapCenterZoom}'; 
            initMap();
        }
        //]]>
        </script>
</apex:outputPanel>        

Le code à ajouter pour pouvoir afficher une map google

<script src="https://maps.googleapis.com/maps/api/js?key=AIz...ktO0ANQ&callback=initMap">
</script>

On referme la page

</apex:outputPanel>
</apex:page>