Quelques astuces pour GIS qui à terme seront déplacées dans un article de la rubrique du plugin.
Définir le logo des points avec des mots clés
Depuis GIS v2 on peut ajouter un logo aux points. Dans la v1 du plugin il existait une astuce qui permettait de définir le logo des points à partir du logo des mots clés d’un groupe spécifique. Pour les nostalgiques, il est toujours possible d’obtenir le même résultat en procédant comme expliqué ci-dessous.
1) créez un groupe de mots-clés nommé marker_icon et attribuez un logo aux mots-clés de ce groupe.
2) attachez un mot de ce groupe aux articles liés à vos points.
3) créez un squelette JSON personnalisé nommé gis_articles_logomot avec le contenu suivant :
<BOUCLE_art(ARTICLES){gis}{id_article ?}{id_rubrique ?}{id_secteur ?}{id_mot ?}{id_auteur ?}{recherche ?}{0, #ENV{limit}}{","}>
{"type": "Feature",
"geometry": {"type": "Point", "coordinates": [#LON, #LAT]},
"id":"#ID_GIS",
"properties": {
"title":[(#TITRE_GIS*|sinon{#TITRE*}|supprimer_numero|json_encode)],
"description":[(#DESCRIPTIF_GIS|sinon{#DESCRIPTIF}|json_encode)]<BOUCLE_logomot(MOTS){id_article}{type=marker_icon}>
[(#LOGO_MOT_NORMAL|gis_icon_properties)]</BOUCLE_logomot>
}}</BOUCLE_art>
4) utilisez le code suivant pour afficher une carte qui sera alimentée par votre squelette JSON personnalisé :
[(#INCLURE{fond=modeles/carte_gis,objets=articles_logomot})]
Et voilà :)
Définir un marker différent pour chaque point dans un article
Le script suivant, inséré dans article.html, permet de figurer un marker différent (s’ils sont bien dans squelettes/images), en se basant sur les deux caractères de fin du Titre de chaque point (et en supprimant ces deux caractères de ce qui est visible publiquement dans l’article sur la carte même).
Dans cet exemple le titre se termine par “-1” pour black, “-2” pour bleu, “-3” pour red, “-4” pour orange, “-5” pour vert. S’il n’y a rien à la fin du titre c’est la couleur par défaut qui s’applique.
<script>
jQgisloader.done(function(){
jQuery(function(){
if (typeof map1 !== "undefined") {
map1.on('layeradd', function(e) {
if (e.layer.feature) {
let properties = e.layer.feature.properties;
let title = properties.title || "";
// Vérifier si l'avant-dernier caractère est un tiret
let suffix = title.slice(-2);
let newTitle = title; // Par défaut, garder le titre inchangé
if (title.charAt(title.length - 2) === "-") {
newTitle = title.slice(0, -2); // Supprime les 2 derniers caractères
}
// Définition des couleurs en fonction du suffixe
let colorMap = {
"-1": "black",
"-2": "bleu",
"-3": "red",
"-4": "orange",
"-5": "vert"
};
// Déterminer la couleur
let color = colorMap[suffix] || "black"; // Par défaut "black"
// Définir l'icône
let iconUrl = 'squelettes/images/marker_' + color + '.png';
// Modifier l'icône du marqueur
e.layer.setIcon(L.icon({
iconUrl: iconUrl,
iconSize: [25, 41],
iconAnchor: [12, 41]
}));
// Mettre à jour le popup avec le titre modifié si nécessaire
e.layer.bindPopup(newTitle);
}
});
} else {
console.error("La carte Leaflet (map1) n'est pas définie !");
}
});
});
</script>
Permettre l’ajout d’une géolocalisation dans le formulaire public de modification d’événement
En partant de l’excellente contribution de mj
Squelette d’Agenda grâce au plugin Agenda
J’ai fait évoluer le code de mod_evenement.html en y ajoutant ce bloc.
<div>
#BLOC_TITRE
<:localiser:>
#BLOC_RESUME
<BOUCLE_agenda_gis(GIS){id_evenement}>
[(#INCLURE{fond=modeles/carte_gis_preview,id_objet=#ID_EVENEMENT,evenement})]
</BOUCLE_agenda_gis>
#BLOC_DEBUT
[(#INCLURE{fond=prive/inclure/gis_objet_formulaires,objet=evenement,id_objet=#ID_EVENEMENT,ajax})]
#BLOC_FIN
</div>
Ce code doit figurer dans une boucle événement, à un endroit ou l’id_evenement est défini.
Dans le cas contrait il serait judicieux de faire afficher par le squelette un message signalant qu’il faut au préalable enregistre le nouvel évenement.
La boucle agenda_gis permet simplement de ne pas afficher de carte tant qu’il n’y a pas de point associé.
L’utilisation de des blocs dépliables n’est évidemment pas obligatoire (nécessite le couteau suisse); pour s’en passer, il suffit d’enlever toutes les balise commençant par #BLOC
Appeler un point géolocalisé à partir d’un squelette
Il est parfois utile d’appeler un point sur une carte, directement à partir d’un lien ou d’un bouton contenu dans son squelette. Une fonction javascript a été ajoutée à cette effet (à partir de GIS 4.4.0).
La fonction gis_focus_marker nécessite deux variables, d’abord l’ID_GIS du point géolocalisé, ensuite l’ID de la carte.
Prenons l’exemple d’une carte, dont les titres des points géolocalisés sont affichés à la suite. Si l’on désire que ces titres permettent de localiser immédiatement le point sur la carte quand on clique dessus, il suffit de s’inspirer du code ci-dessous.
<BOUCLE_article(ARTICLES){id_article}>
<BOUCLE_carte(ARTICLES){gis}{id_article}{0,1}> [(#REM|{on teste que des points géolocalisés sont bien liés à cet article})]
[(#INCLURE{fond=modeles/carte_gis,id_article=#ID_ARTICLE})]
<B_points>
<ul>
<BOUCLE_points(GIS){id_article}{par titre}>
<li><a href="#map1" onclick="javascript:gis_focus_marker(#ID_GIS,1);">#TITRE</a></li>
</BOUCLE_points>
</ul>
</B_points>
</BOUCLE_carte>
</BOUCLE_article>
Utiliser d’autres fonds de carte
Il est possible d’enrichir la liste des fonds de carte proposées par le plugin. Pour cela il suffit de compléter la variable globale $GLOBALS['gis_layers']
depuis votre fichier mes_fonctions.php :
$GLOBALS['gis_layers']['dede'] = array(
'nom' => 'CloudMade',
'layer' => 'L.tileLayer("http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png")'
);
Définition des valeurs à renseigner pour chaque couche ajoutée :
- dede = identifiant technique de la couche
- CloudMade = nom affiché pour la couche
- layer = définition de la couche pour Leaflet (voir la documentation de Leafet)
Exemple : la couche ign 25000ème avec CLE d’accès à la plateforme de Géoportail. Documentation geoservice.ign
$GLOBALS['gis_layers']['geoign'] = array(
'nom' => 'Geoportail IGN',
'layer' => 'L.tileLayer(
"https://wxs.ign.fr/CLE/geoportail/wmts?&REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE=normal&TILEMATRIXSET=PM&FORMAT=image/jpeg&LAYER=GEOGRAPHICALGRIDSYSTEMS.MAPS&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}",
{
minZoom : 0,
maxZoom : 18,
tileSize : 256,
attribution : "IGN-F/Géoportail"
})',
);
Edit aout 2024 suite à la bascule vers Géoplateforme de novembre 2023
(NB : la clé partagée ign_scan_ws est sensée être à durée limitée...)
$GLOBALS['gis_layers']['geoign'] = array(
'nom' => 'Geoportail IGN',
'layer' => 'L.tileLayer(
"https://data.geopf.fr/private/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&FORMAT=image/jpeg&STYLE=normal&LAYER=GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN25TOUR&TILEMATRIXSET=PM&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&apikey=ign_scan_ws",
{
minZoom : 0,
maxZoom : 18,
tileSize : 256,
attribution : "IGN-F/Géoportail"
})',
);
API Javascript de GIS
L’objet de chaque carte est accessible de deux manières : depuis une variable globale mapXX
(où XX est l’id passé en paramètre au modèle de la carte ciblée) ou depuis la propriété map
de l’objet DOM contenant la carte.
// récupérer la carte id 1 depuis la globale
var map = map1;
// récupérer la carte id1 depuis le DOM
var map = $('#map1').get(0).map;
À partir de là on a accès à toutes les fonctions de l’API Leaflet pour la classe L.Map. En bonus, certaines fonctions internes de GIS sont aussi disponibles :
- setGeoJsonFeatureIcon
- setGeoJsonFeaturePopup
- parseGeoJson
- AddJSON
et removeAllMarkers
pour compatibilité avec GIS 3
Événements
L’objet de la carte déclenche deux événements : load
et ready
. On peut s’y brancher simplement à l’aide de jQuery :
$('#map1').on('load', function(){
console.log(this.map);
});
$('#map1').on('ready', function(){
console.log(this.map);
});
Pour compatibilité, il est aussi possible de se brancher sur l’événement load
depuis une fonction de callback :
callback_map1 = function(map) {
console.log(map);
};
Par contre, cette fonction doit être déclarée de façon global pour être accessible depuis le modèle ou la saisie.
—
Le geocoder déclenche l’événement complete
au retour des résultats de la requête. On peut s"y brancher de la manière suivante :
geocoder.on('complete', function(e) {
console.log(e.result); // e.result contient les données renvoyées par le geocoder
});
—
Appeller un callback quand on clique sur un point. Attention il faut prendre en compte les marqueurs simples et les marqueurs cachés dans les clusters.
$('#map1').on('ready', function(){
this.map.eachLayer(function(layer){
var callback_au_clic = function(e) {
if (this.feature) {
console.log(this.feature);
}
}
// Dès qu'on clique sur un élément de la carte
layer.on('click', callback_au_clic);
// Des enfants s'il y a des clusters
if (typeof layer.getChildCount !== 'undefined') {
$.each(layer.getAllChildMarkers(), function(i, sublayer) {
sublayer.on('click', event_recharger);
});
}
});
});
Ajouter plusieurs gpx/kml via le modèle
Depuis la version 4.40.0 :
Permettre depuis l’appel d’une carte gis dans le texte d’un article d’indiquer une liste de tracés kml, gpx ou autre.
Exemple : <carte_gis|point=non|kml=20,21,22,23,24>
Afficher plusieurs traces GPX avec des couleurs différentes.
Comme indiqué dans la doc, il est possible d’afficher plusieurs traces gpx sur une carte, en précisant une liste d’items séparés par une virgule.
Si l’appel est fait depuis le squelette, il est possible de passer une liste d’item, grâce à la balise #LISTE
exemple : gpx=#LISTE{102,103}
(ou 102 et 103 correspondent à l’identifiant des documents)
Si vous souhaitez bien distinguer ces différentes traces, vous pouvez avoir besoin de changer leur couleur.
Pour chaque trace gpx, la librairie leaflet.js génère un svg avec la classe .leaflet-interactive
et y définit la couleur par défaut (#3388FF) dans un attribut stroke.
En css, il est possible de sélectionner la trace souhaitée avec la pseudo-classe nth-child et de surcharger cette couleur.
En insérant la ligne suivante dans votre feuille de style, la deuxième trace deviendra donc rouge :
.leaflet-interactive:nth-child(0n 2) {stroke:#ff0000 !important;}
Correction tardive : ça semble être plutôt
.leaflet-interactive:nth-child(0n+2) {stroke:#000 !important;}
gis_open_popup
De la même manière qu’il existe une fonction « gis_focus_marker », ne devrait-on pas créer une fonction « gis_open_popup » dans gis_utils.js ? Cette fonction permet d’ouvrir un popup sans centrer la carte sur le marker. C’est particulièrement utile lorsque l’on n’a pas beaucoup d’espace pour la carte et que l’infobulle que l’on veut ouvrir est grande :
function gis_open_popup (id, map) {
var carte = eval('map'+ map);
var i, count = 0;
for (i in carte._layers) {
if ((carte._layers[i].feature) && (carte._layers[i].feature.id == id)) {
carte._layers[i].openPopup();
}
count++;
}
}
Critère {gis distance...}
Le critère {gis distance<30}
filtre les objets dont la distance est inférieure à 30 par rapport au point de l’environnement.
Attention, {gis distance < 30}
ne marche pas car il ne faut pas d’espaces !
Au cas où, il y a aussi le critère ’distancefrom’ : {distancefrom #ARRAY{lat,#LAT,lon,#LON},<,30}
.
Récupérer les informations géographiques concernant des coordonnées latitude longitude
Les fonctions gis_infos_coordonnees
et texte_infos_coordonnees
définies ci-après reçoivent un tableau avec au moins une entrée lat
et une entrée lon
.
- gis_infos_coordonnees
renvoie un tableau php avec les informations complètes issues de l’interrogation ’reverse’ du géocodeur (nominatim par défaut) a propos des coordonnées reçues
- texte_infos_coordonnees
renvoie une chaine affichable présentant la localisation des coordonnées reçues. Exemple : Chemin de Saint-Victor, La Citerne, Fontjoncouse, Narbonne, Aude, Occitanie, France métropolitaine, 11360, France
include_spip("inc/distant");
include_spip('inc/modifier');
// inspiré de action/gis_geocoder_rechercher;
function gis_infos_coordonnees ($arguments) {
if (!isset($arguments['lat']))
return "il manque la latitude pour gis_infos_coordonnees";
if (!isset($arguments['lon']))
return "il manque la longitude pour gis_infos_coordonnees";
$geocoder = defined('_GIS_GEOCODER') ? _GIS_GEOCODER : 'nominatim';
if (in_array($geocoder, array('photon','nominatim'))) {
if ($geocoder == 'photon') {
if (isset($arguments['accept-language'])) {
$arguments['lang'] = $arguments['accept-language'];
unset($arguments['accept-language']);
}
$url = 'http://photon.komoot.de/';
} else {
$url = 'http://nominatim.openstreetmap.org/';
$arguments['format'] = 'json';
}
$url = defined('_GIS_GEOCODER_URL') ? _GIS_GEOCODER_URL : $url;
$url = "{$url}reverse?" . http_build_query($arguments);
$data = recuperer_page($url);
return json_decode($data, true);
}
}
function texte_infos_coordonnees($latlon) {
debug_assert (isset ($latlon['lat']) and isset ($latlon['lon']), "oups texte_infos_coordonnees ne reçoit pas lat et lon mais ".print_r($latlon,1));
$data = gis_infos_coordonnees ($latlon);
if (!$data) {
return "Erreur interogation géocodeur";
}
if (!is_array($data)) {
return $data;
}
if (isset($data['display_name']))
return $data['display_name'];
if (isset($data['address']))
return print_r($data['adress'],1);
return 'Format de réponse du geocodeur foireux pour '.print_r($latlon, 1);
}
Utiliser le geocoder depuis PHP
Pour récupérer les infos géocodés dans un tableau PHP sans les afficher
(code généraliste et devant être adapté à vos besoins)
<?php
include_spip("inc/distant");
// inspiré de action/gis_geocoder_rechercher;
function lg_gis_geocoder_rechercher_dist() {
include_spip('inc/modifier');
$mode = _request('mode');
if (!$mode || !in_array($mode, array('search', 'reverse'))) {
return;
}
/* On filtre les arguments à renvoyer à Nomatim (liste blanche) */
$arguments = collecter_requests(array('json_callback', 'format', 'q', 'limit', 'addressdetails', 'accept-language', 'lat', 'lon'), array());
$geocoder = defined('_GIS_GEOCODER') ? _GIS_GEOCODER : 'nominatim';
if (!empty($arguments) && in_array($geocoder, array('photon','nominatim'))) {
if ($geocoder == 'photon') {
if (isset($arguments['accept-language'])) {
$arguments['lang'] = $arguments['accept-language'];
unset($arguments['accept-language']);
}
if ($mode == 'search') {
$mode = 'api/';
} else {
$mode = 'reverse';
}
$url = 'http://photon.komoot.de/';
} else {
$url = 'http://nominatim.openstreetmap.org/';
$arguments['format']='json';
}
$url = defined('_GIS_GEOCODER_URL') ? _GIS_GEOCODER_URL : $url;
$data = recuperer_page("{$url}{$mode}?" . http_build_query($arguments));
$data = json_decode($data,true);
return $data;
}
}
$reponse = array('');
set_request("mode","search");
set_request("q","marseille");
set_request("format","json");
set_request("limit","1");
$arguments = collecter_requests(array('json_callback', 'format', 'q', 'limit', 'addressdetails', 'accept-language', 'lat', 'lon'), array());
var_dump($arguments );
$requete = lg_gis_geocoder_rechercher_dist();
var_dump($requete);
Obtenir la géolocalisation d’une adresse postale
public function get_localisation($adresse) {
$url = 'http://photon.komoot.de/api/';
$url = parametre_url($url, 'limit', 1, '&');
$url = parametre_url($url, 'q', $adresse, '&');
$url = parametre_url($url, 'lang', 'fr', '&');
$data = file_get_contents($url);
if ($data) {
$data = json_decode($data);
return $data;
}
return null;
}
Utiliser un marquer svg et varier la couleur
gis_icon_properties n’accepte pas les SVG. Ce serait possible de le modifier mais en attendant, on peut remplacer [(#LOGO_GIS|gis_icon_properties)]
par l’insertion directe des propriétés pour le svg :
"properties": {
#SET{64marker,
#VAL{<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><path fill='#}
|concat{#GET{couleur}}
|concat{"' d='M16 0C9.8 0 5 4.7 5 10.6c0 2.4 1.6 6.1 4.8 11.7 1.9 3.3 4 6.5 6.1 9.7 2.4-3.4 4.6-6.9 6.3-9.7C25.4 16.7 27 13 27 10.6 27 4.7 22 0 16 0z'/></svg>"}
|base64_encode}
#SET{64marker,#VAL{"data:image/svg+xml;base64,"}|concat{#GET{64marker}}}
"icon": "#GET{64marker}",
"icon_size": [32,32],
"icon_anchor": [16,16],
"popup_anchor": [0,0]
}
Avec ce code, c’est un marqueur svg uni et sans ombre qui est inséré, et sa couleur dépend d’un #GET{couleur}
précalculé, dont la valeur est hexadécimale sans le # au début.
Forcer la mise à jour d’une carte
Par exemple lorsqu’une carte est dans un onglet qui n’est pas affiché initialement, toutes les tuiles ne sont pas affichées lorsque l’onglet est révélé. Ou quand elle est dans une mediabox. Il faut forcer la mise à jour de l’affichage à ce moment.
Exemple de code avec la méthode leaflet invalidateSize
(doc), utilisant l’événement bootstrap shown.bs.tab
qui se déclanche après l’affichage d’un onglet (doc) :
$("#carte-tab").on("shown.bs.tab", function() {
const id = $('#carte-content .formulaire_editer_gis .formMap').attr('id');
const map = window['form_' + id];
map.invalidateSize(false);
});
Rq: c’est pour le formulaire d’édition de point. Pour la vue, il faudrait cibler .carte_gis
au lieu de .formMap
et window[id]
directement.
Convertir une coordonnée décimale en degré (DMS)
Il existe le filtre dec_to_dms fourni par le plugin
https://git.spip.net/spip-contrib-extensions/gis/src/branch/master/gis_fonctions.php#L11
Code alternatif
mes_fonctions.php
/**
* Filtre DDtoDMS - https://stackoverflow.com/questions/22316348/converting-degree-minutes-seconds-dms-to-decimal-in-php#:~:text=%3C%3F,tempma%20%3D%20%220.%22.
* convertir un format decimal en format DMS (Degre Minute Seconde)
* @param decimal $dec
* @return array
*/
function DDtoDMS($dec) {
// Converts decimal format to DMS ( Degrees / minutes / seconds )
$vars = explode(".",$dec);
$deg = $vars[0];
$tempma = "0.".$vars[1];
$tempma = $tempma * 3600;
$min = floor($tempma / 60);
$sec = $tempma - ($min*60);
return array("deg"=>$deg,"min"=>$min,"sec"=>$sec);
}
/**
* Filtre dec_to_dms_insa - affiche une coordonnée en format DMS ex./ 46°50'00.0"N 8°20'00.0"E
* alternative au filtre fourni par GIS https://git.spip.net/spip-contrib-extensions/gis/src/branch/master/gis_fonctions.php#L11
*
* @param decimal $latitude
* @param decimal $longitude
* @return string
*/
function dec_to_dms_insa($latitude, $longitude) {
$latitudeDirection = $latitude < 0 ? 'S': 'N';
$longitudeDirection = $longitude < 0 ? 'W': 'E';
$lat = DDtoDMS($latitude);
$lon = DDtoDMS($longitude);
return $lat['deg'].'°'.$lat['min'].'\''.$lat['sec'].'"'.$latitudeDirection.' '.
$lon['deg'].'°'.$lon['min'].'\''.$lon['sec'].'"'.$longitudeDirection;
}
Dans une boucle SPIP
<BOUCLE_gis(GIS){id_article}>
[(#SET{coord_dms,#LAT|dec_to_dms_insa{#LON}})]
<a href="https://www.google.com/maps/place/[(#GET{coord_dms}|urlencode)]/" class="spip_out">#GET{coord_dms}</a></div>
</BOUCLE_gis>
Changer l’apparence du marker cliqué
Si vous utilisez les popups (les bulles), il est possible de mettre un listener qui réagit au clic sur le marqueur. Vous pouvez alors ajouter une classe pour l’icone cliquée.
<style>
.marqactif {
border: 10px solid #3C99DC;
}
</style>
Et un listener sur la carte:
<script>
map[(#GET{id})].on('popupopen', function (e) {
$('.leaflet-marker-icon').removeClass("marqactif");
$(e.popup._source._icon).addClass("marqactif");
});
</script>
Parfois il faut adapter :
$('#map1').on('click', function(){
this.map.eachLayer(function(layer){
var callback_au_pop = function(e) {
if (this.feature) {
$('.leaflet-marker-icon').removeClass("marqactif");
$(e.popup._source._icon).addClass("marqactif");
}
}
// exposer le marqueur actif
layer.on('popupopen', callback_au_pop);
});
});
Utiliser d’autres plugins leaflet avec GIS : exemple de leaflet.GestureHandling
L’utilisation de plugins Leaflet nécessite d’injecter du javascript dans celui produit par GIS. On peut le faire avec le mécanisme décrit sur les forums et utilisé par Geodiversité.
Par exemple pour utiliser le plugin GestureHandling, qui améliore les comportement de défilement tactile et à le souris :
1. Récupérer le javascript et css des plugins, et les placer dans le dossier squelettes :
-
squelettes/lib/leaflet-gesture-handling.min.css
-
squelettes/lib/leaflet-gesture-handling.min.js
2. Charger le css du plugin sur notre page, par exemple en ajoutant dans le <head>
:
[<link href="(#CHEMIN{lib/leaflet-gesture-handling.min.css}|direction_css)" media="all" rel="stylesheet" type="text/css">]
3. Ajouter le javascript du plugin à celui de GIS, via le pipeline recuperer_fond
. Dans le cas d’un squelette, on peut le faire en ajoutant dans le fichier squelettes/mes_fonctions.php
:
$GLOBALS['spip_pipeline']['recuperer_fond'] .= '|mon_recuperer_fond';
function mon_recuperer_fond($flux) {
// Cartes / GIS : injecte le js du plugin leaflet dans GIS
if ($flux['args']['fond'] == 'javascript/gis.js') {
$flux['data']['texte'] .= "\n\n" . spip_file_get_contents(find_in_path('lib/leaflet-gesture-handling.min.js'));
}
return $flux;
}
Penser à passer par la page de gestion des plugins dans l’interface privée, pour que l’ajout du pipeline dans mes_fonctions.php
soit prise en compte !
4. Finalement, initialiser le plugin leaflet sur la carte GIS une fois celle-ci initialisée, en ajoutant par exemple :
<script>
jQuery('#mapID_DE_MA_CARTE').on('ready', function (map) {
mapID_DE_MA_CARTE.gestureHandling.enable();
});
</script>