23
15 sep 2008

- Introduction -


Ce tutoriel vous permettra d'appréhender au mieux la notion de serveur WFS puis de la mettre en application au moyen d'un exemple concret en vous basant sur MapServer et OpenLayers.

- Qu'est-ce que la norme WFS et comment elle fonctionne
- Faire de MapServer un serveur WFS
- Afficher une carte WFS avec OpenLayers

- Qu'est-ce que la norme WFS et comment elle fonctionne -


Contrairement au format WMS (Web Map Service) orienté image (carte), le format WFS (Web Feature Service) permet au moyen d'une URL formatée d'interroger des serveurs cartographique afin de manipuler des objets géographiques.

Ce protocole initié par l'Open Géospatial Consortium (OGC) est devenu un standard implémenté par la quasi-totalité des serveurs cartographiques. L'objectif du WFS est de permettre un accès en édition aux données géographiques afin de pouvoir les modifier, les créer ou les supprimer.

L'interrogation d'un serveur WFS se fait via une URL envoyée au serveur à laquelle sont passés des arguments bien définis. Ces mots-clés une fois mis bout à bout forment un ensemble compréhensible par le serveur cartographique.

Pour un serveur cartographique le seul paramètre obligatoire est (associé à une opération de type GetFeature) :

  • NAME : nom de la couche à interroger.

Mais il en existe d'autres telles que :

  • BBOX : Etendue des données
  • VERSION : version du protocole
  • SERVICE : Type de service à utiliser (WFS)
  • SRS : Projection utilisée

Les différentes opérations qu'il est possible de réaliser sont les suivantes :

  • GetCapabilities : Description des capacités du serveur WFS. Il indiquera les type de données ainsi que les opérations supportées sur chacune d'entre elles.
  • DescribeFeatureType : Description de la structure de la donnée.
  • GetFeature : Récupère un (ou les) objet géographique de la couche. Il est également possible de spécifier l'objet désiré au travers de requête attributaire ou spatiale.
  • GetGmlObject : A web feature service may be able to service a request to retrieve element instances by traversing XLinks that refer to their XML IDs. In addition, the client should be able to specify whether nested XLinks embedded in returned element data should also be retrieved.
  • Transaction : Support des requêtes transactionnelles. Une transaction peut être composée d'un ensemble d'opérations sur les objets (création, modification, mise à jour, suppression).
  • LockFeature : Possibilité de bloquer l'accès à un objet géographique durant une transaction par la pose d'un verrou.

En se basant sur les opérations ci-dessus, il est possible de classer les serveurs WFS en 3 catégories :

  • Basic WFS : Celui-ci doit, au moins, implémenter les opérations suivantes : GetCapabilities, DescribeFeatureType and GetFeature operations.
  • XLink WFS : Celui-ci doit, en plus des opérations d'un Serveur WFS basique, supporter l'opération GetGmlObject
  • WFS Transactionnel : Celui-ci doit, en plus des opérations d'un Serveur WFS basique, apporter un support transactionnel. Même si cela n'est pas obligatoire, il pourra également être capable de réaliser des opérations de type GetGmlObject et/ou LockFeature opérations

Voici un exemple d'URL :

http: //localhost/cgi-bin/mapserv?map=/var/www/html/wms/africa.map&typename=Africa&SERVICE=WFS&VERSION=1.0.0&
REQUEST=GetFeature&SRS=EPSG%3A4326&BBOX=-67.851625,-85.0776875,107.929625,108.2816875

Une application WFS est composée de 3 parties essentielles : le serveur cartographique, les données et le client.

Un exemple de requête serait la suivante : une requête de type "GetFeature" est envoyée par le navigateur au serveur cartographique. Celui-ci, en fonction de sa configuration et des données vérifie la validité de cette requête en fonction de sa configuration et des données qu'il possède; ensuite, celui-ci retourne le ou les objets géographiques demandés.

Image WFS

- Faire de MapServer un serveur WFS -


Cette partie suppose que vous soyez déjà familier de l'environnement MapServer et que vous sachiez comment est constitué un MapFile. Dans le cas contraire, je vous invite à lire auparavant ce tutoriel : Tutorial MapServer.

Pour spécifier à MapServer de fonctionner en tant que serveur WMS, il sera nécessaire d'ajouter différents attributs à votre MapFile. Les blocs à modifier sont les suivants :

  • Un dans le bloc Web
  • Trois dans le bloc Layer
    • Le bloc Metadata
    • Le bloc Projection
    • L'attribut Dump

Pour le bloc Web il faut ajouter un sous bloc nommé METADATA qui est composé des éléments ci-dessous :


WEB
METADATA
"wfs_title" "WFS Demo Server"
"wfs_onlineresource" "localhost/cgi-bin/mapserv?map=/pathToMapFile/africa.map&?" ## Recommended
"wfs_srs" "epsg:4326" ## Recommended
END
END

