Rechercher dans le blog

Un mardi une appli #9 : Comment créer une application retraçant la propagation de la pandémie de Covid-19 ?

Bonjour, aujourd'hui je vais vous présenter comment a été réalisée une application retraçant la propagation de l'épidémie de Covid-19 depuis ses débuts jusqu'à aujourd'hui.
Tout le code est d'ores et déjà disponible sur mon GitHub et vous pouvez visualiser cette application en plein écran ici. Ce tutoriel est conçu pour que vous puissiez tous le réaliser même si vous n'avez pas de compte ArcGIS Online ni de compte ArcGIS for Developers. 

Dans cette application, je retrace temporellement la progression de la pandémie dans tous les pays du monde, de la date de son apparition jusqu'à aujourd'hui. Nous pouvons faire cela grâce à un widget de slider temporel, et la force de celui-ci est qu'il s'exécute côté client.

Pour commencer j'initialise donc une instance de Map que je passe en paramètre d'une instance Mapview. Dans mon instance de Map je charge la couche contenant les données, ici un Feature Service hébergé sur mon organisation ArcGIS Online. Les données proviennent de la source Our World in Data. De nombreux médias, organismes de recherches et d'enseignement leur font confiance et utilisent leurs données pour leurs besoins :



Tous les jours, on retrouve sur leur site un fichier texte au format CSV mis à jour et contenant les nouveaux cas déclarés de Covid-19, la somme totale des cas par pays, le nombre de morts total par pays et cela pour chaque date depuis les débuts de l'épidémie.

Il faut publier le csv sur ArcGIS Online et activer les paramètres temporels de la couche pour pouvoir accéder aux données temporelles dans notre application pour utiliser le Time Slider. Il suffit de suivre cette procédure en 20 secondes pour activer les paramètres temporels.

Remarque : si vous n'avez pas de compte ArcGIS Online, pas de problème, vous pouvez en créer un gratuitement et souscrir à un plan gratuit ArcGIS for Developers  en cliquant sur Sign up for free. Vous pourrez alors publier votre csv dessus.
Vous pouvez également tirer parti de mon service qui est en accès public dans votre propre application :
https://services.arcgis.com/d3voDfTFbHOCRwVR/arcgis/rest/services/data_full_covid19/FeatureServer

I/ Le rendu des données

Dans un premier temps nous allons configurer le renderer, c'est à dire la manière dont seront représentées les données sur la carte. Ici j'ai choisi de visualiser deux indicateurs du virus Covid-19 sur la carte : le nombre de cas par pays et le taux de létalité*. Pour avoir une lecture intuitive de ma carte, je vais représenter par pays un cercle qui sera de plus en plus gros selon le nombre de cas dans le pays, et la couleur du cercle variera du jaune au rouge selon le taux de létalité. On utilisera donc pour cela une visualisation dite multivariée.
Je vais passer dans le tableau visualVariables qui est une propriété du renderer, deux objets JSON pour chaque visualisation. C'est ici qu'on définit si la symbologie de nos entités se fait selon leur taille, leur couleur, l'opacité ou la rotation.

Grâce à la documentation, je peux alors définir pour le premier objet dans le tableau VisualVariable,  le type size, puis je spécifie le nom du champ d'attribut numérique qui contient les valeurs de données utilisées pour déterminer la couleur de chaque entité via Field :total_cases (nom du champ). Je peux en option lui donner une étiquette qui sera affiché dans la légende avec legendOptions. La distribution statistique des données de cas étant inégales, je ne vais pas respecter une relation linéaire de taille en fonction du pays contenant le nombre de cas le plus bas et le pays ayant le nombre de cas le plus haut. En effet avec le nombre de cas importants en Chine, on ne verrait pas la variation de cas pour les autres pays car les entités auront des tailles trop proches. Il faut donc adapter et créer sa propre échelle. Je vais donc définir mes propres bornes pour mieux distinguer plusieurs échelles de données. Entre deux bornes, la taille des entités suivra une relation linéaire entre la borne inférieure et supérieure.
{ type: "size", field: "total_cases", legendOptions: { title: "Total des cas dans le pays" }, stops: [{ value: 80000, size: "128px" }, { value: 5000, size: "64px" }, { value: 1000, size: "24px" }, { value: 100, size: "6px" }, { value: 1, size: "3px" }, { value: 0, size: "0px" } ] }

