Un fabuleux mapping avec Leaflet.js !

Aujourd’hui, pour changer, je vous parlerai de … front-end ! Bien que plus dévolue back, j’avais envie de faire d’une pierre deux-coups et de réaliser un joli mapping de mon monde Minetest.

Avec Minetest, c’est assez facile de générer des fichiers image qui retracent en 2D l’univers généré dans le jeu, avec minetestmapper. A partir de là, les plus créatifs peuvent s’amuser.

Pour la petite histoire, je suis en fait tombée sur le plan interactif du serveur Minetest LinuxForks, qui est juste splendide, et j’ai eu vraiment envie de me lancer ce défi.

Au commencement, une map qui rame à mort !

Du coup, j’ai intégré ce fichier dans une carte de base. Le code est simple, j’intègre Leaflet.js dans ma page web, et j’y intègre quelques markers. La bibliothèque, je la connaissais déjà, car je l’avais déjà intégrée pour l’outil d’un de mes clients BTP, pour y localiser les chantiers en cours. Mais Leaflet, c’est vraiment l’outil qui permet de créer des cartes, sans forcément qu’elle soit « terrestre », une « mappemonde ». Donc pas mal privilégiée par les geeks gamers.
Voilà grosso modo le code de base du premier jet que j’ai intégré dans mon index (j’y ai intégré après un div vide appelé map, bien entendu):

var map = L.map('map', {
    crs: L.CRS.Simple,
    center: [500, 500],
    scale: function (zoom) {
        return Math.pow(2, zoom);
    },
    zoom: function (scale) {
        return Math.log(scale / 256) / Math.LN2;
    },
});
 
var bounds = [[0,0], [2000,2000]];
var image = L.imageOverlay('map.png', bounds).addTo(map);
// Spawn
var spawn = L.latLng([971.5, 1156.5]);
L.marker(spawn).addTo(map).bindTooltip('Spawn Station', {sticky: false, direction: 'top'});
// Maison de Jym
var jymHome = L.latLng([972, 1137]);
L.marker(jymHome).addTo(map).bindTooltip('Maison de Jym', {sticky: false, direction: 'top'});
 
map.setView( [971.5, 1156.5], 3);

C’est déjà pas mal. La définition de la map en crs permet de générer une map personnalisable, le niveau de zoom est correct, on peut naviguer pépère. Sauf que la carte, qui est grande, met pas mal de secondes à charger, c’est lourd, très lourd. Et ça, c’est un gros souci. Sans compter que la map devient floue à un certain niveau de zoom avancé. A revoir, donc !

Le tiling, solution miracle !

Et puis j’apprends que Leaflet intègre très bien des briques d’images. A la base, on peut sans souci utiliser un coup d’imagemagick pour ce faire et recoller les morceaux, chose que la bibliothèque fait très bien. Donc pile ce dont j’ai besoin !

Pour créer les briques, il y a un script existant qui gère Leaflet. Il est en Python et s’appelle gdal2tiles-leaflet. Ne pas oublier avant de faire un petit sudo apt install python-gdal (pour les utilisateurs d’Ubuntu) et éventuellement installer python si ce n’est déjà fait, juste avant de l’utiliser.

Voici la commande que j’ai lancée :
./gdal2tiles.py -l -p raster -z 0-10 -w none ../map.png ../tiles
Et j’ai attendu quelques bonnes minutes pour que toutes les briques soient générées. C’est un peu long mais ça vaut le coup. Ne pas oublier l’option -p raster car on en aura besoin par la suite, et surtout l’option -l qui définit spécialement des briques adaptées pour leaflet. J’ai demandé un niveau de zoom variable de 0 à 10, pour bien pouvoir avoir chaque détail de ma carte.

Gérer le tiling dans Leaflet

Maintenant qu’on a généré les briques, il faut pouvoir les assembler. Là encore, après pas mal de recherches, j’ai pu trouver de quoi satisfaire ma faim.