Ensuite pour chaque couche il faudra ajouter deux blocs, le premier est obligatoire contrairement au second qui est optionnel mais fortement recommandé ainsi qu'un troisième attribut DUMP :


LAYER
METADATA
### WFS
"wfs_title" "Africa"
"gml_featureid" "NAME"
"gml_include_items" "all"
END
PROJECTION
"init=epsg:4326" ##recommended
END
DUMP TRUE
...
END

Pour un aperçu plus global vous pouvez également télécharger le MapFile qui a servi pour ce tutoriel.

- Afficher une carte WFS avec OpenLayers -


Pour interroger un serveur WFS vous devrez utiliser la classe OpenLayers.Layer.WFS.
Celle-ci prend en arguments obligatoires l'URL du serveur cartographique ainsi que le nom de la couche. Cela se fait de la manière suivante :


africaWFS = new OpenLayers.Layer.WFS(
"Africa WFS", "http://pathToMapServ/cgi-bin/mapserv?map=/pathToMapFile/africa.map&",
{typename: 'Africa'},
{ extractAttributes: true}
);

A noter que si vous désirez pouvoir utiliser les données attributaires de la couche vous devrez ajouter la propriété extractAttributes (true)

- Exemple avec une carte de l'Afrique -


A propos de l'auteur: 
Arnaud Vandecasteele

Fervent défenseur de l'Open Source, Arnaud s'est spécialisé dans le développement d'application cartographiques web. OpenLayers, PostGIS ou encore Django sont autant d'outils qu'il manipule au quotidien.
S'il n'est pas en face de son ordinateur, vous le retrouverez un GPS à la main en train de cartographier pour OpenStreetMap, de faire voler son drone ou sur un tatami !

Commentaires