* Remarque : la létalité dépend donc du nombre de cas déclarés et identifiés dans le pays, ce qui dépend du nombre de tests effectués. La létalité peut donc être artificiellement trop grande si de nombreux cas de Covid-19 existent dans le pays mais ne sont pas identifiés car pas suffisamment grave, donc le diviseur est moins grand qu'il devrait l'être.




Maintenant que le nombre de cas est représenté pour chaque pays par un cercle d'une taille plus ou moins grande; je veux maintenant voir son taux de létalité (c'est à dire le pourcentage de personnes décédées après avoir été contaminées). Je représente le taux de létalité de la maladie par le remplissage coloré des cercles suivant un dégradé de couleur de jaune à rouge. Plus on s'approche du rouge plus la létalité est importante. De la même manière, je spécifie que cette visualisation se fait selon le type color et je spécifie le nom du champ d'attribut numérique qui contient les valeurs de données utilisées pour déterminer la couleur de chaque entité via Fields. Étant donné que je calcule un taux, je normalise le résultat par le total des cas, directement à l'aide du paramètre normalizationFields. J'ai donc des pourcentages en résultat, et je définis pour 4 d'entre eux des bornes avec une couleur précise. Entre deux bornes, la couleur des entités suivra une relation linéaire résultant sur un dégradé de couleur entre la borne inférieure et supérieure.

