Actions - Ajoutez des boutons personnalisés déclenchant des requêtes PostgreSQL
Principe
Ce module permet d’ajouter une ou plusieurs actions dans l’interface web. Le concept a été inspiré par les actions QGIS, qui peuvent être utilisées pour exécuter des scripts dans QGIS.
Exemple d’une action d’entité :
À l’heure actuelle, le seul moteur pour les actions Lizmap est les requêtes PostgreSQL (Python ne sera pas pris en charge). Vous pouvez utiliser la puissance de PostgreSQL et PostGIS pour ajouter une logique spécifique à votre application cartographique.
Lorsque l’utilisateur clique sur un bouton d’action, une requête est envoyée par Lizmap Web Client à la base de données PostgreSQL, avec les données de contexte actuelles (étendue de la carte, ID de l’entité, etc.).
Une fonction spécifique lizmap_get_data
est appelée avec ces paramètres, et renvoie une réponse GeoJSON contenant une ou plusieurs entités générées par la requête SQL pour l’action donnée.
Lizmap Web Client exécutera ensuite quelques callbacks à partir de cette réponse :
zoomer ou centrer sur la géométrie retournée,
sélectionner les entités d’une autre couche intersectant la géométrie renvoyée,
afficher un message, etc.
Trois contextes d’action sont pris en charge :
project
: un menu d’action est ajouté dans la barre de menu de gauche lorsque la carte a au moins une action de projet. Le nouveau panneau affiche un sélecteur d’action et un bouton pour déclencher l’action sélectionnée.layer
: le sélecteur d’action est affiché dans le panneau d”informations visible en cliquant sur le bouton (i) à droite du nom de la couche.features
: des boutons d’action sont ajoutés dans la barre d’outils contextuelle pour les entités de la couche, permettant de déclencher une action spécifique à chaque entité.
Astuce
Pour le contexte project
, la base de données par défaut est utilisée (fichier profiles.ini.php
dans le chapitre configuration).
Exemple du sélecteur d’action :
Démonstration
Vous pouvez vérifier la démo sur les bornes d’incendie sur le site de démonstration.
Cliquez sur une borne incendie et
Soit sélectionnez les bâtiments qui sont à 150m
Ou trouvez la borne incendie la plus proche
Pré-requis
Certaines connaissances en
SQL
etJSON
Pour utiliser le callback
select
, votre couche doit avoir la sélection activée pour la couche. Voir Table attributaire - Configurer la table attributaire et la sélection des entitésLa couche doit publiée en WFS (voir WFS/OAPI) et la clé primaire doit aussi être publiée dans les Propriétés de la couche.
Configurer l’outil
À l’heure actuelle, les actions ne peuvent pas être configurées à partir de l’extension Lizmap dans QGIS. Un fichier de configuration JSON spécifique doit être rédigé et placé à côté du projet QGIS dans le même répertoire. Ce fichier répertorie les actions PostgreSQL à ajouter dans la carte.
Avertissement
Dans Lizmap 3.7, la syntaxe JSON a changé.
Si vous utilisez l’ancienne syntaxe JSON, vous recevrez un avertissement dans Lizmap, vous invitant à migrer vers une version plus récente de la syntaxe.
- Chaque action est caractérisée par un
name
, untitle
, unscope
,layers
, uneicon
, quelquesoptions
facultatives, les propriétés
style
,callbacks
etconfirm
peuvent être utilisées.
Une action peut être proposée pour une liste de
layers
: les identifiants de couche QGIS doivent être utilisés dans le tableaulayers
.Une action peut avoir une liste de``callbacks``
Exemple avec ce fichier de configuration JSON, nom fire_hydrant_actions.qgs.action
si le fichier de projet QGIS est nommé fire_hydrant_actions.qgs
. Dans ce projet, il existe une couche vectorielle appelée Borne incendie
avec l’ID de couche interne emergency_fire_hydrant_04132268_86fb_4d5e_a426_ce3133494091
(vous pouvez obtenir l’ID interne de la couche QGIS avec l’expression @layer_id
)
[
{
"name": "buffer_150",
"title": "Buildings in the fire hydrant area (150m)",
"scope": "feature",
"layers" : [
"emergency_fire_hydrant_04132268_86fb_4d5e_a426_ce3133494091"
],
"confirm": "Do you want to select buildings within 150m from this fire hydrant ?",
"icon": "icon-home",
"options": {
"buffer_size": 150,
"other_param": "yes"
},
"style": {
"graphicName": "circle",
"pointRadius": 6,
"fill": true,
"fillColor": "lightred",
"fillOpacity": 0.3,
"stroke": true,
"strokeWidth": 4,
"strokeColor": "red",
"strokeOpacity": 0.8
},
"callbacks": [
{"method": "zoom"},
{"method": "select", "layerId": "building_90f7692a_0ae2_4a7d_91de_b63cddb92963"}
]
},
{
"name": "closest_fire_station",
"title": "Find the closest fire station from this fire hydrant",
"scope": "feature",
"layers" : [
"emergency_fire_hydrant_04132268_86fb_4d5e_a426_ce3133494091"
],
"confirm": "Do you want to select the closest fire station from this fire hydrant ?",
"icon": "icon-resize-small",
"options": {},
"style": {
"graphicName": "circle",
"pointRadius": 6,
"fill": true,
"fillColor": "lightred",
"fillOpacity": 0.3,
"stroke": true,
"strokeWidth": 4,
"strokeColor": "red",
"strokeOpacity": 0.8
},
"callbacks": [
{"method": "zoom"},
{"method": "select", "layerId": "stations_1a71d61f_cb99_4ac4_8bd4_86304af9be44"}
]
}
]
Le fichier de configuration JSON répertorie les actions déclarées.
Chaque action est un objet défini par :
un
name
qui est l’identifiant de l’action.un
title
qui est utilisé comme étiquette dans l’interface Lizmapun
scope
(contexte) qui peut être :project
,layer
oufeature
un
icon
qui est affiché sur le bouton d’action (Voir la documentation Bootstrap). Un icône SVG peut être utilisé à la place d’une icône bootstrap en tant qu’arrière-plan des boutons d’action de la fenêtre contextuelle. Utilisez un chemin relatif des médias (Média).une propriété optionnelle
confirm
, contenant du texte. Si elle est définie, une boîte de dialogue de confirmation sera affichée à l’utilisateur pour lui demander si l’action doit réellement être lancée ou non. Utilisez-la si l’action peut modifier certaines données dans votre base de données.un objet
options
, donnant quelques paramètres supplémentaires pour cette action. Vous pouvez ajouter tous les paramètres nécessaires. Notez que ces paramètres sont codés en dur et ne peuvent pas être modifiés par l’utilisateur.un objet
style
permettant de configurer le style de géométrie renvoyé. Il suit les attributs de style OpenLayers.un objet
callbacks
permet de déclencher certaines actions après le retour de la géométrie générée. Ils sont définis par un nom demethod
, qui peut actuellement être :zoom
: zoome vers la géométrie retournéeselect
: sélectionner les entités d’une couche donnée coupant la géométrie renvoyée. La couche cible avec l’identifiant interne QGIS doit être ajoutée dans la propriétélayerId
. Dans l’exemple, les entités de la couche contenant les bâtiments, l’IDbuilding_90f7692a_0ae2_4a7d_91de_b63cddb92963
sera sélectionnéredraw
: redessine (rafraîchit) une couche donnée dans la carte. L’ID QGIS de la couche cible doit être ajouté dans la propriétélayerId
.
Comment Lizmap utilise ce fichier de configuration pour lancer des actions
Lizmap détecte la présence de ce fichier de configuration, et ajoute la logique nécessaire lorsque la carte se charge.
Par exemple, pour les actions features
, lorsque l’utilisateur clique sur un objet d’une des couches d’action sur la carte, le panneau contextuel affiche les données de l’entité. En haut de chaque entité du panneau de la popup, une barre d’outils affiche un bouton par action de couche. Le titre de l’action sera affiché en survolant le bouton d’action.
Chaque bouton déclenche l’action correspondante, s’il n’est pas encore actif (sinon il désactive et efface la géométrie dans la carte).
l’application Lizmap vérifie si l’action est bien configuré,
crée la requête PostgreSQL
SELECT public.lizmap_get_data(json)
avec les paramètres écrits en JSON, et l’exécute dans la base de données PostgreSQL de la couche. (Voir exemple ci-dessous)Cette requête retourne un GeoJSON qui est ensuite affiché sur la carte.
Si des
callbacks
ont été configurés, ils sont lancés (selection
,zoom
,redraw
).Un événement Lizmap
actionResultReceived
est émis avec les données renvoyées et les propriétés d’action. Cela permet aux scripts JavaScript définis par l’utilisateur d’utiliser les résultats de l’action.
La requête PostgreSQL créée est construite par Lizmap Web Client et utilise la fonction PostgreSQL lizmap_get_data(json)
qui doit être créée au préalable dans la table de la base de données PostgreSQL. Cette fonction utilise également une fonction plus générique query_to_geojson(text)
qui transforme n’importe quelle chaîne de requête PostgreSQL en une sortie GeoJSON.
Voici un exemple ci-dessous de la requête exécutée dans la base de données PostgreSQL par Lizmap Web Client en interne,
pour la configuration d’exemple donnée ci-dessus,
quand les utilisateurs cliquent sur le bouton d’action buffer_150,
pour l”entité ayant l’identifiant
2592251664
de la coucheFire hydrants
correspondant à la table PostgreSQL
fire_hydrant_actions.emergency_fire_hydrant
:
SELECT public.lizmap_get_data('{
"lizmap_repository": "features",
"lizmap_project": "fire_hydrant_actions",
"action_name": "buffer_150",
"action_scop": "feature",
"layer_name": "Fire hydrant",
"layer_schema": "fire_hydrant_actions",
"layer_table": "emergency_fire_hydrant",
"feature_id": 2592251664,
"map_center": "POINT(3.4345918 43.63399141565576)",
"map_extent": "POLYGON((3.429635077741169 43.63175113378633,3.439548522258832 43.63175113378633,3.439548522258832 43.63623161401291,3.429635077741169 43.63623161401291,3.429635077741169 43.63175113378633))",
"wkt": "",
"buffer_size":150,
"other_param": "yes"
}') AS data;
Vous pouvez voir que Lizmap crée des paramètres JSON avec toutes les informations nécessaires et exécute la fonction PostgreSQL lizmap_get_data(text)
.
L”étendue et le centre de la carte sont également envoyés en tant que paramètres au format WKT (projection EPSG:4326
) et peuvent être utilisés dans la fonction PostgreSQL.
Fonctions PostgreSQL obligatoires
Vous devez créer ces fonctions PostgreSQL :
query_to_geojson(text)
qui renvoie un texte GeoJSON valide à partir de n’importe quelle requête SELECTlizmap_get_data(text)
qui est la « tour de contrôle » des actions Lizmap : elle crée une requête spécifique pour chaque action basée sur les paramètres, puis exécute la requête et renvoie le GeoJSON
Le code SQL suivant est un exemple pour vous aider à créer les fonctions nécessaires. Évidemment, vous devez les adapter à vos besoins.
-- Returns a valid GeoJSON from any query
CREATE OR REPLACE FUNCTION query_to_geojson(datasource text)
RETURNS json AS
$$
DECLARE
sqltext text;
ajson json;
BEGIN
sqltext:= format('
SELECT jsonb_build_object(
''type'', ''FeatureCollection'',
''features'', jsonb_agg(features.feature)
)::json
FROM (
SELECT jsonb_build_object(
''type'', ''Feature'',
''id'', id,
''geometry'', ST_AsGeoJSON(ST_Transform(geom, 4326))::jsonb,
''properties'', to_jsonb(inputs) - ''geom''
) AS feature
FROM (
SELECT * FROM (%s) foo
) AS inputs
) AS features
', datasource);
RAISE NOTICE 'SQL = %s', sqltext;
EXECUTE sqltext INTO ajson;
RETURN ajson;
END;
$$
LANGUAGE 'plpgsql'
IMMUTABLE STRICT;
COMMENT ON FUNCTION query_to_geojson(text) IS 'Generate a valid GEOJSON from a given SQL text query.';
-- Create a query depending on the action, layer and feature and returns a GeoJSON.
CREATE OR REPLACE FUNCTION public.lizmap_get_data(parameters json)
RETURNS json AS
$$
DECLARE
feature_id varchar;
layer_name text;
layer_table text;
layer_schema text;
action_name text;
sqltext text;
datasource text;
ajson json;
BEGIN
action_name:= parameters->>'action_name';
feature_id:= (parameters->>'feature_id')::varchar;
layer_name:= parameters->>'layer_name';
layer_schema:= parameters->>'layer_schema';
layer_table:= parameters->>'layer_table';
-- Action buffer_150
-- Performs a buffer on the geometry
IF action_name = 'buffer_150' THEN
datasource:= format('
SELECT %1$s AS id,
''Buildings within 150m of the fire hydrant have been selected'' AS message,
ST_Buffer(geom, 150) AS geom
FROM "%2$s"."%3$s"
WHERE osm_id = ''%1$s''
',
feature_id,
layer_schema,
layer_table
);
ELSEIF action_name = 'closest_fire_station' THEN
-- Draw a line to the closest fire station
datasource:= format('
WITH tmp_hydrant AS (
SELECT geom FROM fire_hydrant_actions.emergency_fire_hydrant WHERE osm_id = ''%1$s''
)
SELECT
id, name, ST_Distance(hydrant.geom, stations.geom),
''The closest is : '' || stations.name || '', '' || ST_Distance(hydrant.geom, stations.geom)::integer || ''m, flying air distance'' AS message,
ST_MakeLine(stations.geom, hydrant.geom) AS geom,
stations.id AS station_id
FROM
fire_hydrant_actions.stations stations,
tmp_hydrant hydrant
ORDER BY ST_Distance(hydrant.geom, stations.geom)
LIMIT 1
',
feature_id
);
ELSE
-- Default : return geometry
datasource:= format('
SELECT
%1$s AS id,
''The geometry of the object have been displayed in the map'' AS message
geom
FROM "%2$s"."%3$s"
WHERE id = %1$s
',
feature_id,
layer_schema,
layer_table
);
END IF;
SELECT query_to_geojson(datasource)
INTO ajson
;
RETURN ajson;
END;
$$
LANGUAGE 'plpgsql'
IMMUTABLE STRICT;
COMMENT ON FUNCTION public.lizmap_get_data(json) IS 'Generate a valid GeoJSON from an action described by a name, PostgreSQL schema and table name of the source data, a QGIS layer name, a feature id and additional options.';
La fonction
lizmap_get_data(json)
est fournie ici à titre d’exemple. Puisqu’il s’agit du point d’entrée clé, vous devez l’adapter à vos besoins. Il vise à créer une requête pour chaque action, créée dynamiquement pour les paramètres donnés, et à renvoyer une représentation GeoJSON des données par rapport à la requête. Vous devriez avoir une seule entité renvoyée : utilisez l’agrégation si nécessaire. Dans l’exemple ci-dessus, nous utilisons la méthodeformat
pour définir le texte de la requête et la fonctionquery_to_geojson
pour renvoyer le GeoJSON pour cette requête.Vous pouvez utiliser tous les paramètres donnés (nom d’action, schéma de données source et nom de table, identifiant d’entité, nom de couche QGIS) pour créer la requête appropriée pour votre/vos action(s), en utilisant les clauses PostgreSQL
IF THEN ELSIF ELSE
. Voir le contenu de la variableparameters
dans l’exemple ci-dessus, contenant certaines des propriétés du fichier de configuration JSON et certaines propriétés de la couche QGIS :le répertoire et le projet de la carte :
lizmap_repository
&lizmap_project
le nom de l’action
action_name
, par exemplebuffer_500
. Vous devez utiliser un mot simple avec uniquement des lettres, des chiffres et_
,le contexte de l’action
action_scope
, par exemplefeature
,Le nom de la couche QGIS (comme dans la légende QGIS):
layer_name
, par exempleFire hydrant
, seulement pour les actionsfeature
.le schema PostgreSQL de la table
layer_schema
et nom de la tablelayer_table
pour cette couche, seulement pour les contextesfeature
etlayer
l’objet feature id
feature_id
, qui correspond à la valeur du champ clé primaire de l’objet popup, seulement pour les actionsfeature
,les autres propriétés données dans le fichier de configuration JSON, dans la propriété
options
, commebuffer_size
qui vaut150
dans l’exemplele centre de la carte
map_center
et l’étendue de la cartemap_extent
Le
IF ELSE
est utilisé pour faire une requête différente, construite dans la variabledatasource
, en vérifiant le nom de l’actionSi les données de retour contiennent un champ
message
, comme illustré dans l’exemple ci-dessus, le texte contenu dans ce champ sera affiché dans la carte dans une bulle de message.La géométrie renvoyée par la fonction sera affichée sur la carte.
Vous pouvez utiliser votre fonction pour éditer certaines données dans votre base de données, avant de renvoyer un GeoJSON. Pour ce faire, vous devez remplacer la propriété
IMMUTABLE
parVOLATILE
. Veuillez L’UTILISER AVEC PRÉCAUTION !
Actions et scripts JavaScript définis par l’utilisateur
Étant donné que Lizmap Web Client déclenche un événement actionResultReceived
à chaque fois que l’utilisateur clique sur un bouton d’action et que les données sont renvoyées (en même temps que la géométrie du résultat est dessinée sur la carte), vous pouvez utiliser votre propre code JavaScript pour ajouter une logique après l’affichage du résultat.
Voir aussi
Chapitre Ajouter votre propre JavaScript
Par exemple, ici nous écrivons simplement dans la console du navigateur le contenu reçu :
lizMap.events.on({
actionResultReceived: function(e) {
// QGIS Layer id
var layerId = e.layerId;
console.log('Layer ID = ' + layerId);
// Feature ID, which means the value of the primary key field
var featureId = e.featureId;
console.log('Feature ID = ' + featureId);
// Action item with its name and other properties: name, title, options, styles, etc.
var action = e.action;
console.log('Action properties = ');
console.log(action);
// Features returned by the action
var features = e.features;
console.log('Returned object = ');
console.log(features);
}
});
Vous pouvez utiliser ces données à votre guise dans votre code JS.
Les actions peuvent également être exécutées à partir de scripts JavaScript externes : vous pouvez utiliser les méthodes publiques des actions pour exécuter une action, ou réinitialiser l’action active.
// Run an action
lizMap.mainLizmap.action.runLizmapAction(actionName, scope = 'feature', layerId = null, featureId = null, wkt = null);
// Reset the action
lizMap.mainLizmap.action.resetLizmapAction()
Une géométrie WKT, en EPSG:4326
, peut également être envoyée en tant que paramètre supplémentaire. Cela n’est possible que lors de l’exécution de l’action avec JavaScript. Cela permet d’envoyer une géométrie à utiliser par la fonction d’action PostgreSQL lizmap_get_data
en tant que propriété de la variable SQL parameters
. (par exemple pour obtenir des données d’une autre table avec des géométries intersectant cette géométrie WKT transmise)