Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
JavaScript

TP 21 : Afficher la météo de sa position (géolocalisation)

TP du chapitre 21 : réalisez une application web qui localise l'utilisateur et affiche la météo actuelle et les prévisions sur 7 jours grâce à l'API gratuite Open-Meteo. Géolocalisation avec promesse ...

0 chapitres 0 QCM Accès gratuit

À propos de ce cours

TP du chapitre 21 : réalisez une application web qui localise l'utilisateur et affiche la météo actuelle et les prévisions sur 7 jours grâce à l'API gratuite Open-Meteo. Géolocalisation avec promesse et gestion des refus, fetch et JSON, affichage avec template, recherche par ville, cache dans le localStorage, puis bouton Copier (Clipboard), alerte pluie (Notifications), animations (Intersection Observer) et adresse partageable (History). Avec recette, publication sur GitHub Pages et barème.

Présentation du TP
  • Chapitre : 21 – Les API du navigateur.
  • Durée conseillée : 5 heures.
  • Prérequis : les 4 leçons du chapitre 21, fetch et async/await (chapitre 19), le localStorage (chapitre 20), VS Code avec Live Server, un compte GitHub.
  • Sujet : réaliser une application web « Météo de ma position ». En un clic, elle localise l'utilisateur, interroge un service météo en ligne et affiche le temps actuel et les prévisions sur 7 jours. Si l'utilisateur refuse la localisation, il peut chercher une ville. L'application utilise aussi les autres API du chapitre : copier la météo, être alerté de la pluie, animations au défilement et adresse partageable.

TP 21 : application Météo de ma position avec la météo actuelle et les prévisions sur 7 jours

Le service météo : nous utilisons Open-Meteo (open-meteo.com), un service gratuit pour un usage non commercial, sans inscription ni clé, qui accepte les requêtes venant d'une page web. Ses conditions demandent de citer la source : gardez le lien « Données météo : Open-Meteo.com » en bas de page.

Objectifs :

  • Obtenir la position de l'utilisateur, gérer le refus et les erreurs.
  • Interroger deux API REST avec fetch et exploiter leurs réponses JSON.
  • Construire l'affichage avec le DOM de façon sûre (<template>, textContent).
  • Mettre en œuvre les API Clipboard, Notifications, Intersection Observer et History.
  • Organiser le code en modules ES.
Règle du TP : un commit à la fin de chaque partie (au minimum 8 commits), avec un message clair, et le site publié sur GitHub Pages à la fin.
Partie 1 Préparer le projet et le dépôt GitHub
  1. Créez sur GitHub un dépôt public tp21-meteo-geolocalisation avec un README.md, clonez-le et ouvrez-le dans VS Code.
  2. Créez l'arborescence suivante :
tp21-meteo-geolocalisation/
├── index.html
├── css/
│   └── style.css
└── js/
    ├── app.js        ← programme principal (événements)
    ├── geo.js        ← géolocalisation
    ├── meteo.js      ← appels aux API Open-Meteo
    ├── affichage.js  ← tout ce qui touche au DOM
    └── cache.js      ← cache dans le localStorage
  1. Copiez la page index.html fournie. Elle utilise Bootstrap 5.3 et contient déjà toutes les zones : boutons, formulaire, message, indicateur de chargement, carte de la météo actuelle et un <template> pour une carte de prévision.