{ type: "color", field: "total_deaths", normalizationField: "total_cases", legendOptions: { title: "Taux de létalité" }, stops: [{ value: 0.04, color: [196, 47, 30, 0.7], //"#B03A2E" label: "4 % de cas décédés" }, { value: 0.03, color: [228, 145, 67, 0.7], //"#CB4335" label: "3 % de cas décédés" }, { value: 0.02, color: [240, 205, 100, 0.7], //"#EC7063" label: "2 % de cas décédés" }, { value: 0.01, color: [255, 255, 183, 0.7], label: "1 % de cas décédés" } ] }

La propriété renderer de la layer est maintenant définie, définissons une popup pour chaque entité pour avoir plus d'informations sur l'épidémie localement.

II/ La popup

Je veux donner la possibilité à l'utilisateur d'accéder à des informations textuelles par pays. Pour ne pas surcharger l'application, je veux que ces informations soient affichées à la demande de l'utilisateur. Pour cela, on va donner la possibilité à l'utilisateur de cliquer sur une entité pour accéder à une fenêtre contextuelle renseignant plus d'informations comme le nombre de cas à la dernière date de mise à jour de la donnée, le nombre de morts, puis on va calculer le taux de létalité correspondant grâce à une expression arcade.

Qu'est-ce qu'ArcadeArcade est un langage d'expressions pour l'ensemble de la plateforme ArcGIS. Léger et simple à utiliser, il permet de prendre des valeurs en entrée pour réaliser des opérations et produire une valeur en sortie comme les expressions de calculs de cellule dans Microsoft Excel. Les expressions Arcade permettent de calculer dynamiquement des valeurs pour étiqueter les couches, comme base pour le rendu d'une couche, pour définir la variation d'un symbole (taille, rotation, couleur, transparence, ...) et plus encore. Pour en savoir plus n'hésitez pas à consulter l'article dédié du blog arcOrma.

Je veux donc calculer la létalité et intégrer l'information dans la popup. J'accède aux attributs de la couche avec $feature.NAME_ATTRIBUTE puis je calcule très simplement le taux en divisant le nombre de morts par le nombre d'infectés dans le pays et j'arrondis le résultat à 2 chiffres après la virgule avec la fonction Round(). Vous pouvez retrouver toutes les fonctions Arcade ici.

< script type = "text/plain"

id = "lethality-arcade" >

// Returns the share of the dominant demographic as a percentage

var fieldValues = [$feature.total_cases, $feature.total_deaths];

var cases = fieldValues[0];

var deaths = fieldValues[1];

var result = Round((deaths / cases) * 100, 2)

return result + ' %';
</script>

La classe ExpressionInfo permet de définir les expressions Arcade exécutées dans le PopupTemplate d'une couche, c'est ici que je récupère le script défini au-dessus grâce à son id. Ces expressions sont évaluées à un nombre, une chaîne ou un tableau par exemple, pour chaque entité au moment de l'exécution de la popup. Les valeurs s'affichent dans la popup de la vue comme s'il s'agissait de valeurs de champ.
Je vais ici les afficher dans une table en utilisant FieldInfo du contenu du popupTemplate.

Je définis ensuite le contenu principal et le formatage de la popup avec la variable content. Le contenu peut être défini par exemple avec :

  • Une simple chaîne de caractère faisant référence à des valeurs de champ ou à des expressions d'arcade. Les expressions doivent être définies dans la propriété expressionInfos.
  • Des Éléments contextuels - Ces éléments peuvent être utilisés individuellement ou combinés. L'ordre dans lequel ils sont définis détermine la façon dont ils s'affichent dans la fenêtre contextuelle. Voir les éléments ci-dessous décrivant chaque élément :

  1. text - Un élément de contenu textuel qui fournit un texte descriptif en tant que contenu.
  2. media - Un élément de contenu multimédia qui est utilisé pour afficher des médias tels que des graphiques / images.
  3. fields - Un élément de contenu de champs qui contient les champs à afficher dans le contenu. Si ce n'est pas défini directement dans la propriété de contenu, la popup affichera tout ce qui est défini dans la propriété PopupTemplate.fieldInfos.
  4. attachments - Un élément de contenu de pièces jointes associées à l'entité.
Pour en savoir plus, consultez cette documentation.

J'ai choisi de représenter les données dans un tableau avec fields, il faut donc spécifier le tableau fieldinfos  qui définit la façon dont les champs de la couche ou les valeurs des expressions Arcade participent à la popup.

var arcadeExpressionInfos = [


{
name: "lethality-arcade",

title: "Taux de létalité",

expression: document.getElementById("lethality-arcade").text
},
];

var template = {

// autocasts as new PopupTemplate()

title: "COVID-19 - {location}",

content: [{

type: "text",
text: "{location}: Il y a {total_cases} contaminés identifiés et déclarés le {date} dont" +
" {expression/lethality-arcade} de cas décédés."
},
{
type: "fields",
fieldInfos: [{
fieldName: "total_cases",
label: "Nombre de personnes atteintes par le Covid-19 identifiées et déclarées",
format: {
digitSeparator: true,
places: 0
}
},
fieldName: "total_deaths",
{
label: "Nombre de morts par le Covid-19",
format: {
digitSeparator: true,
places: 0
}
},
fieldName: "expression/lethality-arcade",
{
format: {
digitSeparator: true,
places: 0
}
}
}
] ],
expressionInfos: arcadeExpressionInfos
};

III/ Les widgets

Pour donner à l’utilisateur  plus d'outils pour comprendre et explorer les données, j'ai ajouté à l'interface utilisateur (UI) des widgets :
Pour chaque widget il faut spécifier la vue à laquelle ils sont affectés. Puis on les ajoute à la vue avec leur position et leur index pour les ordonner.

Les géosignets permettent pour un utilisateur d'accéder en un clic à une zone d'intérêt préalablement configurée et misz à disposition par le développeur. Nous devons pour cela définir les étendues de plusieurs items bookmarks.

const bookmarks = new Bookmarks({ view: view, bookmarks: [ new Bookmark({ name: "Europe", extent: { spatialReference: { wkid: 102100 }, xmin: -1569877.42, ymin: 4419153.05, xmax: 4378757.87, ymax: 6879813.86 } })

//other bookmarks ...

] }); const bkExpand = new Expand({ view: view, content: bookmarks, expanded: false }); // Add the widget to the top-right corner of the view view.ui.add(bkExpand, "top-right");

Pour ne pas surcharger la vue nous pouvons ajouter un widget ExpandExpand est un bouton qui permet d'afficher ou de retirer un autre widget, ici je retire les géosignets mais je donne la possibilité à l'utilisateur de les afficher. En revanche je veux que la légende soit ouverte par défaut pour que l'utilisateur comprenne tout de suite la carte, mais je lui donne la liberté de fermer la légende.

const legendExpand = new Expand({ expandIconClass: "esri-icon-drag-horizontal", expandTooltip: "Legend", view: view, expanded: true, //Legend widget is visible when the UI is loaded content: legend, }); view.ui.add(legendExpand, "top-left");

Pour leur donner cet aspect sombre je leur ai appliqué le style css dark comme indiqué dans la documentation.

IV/ Le slider temporel

Enfin, vient le cœur de l'application, le slider temporel. Le widget TimeSlider est un widget en version Beta qui simplifie la visualisation des données temporelles dans votre application. Pour qu'il fonctionne il y a plusieurs choses à configurer :

La propriété fullTimeExtent définit la période entière pendant laquelle vous pouvez visualiser vos données sensibles au temps à l'aide du widget TimeSlider

La propriété mode permet de choisir comment visualiser des données temporelles jusqu'à un point dans le temps, à partir d'un point dans le temps, à un instant donné ou à partir de données qui se situent dans une plage de temps. En fonction du mode choisi, le curseur sera différent sur le widget TimeSlider.

La propriété stops définit des emplacements spécifiques sur le TimeSlider où les curseurs se déplacent lorsqu'ils sont manipulés. Vous pouvez définir cette propriété comme un tableau de dates, un nombre d'arrêts régulièrement espacés ou un intervalle de temps spécifique (par exemple, des jours).

Le widget TimeSlider peut être configuré pour appliquer une logique personnalisée chaque fois que le TimeSlider est manipulé en observant sa propriété timeExtent. Par exemple, lorsque la propriété timeExtent du TimeSlider est mise à jour, nous voulons mettre à jour la propriété timeExtent et appliquer un filtre côté client, sur les données issues d'un FeatureLayerView, CSVLayerView ou GeoJSONLayerView. Un filtre sur la vue de la couche peut être utilisé pour filtrer les données qui ne sont pas incluses dans l'intervalle de temps actuel, défini sur le timeSlider

const timeSlider = new TimeSlider({ container: "timeSlider", mode: "instant", view: view }); view.ui.add(timeSlider, "manual"); let timeLayerView; view.whenLayerView(layer).then(function (lv) { timeLayerView = lv; timeSlider.fullTimeExtent = layer.timeInfo.fullTimeExtent; timeSlider.stops = { interval: { value: 1, unit: "days" }, timeExtent: { start: layer.timeInfo.fullTimeExtent.start, //1579564800000, end: layer.timeInfo.fullTimeExtent.end //1584403200000 } } }); view.ui.add(timeSlider, "manual"); timeSlider.watch("timeExtent", function (value) { // update layer view filter to reflect current timeExtent timeLayerView.filter = { timeExtent: value }; });

Pour lancer automatiquement le TimeSlider lorsque je charge l'application, je lance la méthode play() du Slider et je peux choisir sa vitesse. Plus, le chiffre est grand, plus le curseur sera lent.

timeSlider.set({ loop: false, playRate: 100 }); timeSlider.play();

Nous avons vu ensemble comment développer gratuitement et en 400 lignes de codes une application visualisant les données temporelles retraçant l'évolution du Covid-19. Nous avons été capables d'utiliser la visualisation multivariée pour observer plusieurs paramètres d'une entité sur la carte, puis nous avons ajouté des popups tirant parti des expression Arcade pour approfondir les observations de l'utilisateur. Enfin, nous avons ajouté des widgets à l'interface utilisateur pour améliorer l'expérience utilisateur, avec notamment un widget de slider temporel pour visualiser de manière animée les données de la pandémie, et la situation à un instant T. On peut ainsi voir très facilement à quel moment la pandémie a progressé dans le monde et dans certains pays.

Encore une fois, tout le code est disponible sur mon Github, n'hésitez pas à vous en servir pour réaliser vos propres tests. À vous de développer ! 

N'hésitez pas à vous abonner à ce blog pour lire d'autres articles sur le développement Web d'applications cartographiques et découvrir une superbe alternative à Google Maps !
Vous voulez vous aussi réaliser des applications Web cartographiques et dynamiques ? N'hésitez pas à souscrire à un plan gratuit ArcGIS for Developers pour développer vos propres applications cartographiques 2D ou 3D ! Cet article résume ce que vous obtiendrez. Et pour en savoir sur l'API c'est ici.



Aucun commentaire:

Publier un commentaire