Bien avant, il faut intégrer la librairie rastercoords, pour pouvoir faire la correspondance des briques au format « raster » (l’option que nous avions vu plus haut). Et ainsi, avec ce code, vous pouvez déjà disposer d’une map sympa :

;(function (window) {
    function init (mapid) {
        var minZoom = 0
        var maxZoom = 9
        var img = [
            38192, // original width of image
            29792  // original height of image
       ]
    // create the map
    var map = L.map(mapid, {
       minZoom: minZoom,
       maxZoom: maxZoom
    })
 
    var rc = new L.RasterCoords(map, img)
    map.setView(rc.unproject([22000, 15450]), 9)
    L.control.layers({}, {
        //@todo
    }).addTo(map)
 
    L.tileLayer('./tiles/{z}/{x}/{y}.png', {
        noWrap: true,
        attribution: 'Creation Amelie DUVERNET aka Amelaye <a href="http://minetest.amelieonline.net">Projet Amelaye In Minerland</a>'
        }).addTo(map)
    }
 
    init('map')
}(window))

Des markers jolis et dynamiques

Nous avons la carte, il faut maintenant y ajouter les markers. Pour ce faire, vous avez besoin de :
– La librairie font-awesome (on en a besoin pour extra-markers)
– La librairie leaflet-extra-markers

J’ai crée deux fonctions, la première, layerGeo qui permet de récupérer les points définis dans mon fichier geojson.js

function layerGeo (map, rc) {
  var layerGeo = L.geoJson(window.geoInfo, {
    // correctly map the geojson coordinates on the image
    coordsToLatLng: function (coords) {
      return rc.unproject(coords)
    },
    // add a popup content to the marker
    onEachFeature: function (feature, layer) {
      if (feature.properties &amp;&amp; feature.properties.name) {
        layer.bindPopup(feature.properties.name)
      }
    },
    pointToLayer: function (feature, latlng) {
      return L.marker(latlng, {
        icon: feature.properties.id
      })
    }
  })
  map.addLayer(layerGeo)
  return layerGeo
}

Ainsi qu’une autre, qui permet de trouver les « frontières » de la carte, et également d’afficher les coordonnées d’un emplacement au hasard cliqué :

function layerBounds (map, rc, img) {
  // set marker at the image bound edges
  var layerBounds = L.layerGroup([
    L.marker(rc.unproject([0, 0])).bindPopup('[0,0]'),
    L.marker(rc.unproject(img)).bindPopup(JSON.stringify(img))
  ])
  map.addLayer(layerBounds)
 
  // set markers on click events in the map
  map.on('click', function (event) {
    // to obtain raster coordinates from the map use `project`
    var coord = rc.project(event.latlng)
    // to set a marker, ... in raster coordinates in the map use `unproject`
    var marker = L.marker(rc.unproject(coord)).addTo(layerBounds)
    marker.bindPopup('[' + Math.floor(coord.x) + ',' + Math.floor(coord.y) + ']').openPopup()
  })
  return layerBounds
}

Du coup, on revient sur notre code qui affiche la map et on corrige la ligne L.control.layers pour appeler nos fonctions :

L.control.layers({}, {
  'Bounds': layerBounds(map, rc, img),
  'Info': layerGeo(map, rc)
}).addTo(map)

Maintenant passons aux choses sérieuses, la définition de nos coordonnées. Et là, on va passer nos jolis markers, ainsi que les points qui nous intéressent, et tout ceci va se passer au niveau d’un nouveau fichier geojson.js :