<!doctype html>
<html lang="fr">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Météo de ma position</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.9/dist/css/bootstrap.min.css"
      rel="stylesheet"
    />
    <link href="css/style.css" rel="stylesheet" />
    <script type="module" src="js/app.js"></script>
  </head>
  <body class="bg-body-tertiary">
    <main class="container py-4" style="max-width: 760px">
      <h1 class="h3 mb-4 text-center">🌍 Météo de ma position</h1>

      <div class="d-flex flex-wrap gap-2 mb-3">
        <button id="btn-position" class="btn btn-primary" type="button">
          📍 Utiliser ma position
        </button>
        <form id="form-ville" class="d-flex gap-2 flex-grow-1" role="search">
          <input
            id="champ-ville"
            class="form-control"
            type="search"
            placeholder="ou une ville : Tunis, Sfax, Paris…"
            aria-label="Nom de la ville"
            required
            minlength="2"
          />
          <button class="btn btn-outline-primary" type="submit">Rechercher</button>
        </form>
      </div>

      <div id="message" class="alert d-none" role="alert"></div>

      <div id="chargement" class="text-center my-5 d-none">
        <div class="spinner-border text-primary" role="status"></div>
        <p class="mt-2 mb-0" id="chargement-texte">Chargement…</p>
      </div>

      <section id="resultat" class="d-none">
        <div class="card shadow-sm mb-3 carte-actuelle">
          <div class="card-body">
            <div class="d-flex justify-content-between align-items-start">
              <div>
                <h2 id="lieu" class="h5 mb-1"></h2>
                <p id="maj" class="text-body-secondary small mb-0"></p>
              </div>
              <span id="icone" class="icone-actuelle" aria-hidden="true"></span>
            </div>
            <p class="display-4 fw-bold my-2"><span id="temperature"></span> °C</p>
            <p id="description" class="lead mb-3"></p>
            <div class="row text-center g-2">
              <div class="col-4">
                <div class="mesure">Ressenti<br /><b id="ressenti"></b></div>
              </div>
              <div class="col-4">
                <div class="mesure">Humidité<br /><b id="humidite"></b></div>
              </div>
              <div class="col-4">
                <div class="mesure">Vent<br /><b id="vent"></b></div>
              </div>
            </div>
          </div>
          <div class="card-footer d-flex flex-wrap gap-2">
            <button
              id="btn-copier"
              class="btn btn-sm btn-outline-secondary"
              type="button"
            >
              📋 Copier
            </button>
            <button
              id="btn-alerte"
              class="btn btn-sm btn-outline-secondary"
              type="button"
            >
              🔔 M'alerter s'il pleut
            </button>
          </div>
        </div>

        <h2 class="h5 mt-4 mb-3">Prévisions sur 7 jours</h2>
        <div id="previsions" class="row row-cols-2 row-cols-sm-4 g-2"></div>
      </section>

      <p class="text-center small text-body-secondary mt-5">
        Données météo :
        <a href="https://open-meteo.com/" target="_blank" rel="noopener"
          >Open-Meteo.com</a
        >
      </p>
    </main>

    <template id="modele-jour">
      <div class="col">
        <div class="card text-center h-100 jour">
          <div class="card-body p-2">
            <div class="fw-semibold nom-jour"></div>
            <div class="icone-jour" aria-hidden="true"></div>
            <div class="small texte-jour"></div>
            <div><b class="max"></b> / <span class="min text-body-secondary"></span></div>
          </div>
        </div>
      </div>
    </template>
  </body>
</html>
  1. Copiez css/style.css (les classes .jour et .visible serviront à la partie 7) :
.icone-actuelle {
  font-size: 4rem;
  line-height: 1;
}

.icone-jour {
  font-size: 2rem;
}