J'ai configuré un WFS et j'arrive à récupérer les infos dans OpenLayer.
Par contre je voudrais pouvoir afficher dans une info bulle (popup) les valeurs des attributs de la couche WFS : comment accède t-on à ces valeurs ? (j'ai bien configuré avec extractAttributes: true dans la création du layer et "gml_include_items" "all" dans le mapfile).

ça doit être au niveau de l'objet feature mais dans la doc openlayer je ne trouve pas d'explication.

Merci d'avance si vous pouvez me renseigner

Bonjour,

Pour accéder aux données vecteur openlayers utilise feature.attributes.

Par exemple pour l'exemple ci-dessus voici le code utilisé :

options = {
hover: true,
onSelect: function(feature) {OpenLayers.Util.getElement('name').innerHTML = " Pays : " + feature.attributes.NAME},
selectStyle :feature_style
};
sf = new OpenLayers.Control.SelectFeature(africaWFS, options)

J'ai donc déclaré un nouveau control qui se déclenche lorsque l'objet est sélectionne. Vous pouvez faire exactement la même chose mais en ajoutant à l'intérieur de la fonction onSelect l'ouverture du Popup avec les infos rattachés.

J'essayerai de donner un exemple concret cette semaine.

Arnaud

Bonjour,

J'essaye de suivre votre tutoriel, mais j'obtiens des carrés rose à la place de ma carte.

Voici mon Mapfile

..............................................................................................;
# Map file created from QGIS project file C:/Essai/test.qgs
# Edit this file to customize for your map interface
# (Created with PyQgis MapServer Export plugin)
MAP
NAME "essai"
# Map image size
SIZE 1024 768
UNITS meters

EXTENT 823426.170040 1855953.400000 1000161.829960 1975552.600000
FONTSET 'C:/essai/fonts.txt'
SYMBOLSET 'C:/essai/symbols.txt'
PROJECTION
"init=EPSG:27571"
END

# Background color for the map canvas -- change as desired
IMAGECOLOR 255 255 255
IMAGEQUALITY 95
IMAGETYPE png

OUTPUTFORMAT
NAME png
DRIVER 'GD/PNG'
MIMETYPE 'image/png'
IMAGEMODE RGBA
EXTENSION 'png'
END
# Legend
LEGEND
IMAGECOLOR 255 255 255
STATUS ON
KEYSIZE 18 12
LABEL
TYPE BITMAP
SIZE MEDIUM
COLOR 0 0 89
END
END

# Web interface definition. Only the template parameter
# is required to display a map. See MapServer documentation
WEB

# Set IMAGEPATH to the path where MapServer should
# write its output.
IMAGEPATH 'C:/ms4w/Apache/htdocs/mapserver/tmp/'

# Set IMAGEURL to the url that points to IMAGEPATH
# as defined in your web server configuration
IMAGEURL '/mapserver/tmp/'

# WMS server settings
METADATA
'ows_title' 'essai'
'ows_onlineresource' 'http://localhost/cgi-bin/mapserv.exe?map=C:/essai/essai.map'
'ows_srs' 'EPSG:27571'
END

#Scale range at which web interface will operate
# Template and header/footer settings
# Only the template parameter is required to display a map. See MapServer documentation
TEMPLATE 'fooOnlyForWMSGetFeatureInfo'
END

LAYER
NAME 'Dep'
TYPE POLYGON
DUMP true
TEMPLATE fooOnlyForWMSGetFeatureInfo
EXTENT 823426.170040 1855953.400000 1000161.829960 1975552.600000
DATA 'C:/essai/Dep.shp'
METADATA
'ows_title' 'Dep'
END
STATUS ON
PROJECTION
"init=EPSG:27571"
END
CLASS
NAME 'Dep'
STYLE
SYMBOL 0
SIZE 7.0
OUTLINECOLOR 0 0 0
COLOR 1 144 50
END
END
END

END
..............................................................................................

et mon HTML







________________________________________________________________________________________

Voila comme promis mais avec un peu de retard le tutoriel pour afficher les données dans un popup
> http://geotribu.net/node/86

Bonjour,
je suis entrain de réaliser votre tuto très bien réalisé; Cependant quand je passe ma souris sur la carte d'Afrique la surbrillance reste des fois sur les pays.
Merci

Peut-être un petit temps de latence. Le javascript n'est pas connu pour son optimisation :)
Vivement TraceMonkey

Bonjour,

Quelle est la syntaxe pour réaliser une telle carte avec php mapscript s'il vous plaît ?

OpenLayers et php Mapscript sont deux technologies différentes (Javascriopt / PHP) ayant des objectifs différents. Il n'est pas possible de réaliser ce genre de traitements avec PHP Mapscript.

Merci pour votre réponse.

Je voulais dire qu'elle est la syntaxe pour construire le mapfile avec mapscript.

Depuis j'ai réussi avec des données issues du format EDIGéO :

$table = 'edigeo';
$con = pg_connect('dbname = mabase user = monuser password = monpass');
$map = ms_newMapObj('');
$extent = pg_query($con, 'select xmin(extent(the_geom)), ymin(extent(the_geom)), xmax(extent(the_geom)), ymax(extent(the_geom)) from '.$table);
$extent = pg_fetch_row($extent);
$map->setextent($extent[0], $extent[1], $extent[2], $extent[3]);
$map->set('width', 530);
$map->set('height', 400);
$map->set('units', MS_METERS);

$map->web->set('imagepath', 'tmp/');
$map->web->set('imageurl', '/tmp/');
$map->setMetadata('wfs_title', $table);
$map->setMetadata('wfs_onlineresource', 'mapserver/cgi-bin/mapserv?map=/monpath/tmp/'.$table.'.map&?');
$map->setMetadata('wfs_srs', 'epsg:4326');

$layer = ms_newLayerObj($map);
$layer->set('data', 'the_geom from '.$table.'');
$layer->set('connection', 'host=localhost dbname=mabase user=monuser password=monpass');
$layer->set('connectiontype', MS_POSTGIS);
$layer->set('status', MS_ON);
$layer->set('type', MS_LAYER_POLYGON);
$layer->set('name', $table);
$layer->setMetadata('wfs_title', $table);
$layer->setMetadata('gml_featureid', 'GID');
$layer->setMetadata('gml_include_items', 'all');
$layer->set('dump', TRUE);

$img = $map->draw();
$url = $img->saveWebImage();
$map->save('./tmp/'.$table.'.map');

pg_close($con);
?>




width.' height='.$map->height.'/>' ?>


Maintenant, je cherche comment optimiser l'affichage en fonction du nombre d'entité (j'ai plus de 3900 features) côté client. Si vous avez des pistes...

Ici votre image est générée dynamiquement depuis PhpMapScript. Pourquoi n'avoir pas simplement utilisé un simple mapfile? En effet, avec MapScript comment allez-vous utilisez OpenLayers?

Si vous utiliser toujours OpenLayers le mieux est de s'orienter vers la classe strategy.

Bonjour,

Le phpmapscript génére dynamiquement le mapfile (format WFS) que j'appelle depuis OpenLayers comme vous le décrivez dans le tuto.. Je génère l'image uniquement pour vérifier que le script fonctionne.

Je vais regarder la classe strategy.

Merci !

arrivez-vous à reprojeter la couche WFS en epsg:900913 (google mercator) dans openlayers ?

moi je lutte et je n'y arrive pas !!!!!!!!!!!!!!!!!!!!

Bonjour,

Je n'utilise que très rarement les cartes Google Maps.
Néanmoins, je vous propose de vous joindre à cette discussion sur le forumSIG où l'auteur a également les mêmes soucis.

Arnaud

Bonjour,

J'ai une petite question idiote à laquelle j'ai du mal à trouver une réponse :
Est-il possible de "poster" des features via MapServer dans une base PostGIS ou bien est-on obliger de passer par FeatureServer ou autre ?

Bonjour,

Non il n'est pas possible à d'utiliser MapServer comme un serveur WFS-T (cf site de MapServer).

GeoServer le permet.

Arnaud

Merci beaucoup, je n'avais pas vu la dernière section :
"This is just a basic WFS (read-only): transaction requests are not supported and probably never will given the nature of MapServer. GeoServer is recommended for those needing WFS-T support."

Bonjour,

Je viens de lire dans la doc de OpenLayers qu'ils comptaient supprimer la classe OpenLayers.Layer.WFS à la prochaine mise à jour majeure (la 3.0) : http://dev.openlayers.org/apidocs/files/OpenLayers/Layer/WFS-js.html. Ils conseillent d'utiliser OpenLayers.Layer.Vector à la place.

Je me serais bien contenté de la méthode présentée ici mais comme elle a l'air d'avoir une espérance de vie plutôt courte, je ne sais pas si c'est un si bon choix que ca. Par contre, j'ai beau eu chercher, je n'ai pas trouvé d'infos sur la façon dont utiliser la classe OpenLayers.Layer.Vector avec MapServer...

Bonjour,

Le WFS est un protocole. Protocole qu'implémente ensuite divers clients, dont OpenLayers.
Si c'est la partie code qui vous freine, vous pouvez directement utiliser la classe Vector à la place.
Cela sera complètement transparent.

En tout cas merci pour l'info, je ne savais pas que la classe WFS allait être à terme supprimée.

Arnaud

Bonjour,

Merci de votre réponse. J'avais laissé le sujet de côté pendant quelque temps, d'où le temps de ma réponse.

Cependant, c'est remplacer la classe WFS par la classe Vector qui me pose problème.

Quelques bouts de code :
Ceci ne fonctionne pas :

new OpenLayers.Layer.Vector(
	"estran",
	{
		strategies: [new OpenLayers.Strategy.BBOX()],
		protocol: new OpenLayers.Protocol.WFS(
		{
			url: "http://localhost/cgi-bin/mapserv?",
			featureType: "lanildut",
			version: "1.1.0",
			srsName: "EPSG:27582",
			featureNS: "http://127.0.0.1"
		}),
		projection: new OpenLayers.Projection("EPSG:27582"),
	}
)

Alors que avec ceci, aucun problème :

new OpenLayers.Layer.WFS(
	"lanildut",
	"http://localhost/cgi-bin/mapserv?",
	{
		map: MAPFILE,
		typename: "lanildut",
		projection: "EPSG:27582",
		extractAttributes: true,
		transparent: true
	},
	{style: featureStyle(1, "#000000", "#339933", 0.5)}
)

Il y a sans doute quelque chose que j'ai mal saisi avec les nouveaux paramètres qui se rajoutent tels que "FeatureNS"...
Des suggestions ?

Bonsoir,

je suis assez nouveau dans le monde du sig, et cette article est intéressant.
j'aurai besoin de faire ceci avec un serveur mapserver + openlayers :
afficher une image, pouvoir me déplacer dedans, zoomer, replacer, les bases en fait.
Mais aussi pouvoir activer/désactiver l'affichage de layers.
pas d'édition donc.

Seulement, avec wms, il renvoie une image, donc, une activation/désactivation de layers ne serait possible.
avec wfs alors ? mais comment procéder ?
Pouvez-vous m'aiguiller sur comment je dois m'orienter ?

Merci d'avance.

Bonjour et merci pour ces infos.
Je ne sais pas si je dois poster ici ou dans la section Android mais j'aurai besoin de récupérer un flux WFS à destination d'un Android.
Saurais-tu comment ouvrir le flux WFS avec un système Android ? Pour ce qui est de positionner un item, je sais faire, donc positionner une liste d'item ne doit pas être très compliqué, même en fonction d'un zoom ou autre. Ce qui m'intéresse est vraiment la lecture de ce flux WFS car j'aimerai avoir monFlux.mesItems grossièrement.
Si tu ne sais pas, aurais-tu une idée d'où trouver l'info ?
Merci.

Bonjour,
un flux WFS n'est rien d'autre que du XML.
Donc il suffit de parser ce flux dans Android - il doit bien y avoir une classe Java qui fait ça.

Fabien

En effet, merci.
J'ai commencé à récupérer ce fichier XML mais avant de le parser manuellement, j'ai trouvé une lib qui le fait déjà, alors autant ne pas réinventer la roue. Il s'agit de GeoTools que l'on peut trouver gratuitement ici sous forme de projet d'essai ou de fichier jar (zippé) tout simplement.
Pour ma part, j'ai eu besoin du fichier << net.opengis.wfs-2.7.0.1.jar >>.
Encore merci et bonne continuation !