11515 sujets

JavaScript, DOM et API Web HTML5

Bonsoir. Juste un petit partage :

J'ai constaté que certaines de mes cartes Open Street Map France (le "France" est important ici) ne fonctionnaient plus alors que pas de problème auparavant :

Voici le paramètre type :
https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png

Une page d'exemple : Maps

Alors je me suis dit : l'API a changé ? Il faut une clef ? Ça fait au moins 48 h que les tuiles sont indisponibles, ça ne peut donc pas être un problème de serveur...

Et bien si : layer.openstreetmap

Je vais devoir modifier mon script. Il va falloir que je teste la disponibilité des tuiles et trouver une solution de replis le cas échéant.

Les aléas du libre...
Modifié par Olivier C (11 Sep 2024 - 22:19)
Modérateur
Salut Olivier,

De suite, j'ai regardé en console. Il y a une série d'erreurs (qui ne vient pas de toi). Il ne charge pas certaines images. Ça renvoie une 502.

exemple : https://b.tile.openstreetmap.fr/osmfr/10/523/366.png

Je ne connais pas vraiment leaflet. Cependant, peut-être que tu peux mettre en place un try/catch. Dans l'exception, tu renvoies un message d'erreur ou tu essaies de résoudre ce problème (pas gagné)
Modifié par niuxe (11 Sep 2024 - 21:40)
Bonsoir Niuxe,

Je viens de coder la solution avec ma proposition énoncée à la fin de mon premier post (la solution de repli sur un serveur de tuiles par défaut). Ça fonctionne. Je donne tout le code si ça peut intéresser du monde :
const maps = () => {
  const titleServerDefault = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
  const svgIcon =
    '<svg class="marker-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 14C146 14 57 102 57 211c0 172 199 295 199 295s199-120 199-295c0-109-89-197-199-197zm0 281a94 94 0 1 1 0-187 94 94 0 0 1 0 187z"/><path d="M256 14v94a94 94 0 0 1 0 187v211s199-120 199-295c0-109-89-197-199-197z"/></svg>'

  document.querySelectorAll('.map').forEach(item => {
    const map = L.map(item.id),
      el = document.getElementById(item.id),
      P = JSON.parse(el.dataset.places),
      markers = []

    // Sélection du serveur de tuiles
    const tileServerUrl = el.dataset.tileserver || titleServerDefault

    const tileLayer = L.tileLayer(tileServerUrl, {
      minZoom: el.dataset.minzoom || 2,
      maxZoom: el.dataset.maxzoom || 18,
      zoom: el.dataset.zoom || el.dataset.maxzoom,
      attribution: el.dataset.attribution || '',
    }).addTo(map)

    // Gestion des erreurs de tuiles : bascule vers le serveur par défaut en cas d'échec
    tileLayer.on('tileerror', () => {
      console.error(`Erreur de chargement des tuiles sur ${tileServerUrl}. Utilisation du serveur par défaut.`)
      L.tileLayer(titleServerDefault, {
        minZoom: el.dataset.minzoom || 2,
        maxZoom: el.dataset.maxzoom || 18,
        zoom: el.dataset.zoom || el.dataset.maxzoom,
        attribution: el.dataset.attribution || '',
      }).addTo(map)
    })

    const divIcon = L.divIcon({
      className: 'leaflet-data-marker',
      html: svgIcon,
      iconAnchor: [20, 40],
      iconSize: [40, 40],
      popupAnchor: [0, -60],
    })

    for (let i = 0; i < P.length; i++) {
      const marker = L.marker(P[i][1], { icon: divIcon })
      if (P[i][0]) marker.bindPopup(P[i][0])
      marker.addTo(map)
      markers.push(marker)
    }

    const group = new L.featureGroup(markers)
    map.fitBounds(group.getBounds())
    el.dataset.zoom && map.setZoom(el.dataset.zoom)
  })
}