;(function (window) {
    // Markers
    var spawnStation = L.ExtraMarkers.icon({
        icon: 'fa-anchor',
        markerColor: 'red',
        shape: 'star',
        prefix: 'fa'
    });
 
    var metroStation = L.ExtraMarkers.icon({
        icon: 'fa-subway',
        markerColor: 'blue',
        shape: 'circle',
        prefix: 'fa'
    });
 
    var castle = L.ExtraMarkers.icon({
        icon: 'fa-dungeon',
        markerColor: 'violet',
        shape: 'square',
        prefix: 'fa'
    });
 
    // etc ...
 
    // geoJson definitions
    window.geoInfo = [
        {
            'type': 'Feature',
            'properties': {
                'name': 'Spawn Station',
                'id': spawnStation
            },
            'geometry': {
                'type': 'Point',
                'coordinates': [22087, 15321]
            }
        },
        // Castles
        {
            'type': 'Feature',
            'properties': {
                'name': 'Chateau dans le ciel',
                'id': castle
            },
            'geometry': {
                'type': 'Point',
                'coordinates': [22088,14112]
            }
        },
        {
            'type': 'Feature',
            'properties': {
                'name': 'Chateau Royal',
                'id': castle
            },
            'geometry': {
                'type': 'Point',
                'coordinates': [22229,15299]
            }
        },
        {
            'type': 'Feature',
            'properties': {
                'name': 'Chateau Amelaye',
                'id': castle
            },
            'geometry': {
                'type': 'Point',
                'coordinates': [22060,15402]
            }
        }, // etc ...
    ]
}(window))

Là, j’ai pas mal galéré car il faut injecter les markers personnalisés dans un fichier qui doit être scrupuleusement rigoureux, car geojson est une norme. J’ai donc passé les variables qui correspondent à la définition des « templates de markers » dans la propriété id. C’est caduque, bricolé, mais ça marche.

Et voilà le résultat (disponible en ligne) :

Nous voici maintenant avec une map rapide à charger, qui intègre pas mal de points personnalisables. Il reste pas mal de choses à faire, j’ai bien envie d’utiliser une base de données pour créer une API, voire une base ElasticSearch qui se chargera de générer le fichier geojson, mais pour l’instant je n’ai pas décidé de la suite. Qu’en pensez-vous ?

Voici pour plus de détails le rendu final sur mon Github.

Comment monter un serveur d’archives PHP sans pleurer …

Mine de rien, concernant mon métier j’ai fait mes premiers pas en commençant mes études, ce qui commence à faire … un petit paquet d’années. Du coup en fait j’ai gardé (presque) toutes mes archives … ce qui n’est pas problématique quand on est en « full HTML » mais quand on passe en PHP, là c’est plus touchy. Parce que mes premiers sites sont en PHP4, en sachant qu’on se dirige vers la version 8, il y a eu pas mal de chemin depuis : renforcement de la rigueur, changements dans la conception objet, méthodes deprecated et j’en passe.

Essayez de faire marcher un site crée en 2003 sur un PHP 7 … ça m’étonnerait que vous puissiez faire quelque chose de concret. Du coup, pour faire comme moi, trouver des trucs de geek à faire en étant confiné (même si j’ai la chance de pouvoir continuer à travailler, il faut s’occuper le soir 😉 ) et faire tourner un serveur d’archives sur une Ubuntu récente, que faire ?

PHP 5.6 avec FPM

Il y a une solution pour les sites les plus récents : PHP-FPM, qui permet de faire tourner PHP en service indépendamment d’Apache (oui désolée je suis Apache-addict, question d’habitude), ce qui vous permet de pouvoir faire tourner un PHP5.6. C’est assez simple …

En premier lieu, on prépare le terrain, on se base déjà sur le fait que vous avec déjà Apache installé dans votre système.

Quelques librairies de base

Puis il vous faut ajouter le fameux repository d’Ondřej Surý, qui a fait un travail remarquable à ce sujet :

Et hop, magie !

N’oubliez pas d’ajouter les librairies MySQL au besoin. Une fois les installations bien terminées, faites un petit service status, histoire de bien vérifier que les services tournent :

Donc si vous avez un message de ce style, c’est que tout va bien 🙂

Donc là, vous pouvez faire tourner non seulement des sites tout neufs, mais également des sites que vous avez conçu il y a au moins trois-quatre ans. Il suffit après de configurer votre vhost comme suit pour switcher de version :

