« SliderBot » : différence entre les versions

De Wikipast
Aller à la navigation Aller à la recherche
Aucun résumé des modifications
Aucun résumé des modifications
 
(186 versions intermédiaires par 4 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
== Objectifs ==
Le but du SliderBot est d'afficher sur une carte interactive la position des personnes au cours du temps, à partir des biographies rédigées sur Wikipast.
 
Le SliderBot extrait tout d'abord les données de Wikipast, en parcourant les pages consacrées aux années. Pour chaque année, les informations parsées sont les mentions de villes et de personnes. Ces informations sont ensuite sauvegardées dans un fichier JSON.
Ce fichier est enfin utilisé pour afficher les lieux mentionnées et les personnes respectives sur la carte, pour une année spécifique, en utilisant un code JavaScript et l'API GoogleMaps.


* Crée une base de données en associant [Personne, Date, Lieu] via un scrapping de page.
En appliquant cette méthode pour chaque année existante dans la base de données de Wikipast, il est possible de créer une carte interactive permettant de naviguer à travers le temps et l'espace et ainsi visualiser la distribution d'événements sur le globe au fil du temps.
* Crée un ensemble de cartes affichant les positions de toutes les personnes recensées pour chaque date (discretisation par année ?).
* Création d’un slider dynamique, où l’annee souhaitée est choisie, et affiche la carte correspondante (JavaScript ou HTML)


== Description ==
== Objectifs ==


Le bot extrait les données nécessaires de Wikipast, en parcourant chaque page existante d'années à travers le temps. Les informations retenues sont d'abord les mentions de villes et deuxièmement les mentions de personnes respectives pour chaque année. Ce travail est fait par un code python, qui va sauvegarder l'information dans un tableaux.  
* Création d'une base de données en associant [Personne, Date, Lieu] via un scrapping de page.
Le tableaux va être utilisé pour afficher les villes qui ont été mentionné et les personnes respectives sur une carte, pour une année spécifique, en utilisant un code en JavaScript. Ceci va être appliquée pour chaque année existante dans la base de données de Wikipast pour créer une carte interactive dont on peut naviguer à travers le temps et l'espace et visualizer la distribution de personnes dans le globe.
* Création d'un ensemble de cartes affichant les positions de toutes les personnes recensées pour chaque date (discretisation par année).
* Création d’un slider dynamique permettant de chosir l'année et affichage de la carte correspondante (JavaScript / HTML).


== Fonctionnement ==
== Fonctionnement ==
=== Server ===


Node.js est une plateforme logicielle libre en JavaScript orientée vers les applications réseau qui doivent pouvoir monter en charge.
=== Extraction de données ===
Parmi les modules natifs de Node.js, on retrouve HTTP qui permet le développement de serveurs HTTP.
 
{| class="wikitable"
! scope="col" | Username
! scope="col" | Password
|-
| SliderBot
| SliderBot123
|-
|}


==== server.js ====
==== Récupération des lieux et personnes ====


Le serveur est initialisé a l'aide du module 'express' de Node.js, pour générer le cadre d'une application.
Le résultat du parsing, avec les services de BeautifulSoup, est une liste de tuples {année, lieu, personne}, issue des pages d'années générées par '''ChronoBot'''. Ceci est principalement fait par la fonction '''getYearCityAndName''':
La méthode app.post() indique au serveur qu'il est en attente d'une demande HTTP.
La méthode app.listen() définie le chemin sur lequel le serveur est prêt à l'écoute.
<pre>
<pre>
const express = require('express');
def getYearCityAndName(div):
const app = express();


app.post('/', function(req, res){
  tagDate = div[div.index("<a"):div.index("</a>")]
    console.log('Post request received: ');
  date = tagDate[tagDate.index(">") + 1:]
    res.writeHead(200, { 'Content-Type': 'text/plain' });
  year = date.split(".")[0]
    req.on('data', function (chunk) {
        var buffer = JSON.stringify(chunk);
        var data = getData(buffer);
        console.log('GOT DATA : '+data);     
    });
    res.end(JSON.stringify({"data":0}));
});


app.listen(3000, () => {
  if not year.isnumeric():
    console.log('Server listening on port 3000!');
      return
});
</pre>
 
==== communication.js ====


JSON (JavaScript Object Notation) est un format de données textuelles dérivé de la notation des objets du langage JavaScript. La fonction sendReq() envoie une demande au server pour recevoir l'information sous ce format.
  if div[div.index("</a>") + 7] == '-' or div[div.index("</a>") + 4] == '.' or div[div.index("</a>") + 7] == ' ':
      return
  else:
      skipDate = div[div.index("<a")+ 2:]
      tagCityStart = skipDate[skipDate.index("<a"):]
      tagCity = tagCityStart[:tagCityStart.index("</a>")]
      city = tagCity[tagCity.index(">") + 1:]
      skipCity = tagCityStart[tagCityStart.index("</a>")+6:tagCityStart.index("</li>")]


<pre>
      class MyHTMLParser(HTMLParser):
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
          data = []
var IP_ADDRESSE = "128.179.178.60";
          def handle_starttag(self, tag, attrs):
var PORT = "3000";
              if tag == 'a':
                  for attr in attrs:
                      if str(attr[0]) == 'title':
                          if isName(attr[1]):
                              self.data.append(attr[1])


function sendReq(){
      parser = MyHTMLParser()
    var request = new XMLHttpRequest();
      parser.feed(skipCity)
    request.open('POST', 'http://'+IP_ADDRESSE+':'+PORT, true);
       if len(parser.data) != 0:
    request.setRequestHeader("Content-type", "application/json");
          return (year, city, parser.data)
    request.onload = () => {
       if (request.status >= 200 && request.status < 400) {
        const res = JSON.parse(request.responseText);
        console.log(res);
      } else {
        console.log("Error on server side.")
      }
    };
    request.onerror = () => {
        console.log("Error on communication.");
    };
    request.send(JSON.stringify({"year":1996}));
 
sendReq();
</pre>
</pre>


=== Extraction de données ===
Pour obtenir une meilleure reconnaissance / sélection des personnes, au delà des tests regex effectués par '''getYearCityAndName''', la libraire '''NLTK''' est employée dans la fonction '''isName''' qui détermine si une chaîne de caractères correspond bien à un nom de personne.


BeautifulSoup est une bibliothèque de parsage (analyse syntaxique) en code Python pour le langage HTML/XML. Dans ce cas elle est utilisé pour extraire des données de Wikipast: villes et personnes mentionnées, dans les pages existantes concernant les années.
<pre>
Geopy est une bibliothèque Python, utilisée pour localizer les coordonnées d'endroits spécifiques dans le monde (dans ce cas des villes).
def isName(string):
  words = [y for y in string.split() if not y.isupper()]
  string_upd = remove_accents(" ".join(words))
  if string_upd.istitle():
      for chunk in ne_chunk(pos_tag(word_tokenize(remove_accents(string)))):
          if hasattr(chunk, 'label'):
              if chunk.label() == 'PERSON':
                  #print(' '.join(c[0] for c in chunk))
                  return True


==== maincode.py ====
  return False
</pre>


Le résultat du parsing est une liste de tuples, contenant des années, villes et personnes associés. Si la location sur une page Wikipast est décrite seulement par le pays, pour pouvoir l'afficher sur une carte, ça va être défini à sa capitale. Ceci est fait à l'aide d'un fichier 'countries.py' qui contiens une liste avec les pays dans le monde et leur capitale. La location est convertie en coordonnées, avec les services de geo-localization de Geopy. Finalement un fichier JSON, en prenant en paramètre des arrays en format [nom, longitude, latitude].
==== Localisation ====
Il convient ensuite de convertir ces tuples {lieu, personne} en {coordonnées, personne}. Cette étape nécessite la librairie Python '''Geopy'''.
===== Pays vs. Capitale =====
Si le lieu d'un événement sur une page Wikipast est décrite seulement par son pays, plutôt que de l'afficher au centre géographique du pays, l'événement sera plutôt affiché comme s'étant produit dans la capitale de ce pays. Ceci est fait à l'aide d'un fichier '''countries.py''' [https://github.com/EtienneBonvin/SliderBot/blob/master/bot/countries.py] qui contient la liste des pays du monde et leur capitale, par la fonction '''capitalIfCountry''':
<pre>
def capitalIfCountry(location):
"""
If location is a country, then it changes it to its capital
"""
  dataPays = next((item for item in countries if item["name"] == location), False)
  if(dataPays):
      return (dataPays['capital'])
  else:
      return location
</pre>


===== Conversion de coordonnées =====
Le lieu est convertie en coordonnées, avec les services de geo-localisation de Geopy, par la fonction '''finalNameCoordTuple''':
<pre>
<pre>
from urllib.request import urlopen
def finalNameCoordTuple(tuples):
from bs4 import BeautifulSoup
"""
import sys
Produces an array 'output' containing [Name, latitude, longitude]
import io
If location is a country, then it changes it to its capital
import json
"""
import time
  output = []
from countries import countries
  for tuple in tuples:
from geopy import *
      coord = geolocator.geocode(capitalIfCountry(tuple[1]))
from geopy.geocoders import *
      output.append([tuple[2], coord.latitude, coord.longitude])
from html.parser import HTMLParser
  return output
import unicodedata
</pre>
Ici, les différents éléments du tuple proviennent du parsing : tuple[1] correspond au nom du lieu et tuple[2] correspond à la personne.


def masterFunction(year):
=====Répulsion de lieux identiques=====
L'affichage correct de plusieurs événements se déroulant au même lieu se révèle problématique. En effet, geocode renvoie une position (latitude, longitude) précisément identique pour chaque ville. Lorsque deux événements se passent au même lieu, les points se confondent rigoureusement et l'API Google ne permet, logiquement, pas de les séparer (peu importe le niveau de zoom).


    """
Pour parer à cette éventualité, la fonction '''repulsePoints''' décale sur la carte de quelques centaines de mètres les événements (par un simple incrément arbitraire de longitude et latitude).
    Core function : creates a JSON file with [Name, lat, long]
<pre>
    for any year (argument)
def repulsePoints(tuples):
    """
        for i in range (len(tuples)):
            for j in range (len(tuples)):
                if (i != j and tuples[i][1] == tuples[j][1] and tuples[i][2] == tuples[j][2]):
                    tuples[j][1] = tuples[i][1] + 0.005
                    tuples[j][2] = tuples[i][2] + 0.005
        return tuples
</pre>
Cela étant dit, une telle méthode peut donner l'impression d'une précision artificielle sur les lieux (qu'il sera impossible d'éviter, les lieux sur Wikipast étant au mieux localisés par ville).


    #==========Initialisation===========
==== Fichier JSON ====
 
===== Ajout du lien Wikipast =====
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
La fonction '''addWikiLink''' ajoute aux tuples crées plus tôt le lien de la page WikiPast de la personne concernée.
    response=urlopen("http://wikipast.epfl.ch/wikipast/index.php/"+str(year))
<pre>
    page_source=response.read()
def addWikiLink(input_string):
    soup=BeautifulSoup(page_source,'html.parser')
     output_string=input_string
    stringSoup = str(soup)
     for i in range (len(input_string)):
    geolocator = Nominatim()
         output_string[i].append('http://wikipast.epfl.ch/wikipast/index.php/'+input_string[i][0].split(', ')[0].replace(" ", "_"))
 
    return output_string
    #=============String===============
</pre>
   
Dans le cas où plusieurs personnes sont concernées par un événement, le choix a été fait de lier la page Wiki de la première personne seulement.
    """check that every word starts with upperletter and max length of 20 chars"""
===== Création du JSON =====
 
Finalement le fichier JSON est généré, et en prenant en paramètres des arrays en format [nom, latitude, longitude, lien wiki] et l'année souhaitée (commune à toutes les fonctions du programme), produit un fichier de la forme (par exemple ici 1986.json, où l'on a gardé qu'un événement), qui est enregistré dans un dossier '''server/maps''' sur GitHub :
    def isName(string):
<pre>
        if len(string) < 20 and string.istitle():
[
            return True
     {
        return False
      "proprietes" : ["Enzo Ferrari",44.5255217,10.8663607, "http://wikipast.epfl.ch/wikipast/index.php/Enzo_Ferrari"]
 
     }
    def remove_accents(input_str):
]
        nfkd_form = unicodedata.normalize('NFKD', input_str)
</pre>
        return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])
La fonction est la suivante:
 
<pre>
     #=============Parsing===============
def createJson(inputData, year):
 
"""
     def getNthDiv(stringSoup, n):
Takes array [Name, lat, long] for each year
        divStart = stringSoup[stringSoup.index("mw-content-ltr") + len("mw-content-ltr"):]
Outputs in .json such that it can be plotted
        for i in range(n):
"""
            try:
    indent = '    '
                nextIndex = divStart.index("<li")
    indent2 = '      '
            except ValueError:
    maxLimit = len(inputData)
                return None
    with open(str(year)+'.json', 'w') as outfile:
            divStart = divStart[nextIndex + len("<li"):]
        outfile.write('['+'\n')
        return divStart
        for i in range (maxLimit):
 
            if i == (maxLimit-1):
    def getYearCityAndName(div):
                outfile.write(indent + '{'+'\n')
         tagDate = div[div.index("<a"):div.index("</a>")]
                outfile.write(indent2 + ' "proprietes" : '+'['+'"'+remove_accents(str(inputData[i][0]))+'"'+ ','+ str(inputData[i][1])+ ','+ str(inputData[i][2])+']'+'\n')
        date = tagDate[tagDate.index(">") + 1:]
                outfile.write(indent + '}'+'\n')
        year = date.split(".")[0]
            else:
        if not year.isnumeric():
                outfile.write(indent + '{'+'\n')
            return
                outfile.write(indent2 + ' "proprietes" : '+'['+'"'+remove_accents(str(inputData[i][0]))+'"'+ ','+ str(inputData[i][1])+ ','+ str(inputData[i][2])+']'+'\n')
        if div[div.index("</a>") + 7] == '-' or div[div.index("</a>") + 4] == '.' or div[div.index("</a>") + 7] == ' ':
                outfile.write(indent + '}'+','+'\n')
            return
        outfile.write(']')
        else:
    outfile.close()
            skipDate = div[div.index("<a")+ 2:]
            tagCityStart = skipDate[skipDate.index("<a"):]
            tagCity = tagCityStart[:tagCityStart.index("</a>")]
            city = tagCity[tagCity.index(">") + 1:]
            skipCity = tagCityStart[tagCityStart.index("</a>")+6:tagCityStart.index("</li>")]
            class MyHTMLParser(HTMLParser):
                data = []
                def handle_starttag(self, tag, attrs):
                    if tag == 'a':
                        for attr in attrs:
                            if str(attr[0]) == 'title':
                                if isName(attr[1]):
                                    self.data.append(attr[1])
            parser = MyHTMLParser()
            parser.feed(skipCity)
            if len(parser.data) != 0:
                #print(year, city, parser.data)
                return (year, city, parser.data)
 
    #===========Location===========
 
    def capitalIfCountry(location):
 
        """
        If location is a country, then it changes it to its capital
        """
 
        dataPays = next((item for item in countries if item["name"] == location), False)
        if(dataPays):
            return (dataPays['capital'])
        else:
            return location
 
     def finalNameCoordTuple(tuples):
 
        """
        Produces an array 'output' containing [Name, latitude, longitude]
        If location is a country, then it changes it to its capital
        """
 
        output = []
        for tuple in tuples:
            coord = geolocator.geocode(capitalIfCountry(tuple[1]))
            output.append([tuple[2], coord.latitude, coord.longitude])
        return output
 
    #=============Print=============
 
    def printTuples(tuples):
        for tuple in tuples:
            print(tuple[0]+" : "+tuple[1])
 
    def tuplesWithMultiplicity(yearCityList):
        tuples = []
        for i in range(0, len(yearCityList)):
            compte = yearCityList.count(yearCityList[i])
            tuples.append((yearCityList[i], compte))
        return [list(item) for item in set(tuple(row) for row in tuples)]
 
     def printBigTuples(tuples):
        for tuple in tuples:
            print(tuple[0][0], tuple[0][1], tuple[1])
 
    def printFinalTuples(tuples):
        for tuple in tuples:
            print('Who : ' + tuple[0] + ' Where : ' + tuple[1] + ' ' + tuple[2])
 
    #===========JSON file===========
 
    def createJson(inputData, year):
 
        """
        Takes array [Name, lat, long] for each year
        Outputs in .json such that it can be plotted
        """
 
        indent = '    '
        indent2 = '      '
        maxLimit = len(inputData)
        with open(str(year)+'.json', 'w') as outfile:
            outfile.write('['+'\n')
            for i in range (maxLimit):
                if i == (maxLimit-1):
                        outfile.write(indent + '{'+'\n')
                        outfile.write(indent2 + ' "proprietes" : '+'['+'"'+remove_accents(str(inputData[i][0]))+'"'+ ','+ str(inputData[i][1])+ ','+ str(inputData[i][2])+']'+'\n')
                        outfile.write(indent + '}'+'\n')
                else:
                    outfile.write(indent + '{'+'\n')
                    outfile.write(indent2 + ' "proprietes" : '+'['+'"'+remove_accents(str(inputData[i][0]))+'"'+ ','+ str(inputData[i][1])+ ','+ str(inputData[i][2])+']'+'\n')
                    outfile.write(indent + '}'+','+'\n')
            outfile.write(']')
        outfile.close()
 
    def createCoreList():
        counter = 1
        divDate = getNthDiv(stringSoup, counter)
        yearCityNameList = []
        while divDate != None:
            element = getYearCityAndName(divDate)
            if element is not None:
                yearCityNameList.append(element)
            counter += 1
            divDate = getNthDiv(stringSoup, counter)
        return yearCityNameList
 
    createJson(finalNameCoordTuple(createCoreList()),year)
 
masterFunction(1962)
</pre>
</pre>
Tous les besoins précis de structure d'un fichier JSON sont pour l'instant (faute de mieux) gérés manuellement (comme la conversion en '''str''' des '''float''' de coordonnées, ou bien les retours à la ligne).


=== Placement sur la carte ===
=== Placement sur la carte ===


Le placement des points (personne, ville) est fait avec un code en language HTML (HyperText Markup Language), à partir du fichier JSON crée auparavant. Les points respectives à chaque année sont affichés sur une carte Google Maps. Un slider est aussi crée pour pouvoir naviguer dans le temps.
Le placement des points (personne, ville) est fait avec un code en Javascript, à partir du fichier JSON créé auparavant. Les points respectifs à chaque année sont affichés sur une carte Google Maps. Un slider est aussi créé pour pouvoir naviguer dans le temps.


==== index.html ====


La fonction initCarte() initialise la carte et le slider avec les caractéristiques définies.
Le code est caractérisé par ces trois fonctions principales (commentées) :
La fonction update() extrait tous les points du fichier JSON.
La fonction ajoutePoints() affiche les points sur la carte.


'''main()''' initialise la carte avec les caractéristiques définies : sa position par défaut, l'option zoom et le slider, prédéfini à une certaine année.
<pre>
<pre>
<!DOCTYPE html>
function main(){
<html>
     //Initialise les éléments de la page
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>SliderBot</title>
  <style>
    html, body{
      height:100%;
      font-family: Arial, Verdana, sans-serif;
    }
    #carte{
      height:55%;
      width:100%;
      border-radius:4px;
    }
    #titre, p, div{
      text-align:center;
    }
    #slider_annee{
      width:50%;
    }
  </style>
</head>
<body>
  <h1 id="titre">SliderBot</h1>
  <div id="carte"></div>
  <div>
    <input type="range" min="1600" max="2018" value="1809" id="slider_annee">
  </div>
  <div>
    <button onclick="diminuer()">-</button>
    <button onclick="augmenter()">+</button>
    <p>Année: <b><span id="annee"></b></span></p>
  </div>
  <div>
    <button onclick="recentrer(2,30,20)">Monde</button>
    <button onclick="recentrer(3,4,18)">Afrique</button>
    <button onclick="recentrer(3,48,-100)">Amérique du Nord</button>
    <button onclick="recentrer(3,-25,-65)">Amérique du Sud</button>
    <button onclick="recentrer(3,35,96)">Asie</button>
    <button onclick="recentrer(4,51.5,16.36)">Europe</button>
    <button onclick="recentrer(4,-30.5,143)">Océanie</button>
  </div>
  <script>
 
  function initCarte(){
 
     //Initialise slider
     var slider = document.getElementById("slider_annee");
     var slider = document.getElementById("slider_annee");
     var output = document.getElementById("annee");
     var output = document.getElementById("annee");
 
    var b_autoplay = document.getElementById("autoplay");
     // Display the default slider value
     // Affiche la valeur par défaut de l'année
     output.innerHTML = slider.value;
     output.innerHTML = slider.value;
    // Chaque fois qu'on bouge le slider :
     slider.onmouseup = function() {
     slider.onmouseup = function() {
       //Met à jour la carte quand on relâche le slider
       //Met à jour la carte quand on relâche le slider
       update();
       update();
     }
     }
     slider.oninput = function() {
     slider.oninput = function() {
       //Met à jour la valeur du slider quand on change la valeur
       //Met à jour la valeur du slider quand on change la valeur
       output.innerHTML = this.value;
       output.innerHTML = this.value;
     }
     }
     //Options de la carte
     //Options de la carte
     var options={
     var options={
Ligne 347 : Ligne 191 :
       center:{lat:30,lng:20}
       center:{lat:30,lng:20}
     }
     }
     //Met à jour la carte la première fois
     //Met à jour la carte la première fois
     update();
     update();
    // ...
}
</pre>


'''update()''' extrait tous les points du fichier JSON pour l'année définie par le slider, ceci fait à travers un liens direct vers GitHub.
<pre>
     function update(){
     function update(){
 
       //Réinitialise tableaux
       //Réinitialise tableau
      fenetres=[];
       markers=[];
       markers=[];
       //Création de la carte
       //Création de la carte
       var carte=new google.maps.Map(document.getElementById('carte'), options);
       carte=new google.maps.Map(document.getElementById('carte'), options);
      //Ferme les fenêtres d'infos si on clique en dehors des markers
      carte.addListener('click', function(){
        for(var i=0; i<fenetres.length; i++){
          fenetres[i].close();
        }
      });


       //Chercher le fichier JSON
       //Chercher le fichier JSON
       var requestURL = 'https://raw.githubusercontent.com/EtienneBonvin/SliderBot/master/bot/test3.json?token=AkepwChC82KwQVTpNSOKDwl8FbLc6MEjks5a-YRKwA%3D%3D';
       var requestURL = 'https://raw.githubusercontent.com/EtienneBonvin/SliderBot/master/server/maps/'+String(slider.value)+'.json';
       var request = new XMLHttpRequest();
       var request = new XMLHttpRequest();
       request.open('GET', requestURL);
       request.open('GET', requestURL);
Ligne 367 : Ligne 220 :
       request.onload = function() {
       request.onload = function() {
         var reponse = request.response;
         var reponse = request.response;
 
         //On appelle la fonction ajoutant les points
         //On ajoute les points
         for(var i=0; i<reponse.length; i++){
         for(var i=0; i<reponse.length; i++){
           ajouterPoint(reponse[i]);
           ajouterPoint(reponse[i]);
         }
         }
         //On regroupe les points
         //On regroupe les points
         var regroupement = new MarkerClusterer(carte, markers,{
         var regroupement = new MarkerClusterer(carte, markers,{
Ligne 379 : Ligne 230 :
       }
       }
     }
     }
</pre>


    function ajouterPoint(proprietes){
'''ajouterPoint()''' affiche les points sur la carte grâce aux coordonnées spécifiées (latitude, longitude) avec comme label le nom et (éventuellement) le lien wiki.
        var point=new google.maps.Marker({
<pre>
          position:{lat:proprietes["proprietes"][1], lng: proprietes["proprietes"][2]},
function ajouterPoint(proprietes){
          map:carte,
      //On définit l'emplacement du marker
        });
      var point=new google.maps.Marker({
         var fenetreInfo=new google.maps.InfoWindow({
        position:{lat:proprietes["proprietes"][1], lng: proprietes["proprietes"][2]},
          content:proprietes["proprietes"][0]
        map:carte,
        });
      });
        point.addListener('click', function(){
      // On ajoute une fenêtre d'info (avec également lien si disponible)
           fenetreInfo.open(carte,point);
      if(proprietes["proprietes"][3]){
        });
        var description='<a href="'+proprietes["proprietes"][3]+'">'+proprietes["proprietes"][0]+'</a>';
        markers.push(point);
      }
      else {
         var description=proprietes["proprietes"][0];
      }
      var fenetreInfo=new google.maps.InfoWindow({
        content:description
      });
      //Au clic, on ouvre la fenêtre d'info et au ferme toutes les autres...
      point.addListener('click', function(){
        for(var i=0; i<fenetres.length; i++){
           fenetres[i].close();
        }
        fenetreInfo.open(carte,point);
      });
      markers.push(point);
      fenetres.push(fenetreInfo);
     }
     }
</pre>
=== Exemple ===
Les 3 figures donnent un exemple de carte générée par le SliderBot, ici pour l'année 1945, à trois niveaux de zoom (contrôlé soit par les boutons de localisation, soit directement par la carte intégrée Google).
Quand l'échelle est telle que les points peuvent être séparés, les noms de personnes sont affichés et le lien cliquable vers le wiki est intégré au label. Cet example témoigne aussi de la répulsion des points : les deux marqueurs correspondants à la conférence de Yalta sont décalés de quelques centaines de mètres pour améliorer la lisibilité.
<gallery widths=330px heights=300px>
Fichier:Sliderbot_zoom_monde.png
Fichier:Sliderbot_zoom_europe.png
Fichier:Sliderbot_zoom_ville.png
</gallery>


    //Déclarer comme ça la fonction permet qu'elle soit accessible en dehors d'initCarte()
== Discussion et améliorations ==
    recentrer=function (niv_zoom,latitude,longitude){
      options={
        zoom:niv_zoom,
        center:{lat:latitude,lng:longitude}
      }
      update();
    }


    augmenter=function(){
=== Serveur Node.js vs GitHub ===
      slider.stepUp(1);
      output.innerHTML = slider.value;
      update();
    }


    diminuer=function(){
==== Node.js : efficace mais requiert un serveur externe ====
      slider.stepDown(1);
Initialement, un server externe avait été créé avec Node.js pour transmettre l'information qui a été extraite vers le code HTML et l'afficher sur la carte. Solution efficace, qui permet un processus complètement automatisé, mais qui nécessite un serveur externe.
      output.innerHTML = slider.value;
      update();
    }
  }


  </script>
==== GitHub : serveur déjà existant mais les fichiers ne sont pas mis à jour automatiquement. ====
  <script
Finalement, une méthode plus simple est d'accéder aux fichiers JSON depuis un repository GitHub public. Ceci est fait dans '''index.html''' dans la fonction '''update()''', qui va chercher le fichier JSON d'une année spécifié par le slider :
    src="https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js">
<pre>
  </script>
var requestURL = 'https://raw.githubusercontent.com/EtienneBonvin/SliderBot/master/server/maps/'+String(slider.value)+'.json';
  <script async defer
    src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDUYredUZ1kaPBxNz0ToUQas1MwCF4-ER4&callback=initCarte">
  </script>
</body>
</html>
</pre>
</pre>


=== Exemple ===
Cela étant dit, le code Python ne gère pas la mise en ligne sur le serveur GitHub automatique des JSON. Pour que la visualisation des résultats soit possible, les fichiers doivent être, pour l'instant, mis à jour à la main. Aucune méthode automatisée n'ayant été trouvée.
 
=== Analyse des performances ===
==== Performances ====
* Temps pour calculer les JSON correspondants à toutes les années disponibles sur Wikipast (de 1765 à 1999) : 1 minute 51 secondes.
 
* Temps moyen par année : 0.47 secondes.
 
* Pourcentage des événements correctement recupérés : estimés à environ 80%.
 
==== Limitations ====
 
* La principale limitation du SliderBot provient de la wikification des articles créés : sans une structure efficace '''Date/Lieu''', ou bien une '''mention explicite de la personne''' dans la ligne d'événement (et non, e.g. ''1947 / Paris. Récompense pour tel livre''), le bot n'a pas de moyen de correctement parser les informations.
 
* D'autre part, mis à part les imprécisions de wikifications humaines, le '''SliderBot''' nécessite le fonctionnement correct et fréquent du '''ChronoBot''', son fonctionnement étant basé sur les pages annuelles de '''ChronoBot'''.
 
* Une autre limitation est la répétition de points si deux événements sont identiques.
**Si l'on reprend l'exemple de l'année 1945, l'événement associé à la conférence de Yalta apparaît à deux reprises, une fois issu de la biographie de Roosevelt et une fois de celle de Churchill ; il est donc affiché deux fois sur la carte.
**Pour parer à cette situation, on pourrait imaginer une amélioration du bot qui comparerait les personnes présentes à deux {date et lieu} identiques et ignorerait l'un des deux événements.
 
* Il arrive également que la librairie Geopy ne renvoie pas les bonnes coordonnées malgré un parsing correct. Cela peut arriver à la fois quand deux lieux portent le même nom.
** Par exemple, la capitale égyptienne ''Le Caire'' est marquée dans le sud de la France (c.f. 1943), où un village ''Le Caire'' se trouve ; ceci provient du fait que la capitale est nommée ''Cairo'' en anglais, langue de Geopy.
** Aucune solution à ce problème n'a été établie, car il provient directement du module Geopy, qui semble ne pas se soucier de la taille / importance de la ville qu'il renvoie.
* Une autre limitation, négligeable cette fois, provient du module Geopy et de son utilisation automatisée.
** Pour limiter la saturation de leurs serveurs, les administrateurs du module brident une utilisation trop intensive.
** Il faut simplement éviter une réactualisation des fichiers trop fréquente ; c'est-à-dire, au delà de plus de 10 fois par heure, ce qui ne saurait être un problème.
* Comme mentionné plus haut, la pipeline n'est pas encore complètement automatisée : la mise en ligne des JSON sur GitHub reste manuelle. Un problème majeur pour la parfaite autonomie du bot, mais qui ne perturbe pas son bon fonctionnement.
* Enfin, notre code nécessite encore pour le parsing et la production de fichiers JSON, la suppression systématique des accents (problèmes d'encodage).
**Ceci se révèle problématique dans le lien des pages Wikipast (qui nécessitent les accents corrects) : plutôt que de linker vers une page inexistante, le choix a été fait de mettre un lien vers le wiki uniquement pour les personnes sans accents.
**Aucune solution pour parer à ce problème de conservation d'accents n'a encore été concluante. Ce serait une amélioration majeure à apporter à ce projet.
 
=== Possibles collaborations avec d'autres bots ===
Au delà de la collaboration essentielle avec '''ChronoBot''', d'autres projets peuvent être mis à contribution :
==== ImageBot ====
 
*Il serait intéressant de collaborer avec '''ImageBot''', et afficher sur la carte les mêmes portraits que ceux qu'il ajoute à Wikipast, en passant, soit par un parsing des pages Wikipast, soit directement en reprenant une partie du code de '''ImageBot'''.
*Une autre possibilité pour afficher les portraits des personnes concernées serait de passer par l'API Google Images, dont l'intégration serait plus simple. Toutefois, elle nécessite un abonnement payant (non justifié pour une telle amélioration).
 
==== Autres bots de structure ====
=====  CreatoBot =====
 
Une des limitations de notre bot et qu'un nombre non négligeable d'années et / ou de lieux n'ont pas été correctement mis en hypermot. Le travail de '''CreatoBot''', visant à ajouter ces hypermots quand nécessaire, augmentera le nombre de données réunies.
 
===== FormatBot =====
Utilisable pour améliorer la wikification des dates.
 
=====TangoBot=====
Utilisable pour éviter la redondance des événements.
 
=== Mise à jour du bot===
 
Le bot devrait être mis à jour environ une fois par année, ou après un ajout conséquent de pages. Il doit être lancé après une actualisation du travail de '''ChronoBot''' (et éventuellement des autres bots mentionnés).
Pour mettre à jour il suffit de lancer '''maincode.py''' et de repush les fichiers JSON sur Github.
 
== Code Complet ==
'''Repository public''' : https://github.com/EtienneBonvin/SliderBot


Ceci est un exemple de comment la carte est visualisé pour l'année 1809. Dans le bas de la carte il y a le slider pour avancer ou retourner en arrière dans le temps. La carte contient l'option de naviguer dans l'espace et de zoom. Quand le zoom est assez proche pour distinguer les différentes personnes comme points individuels, ils sont représentés par leurs images (si existantes).
'''Code pour l'extraction de données''' : https://github.com/EtienneBonvin/SliderBot/blob/master/bot/maincode.py


[[Fichier:Exemple carte.png]]
'''Fichier HTML''' : https://github.com/EtienneBonvin/SliderBot/blob/master/bot/index.html
==Diapositives==
<gallery widths=260px heights=120px>
Fichier:Presentation_SliderBot_slide1.jpeg
Fichier:Presentation_SliderBot_slide_2.jpeg
Fichier:Presentation_SliderBot_slide_3.jpeg
Fichier:Presentation_SliderBot_slide_4.jpeg
Fichier:Presentation_SliderBot_slide_5.jpeg
Fichier:Presentation_SliderBot_slide_6.jpeg
Fichier:Presentation_SliderBot_slide_7.jpeg
Fichier:Presentation_SliderBot_slide_8.jpeg
</gallery>


== Groupe ==
== Groupe ==


{| class="wikitable"
{| class="wikitable"
  ! scope="col" | Nom et Prénom
  ! scope="col" | Nom
  ! scope="col" | Pseudo
  ! scope="col" | Pseudo
  |-
  |-
  | Guhennec Paul
  | Paul Guhennec
  | Pguhennec
  | Pguhennec
  |-
  |-
  | Wildi Maël
  | Maël Wildi
  | mwildi
  | mwildi
  |-
  |-
  | Bonvin Etienne
  | Etienne Bonvin
  | AbsInt
  | AbsInt
  |-
  |-
  | Raynal Mathilde
  | Mathilde Raynal
  | PizzaWhisperer
  | PizzaWhisperer
  |-
  |-
  | Politi Stefano
  | Stefano Politi
  | spoliti
  | spoliti
  |-
  |-
  |}
  |}

Dernière version du 22 mai 2018 à 09:18

Le but du SliderBot est d'afficher sur une carte interactive la position des personnes au cours du temps, à partir des biographies rédigées sur Wikipast.

Le SliderBot extrait tout d'abord les données de Wikipast, en parcourant les pages consacrées aux années. Pour chaque année, les informations parsées sont les mentions de villes et de personnes. Ces informations sont ensuite sauvegardées dans un fichier JSON. Ce fichier est enfin utilisé pour afficher les lieux mentionnées et les personnes respectives sur la carte, pour une année spécifique, en utilisant un code JavaScript et l'API GoogleMaps.

En appliquant cette méthode pour chaque année existante dans la base de données de Wikipast, il est possible de créer une carte interactive permettant de naviguer à travers le temps et l'espace et ainsi visualiser la distribution d'événements sur le globe au fil du temps.

Objectifs

  • Création d'une base de données en associant [Personne, Date, Lieu] via un scrapping de page.
  • Création d'un ensemble de cartes affichant les positions de toutes les personnes recensées pour chaque date (discretisation par année).
  • Création d’un slider dynamique permettant de chosir l'année et affichage de la carte correspondante (JavaScript / HTML).

Fonctionnement

Extraction de données

Récupération des lieux et personnes

Le résultat du parsing, avec les services de BeautifulSoup, est une liste de tuples {année, lieu, personne}, issue des pages d'années générées par ChronoBot. Ceci est principalement fait par la fonction getYearCityAndName:

def getYearCityAndName(div):

  tagDate = div[div.index("<a"):div.index("</a>")]
  date = tagDate[tagDate.index(">") + 1:]
  year = date.split(".")[0]

  if not year.isnumeric():
      return

  if div[div.index("</a>") + 7] == '-' or div[div.index("</a>") + 4] == '.' or div[div.index("</a>") + 7] == ' ':
      return
  else:
      skipDate = div[div.index("<a")+ 2:]
      tagCityStart = skipDate[skipDate.index("<a"):]
      tagCity = tagCityStart[:tagCityStart.index("</a>")]
      city = tagCity[tagCity.index(">") + 1:]
      skipCity = tagCityStart[tagCityStart.index("</a>")+6:tagCityStart.index("</li>")]

      class MyHTMLParser(HTMLParser):
          data = []
          def handle_starttag(self, tag, attrs):
              if tag == 'a':
                  for attr in attrs:
                      if str(attr[0]) == 'title':
                          if isName(attr[1]):
                              self.data.append(attr[1])

      parser = MyHTMLParser()
      parser.feed(skipCity)
      if len(parser.data) != 0:
          return (year, city, parser.data)

Pour obtenir une meilleure reconnaissance / sélection des personnes, au delà des tests regex effectués par getYearCityAndName, la libraire NLTK est employée dans la fonction isName qui détermine si une chaîne de caractères correspond bien à un nom de personne.

def isName(string):
  words = [y for y in string.split() if not y.isupper()]
  string_upd = remove_accents(" ".join(words))
  if string_upd.istitle():
      for chunk in ne_chunk(pos_tag(word_tokenize(remove_accents(string)))):
          if hasattr(chunk, 'label'):
              if chunk.label() == 'PERSON':
                  #print(' '.join(c[0] for c in chunk))
                  return True

  return False

Localisation

Il convient ensuite de convertir ces tuples {lieu, personne} en {coordonnées, personne}. Cette étape nécessite la librairie Python Geopy.

Pays vs. Capitale

Si le lieu d'un événement sur une page Wikipast est décrite seulement par son pays, plutôt que de l'afficher au centre géographique du pays, l'événement sera plutôt affiché comme s'étant produit dans la capitale de ce pays. Ceci est fait à l'aide d'un fichier countries.py [1] qui contient la liste des pays du monde et leur capitale, par la fonction capitalIfCountry:

def capitalIfCountry(location):
"""
If location is a country, then it changes it to its capital
"""
   dataPays = next((item for item in countries if item["name"] == location), False)
   if(dataPays):
      return (dataPays['capital'])
   else:
      return location
Conversion de coordonnées

Le lieu est convertie en coordonnées, avec les services de geo-localisation de Geopy, par la fonction finalNameCoordTuple:

def finalNameCoordTuple(tuples):
"""
Produces an array 'output' containing [Name, latitude, longitude]
If location is a country, then it changes it to its capital
"""
   output = []
   for tuple in tuples:
      coord = geolocator.geocode(capitalIfCountry(tuple[1]))
      output.append([tuple[2], coord.latitude, coord.longitude])
   return output

Ici, les différents éléments du tuple proviennent du parsing : tuple[1] correspond au nom du lieu et tuple[2] correspond à la personne.

Répulsion de lieux identiques

L'affichage correct de plusieurs événements se déroulant au même lieu se révèle problématique. En effet, geocode renvoie une position (latitude, longitude) précisément identique pour chaque ville. Lorsque deux événements se passent au même lieu, les points se confondent rigoureusement et l'API Google ne permet, logiquement, pas de les séparer (peu importe le niveau de zoom).

Pour parer à cette éventualité, la fonction repulsePoints décale sur la carte de quelques centaines de mètres les événements (par un simple incrément arbitraire de longitude et latitude).

def repulsePoints(tuples):
        for i in range (len(tuples)):
            for j in range (len(tuples)):
                if (i != j and tuples[i][1] == tuples[j][1] and tuples[i][2] == tuples[j][2]):
                    tuples[j][1] = tuples[i][1] + 0.005
                    tuples[j][2] = tuples[i][2] + 0.005
        return tuples

Cela étant dit, une telle méthode peut donner l'impression d'une précision artificielle sur les lieux (qu'il sera impossible d'éviter, les lieux sur Wikipast étant au mieux localisés par ville).

Fichier JSON

Ajout du lien Wikipast

La fonction addWikiLink ajoute aux tuples crées plus tôt le lien de la page WikiPast de la personne concernée.

def addWikiLink(input_string):
    output_string=input_string
    for i in range (len(input_string)):
        output_string[i].append('http://wikipast.epfl.ch/wikipast/index.php/'+input_string[i][0].split(', ')[0].replace(" ", "_"))
    return output_string

Dans le cas où plusieurs personnes sont concernées par un événement, le choix a été fait de lier la page Wiki de la première personne seulement.

Création du JSON

Finalement le fichier JSON est généré, et en prenant en paramètres des arrays en format [nom, latitude, longitude, lien wiki] et l'année souhaitée (commune à toutes les fonctions du programme), produit un fichier de la forme (par exemple ici 1986.json, où l'on a gardé qu'un événement), qui est enregistré dans un dossier server/maps sur GitHub :

[
    {
       "proprietes" : ["Enzo Ferrari",44.5255217,10.8663607, "http://wikipast.epfl.ch/wikipast/index.php/Enzo_Ferrari"]
    }
]

La fonction est la suivante:

def createJson(inputData, year):
"""
Takes array [Name, lat, long] for each year
Outputs in .json such that it can be plotted
"""
    indent = '    '
    indent2 = '      '
    maxLimit = len(inputData)
    with open(str(year)+'.json', 'w') as outfile:
        outfile.write('['+'\n')
        for i in range (maxLimit):
            if i == (maxLimit-1):
                outfile.write(indent + '{'+'\n')
                outfile.write(indent2 + ' "proprietes" : '+'['+'"'+remove_accents(str(inputData[i][0]))+'"'+ ','+ str(inputData[i][1])+ ','+ str(inputData[i][2])+']'+'\n')
                outfile.write(indent + '}'+'\n')
             else:
                outfile.write(indent + '{'+'\n')
                outfile.write(indent2 + ' "proprietes" : '+'['+'"'+remove_accents(str(inputData[i][0]))+'"'+ ','+ str(inputData[i][1])+ ','+ str(inputData[i][2])+']'+'\n')
                outfile.write(indent + '}'+','+'\n')
        outfile.write(']')
    outfile.close()

Tous les besoins précis de structure d'un fichier JSON sont pour l'instant (faute de mieux) gérés manuellement (comme la conversion en str des float de coordonnées, ou bien les retours à la ligne).

Placement sur la carte

Le placement des points (personne, ville) est fait avec un code en Javascript, à partir du fichier JSON créé auparavant. Les points respectifs à chaque année sont affichés sur une carte Google Maps. Un slider est aussi créé pour pouvoir naviguer dans le temps.


Le code est caractérisé par ces trois fonctions principales (commentées) :

main() initialise la carte avec les caractéristiques définies : sa position par défaut, l'option zoom et le slider, prédéfini à une certaine année.

function main(){
    //Initialise les éléments de la page
    var slider = document.getElementById("slider_annee");
    var output = document.getElementById("annee");
    var b_autoplay = document.getElementById("autoplay");
    // Affiche la valeur par défaut de l'année
    output.innerHTML = slider.value;
    slider.onmouseup = function() {
      //Met à jour la carte quand on relâche le slider
      update();
    }
    slider.oninput = function() {
      //Met à jour la valeur du slider quand on change la valeur
      output.innerHTML = this.value;
    }
    //Options de la carte
    var options={
      zoom:2,
      center:{lat:30,lng:20}
    }
    //Met à jour la carte la première fois
    update();
    // ...
}

update() extrait tous les points du fichier JSON pour l'année définie par le slider, ceci fait à travers un liens direct vers GitHub.

    function update(){
      //Réinitialise tableaux
      fenetres=[];
      markers=[];
      //Création de la carte
      carte=new google.maps.Map(document.getElementById('carte'), options);
      //Ferme les fenêtres d'infos si on clique en dehors des markers
      carte.addListener('click', function(){
        for(var i=0; i<fenetres.length; i++){
          fenetres[i].close();
        }
      });

      //Chercher le fichier JSON
      var requestURL = 'https://raw.githubusercontent.com/EtienneBonvin/SliderBot/master/server/maps/'+String(slider.value)+'.json';
      var request = new XMLHttpRequest();
      request.open('GET', requestURL);
      request.responseType = 'json';
      request.send();
      request.onload = function() {
        var reponse = request.response;
        //On appelle la fonction ajoutant les points
        for(var i=0; i<reponse.length; i++){
          ajouterPoint(reponse[i]);
        }
        //On regroupe les points
        var regroupement = new MarkerClusterer(carte, markers,{
          imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'
        });
      }
    }

ajouterPoint() affiche les points sur la carte grâce aux coordonnées spécifiées (latitude, longitude) avec comme label le nom et (éventuellement) le lien wiki.

function ajouterPoint(proprietes){
      //On définit l'emplacement du marker
      var point=new google.maps.Marker({
        position:{lat:proprietes["proprietes"][1], lng: proprietes["proprietes"][2]},
        map:carte,
      });
      // On ajoute une fenêtre d'info (avec également lien si disponible)
      if(proprietes["proprietes"][3]){
        var description='<a href="'+proprietes["proprietes"][3]+'">'+proprietes["proprietes"][0]+'</a>';
      }
      else {
        var description=proprietes["proprietes"][0];
      }
      var fenetreInfo=new google.maps.InfoWindow({
        content:description
      });
      //Au clic, on ouvre la fenêtre d'info et au ferme toutes les autres...
      point.addListener('click', function(){
        for(var i=0; i<fenetres.length; i++){
          fenetres[i].close();
        }
        fenetreInfo.open(carte,point);
      });
      markers.push(point);
      fenetres.push(fenetreInfo);
    }

Exemple

Les 3 figures donnent un exemple de carte générée par le SliderBot, ici pour l'année 1945, à trois niveaux de zoom (contrôlé soit par les boutons de localisation, soit directement par la carte intégrée Google).

Quand l'échelle est telle que les points peuvent être séparés, les noms de personnes sont affichés et le lien cliquable vers le wiki est intégré au label. Cet example témoigne aussi de la répulsion des points : les deux marqueurs correspondants à la conférence de Yalta sont décalés de quelques centaines de mètres pour améliorer la lisibilité.

Discussion et améliorations

Serveur Node.js vs GitHub

Node.js : efficace mais requiert un serveur externe

Initialement, un server externe avait été créé avec Node.js pour transmettre l'information qui a été extraite vers le code HTML et l'afficher sur la carte. Solution efficace, qui permet un processus complètement automatisé, mais qui nécessite un serveur externe.

GitHub : serveur déjà existant mais les fichiers ne sont pas mis à jour automatiquement.

Finalement, une méthode plus simple est d'accéder aux fichiers JSON depuis un repository GitHub public. Ceci est fait dans index.html dans la fonction update(), qui va chercher le fichier JSON d'une année spécifié par le slider :

var requestURL = 'https://raw.githubusercontent.com/EtienneBonvin/SliderBot/master/server/maps/'+String(slider.value)+'.json';

Cela étant dit, le code Python ne gère pas la mise en ligne sur le serveur GitHub automatique des JSON. Pour que la visualisation des résultats soit possible, les fichiers doivent être, pour l'instant, mis à jour à la main. Aucune méthode automatisée n'ayant été trouvée.

Analyse des performances

Performances

  • Temps pour calculer les JSON correspondants à toutes les années disponibles sur Wikipast (de 1765 à 1999) : 1 minute 51 secondes.
  • Temps moyen par année : 0.47 secondes.
  • Pourcentage des événements correctement recupérés : estimés à environ 80%.

Limitations

  • La principale limitation du SliderBot provient de la wikification des articles créés : sans une structure efficace Date/Lieu, ou bien une mention explicite de la personne dans la ligne d'événement (et non, e.g. 1947 / Paris. Récompense pour tel livre), le bot n'a pas de moyen de correctement parser les informations.
  • D'autre part, mis à part les imprécisions de wikifications humaines, le SliderBot nécessite le fonctionnement correct et fréquent du ChronoBot, son fonctionnement étant basé sur les pages annuelles de ChronoBot.
  • Une autre limitation est la répétition de points si deux événements sont identiques.
    • Si l'on reprend l'exemple de l'année 1945, l'événement associé à la conférence de Yalta apparaît à deux reprises, une fois issu de la biographie de Roosevelt et une fois de celle de Churchill ; il est donc affiché deux fois sur la carte.
    • Pour parer à cette situation, on pourrait imaginer une amélioration du bot qui comparerait les personnes présentes à deux {date et lieu} identiques et ignorerait l'un des deux événements.
  • Il arrive également que la librairie Geopy ne renvoie pas les bonnes coordonnées malgré un parsing correct. Cela peut arriver à la fois quand deux lieux portent le même nom.
    • Par exemple, la capitale égyptienne Le Caire est marquée dans le sud de la France (c.f. 1943), où un village Le Caire se trouve ; ceci provient du fait que la capitale est nommée Cairo en anglais, langue de Geopy.
    • Aucune solution à ce problème n'a été établie, car il provient directement du module Geopy, qui semble ne pas se soucier de la taille / importance de la ville qu'il renvoie.
  • Une autre limitation, négligeable cette fois, provient du module Geopy et de son utilisation automatisée.
    • Pour limiter la saturation de leurs serveurs, les administrateurs du module brident une utilisation trop intensive.
    • Il faut simplement éviter une réactualisation des fichiers trop fréquente ; c'est-à-dire, au delà de plus de 10 fois par heure, ce qui ne saurait être un problème.
  • Comme mentionné plus haut, la pipeline n'est pas encore complètement automatisée : la mise en ligne des JSON sur GitHub reste manuelle. Un problème majeur pour la parfaite autonomie du bot, mais qui ne perturbe pas son bon fonctionnement.
  • Enfin, notre code nécessite encore pour le parsing et la production de fichiers JSON, la suppression systématique des accents (problèmes d'encodage).
    • Ceci se révèle problématique dans le lien des pages Wikipast (qui nécessitent les accents corrects) : plutôt que de linker vers une page inexistante, le choix a été fait de mettre un lien vers le wiki uniquement pour les personnes sans accents.
    • Aucune solution pour parer à ce problème de conservation d'accents n'a encore été concluante. Ce serait une amélioration majeure à apporter à ce projet.

Possibles collaborations avec d'autres bots

Au delà de la collaboration essentielle avec ChronoBot, d'autres projets peuvent être mis à contribution :

ImageBot

  • Il serait intéressant de collaborer avec ImageBot, et afficher sur la carte les mêmes portraits que ceux qu'il ajoute à Wikipast, en passant, soit par un parsing des pages Wikipast, soit directement en reprenant une partie du code de ImageBot.
  • Une autre possibilité pour afficher les portraits des personnes concernées serait de passer par l'API Google Images, dont l'intégration serait plus simple. Toutefois, elle nécessite un abonnement payant (non justifié pour une telle amélioration).

Autres bots de structure

CreatoBot

Une des limitations de notre bot et qu'un nombre non négligeable d'années et / ou de lieux n'ont pas été correctement mis en hypermot. Le travail de CreatoBot, visant à ajouter ces hypermots quand nécessaire, augmentera le nombre de données réunies.

FormatBot

Utilisable pour améliorer la wikification des dates.

TangoBot

Utilisable pour éviter la redondance des événements.

Mise à jour du bot

Le bot devrait être mis à jour environ une fois par année, ou après un ajout conséquent de pages. Il doit être lancé après une actualisation du travail de ChronoBot (et éventuellement des autres bots mentionnés). Pour mettre à jour il suffit de lancer maincode.py et de repush les fichiers JSON sur Github.

Code Complet

Repository public : https://github.com/EtienneBonvin/SliderBot

Code pour l'extraction de données : https://github.com/EtienneBonvin/SliderBot/blob/master/bot/maincode.py

Fichier HTML : https://github.com/EtienneBonvin/SliderBot/blob/master/bot/index.html

Diapositives

Groupe

Nom Pseudo
Paul Guhennec Pguhennec
Maël Wildi mwildi
Etienne Bonvin AbsInt
Mathilde Raynal PizzaWhisperer
Stefano Politi spoliti