/**
 * Teste la présence de la bibliothèque Leaflet.js chargée en asynchrone et, si oui, exécution du script de configuration.
 * @param {object} L - Variable globale produite par Leaflet, permet de tester l'initialisation de la bibliothèque.
 */
window.addEventListener('load', () => {
  if (typeof L !== 'undefined') maps()

  document.addEventListener('readystatechange', () => {
    if (document.readyState === 'complete' && typeof L !== 'undefined') maps()
  })
})
Modérateur
Olivier C a écrit :


window.addEventListener('load', () => {
  if (typeof L !== 'undefined') maps()

  document.addEventListener('readystatechange', () => {
    if (document.readyState === 'complete' && typeof L !== 'undefined') maps()
  })
})



C'est à cet endroit que tu devrais placer un try/catch. Si L est undefined, tu renvoies une exception. Tu peux par exemple, informer l'utilisateur que la carte est indisponible. Smiley smile
Je tenterai ta solution en rentrant chez moi ce soir. En effet, ma solution marche très bien sur desktop, mais j'ai encore des soucis sur mobile. Je ne sais pas pourquoi j'ai un souci sur ces derniers (autant Chrome que Firefox), du coup ça ne coûte rien d'essayer.
Ah ! Les serveurs d'OSM France se sont remis à fonctionner. Pas grave, je vais quand même simuler une erreur avec une fausse URL d'appel : c'est trop intéressant comme sujet à traiter.

Et même, les serveurs peuvent répondre mais en même temps être très long à répondre. Du coup je crois que je vais proposer des tuiles par défaut, qui seront remplacées dès que la disponibilité du serveur ciblé est OK.

Ça me plaît bien comme challenge. Je vais essayer de faire tout ça cette nuit.
niuxe a écrit :
C'est à cet endroit que tu devrais placer un try/catch. Si L est undefined, tu renvoies une exception. Tu peux par exemple, informer l'utilisateur que la carte est indisponible. Smiley smile

J'avais mal lu. Alors la variable "L" fais partie de la bibliothèque Leaflet, le test sur cette variable permet de vérifier qu'elle est bien chargée avant mon script. Moi ce que je veux c'est vérifier la disponibilité des tuiles d'un serveur cible, mon script plus haut le fait en partie mais apparemment il ne couvre pas tous les cas de figure. Je vais essayer d'y remédier.
Petit retour d'expérience :

Alors, dans un premier temps j'ai tenté de tester le HEAD :
/**
 * Fonction qui vérifie si le serveur de tuiles est disponible.
 * @param {string} url - URL du serveur de tuiles.
 * @returns {Promise<boolean>} - Résout true si le serveur est disponible, false sinon.
 */
const checkTileServer = async url => {
  try {
    const response = await fetch(url, { method: 'HEAD' })
    return response.ok // Si la réponse est ok (code 2xx), le serveur est disponible
  } catch (error) {
    return false // Si une erreur survient, le serveur est considéré comme indisponible
  }
}

Mais nop : ceci s'est révélé inefficace, soit par ce que le serveur ne supporte pas la requête, soit qu'il ne donne pas la réponse demandée. Du coup plan B, tester une seule tuile, et là, bingo c'est OK :
/**
 * Fonction qui vérifie si une tuile du serveur de tuiles est disponible.
 * @param {string} url - URL du serveur de tuiles.
 * @returns {Promise<boolean>} - Résout true si la tuile est disponible, false sinon.
 */
const checkTileServer = async url => {
  const tileUrl = url.replace('{z}', '0').replace('{x}', '0').replace('{y}', '0') // Test de la tuile (0,0,0)
  try {
    const response = await fetch(tileUrl, { method: 'GET' })
    return response.ok // Si la réponse est ok (code 2xx), le serveur est disponible
  } catch (error) {
    return false // Si une erreur survient, le serveur est considéré comme indisponible
  }
}

C'est moins sexy que la solution précédente (entendez moins optimisé), mais ça fait le taf.
EDIT : non, ça ne fait pas le taf, il y a des effets de bord, du coup je reviens à ma première solution pour l'instant.