Vérifiez bien que les services suivants soient activés :
actions proxy_fcgi alias
Et les librairies suivantes installées pour PHP 5.6 au risque de voir une belle page blanche qui ne logge rien de pertinent :
php5.6-xml php5.6-gd php5.6-mcrypt php5.6-mysql php5.6-pdo

Et pour les versions antérieures à PHP 5.6 ?

Après, les choses se compliquent. Ne cherchez pas à installer ne serait-ce que PHP 5.2 à la mano, rien n’est compatible avec les versions actuelles d’OpenSSL et vous risquez de casser pas mal de trucs comme ça m’est arrivé. Et ainsi d’avoir à vous repalucher les installs d’Apache et OpenSSL, les joies de l’admin sys et de l’expérimentation, en somme 🙂 … – note : ce serveur n’est pas celui que j’utilise pour mes sites importants mais pour mon « bac à sable » 😉 …

Du coup j’ai opté pour un bel outil bien tendance qui a le vent en poupe chez les devOps, et qui est vraiment pratique, j’ai nommé : Docker ! J’ai finallement consenti à créer donc un conteneur qui ne servira qu’à mes plus anciens sites, et la bonne nouvelle, c’est qu’il n’y a pas tant de différences entre les dernières versions de PHP 4 et PHP 5.2 (au niveau du fait de pouvoir faire fonctionner un site en PHP 4 sur un serveur PHP 5.2, l’inverse ne sera pas forcément possible – et même fortement impossible), ce qui signifie que mes plus vieux sites tournent avec sans souci, et que je n’ai donc pas à créer de conteneurs supplémentaires (du moins, pour ma part ! ) !

Comme je suis une nana sympa, j’ai mis sur GitHub l’image que j’utilise, que vous pouvez déployer avec docker-compose, vous pouvez même définir un répertoire mysqldump pour alimenter la base directement en ligne de commande. Il y a également un PhpMyAdmin pour se simplifier la vie. Il faut juste modifier les vhosts pour que ça pointe sur vos sites à vous, qui seront accessible par défaut sur le port 8052 (80 port par défaut d’Apache et 52 comme PHP 5.2 😉 )

N’oubliez pas de préciser au besoin comme dans mon exemple, si vos extensions sont .php3, je sais ça fait super bizarre de retrouver ce genre d’extension …

Pour le plaisir, je vous montre une petite capture d’écran de ma page de garde, où je peux naviguer de site en site … c’est amusant de replonger dans le passé, comme ça :

Après, c’est un projet qui donne plein d’idées : par exemple vous voyez les petits screenshots ? Ils sont en réalité bien moches quand on passe en responsive, car dimensionnés assez petits (je pensais qu’à l’époque, ça suffisait). Bien là j’ai mis en place un process sous Puppeteer qui permet de faire des captures d’écran automatiquement ! Je pense que je vais pas mal trouver d’idées inspirantes que je vous partagerai par la suite 😉

Mon projet de refacto BioPHP – Partie 1

J’aime bien la biologie, depuis ma tendre enfance. Je pensais même pouvoir un jour pouvoir faire de la bioinformatique mais j’ai vite été lassée des études en fac, donc j’ai dû faire un peu une croix sur ce projet.

Then, I left to Marseille, tout ça …

Mais dernièrement, j’ai eu pas mal de remous professionnels, quitté la dernière entreprise où j’étais en CDI (plutôt rageusement). J’ai donc passé quelques mois à construire des relations avec des clients, privilégiant l’approche freelance, et j’ai également trouvé un projet perso qui puisse m’occuper et approfondir mes connaissances en Symfony. Parce que depuis Spark, j’ai horreur d’être inoccupée. Et puis quand mon chat est mort, il a fallu un échappatoire pour penser à autre chose. Et puis j’avais une revanche perso à relever (n’est-ce pas, Guillaume ?).

