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.

Création de mon Wiki Minetest !

Être sérieux tout en s’amusant, beau concept ! J’adhère !

Comme certains le savent déjà, je n’ai pas super bien vécu le confinement. Je pensais être une louve solitaire dans l’âme, pensant que son rêve le plus cher serait d’être débarquée sans retour sur une île déserte.
Ben non ! En fait. J’aime les gens.
Bref, durant cette période de confinement, il a bien fallu s’occuper. J’ai eu la chance de pouvoir continuer à travailler normalement, étant déjà autoentrepreneur à la maison, mais j’avoue que en dehors de ces heures, il a fallu m’occuper, si mes sides-projects sont prenants, il me fallait « sortir », et ça j’avoue que ça m’a bien manqué, surtout pour l’urbex. Alors je suis sortie d’une autre façon.

Il y a dix ans de ça, je découvrais Minecraft … de loin. Je l’ai associé malgré moi à une tête de nœud qui voulait jouer les petits chefs, et qui pendant les pauses de midi, passait son temps sur la plate-forme, tout en ingurgitant un répugnant « hachis parmentier » made in Carrefour ou Casino. Ecoeurée par l’odeur de ce truc infâme qui se voulait être de la nourriture, je regardais ce qu’il faisait du coin de l’œil, le traitant intérieurement de « bébé qui joue aux Lego ». Je me suis ensuite renseignée sur le projet, et j’avais même écrit un article assassin sur ce blog, espérant que mon ennemi allait tomber dessus et se sentir outrageusement blessé dans son égo trop développé.

Dix ans plus tard, je me rends compte que ce programme a toujours du succès et que bon nombre de geeks dans mon entourage jouaient au dérivé libre et gratuit de Minecraft (parce que racheté par Microsoft, c’est devenu diabolique) appelé Minetest. Je me suis dit alors que si ces gens, intelligents, aimaient ce jeu c’est qu’il devait y avoir une bonne raison. Oui je sais, j’ai tendance à mettre des cases « tout blanc tout noir ».

Des cookies ! Plein de cookies !!!

Je m’y suis mise en test, en mode survival, en mode créative, un peu touché à tout, construit une maison de fortune, et j’ai été surprise de tout ce qu’on pouvait faire. Puis j’ai un pote qui m’a dit « viens sur mon serveur, j’y joue avec mon fils, on est bien et des cookies t’attendent ! » … donc là j’ai commencé à construire des trucs plus sympas, un cottage en bord de mer, que j’ai relié à un « donjon », devenu base de crafting (fabrication). Puis ayant fini, j’ai crée un château, puis des champs cultivables, avec des éléments craftables pour créer des petits plats, une ferme avec des vaches et des moutons, etc …

Si c’est pas sympa, franchement ?
Domaine avec château, champs et ferme
Salut, toi !

Personnalisable à l’infini !

Laissant jouer mon pote en famille, j’ai récupéré les sources du monde qu’il avait crée pour l’installer sur mon serveur. J’ai réinstallé les mods pour qu’ils soient compatibles avec ma version, et j’ai commencé à installer d’autres mods sympas. J’avais en effet commencé à faire une sorte de station de métro à côté du château, avec les rails de base, et j’ai donc voulu tester le formidable AdvTrains pour qu’on croit vraiment, à ce métro. Et franchement, je me suis bien amusée à y mettre des wagons qui semblent bien inspirés du U-Bahn berlinois, qui roulent réellement ! Et je pourrai même à l’avenir les automatiser ! Oui, c’est exactement une version virtuelle du petit train électrique !

Prenez place …

C’est ainsi que j’ai commencé à créer un vrai petit village avec : église, mairie (j’ai même un cimetière où j’ai mis les prénoms de mes exs) … à travers différents mods : mod_church, homedecor, x-decor … pour ne citer que ceux-là. Parce que oui, on peut optimiser son jeu comme on le souhaite, soit en installant des mods existants, soit en les créant soi-même avec le langage LUA. Et là on commence à toucher le côté geek du jeu (cœur avec les doigts <3).

J’essaie pas mal de m’inspirer des tutos de Richard Jeffres, qui a l’air de bien s’amuser et qui fait vraiment des choses impressionnantes.

Une de mes dernières créations en date : garage privé et voitures de luxe

Comme dans mon rêve

Ce que certains mods apportent, ce sont des extensions aux biomes existants. Parce que oui, ton monde dans Minetest et juste immense et s’étale en 3 dimensions sur 60000 blocs pour la dernière version. Je pense que peu de joueurs ont déjà tenté de générer la map complète de leur univers. C’est ainsi qu’on traverse savanes, jungles, icebergs, déserts … Mais on peut y ajouter une touche bien fantaisiste.

Le mod Ethereal apporte de nouveaux biomes, ainsi que de nouveaux arbres et plantes … il y a également le mod Caverealms qui apporte aussi de nouveaux styles de caves. J’ai également installé Nether mais je ne le trouve pas aussi sympa que ça en a l’air. Il faudra voir à l’utilisation.

Oui, ce sont des champignons géants.
Je sais pas vous mais je trouve ça trop joli <3

Geek stuff

Bien sûr je ne compte pas m’arrêter là. Si je compte agrandir mon petit village en y ajoutant des restaurants et des boutiques, j’ai également très envie de faire des choses bien plus poussées qui font l’attrait de Minetest : créer des usines et automatiser le métro. Car avec le langage LUA, qui peut s’injecter dans les éléments du jeu, on comprend l’intérêt ludique de ce qu’on appelle Serious Gaming.

J’ai commencé à suivre quelques tutoriels justement pour commencer à comprendre comment tout cela fonctionne.

Un calculateur qui affiche le résultat en chiffres et en lettres (avec Mesecons et Digilines)

Du coup voilà, j’ai plein de projets et espère bien ramener des gens car je m’y sens un peu seule sur ce serveur. Donc j’ai commencé à créer un wiki et ferai à l’avenir quelques vidéos pour vous monter un peu ce mignon petit monde 😉

Et hop : http://minetest.amelieonline.net

Et sinon la moralité de cette histoire : quand on ne connaît pas, on ne juge pas 😉 ! A plus tard dans la Matrice 😉 !