.mesure {
  background: var(--bs-tertiary-bg);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

/* Apparition des cartes de prévision (Intersection Observer) */
.jour {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 0.5s ease,
    transform 0.5s ease;
}

.jour.visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .jour {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
  1. Créez les cinq fichiers JavaScript, vides pour l'instant. Dans app.js, écrivez console.log("TP 21 prêt"); et lancez la page avec Live Server. Vérifiez le message dans la console.
Pourquoi Live Server ? Le script est un module (type="module") : il ne fonctionne pas en ouvrant le fichier directement (file://). De plus, la géolocalisation et le presse-papiers exigent un contexte sécurisé : http://127.0.0.1:5500 en est un.

Commit : git add . puis git commit -m "Partie 1 : structure du projet" et git push.

Partie 2 Obtenir la position de l'utilisateur

Dans js/geo.js:

  1. Écrivez et exportez la fonction obtenirPosition(options) qui enveloppe navigator.geolocation.getCurrentPosition() dans une promesse (voir la leçon sur la géolocalisation). Si l'API n'existe pas, la promesse est rejetée avec une erreur claire.
  2. Options par défaut : enableHighAccuracy: false (une météo n'a pas besoin d'une précision au mètre), timeout: 10000, maximumAge: 300000 (5 minutes). Les options passées en paramètre doivent pouvoir les remplacer : utilisez la syntaxe de décomposition { ...defauts, ...options }.
  3. Écrivez et exportez messageErreurGeo(erreur), qui renvoie un message en français pour les codes 1, 2 et 3. Pour le refus (code 1), le message invite à rechercher une ville.

Dans js/app.js, importez ces fonctions et, au clic sur « Utiliser ma position », affichez provisoirement les coordonnées dans la console ou le message d'erreur dans la zone #message.

Tests : avec DevTools → ⋮ → More tools → Sensors, simulez Tunis, Sfax, puis Location unavailable. Refusez ensuite l'autorisation (icône à gauche de l'adresse) et vérifiez le message.

Commit : "Partie 2 : géolocalisation".

Partie 3 Interroger l'API météo

3.1 – Découvrir l'API dans le navigateur. Ouvrez cette adresse dans un nouvel onglet (coordonnées de Tunis) et observez le JSON :

https://api.open-meteo.com/v1/forecast?latitude=36.8065&longitude=10.1815&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m&daily=weather_code,temperature_2m_max,temperature_2m_min&timezone=auto&forecast_days=7
  • current contient la météo actuelle : time, temperature_2m (°C), relative_humidity_2m (%), apparent_temperature (ressenti), weather_code, wind_speed_10m (km/h).
  • daily contient des tableaux parallèles: daily.time[2], daily.weather_code[2] et daily.temperature_2m_max[2] concernent le même jour.
  • timezone=auto donne les heures et les jours dans le fuseau horaire du lieu.
  • Modifiez la latitude en 999: quelle réponse obtenez-vous (code HTTP et contenu) ?

3.2 – Le module js/meteo.js.

  1. Écrivez construireUrlMeteo(latitude, longitude) qui construit l'adresse ci-dessus avec URLSearchParams. Arrondissez les coordonnées à 4 décimales (toFixed(4)).
  2. Écrivez une fonction interne lireJson(url): elle appelle fetch, lève une erreur "Le service météo a répondu 400." si reponse.ok est faux, sinon renvoie reponse.json().
  3. Exportez chargerMeteo(latitude, longitude).
  4. Les codes météo suivent la norme WMO de l'Organisation météorologique mondiale. Copiez cette table et écrivez decrireCode(code) qui renvoie { texte, icone }, avec « Temps inconnu » et « ❔ » pour un code absent (opérateur ??) :
const CODES = {
  0: ["Ciel dégagé", "☀️"],
  1: ["Plutôt dégagé", "🌤️"],
  2: ["Partiellement nuageux", "⛅"],
  3: ["Couvert", "☁️"],
  45: ["Brouillard", "🌫️"],
  48: ["Brouillard givrant", "🌫️"],
  51: ["Bruine légère", "🌦️"],
  53: ["Bruine", "🌦️"],
  55: ["Bruine dense", "🌧️"],
  56: ["Bruine verglaçante", "🌧️"],
  57: ["Bruine verglaçante dense", "🌧️"],
  61: ["Pluie faible", "🌦️"],
  63: ["Pluie modérée", "🌧️"],
  65: ["Pluie forte", "🌧️"],
  66: ["Pluie verglaçante", "🌧️"],
  67: ["Pluie verglaçante forte", "🌧️"],
  71: ["Neige faible", "🌨️"],
  73: ["Neige modérée", "🌨️"],
  75: ["Neige forte", "❄️"],
  77: ["Grains de neige", "🌨️"],
  80: ["Averses faibles", "🌦️"],
  81: ["Averses", "🌧️"],
  82: ["Averses violentes", "⛈️"],
  85: ["Averses de neige", "🌨️"],
  86: ["Fortes averses de neige", "❄️"],
  95: ["Orage", "⛈️"],
  96: ["Orage avec grêle", "⛈️"],
  99: ["Orage avec forte grêle", "⛈️"],
};
  1. Écrivez estPluvieux(code): vrai pour la bruine et la pluie (51 à 67), les averses de pluie (80 à 82) et les orages (95 et plus).

Testez dans app.js: après la géolocalisation, console.log(await chargerMeteo(...)). Dans l'onglet Réseau, retrouvez la requête et son statut.

Commit : "Partie 3 : appel à l'API Open-Meteo".

Partie 4 Afficher la météo

Dans js/affichage.js, écrivez et exportez :

  1. afficherChargement(texte) et masquerChargement(): affichent ou masquent #chargement (classe d-none) avec le texte donné.
  2. afficherMessage(texte, type): affiche #message avec la classe alert-danger, alert-warning ou alert-info.
  3. afficherMeteo(meteo, lieu): remplit la carte (température arrondie, description, icône, ressenti, humidité, vent, heure de mise à jour) puis affiche #resultat.
  4. Une fonction interne afficherPrevisions(daily): vide #previsions avec replaceChildren(), puis, pour chaque jour, clone le template (modele.content.cloneNode(true)), le remplit et l'ajoute. Le premier jour s'appelle « Aujourd'hui », les autres « ven. 13 », « sam. 14 »…
  • Utilisez Intl.DateTimeFormat("fr-FR", { weekday: "short", day: "numeric" }) pour les jours et { hour: "2-digit", minute: "2-digit" } pour l'heure.
  • Piège des fuseaux horaires : new Date("2026-11-13") est interprété à minuit UTC, ce qui peut donner la veille selon le pays. Lisez la date à midi : new Date(`${date}T12:00`).
  • Sécurité : toutes les données venant d'une API sont insérées avec textContent, jamais avec innerHTML.

Dans app.js, écrivez montrer(latitude, longitude, lieu) qui affiche le chargement, appelle l'API, affiche la météo, gère les erreurs dans un try / catch et masque le chargement dans un finally. Le lieu affiché est « Ma position (36.81, 10.18) ».

Testez aussi une panne : DevTools → Réseau → Offline, puis cliquez sur le bouton. Un message clair doit s'afficher, sans erreur rouge dans la console.

Commit : "Partie 4 : affichage de la météo et des prévisions".

Partie 5 Solution de repli : chercher une ville

L'utilisateur qui refuse la géolocalisation doit pouvoir utiliser l'application. Open-Meteo propose une API de géocodage qui transforme un nom de ville en coordonnées :

https://geocoding-api.open-meteo.com/v1/search?name=Sfax&count=1&language=fr&format=json
  1. Ouvrez cette adresse, puis essayez avec name=Zzzz. Que contient la réponse quand aucune ville n'est trouvée ?
  2. Dans meteo.js, exportez rechercherVille(nom) qui renvoie { nom, pays, latitude, longitude } du premier résultat, ou lève l'erreur Aucune ville trouvée pour « Zzzz ».
  3. Dans app.js, à l'envoi du formulaire (submit + preventDefault()), recherchez la ville puis affichez sa météo avec le lieu « Sfax, Tunisie ».
  4. En cas de refus de la géolocalisation, placez le curseur dans le champ de recherche (focus()).

Commit : "Partie 5 : recherche par ville".

Partie 6 Mettre la météo en cache

Inutile de rappeler le service à chaque clic : la météo change peu en quelques minutes. Dans js/cache.js (révision du chapitre 20) :

  1. cleCache(latitude, longitude) renvoie par exemple "meteo:36.81,10.18" (2 décimales : deux positions voisines partagent le même cache).
  2. ecrireCache(cle, donnees) enregistre { date: Date.now(), donnees } en JSON.
  3. lireCache(cle) renvoie les données si elles ont moins de 10 minutes, sinon null. Un contenu illisible (JSON invalide) ne doit pas faire planter l'application (try / catch).
  4. Dans app.js, la fonction meteoAvecCache() lit le cache avant d'appeler l'API.

Test : cliquez deux fois sur « Utiliser ma position » et vérifiez dans l'onglet Réseau qu'une seule requête part vers api.open-meteo.com. Observez la clé dans Application → Local Storage.

Commit : "Partie 6 : cache de 10 minutes".

Partie 7 Les autres API du chapitre

7.1 – Copier la météo (Clipboard). Le bouton « 📋 Copier » copie un résumé comme Météo – Sfax, Tunisie : 19 °C, partiellement nuageux, vent 14 km/h. (fonction resumeTexte() dans affichage.js). Le bouton affiche « ✅ Copié ! » pendant 2 secondes, ou « ❌ Copie impossible ».

7.2 – Alerte pluie (Notifications). Le bouton « 🔔 M'alerter s'il pleut » demande la permission si nécessaire, puis affiche une notification (avec tag: "alerte-pluie") :

  • « De la pluie est prévue aujourd'hui : prenez un parapluie ! » si le jour 0 est pluvieux ;
  • « De la pluie est prévue dans 2 jour(s). » pour le premier jour pluvieux suivant (findIndex(estPluvieux)) ;
  • « Pas de pluie prévue dans les 7 prochains jours. » sinon.

Si la permission est refusée ou l'API absente, un message alert-info l'explique dans la page.

7.3 – Apparition des prévisions (Intersection Observer). Créez un observateur (threshold: 0.2) qui ajoute la classe visible aux cartes .jour quand elles entrent dans l'écran, puis arrête de les observer. Ajoutez un léger décalage pour un effet « en cascade » : carte.style.transitionDelay = `${i * 80}ms`. Testez avec une fenêtre étroite pour que les cartes soient sous l'écran.

7.4 – Adresse partageable (History).

  1. Après une recherche réussie, l'adresse devient index.html?ville=Sfax avec history.pushState(), et le titre de l'onglet « Météo – Sfax ».
  2. Après une géolocalisation, enregistrez l'état { type: "position" } avec une adresse sans paramètre.
  3. Au chargement de la page, si l'adresse contient ?ville=… (URLSearchParams), la météo de cette ville s'affiche directement : on peut donc envoyer le lien à un ami.
  4. Gérez popstate: les boutons Précédent / Suivant réaffichent la ville ou la position correspondante, sans ajouter de nouvelle entrée à l'historique.

Commit : "Partie 7 : Clipboard, Notifications, Intersection Observer et History".

Partie 8 Recette, publication et README

8.1 – Recette. Recopiez ce tableau dans le README.md et complétez la colonne « Résultat » :

N°ScénarioRésultat attenduRésultat
R1Clic sur « Utiliser ma position », permission accordée (Sensors : Tunis)Météo de « Ma position (36.81, 10.18) » et 7 cartes de prévision
R2Permission refuséeMessage jaune, curseur dans le champ ville
R3Sensors : Location unavailableMessage « position introuvable »
R4Recherche « sfax »« Sfax, Tunisie » et adresse ?ville=Sfax
R5Recherche « Zzzz »« Aucune ville trouvée pour « Zzzz ». »
R6Réseau Offline puis rechercheMessage d'erreur, pas d'erreur dans la console
R7Deux clics sur « Utiliser ma position »Une seule requête météo (cache)
R8Bouton Copier puis Ctrl + V dans un éditeurLe résumé de la météo est collé
R9Bouton « M'alerter s'il pleut »Notification système correcte
R10Recherche Sfax, puis Paris, puis PrécédentRetour à Sfax, adresse ?ville=Sfax
R11Ouverture directe de index.html?ville=ParisMétéo de Paris affichée immédiatement
R12Ville saisie : <img src=x onerror=alert(1)>Aucune fenêtre d'alerte (pas de XSS)

8.2 – Publication. Publiez le site avec GitHub Pages (Settings → Pages → Deploy from a branch → main / root). Le site est servi en HTTPS: testez la géolocalisation sur votre téléphone avec le lien obtenu.

8.3 – README. Présentation, lien du site, capture d'écran, API utilisées (Geolocation, Fetch, Clipboard, Notifications, Intersection Observer, History, Web Storage), tableau de recette, mention de la source des données (Open-Meteo).

Commit : "Partie 8 : recette, README et publication".

Bonus Pour aller plus loin
  • Heure par heure : ajoutez le paramètre hourly=temperature_2m,weather_code et affichez les 24 prochaines heures dans une bande qui défile horizontalement.
  • Jour et nuit : ajoutez is_day dans current et affichez une lune 🌙 à la place du soleil la nuit, avec le thème sombre de Bootstrap (data-bs-theme="dark").
  • Villes favorites : un bouton ⭐ enregistre la ville dans le localStorage; les favoris s'affichent sous forme de boutons.
  • Suivi en direct : une case « Suivre ma position » utilise watchPosition() et ne recharge la météo que si l'on s'est déplacé de plus de 5 km (fonction distance() de la leçon).
  • Permission : avec navigator.permissions.query({ name: "geolocation" }), masquez le bouton « Utiliser ma position » si l'accès a déjà été refusé.
Livrables et barème
  • Le lien du dépôt GitHub (au minimum 8 commits) et le lien du site publié.
  • Le README.md avec le tableau de recette complété.
CritèrePoints
Géolocalisation : promesse, options, les trois erreurs gérées (partie 2)3
Appel à l'API météo, vérification de reponse.ok, traduction des codes (partie 3)3
Affichage de la météo et des prévisions, template, dates correctes, chargement et erreurs (partie 4)3
Recherche par ville et cas « aucune ville » (partie 5)2
Cache de 10 minutes dans le localStorage (partie 6)2
Clipboard et Notifications avec gestion des refus (parties 7.1 et 7.2)2
Intersection Observer et History : adresse partageable, Précédent / Suivant (parties 7.3 et 7.4)2
Qualité : modules, aucun innerHTML avec des données, recette, README, commits réguliers, site publié3
Total20
Bonus+2
Conseil : avancez partie par partie et testez à chaque étape avec les DevTools (Sensors, Réseau, Application). Une application qui gère proprement les refus et les pannes vaut mieux qu'une application qui ne fonctionne que dans le cas idéal.