J’ai trouvé donc le projet BioPHP (qui est sans URL Fixe, y’a un site ici aussi), cherchant une librairie de bioinformatique en PHP, par curiosité après avoir vu que des librairies du genre existaient en GO. Et … comment dire ? Voir des 2003, dernières mises à jour en 2009 peut-être, des fonctionnalités en PHP4, du HTML, du PHP, des data mélangées dans le code. Aucun design pattern, du code spaghetti, des fonctionnalités qui se retrouvent un peu partout. Ouch. Pourtant les fonctionnalités sont intéressantes et offrent du potentiel, les algorithmes sont hyper poussés. Cette librairie a été développée par d’excellents biologistes qui ne sont pas familiarisés avec le dev. D’ailleurs en poussant la recherche, le projet est critiqué et mal noté.

L’idée de départ des gars était super sympa : intégrer une librairie pour créer des applis de bioinformatique (plutôt orientées génétique) en PHP, comme il en existe en PERL, Go, Python … Mais il est vrai qu’à l’époque, à moins de télécharger bêtement et utiliser les fonctions require, on ne pouvait pas faire grand-chose d’autre. A part peut-être les intégrer dans PEAR ou PECL, chose que je n’ai jamais faite, donc je ne connais pas le process. PHP était vraiment à l’époque le vilain petit canard du monde des langages, et c’est à ce niveau qu’on voit son évolution fulgurante. Car aujourd’hui les outils ont évolué, nous avons les frameworks, Composer, Packager, Github, les tests unitaires et fonctionnels … en un coup de « composer install », on peut intégrer une librairie.

Comment procéder quand on est face à ce genre de challenges ? A vrai dire je n’ai pas vraiment établi de plan d’action dès le départ, peut-être une mauvaise idée, mais je me suis dit que c’était un projet pour lequel j’avais tout mon temps, et le droit à l’erreur. Car ce projet pris sur mon temps perso a été commencé en février dernier. En gros voici comment je suis en train de procéder :