PS : au départ je voulais faire un sujet "bar du forum", mais comme ça a viré rapidement sur un aspect technique avant même d'amorcer une conversation "sur les aléas du libre" (c'est ça de se comporter en autiste aussi), si un modo veut passer le sujet en "JavaScript"...
Modifié par Olivier C (13 Sep 2024 - 15:42)
Modérateur
Hello Olivier,

J'ai déplacé ton sujet dans Javascript.
Je vois 3 petites choses :
- tu fais un return false et suivant ce que j'ai lu précédemment, tu n'informes pas l'utilisateur.
- tu return reponse.ok (soit un bool de mémoire). Or, il me semble que tu devrais faire un return de response. Mais ta response te retourne quoi à l'origine ? Json, un text ? Il me parait pertinent que tu fasses plutôt un test (si response.ok et response.status === 200 => return response)
- Il me semble aussi que tu devrais faire un : return await response.
niuxe a écrit :
- tu fais un return false et suivant ce que j'ai lu précédemment, tu n'informes pas l'utilisateur.

Ça, effectivement, c'est une mauvaise pratique criante : si je dois revenir sur un bug lié à ce code plusieurs mois plus tard je serais dans l'incapacité de comprendre ce qui ce passe. J'avais lu pleins de trucs sur le sujet en plus... Je corrigerais.
niuxe a écrit :
- tu return reponse.ok (soit un bool de mémoire). Or, il me semble que tu devrais faire un return de response. Mais ta response te retourne quoi à l'origine ? Json, un text ? Il me parait pertinent que tu fasses plutôt un test (si response.ok et response.status === 200 => return response)

J'avais pas retenu l'idée au final, du coup je ne sais déjà plus ce que ça renvoie. Par curiosité je retournerai y jeter un œil. Effectivement, ça pourrait être intéressant.
niuxe a écrit :
- Il me semble aussi que tu devrais faire un : return await response.

Comme je le fais déjà sur la variable "response" à la ligne précédente il n'y a pas besoin normalement.

https://www.youtube.com/watch?v=WN1uHFfjc6I => Je me souviens de cette ref' !
Modifié par Olivier C (15 Sep 2024 - 09:33)
Je reposte donc ce soir, avec - encore - une nouvelle MAJ de mon code et quelques explications inline ; pour l'instant ça à l'air de faire l'affaire :
/**
 * Fonction qui vérifie la disponibilité d'une tuile sur un serveur de tuiles spécifique avec une stratégie de retry.
 * @param {string} urlTemplate - Modèle d'URL du serveur de tuiles avec des sous-domaines {s}.
 * @param {Array<string>} subdomains - Liste des sous-domaines à tester (e.g. ['a', 'b', 'c']).
 * @param {number} retries - Nombre de tentatives par sous-domaine.
 * @param {number} delay - Délai entre chaque tentative en millisecondes.
 * @returns {Promise<boolean>} - Résout true si la tuile est disponible, false sinon.
 */
const checkTileServerWithSubdomainsAndRetry = async (urlTemplate, subdomains = ['a', 'b', 'c'], retries = 3, delay = 1000) => {
  for (const subdomain of subdomains) {
    const tileUrl = urlTemplate.replace('{s}', subdomain).replace('{z}', '16').replace('{x}', '33440').replace('{y}', '23491') // Test d'une tuile existante

    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        const response = await fetch(tileUrl, { method: 'GET' })
        //console.log(response) => retournera un JSON, avec notamment l'état du serveur (200, etc) et l'URL d'une tuile à tester.
        if (response.ok) {
          console.log(`Map : chargement des tuiles.`)
          return true
        }
      } catch (error) {
        console.error(`Map : échec du chargement des tuiles ; une nouvelle tentative de chargement va débuter...`)
      }
      // Attendre avant de réessayer
      await new Promise(resolve => setTimeout(resolve, delay))
    }
  }
  console.error(`Map : serveur de tuiles indisponible.`)
  return false
}
Meilleure solution