C'est la suite du
#30DayMapChallenge, et la deuxième carte que je devais réaliser est celle du jour 12, sur le
thème space and time (temps et espace).
Je trouve que la manière organique dont l'occupation de l'espace évolue avec
le temps est fascinante, que ce soit sur des périodes de dizaines, centaines,
milliers, millions d'années ou avec le rythme des saisons, sous l'influence de
l'anthropisation ou au gré d'événements naturels, tous ces facteurs étant
intimement liés.
Ou, pour pouvoir faire référence au titre d'un des épisodes d'un célèbre jeu
vidéo où un type en tunique verte détruit des poteries pour sauver la
princesse, nous pouvons aussi utiliser l'image d'un poumon qui
respire.
L'application
Et voici l'application que toutes ces réflexions m'ont inspiré : Breath of the Arctic. N'hésitez pas à cliquer sur le lien pour la consulter en pleine page pour un meilleur confort visuel. Cette application en projection polaire représente les variations d'étendue de l'Arctique, c'est-à-dire des parties de l'océan ayant gelé pour former une "mer de glace", mois par mois de la fin de l'année 1978 à nos jours. La ligne discontinue blanche montre la superficie maximale enregistrée en mars 1979.
Les données proviennent du
National Snow and Ice Data Center
(NSIDC) et sont accessibles
depuis le Living Atlas d'ArcGIS.
Le code
Nous allons maintenant regarder ensemble le code permettant de faire
fonctionner l'application. Celle-ci a été crée grâce à l'API JavaScript d'ArcGIS. Comme d'habitude, vous pouvez recréer cette application de votre côté grâce
au tutoriel, mais également retrouver le code complet
sur mon Github
ou le tester interactivement avec
ce Codepen. Si vous sentez que le niveau de ce tutoriel est un peu trop élevé pour vous
et que les notions sont expliquées trop rapidement, je vous conseille
d'explorer
cette série
qui prend le temps d'expliquer pas à pas les notions fondamentales. Cela étant
dit, c'est parti pour le code !
Le code HTML est extrêmement simple, avec un appel à l'API JavaScript
https://js.arcgis.com/4.30/
ainsi qu'à son style
https://js.arcgis.com/4.30/esri/themes/dark/main.css
, que nous
allons utiliser ici en mode sombre (dark). Je référence également ma propre
feuille de style et mon propre script que j'ai séparés du HTML. Dans le corps
du HTML, j'insère une balise <div id = "full">
qui
encapsule toutes mes autres balises, une balise <div id = "dayeDisplay>
qui accueillera la date d'enregistrement de la donnée et une balise
<viewDiv>
qui contiendra la vue de ma carte.
<html lang="en"> <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>
Breath of the Arctic
</title>
<link rel="stylesheet" href="style/style.css" />
<link rel="stylesheet" href="https://js.arcgis.com/4.30/esri/themes/dark/main.css" />
<script src="https://js.arcgis.com/4.30/"></script>
<script src="app/main.js"></script>
<script type="module" src="https://js.arcgis.com/calcite-components/2.1.0/calcite.esm.js"></script>
</head>
<body >
<div id="full">
<div id="dateDisplay">
<h3>-/-</h3>
</div>
<div id="viewDiv"></div>
</div>
</body>
</html>
Côté CSS, je m'assure simplement que la taille de la
<viewDiv>
prendra l'entièreté de l'écran et que la date
s'affichera avec un style sympa quelle que soit la taille de l'écran.html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
position: absolute;
background-color: "#212526";
}
h3 {
z-index: 200;
padding-top: 1%;
font-weight: normal;
font-size: 170%;
font-family: "Avenir Next", "Avenir", "Helvetica Neue", sans-serif;
position: absolute;
z-index: 9;
text-align: center;
width: 100%;
height: 10%;
margin: auto;
user-select: none;
}
#dateDisplay {
z-index: 150;
font-weight: normal;
position: absolute;
font-size: 120%;
font-family: "Avenir Next", "Avenir", "Helvetica Neue", sans-serif;
position: absolute;
user-select: none;
padding-top: 1.6%;
}
@media screen and (min-width: 500px) {
#dateDisplay {
right: 1%;
}
}
@media screen and (max-width: 500px) {
#dateDisplay {
width: 100%;
text-align: center;
top: 5%;
}
}
Il est maintenant temps de faire appel à l'API JavaScript pour insérer notre
carte ! Comme d'habitude, je commence par charger les modules qui me
seront utiles dans le require (ici,
WebMap,
MapView, FeatureLayer, GraphicsLayer et
Graphic), puis je déclare la fonction qui contiendra toutes les instructions
utiles à l'affichage de ma carte et je lui passe en arguments chacune de ces
classes dans le même ordre que dans le require.
require([
"esri/WebMap",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/layers/GraphicsLayer",
"esri/Graphic"
], function (WebMap, MapView, FeatureLayer, GraphicsLayer, Graphic) {
//le reste du code ira ici
});
La première étape consiste ensuite à appeler une
WebMap
à l'intérieur de mon application. Contrairement à une simple Map qui doit
être construite de zéro dans le code, la WebMap va se baser sur une carte
construite grâce au MapViewer dans ArcGIS Online ou ArcGIS Enterprise. Cette
approche permet de faire gagner un temps considérable de développement, en
étant capable de configurer les couches et leur symbologie, le fond de
carte, le système de coordonnées, mais aussi d'éventuels géosignets etc.
sans aucune ligne de code. Si la carte est mise à jour, cela se répercutera
également sur l'application sans avoir à maintenir le code. Ici, j'ai donc
préparé ma carte dans le MapViewer avec 4 couches dont j'ai configuré la
symbologie : les pays du monde pour le fond de carte, l'étendue maximale
enregistrée pour l'Arctique (que vous voyez apparaître en pointillés
blancs), la couche contenant tous les enregistrements d'étendue de
l'Arctique depuis 1978 (qui pour l'instant est cachée mais dont nous nous
servirons dans le code), et une couche de dessin qui est simplement un
cercle blanc dont j'ai modifié la symbologie et qui me permet de créer
l'effet de halo que vous voyez autour du pôle Nord.
J'ajoute donc simplement cette WebMap à mon code en indiquant son id, que
vous pouvez retrouver facilement dans l'url de la page de l'item. (exemple
ici :
https://esrifrance.maps.arcgis.com/home/item.html?id=360827c4ab4f4aad85b771bd453a7254).
Vous pouvez directement utiliser la même carte que moi, car je l'ai partagée
en public.
const map = new WebMap({
portalItem: {
id: "360827c4ab4f4aad85b771bd453a7254"
}
});
Je crée ensuite une vue sur ma carte grâce à la
MapView, en indiquant le
centre
(en long/lat) ainsi que le niveau de zoom. Ici, je rajoute également des
contraintes
afin d'empêcher l’utilisateur final de modifier le niveau de
zoom. Je
retire
également les éléments d'interface utilisateur (UI) intégrés par défaut à la MapView pour épurer l'application.
const view = new MapView({
container: "viewDiv",
map: map,
center: [2277430.5171051705, 2076536.5900727776],
zoom: 4,
constraints: {
maxZoom: 4,
minZoom: 4
}
});
view.ui.remove(["zoom"]);
Pour la suite, je veux m'assurer que la carte et ses couches soient
entièrement chargées avant de les manipuler. J'utilise pour cela une
instruction
map.when(() => { ... })
à l'intérieur de
laquelle je placerai toute la suite de mon code.
J'accède ensuite à la couche contenant l'historique d'étendue de l'Arctique.
Le calque étant en 3ème position de la carte, je le retrouve à l'index 2 en
accédant aux
layers
de la map et je le stocke à l'intérieur de la variable
arcticExtentLayer
.
const arcticExtentLayer = map.layers.items[2];
arcticExtentLayer.visible = false;
Je crée maintenant
une couche graphique (
GraphicsLayer
)
que j'ajoute à la carte. Pour l'instant, cette couche est vide mais elle me
servira à afficher dynamiquement une à une les différentes étendues de
l'Arctique depuis 1978.
const graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
Avec
la méthode
queryFeatures()
, je viens interroger la couche arcticExtentLayer
. La query va
interroger la couche et en retourne la géométrie et les attributs de chacune
des entités sous la forme d'un tableau de
Graphics
contenu dans un
FeatureSet, auquel on accède grâce à la méthode .then()
. Pour la suite,
nous travaillerons à l'intérieur du .then()
jusqu'à la fin
du tutoriel.arcticExtentLayer.queryFeatures().then((results) => { //le reste du code ira ici });
Chaque objet est ensuite accessible en parourant
results.features
grâce à la méthode .map().
Nous
allons retourner un
Graphic
pour chacune des entités de la couche, et les stocker dans un tableau que
nous appelons features
. Pour construire les Graphics, nous
renseignons les propriétés
geometry
et
attributes
en nous servant des informations renvoyées par la query, et nous définissons
à la main la
symbologie, ici un polygone bleu clair avec un contour bleu vif.
const features = results.features.map((feature) => {
return new Graphic({
geometry: feature.geometry,
attributes: feature.attributes,
symbol: {
type: "simple-fill",
color: [203, 242, 242, 0.3],
outline: { color: [133, 242, 242], width: 1 }
}
});
});
Nous allons enfin créer une fonction qui nous permet d'afficher un à un les
graphiques créés ci-dessus. Pour cela, nous initialisons une variable
index
à 0 qui nous permettra d'appeler un à un les graphiques
stockés dans le tableau features
. Nous créons ensuite la
fonction displayFeature()
. Celle-ci commence par
retirer
tous les graphiques potentiellement présents à l'écran pour s'assurer que
nous affichons les étendues de l'Arctique une par une. Nous appelons ensuite
le graphique correspondant à l'index actuellement parcouru (celui-ci
s'incrémente de 1 à chaque passage dans la fonction afin de parcourir tous
les graphiques un par un), et nous ajoutons ce graphique à la carte.
Afin d'indiquer à l'utilisateur de quand date l'étendue actuellement
affichée à l'écran, la fonction récupère également les informations de mois
et d'année contenues dans les attributs du graphique et les injecte dans le
HTML. Enfin, afin de souligner les changements d'années, les années paires
et impaires sont affichées dans des couleurs différentes.
let index = 0;
function displayFeature() {
graphicsLayer.removeAll(); // Supprime les graphiques précédents
const currentFeature = features[index]; // Récupère la fonctionnalité actuelle
graphicsLayer.add(currentFeature); // Ajoute le graphique à la couche
// Extraction et affichage des informations temporelles
const month = currentFeature.attributes["Rec_Month"];
const year = currentFeature.attributes["Rec_Year"];
dateDisplay.innerHTML = `${month}/${year}`;
// Modification de la couleur de la date en fonction de l'année (paire ou impaire)
if (year % 2 === 0) {
dateDisplay.style.color = "rgb(108, 166, 166)"; // Années paires
} else {
dateDisplay.style.color = "rgb(150, 212, 212)"; // Années impaires
}
// Incrémentation de l'index pour passer à la fonctionnalité suivante
index = (index + 1) % features.length;
}
Afin, nous appelons la fonction tous les 100 millisecondes
setInterval()
. Cela crée une animation en affichant une
fonctionnalité à la fois dans graphicsLayer
, donnant
l’impression de visualiser les données géospatiales de manière continue.
setInterval(displayFeature, 100);
Voici le code JS complet que vous devriez obtenir :
require([
"esri/WebMap",
"esri/views/MapView",
"esri/layers/FeatureLayer",
"esri/layers/GraphicsLayer",
"esri/Graphic"
], function (WebMap, MapView, FeatureLayer, GraphicsLayer, Graphic) {
const map = new WebMap({
portalItem: {
id: "360827c4ab4f4aad85b771bd453a7254"
}
});
const view = new MapView({
container: "viewDiv",
map: map,
center: [2277430.5171051705, 2076536.5900727776],
zoom: 4,
constraints: {
maxZoom: 4,
minZoom: 4
}
});
view.ui.remove(["zoom"]);
map.when(() => {
const arcticExtentLayer = map.layers.items[2];
arcticExtentLayer.visible = false;
const graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
arcticExtentLayer.queryFeatures().then((results) => {
const features = results.features.map((feature) => {
return new Graphic({
geometry: feature.geometry,
attributes: feature.attributes,
symbol: {
type: "simple-fill",
color: [203, 242, 242, 0.3],
outline: { color: [133, 242, 242], width: 1 }
}
});
});
let index = 0;
let previousYear = null;
function displayFeature() {
graphicsLayer.removeAll();
const currentFeature = features[index];
graphicsLayer.add(currentFeature);
// Récupérez les informations de date et mettez à jour l'affichage
const month = currentFeature.attributes["Rec_Month"];
const year = currentFeature.attributes["Rec_Year"];
dateDisplay.innerHTML = `${month}/${year}`;
if (year % 2 === 0) {
dateDisplay.style.color = "rgb(108, 166, 166)"; // Années paires en bleu foncé
} else {
dateDisplay.style.color = "rgb(150, 212, 212)"; // Années impaires en bleu clair
}
index = (index + 1) % features.length;
}
setInterval(displayFeature, 100); // Mise à jour toutes les 100 ms
});
});
});
La partie cartographique est terminée ! De mon côté, j'ai enrichi
l'application grâce à un loader en utilisant des éléments de Calcite, comme
nous l'avions vu dans
ce tutoriel. J'ai également rajouté un titre, des crédits et une fenêtre informative
quand l'utilisateur clique sur "En savoir +" en ajoutant un peu de HTML et
de CSS. Nous verrons cela en détails d'ici la fin de l'année dans un
tutoriel consacré aux composants. En attendant, n'hésitez à nouveau pas à
consulter le code complet
sur Github
ou le
sur Codepen afin d'essayer d'en faire de même !
Si cet article vous a plus, n'hésitez pas à retrouver les tutoriels de
mes deux autres créations cartographiques pour ce challenge : la carte d'élévation de la France en hexagones, et une carte d'isohypses pour représenter les concentrations des photos que j'ai prises dans ma ville.
Aucun commentaire:
Enregistrer un commentaire