Fonctionnement global de la librairie est des éléments-satellites après la refacto
  1. Installation de Symfony, création de 3 bundles : AppBundle (core de l’appli, MinitoolsBundle (qui intègre des outils qui pouvaient utiliser la librairie), et DatabaseBundle, qui m’a servi de « bundle temporaire » pour créer mes classes entités, raccordées avec Doctrine (parce que franchement, la classe seq.php … non, non, et non).
  2. Premier coup de machette dans le code. Nettoyage des fonctionnalités, renommage des variables suivant leur type, restructuration des classes originales. Séparation des classes-entités de ce qui sera intégré dans des services.
  3. Mise de côté des data. D’abord j’avais pensé à les inclure en YAML dans des paramètres, très mauvaise idée. Base de données ? Je voudrais plus la réserver à l’appli, pour moi la BDD ne doit servir qu’au stockage des séquences. Reste une idée sympa, les intégrer dans une API, ce que je voulais construire depuis longtemps pour utiliser API-Platform, un outil performant qui permet de pouvoir créer facilement ses propres API Rest. Le plus : on peut créer une couche interface qui permettra de créer un adapter, et donc de changer d’API, au cas où, tout en implémentant le modèle requis. Même si le CRUD n’est pas à 100% respecté. Les données de l’API ne sont pas destinées à être modifiées, dans l’immédiat.
  4. Refacto des « Minitools » pour bien m’imprégner de la logique de ces outils, création de l’interface web en TWIG, des services correspondants : je repère les duplicatas, crée de nouveaux services qui pourront être utiles dans le core. Car à la base, ces tools devaient intégrer les fonctionnalités de BioPHP. De mon côté ils me permettent aussi de me replonger dans les fondamentaux de la biologie : qu’est-ce qu’une protéine ? un acide aminé ? un enzyme etc …
  5. Refacto en test manuel : depuis le temps, les fonctionnalités sont en friche, donc je les fais remarcher, petit à petit, en continuant le labeur de nettoyage et de refacto. Je repère ainsi les fonctionnalités qui seront intégrées dans les « Minitools ». Cette partie sera désolidarisée (renommée en BioTools, pour caler au nommage général) du package à terme, pour des raisons logiques : le multi-repo c’est le bien, je vous dis.
  6. Tests unitaires du core : maintenant que je sais que ça marche, je repasse en mode « semi-TDD » et intègre les tests unitaires. Car un gros travail de refacto m’attend : la définition des différents design patterns de AppBundle. -> je suis ici \o/ .
  7. Découpage du projet pour en créer un projet Packagist. Et certainement montée en version de Symfony, redéfinition des dépendances d’injection etc …
  8. Création des tests unitaires et fonctionnels (avec Behat) de BioTools, refacto et envoi à Packagist.

Au prochain épisode, je rentrerai plus en détails sur la refacto d’AppBundle et des design patterns choisis.

Ha oui, et sinon, toutes les infos du projet sont là … Amelaye’s BioPHP.

La supercherie des Petits Malins

Qui se souvient de l’ours Gaby, l’ami des touts petits ?

Les enfants que nous étions à l’époque (pour ceux qui l’étaient) étaient perplexes face à ce nounours qui ne faisait même pas une simple figuration dans ce dessin animé. Pourquoi ces paroles (”le bouton tout rond”), ce générique ?
Effectivement le personnage n’a existé que dans le générique, sous l’apparence d’un ours faisant du ski (ne pas confondre avec Bobby, un autre ours qui lui fait partie du DA). Ne cherchez pas dans les épisodes, vous ne l’avez pas manqué … il n’y est pas.
En fait Gaby est à l’origine un jouet parlant, qui racontait des histoires sous la pression d’un bouton, commercialisé à l’époque par Bandai … Ce qui est malin (c’est le cas de le dire !) de la part de la firme, c’est de se servir d’un dessin animé japonais, “Marple Town”, sur le point d’être diffusé en France à la fin des années 80, et d’utiliser le générique en le réinventant pour y greffer le personnage de Gaby.
Je n’ai pas résisté à vous faire part du générique d’origine très kawaï :

Comme vous voyez, pas de Gaby à l’horizon.

Ceci expliquerai également les dessins grossiers dudit personnage, dont les traits n’ont rien à voir avec les personnages centraux.

Je vous laisse méditer sur cette triste histoire … et mes pensées à tous les Gabriel-le-s de la terre qui ont eu droit à cette ritournelle …

Flowchart to your heart

Voilà un petit site de rencontres qui va satisfaire les Geeks : okcupid.com !

Ou comment mettre sous forme de graphes les exigences de chacun-e-s en matière de rencontres de l’âme-sœur …

Après avoir répondu à plusieurs questions, le programme met en forme vos réponses (QCM) sous forme de graphiques, genre « schéma de procédures » qui montre tout simplement d’une part le chemin de ces exigences, mais en plus met en corrélation ces réponses pour voir qui vous correspond le mieux.

Vous recevez ensuite le fameux graphe par e-mail, ça donne ça :

Cliquez sur l’image pour voir le PDF envoyé

Ça change du « je suis romantique, j’aime avoir une main dans la mienne, diner au restau, écouter le chant des oiseaux sur le clapotis de l’eau qui reflètera ton doux visage » (meilleur des cas) ou du « je sais pas me présenter, lol. Connaissez-moi, vous verrez bien, trolol » (pire des cas).

Et si le cœur vous en dit (ou désespéré de la vie, ou n’avez que ça à faire, ou encore êtes super curieux-se), vous pouvez même en faire plusieurs, le nombre de questions peut aller loin :

NB : Pour ma part j’ai fait ça pour m’amuser, les rencontres sur le Net j’ai déjà donné. Moi les gens je préfère les voir en vrai que recevoir des signaux numériques émis par eux, j’ai donc désactivé mon compte après.

Sur ce, bon flowchart 8) !