Mise en place d'un webservice de discrétisation en utilisant PyWPS et R
06-01-2010
Introduction et objectifs
Les services de type WPS étant à mon humble avis intéressants et porteurs d’avenir, je me suis lancé dans la mise en place d’un service de ce type pour réaliser une opération complexe et souvent difficile à réaliser dans les logiciels classiques : la discrétisation (découpage en classes) d’une variable continue, en vue de la représenter sous la forme d’une carte en plages de couleurs (choroplèthe).
Après une petite recherche de solutions logicielles utilisables, j’ai sélectionné R et le package classInt pour réaliser la discrétisation, et PyWPS pour la gestion du service. A l’heure ou j‘écris la solution de WPS Zoo n’est malheureusement pas encore disponible pour tests, mais cela ne saurait tarder.
Contexte matériel et logiciel
Pour que ce service soit publiquement testable, il a été mis en place sur le présent serveur, une machine sous Linux Debian, mais a priori la procédure peut être appliquée aussi sur un serveur sous un autre OS (à vérifier).
Le serveur HTTP est naturellement Apache, configuré pour gérer les applications en cgi-bin. Il a été installé à partir des sources dans /usr/local/apache2.
Étapes de l’installation
1- Installer Python
Opération simple sous Debian, en utilisant apt-get. Sinon télécharger les sources et compiler. La version 2.5 minimum est recommandée. Sur GéoTests c’est la 2.6 qui est actuellement en place.
2- Installer R et les packages utiles
Pour pouvoir discrétiser une variable de manière automatique avec le logiciel R, il faut l’accompagner de packages optionnels.
L’installation de R peut se faire à partir de ses paquets debian, ou à partir des sources téléchargées, configurées et compilées. C’est cette dernière méthode que je préfère, car il me semble que le logiciel est ainsi adapté au mieux à la machine qui le fait tourner (type de CPU, bibliothèques C présentes, etc.).
Par défaut R s’installera dans /usr/local/bin/R, vérifiez que votre variable d’environnement PATH contient bien ce répertoire (ou créez un len symbolique dans /usr/bin).
Les packages R utiles dans notre cas sont :
- e1071 : utilisé par classInt
- classInt : les fonctions de discrétisation.
- XML : servira à convertir en vecteurs de données R les séries fournies en XML par le service WPS.
Pour installer un package R, il faut télécharger le package et lancer la commande :
bq. R CMD INSTALL fichier_du_package.tar.gz
Pour tester si cela fonctionne dans R, vous pouvez reproduire la session suivante qui va réaliser une discrtisation de Jenks avec les données d’exemple fournies dans le package classInt :
library(e1071)
library(classInt)
library(XML)
data(jenks71)
classIntervals(jenks71$jenks71, n = 4, style="jenks")
Les commandes “library” chargent les packages optionnels, data() récupère les données d’exemple, et classInt effecute concrêtement la discrétisation. Vous devriez obtenir la sortie suivante :
style: jenks
one of 161,700 possible partitions of this variable into 4 classes
[15.57,41.2] (41.2,68.45] (68.45,100.1] (100.1,155.3]
35 43 19 5
3- Installer la bibliothèque de fonctions RPy²
Cette bibliothèque de fonctions va nous permettre d’exécuter des commandes et des fonctions R directement dans Python, ce qui permet une plus grande intégration des outils dans notre traitement WPS.
Comme cette bibliothèque est listée dans l’index des ressources de Python, elle peut s’installer très facilement en utilisant la commande :
easy_install rpy2
Sinon on peut toujours récupérer les sources sur le site de cette bibliothèque.
4- Installer et configurer PyWPS
PyWPS est disponible sous la forme de paquetages debian téléchargeables sur le site du logiciel. Mais là encore, il est préfé ici le téléchargement des sources et l’installation par la commande :
python setup.py install
Cette commande va générer un fichier /usr/bin/wps.py, qu’il faut ensuite lier dans le répertoire cgi-bin d’Apache :
ln -s /usr/bin/wps.py /usr/local/apache2/cgi-bin/wps.py
Il faut ensuite copier le fichier /pywps/etc/pywps.cfg des sources vers le fichier /etc/pywps.cfg de la machine.
Ce fichier /etc/pywps.cfg doit être modifié pour être adapté à nos besoins, notamment :
- la valeur de “serveraddress” dans le bloc [wps], pour y insérer l’adresse du service, ici en l’occurence “http://www.geotests.net/cgi-bin/wps”.
- les informations d’identification dans le bloc [provider] (nom, localisation et qualités de l’administrateur du service).
- les paramètres d’exécution dans le bloc [server], particulièrement les chemins de sortie (qui sont à créer et rendre inscriptibles par l’user Apache).
Notes au passage que l’on peut activer le debogage (debug=true) et choisir le nom du fichier de log (qui contiendra les informations de débogage) dans ce dernier bloc.
Pour tester si PyWPS fonctionne, on peut lui lancer une requête de GetCapabilities qui décrira les processus (processes en anglais, c’est à dire les opérations de traitement) disponibles par défaut :
http://www.geotests.net/cgi-bin/wps?version=1.0.0&service=Wps&request=Getcapabilities
5- Création d’un process de discrétisation
La partie créative de notre procédure peut alors démarrer.
La première opération à faire est de nommer notre process et de l’ajouter à la liste des processes du serveur, en modifiant le fichier /usr/local/pywps/processes/__init__.py
J’ai nommé ma tentative “testRProcess”, ce qui donnera dans ce fichier :
"""Example process, which can be used with PyWPS"""
__all__ = ["exampleBufferProcess", "exampleXSLTProcess",
"exampleBufferNoInputsProcess",
"exampleLosProcess",
"ultimatequestionprocess","dummyprocess","testRProcess"]
Ensuite il faut naturellement écrire le fichier Python qui contiendra le code de notre process. Ce fichier devra être nommé testRProcess.py, logiquement, et se trouver dans ce même répertoire /usr/local/pywps/processes
Voici le contenu de ce fichier, qui sera décrit par la suite : testRProcess.py
Ce fichier est basé sur le code d’exempleBufferProcess, fourni avec PyWPS. Voici en quoi il diffère.
- Juste avant l’initialisation du process, j’ai ajouté quelques lignes.
from types import *
import os
os.environ['PYTHON_EGG_CACHE'] = '/tmp'
Elles permettent tout d’abord de charger la classe “types” de python, ce qui permettra de définir qu’un des paramètres de notre process, la méthode de discrétisation, sera de type String. Ensuite on trouve l’import de la classe “os” de python et son utilisation pour définir le répertoire temporaire dans lequel l’exécution pourra écrire ses données intermédiaires (“egg cache”).
- Dans l’initialisation WPS.init il faut personnaliser les paramètres, en indiquant l’identificateur du process (testRProcess), sa version, un résumé éventuel et le fait de ne pas avoir besoin de Grass.
- ensuite il faut définir les entrées et les sorties du process, c’est à dire la paramètres et le type de retour.
En entrée, on aura tout d’abord la variable à discrétiser, sous la forme d’un flux ou fichier XML. C’est un type “complexData” dans le jargon du WPS.
Ensuite, on trouve le paramètre du nombre de classes à produire, qui est de type “litteralInput”, entier, et le nom de la méthode de discrétisation à mettre en oeuvre, qui est aussi un “litteralInput” mais de type chaîne de caractères (String), et à choisir parmi les possibilités offertes par classInt.
Enfin la sortie du process sera un petit texte reprenant les bornes des classes et le nombre de leurs individus produits par la méthode (“litteralOuput” de type texte).
- les traitements proprement dits se réalisent dans la partie “execute” du process.
On importe les fonctions de RPy² par l’instruction :
bc. import rpy2.robjects as robjects
Ensuite on peut lancer les commandes R en Python directement, en utilisant la syntaxe :
bc. robjects.r(‘commande python’)
Ou en affectant un objet R à un objet Python :
objetPython = robjects.r.objetR
L’utilisation de ces deux méthodes nous permet de charger les packages R utiles, puis de fournir la variable à discrétiser par référence (URL d’un fichier XML) à la fonction qui va la transformer en tavleau (vecteur) R :
robjects.r('doc = xmlRoot(xmlTreeParse("%s"))' % self.getInputValue('donnees'))
On met en forme ce tableau pour qu’il soit composé de valeurs numériques gérables par la fonction de discrétisation :
jenksData = robjects.r('xmlSApply(doc, function(x) as.numeric(xmlSApply(x, xmlValue)))')
Et enfin on exécute cette fonction en lui fournissant les paramètres rcupérés :
out = str(ci(jenksData, n = self.getInputValue('nbClasses'), style = self.getInputValue('methode')))
(“ci” est la fonction R “classIntervals” récupérée sous forme de fonction Python pour plus de clarté).
La dernière instruction du process python permet de générer la sortie texte :
self.textOut.setValue(out)
Test
Pour tester le bon fonctionnement de notre process, il faut interroger le webservice, ce qui peut se faire en mode GET (par l’URL) ou en mode POST (par l’envoi de variables au travers d’un formulaire HTML ou d’une méthode AJAX).
Voici un exemple de requête par l’URL :
http://www.geotests.net/cgi-bin/wps?version=1.0.0&service=Wps&request=Execute&Identifier=testRProcess&datainputs=nbClasses=6;methode=quantile;donnees=http://www.geotests.net/test/jenks/values.xml
Et un exemple de requête par POST en PHP (utilisant curl pour construire la requête) :
http://www.geotests.net/test/jenks/requete_post.php
(source de ce fichier)
Les commentaires sont bienvenus, comme j’ai construit ce système sur plusieurs jours j’ai peut-être oublié un élément de configuration !
jan 9, 16:01
Merci pour cet excellent exemple de web processing.
En adaptant sensiblement votre code Python et en l’accompagnant d’un fichier de configuration du service (.zcfg, qui contient l’intégralité des méta-données concernant le service), nous pouvons facilement créer un ServiceProvider Python qui contienne un web service similaire en utilisant le projet ZOO (http://www.zoo-project.org)
Les liens et les balises [code] étant inactifs dans la rédaction de commentaire dans TextPattern, la suite de nos explications sont hébergées ici (désolé):
http://www.postgis.fr/node/500
Cordialement,
L’équipe ZOO Project
jan 11, 09:57
Merci à vous pour ce complément utile, l’impatience de pouvoir tester Zoo grandit ! :-)