Carnet Wiki

Astuces GIS

Version 39 — Décembre 2020 erational

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à :)

Permettre l’ajout d’une géolocalisation dans le formulaire public de modification d’événement

En partant de l’excellente contribution de mj
http://contrib.spip.net/Squelette-d-Agenda-grace-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.).

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 des fonds de carte personnalisés

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)

API Javascript de GIS

L’objet de chaque carte est accessible de deux manières : depuis une variable globale mapXX (ou XX est l’id 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 = ;
	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 latitude / longitude décimal en degré degrés (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 . Code alternatif 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 &lt; 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>