Créer des Services Web JavaScript

Introduction

Cette section illustre comment vous pouvez construire simplement en JavaScript des services en réutilisant des services existants et en les chaînants ensembles pour en construire de nouveaux. Vous allez créer un fournisseur de services ZOO utilisant les services que vous avez vu auparavant et le serveur WFS en utilisant l’API ZOO. L’objectif final est d’interroger tous les POI inclus dans un tampon autour d’un objet linéaire et de les mettre en évidence à l’aide d’un masque autour de ce tampon. La capture d’écran ci-dessous vous montre le résultat attendu:

_images/BufferMaskAndRequest_Level_15_2014.png

Vous pouvez décomposer le résultat ci-dessus en deux différents : le masque autour du tampon et les points inclus dans le tampon. Donc, vous allez créer deux services différents : l’un appelé BufferMask et l’autre appelé BufferRequest.

Mais avant d’implémenter tout service JavaScript, nous présentons un rapidement la façon dont l’API ZOO doit être utilisée.

Comme précédemment, nous créons en premier un nouveau répertoire pour stocker les fichiers de notre nouveau fournisseur de services :

mkdir -p ~/jschains/

Aperçu de l’API ZOO

Le support JavaScript du ZOO-Kernel vous donne la possibilité d’exécuter des services implémentés en JavaScript côté serveur. Le JavaScript est un langage de programmation populaire, mais la plupart du temps utilisé côté client, disons à partir d’un navigateur, mais ici c’est un peu différent...

Pour supporter le langage JavaScript, le ZOO-Kernel utilise l’API SpiderMonkey pour créer un environnement d’exécution javascript à partir duquel il va charger votre fichier JS puis extraire la fonction correspondante au service et à l’exécuter en lui fournissant les paramètres pré-remplis par ses soins. L’environnement d’exécution JavaScript créé par le ZOO-Kernel dépend de votre configuration. Si vous avez placé ZOO-api.js et ZOO-proj4js.js dans le même répertoire que votre ZOO-Kernel, cela signifie que votre environnement chargera l’API ZOO et Proj4js avant votre service. Dans ce cas, vous pourrez donc accéder aux classes définies dans l’API ZOO JavaScript pour manipuler des données géographiques. Pour plus d’informations, merci de vous référer à la Documentation de l’API ZOO.

Même si il peut être utile pour exécuter le JavaScript côté serveur, vous devriez vous rappeler que certaines fonctions de base JavaScript que vous avez l’habitude d’utiliser n’existent pas ou ont un comportement différent. Par exemple, la simple fonction alert affichera des messages dans les journaux d’erreurs Apache plutôt que dans une fenêtre comme lorsqu’il est utilisé depuis un navigateur. La fonction alert peut être utilisée comme suit:

alert("My alert message");

Il n’y a pas non plus de XMLHttpRequest disponible dans l’environnement JavaScript où votre service s’exécute. Heureusement, le ZOO-Kernel expose une fonction C à l’environnement JavaScript nommée: JSRequest. Cette fonction vous permet depuis vos services JavaScript d’appeler d’autres services WPS (locaux ou distants) ou d’autres types de services OGC tels que le WFS. Lorsque vous utilisez l’API ZOO, il est possible d’appeler des services en utilisant une instance ZOO.Process [1], pour parser les réponses WPS utilisant ZOO.Format.WPS (cf. ref).

En comparant avec les services Python que vous avez déjà vu dans les sections précédentes, les fonctions correspondant à un service doivent toujours prendre trois paramètres en JavaScript : conf, inputs et outputs [2]. Néanmoins, comme le ZOO-Kernel n’est pas en mesure d’accéder aux espaces mémoires modifiés [3] par le code de service JavaScript, plutôt que de simplement retourner un entier comme en Python, vous devrez ici retourner à la fois une valeur de nombre entier représentant le statut de votre service et les valeurs outputs résultantes dans un objet JavaScript [4].. Vous pouvez voir dans ce qui suit un exemple de code de service JavaScript:

function SampleService(conf,inputs,outputs){
  var resultValue=someComputation(inputs);
  return
    {
        result: ZOO.SERVICE_SUCCEEDED,
        outputs: { "Result": { "mimeType": "application/json", "value": resultValue } }
    };
}

Afin d’implémenter les services dont nous aurons besoin pour obtenir notre service BufferRequest final, nous allons commencer par le plus simple.

Le service Mask

Dans cette section, nous apprendrons comment créer notre premier service JavaScript qui renvoit simplement un masque rectangulaire autour d’un objet sélectionné. Pour construire ce masque, nous utiliserons le service Buffer pour créer une zone tampon assez grande autour d’une géométrie sélectionnée pour couvrir une part importante de notre carte. Vous pouvez voir le résultat attendu dans la capture d’écran suivante:

_images/Mask_Level_12_2014.png

Comme précédemment, nous allons d’abord commencer par créer un ZCFG, ensuite nous créerons le code source JavaScript et finirons par le déploiement de notre fournisseur de services.

Le ZCFG

Ouvrez le fichier nommé ~/jschains/Mask.zcfg avec votre éditeur de texte favori et ajoutez le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[Mask]
 Title = Compute mask
 Abstract = Compute mask around a geometry
 processVersion = 1
 storeSupported = true
 statusSupported = true
 serviceProvider = foss4gws.js
 serviceType = JS
 <DataInputs>
  [InputData]
   Title = The feature
   Abstract = The feature to run the service with
   minOccurs = 1
   maxOccurs = 1
   <ComplexData>
    <Default>
    mimeType = text/xml
    encoding = utf-8
    </Default>
   </ComplexData>
 </DataInputs>
 <DataOutputs>
  [Result]
   Title = The resulting feature
   Abstract = The feature created by the service.
   <ComplexOutput>
     <Default>
     mimeType = application/json
     </Default>
   </ComplexOutput>
 </DataOutputs>

Ici, vous définissez simplement un ComplexData par défaut, à la fois pour inputData et Result : un GML et un GeoJSON respectivement [5].

Le Service JavaScript

Comme nous allons interroger le service Buffer de nombreuses fois dans nos différents services, nous commençons donc par définir une fonction Buffer comme suit. Elle utilise le ZOO.Process pour interroger le service Buffer que vous avez vu dans la section précédente.

Ouvrez un fichier nommé ~/jschains/foss4gws.js et ajoutez le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var zoo_url='http://localhost/cgi-bin/zoo_loader.cgi';
var mapfile="/var/data/maps/project_WS2014.map";
var mapserv_url="http://localhost/cgi-bin/mapserv?map="+mapfile;

function Buffer(inputData,bDist){

  // Create all required ZOO.formats
  var fJ=new ZOO.Format.JSON();
  var fGJ=new ZOO.Format.GeoJSON();
  var fWPS=new ZOO.Format.WPS();

  // Pass the value as json
  var myInputs = {
      InputPolygon: { type: 'complex', value: fGJ.write(inputData), mimeType: "application/json"},
      BufferDistance: {type: 'float', "value": bDist }
  };
  var myOutputs= { Result: { type: 'RawDataOutput', "mimeType": "application/json" } };
  var myProcess = new ZOO.Process(zoo_url,'BufferPy');
  var myExecuteResult=myProcess.Execute(myInputs,myOutputs);

  return fGJ.read(myExecuteResult);

}

De la ligne 12 à la ligne 15, vous donnez une chaîne GeoJSON (créé à partir de inputData) pour InputPolygon et, à la ligne 14, vous affectez à BufferDistance la valeur bDist. À la ligne 16, vous spécifiez que vous souhaitez récupérer Result sous la forme d’un RawDataOutput, ainsi vous n’aurez pas à parser la réponse WPS.

À la ligne 17, vous créez une instance ZOO.Process fournissant l’url du ZOO-Kernel et le nom du service. Puis, à la ligne 18, vous exécutez la requête en passant les entrées et sorties définies précédemment (des lignes 12 à 15).

Maintenant que vous avez votre fonction Buffer, il est temps de créer votre premier service JavaScript. Donc, éditez votre fichier foss4gws.js créé précédemment et ajoutez-y le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Mask(conf,inputs,outputs){

  // Create all required ZOO.formats
  var fGML=new ZOO.Format.GML();
  var fGJ=new ZOO.Format.GeoJSON();

  // Read the input GML
  var inputData=fGML.read(inputs["InputData"]["value"]);

  // Compute Buffer
  var bufferResultAsJSON=Buffer(inputData,0.015);

  // Create the Buffer result BBOX and store its geometry in a ZOO.Feature
  var bbox = new ZOO.Bounds();
  var bounds=bufferResultAsJSON[0].geometry.getVertices();
  for(var t in bounds){
    bbox.extend(bounds[t]);
  }
  var finalG=bbox.toGeometry();
  var result=new ZOO.Feature(finalG,{"name": "Result1000"});

  // Return the created feature
  return {
      result: ZOO.SERVICE_SUCCEEDED,
      outputs: { "Result": { mimeType: "application/json", value: fGJ.write(result) } }
  };

}

Publier et utiliser votre service

Maintenant que vous avez à la fois votre ZCFG et le code de votre service de prêt, vous devez le déployer en utilisant la commande suivante:

cp ~/jschains/* /usr/lib/cgi-bin

Vous êtes maintenant prêt à utiliser votre service JavaScript en chargeant cet url, cliquez sur une rue, puis cliquez sur le bouton “Mask”.

Le Service BufferMask

Dans cette section, nous implémenterons un service simple JavaScript qui sera capable de créer un trou dans le masque que vous avez créé dans la section précédente. Ce service sera utilisé pour mettre en évidence la zone tampon autour d’un objet sélectionné. Vous avez un aperçu du résultat attendu dans la capture d’écran suivante:

_images/BufferMask_Level_15_2014.png

Le ZCFG

Ouvrez le fichier nommé ~/jschains/BufferMask.zcfg avec votre éditeur de texte favori et copiez / collez le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[BufferMask]
 Title = Compute buffer mask
 Abstract = Compute buffer mask around a geometry
 processVersion = 1
 storeSupported = true
 statusSupported = true
 serviceProvider = foss4gws.js
 serviceType = JS
 <DataInputs>
  [InputData]
   Title = The feature
   Abstract = The feature to run the service with
   minOccurs = 1
   maxOccurs = 1
   <ComplexData>
    <Default>
    mimeType = text/xml
    encoding = utf-8
    </Default>
   </ComplexData>
 </DataInputs>
 <DataOutputs>
  [Result]
   Title = The resulting feature
   Abstract = The feature created by the service.
   <ComplexOutput>
     <Default>
     mimeType = application/json
     </Default>
   </ComplexOutput>
 </DataOutputs>

Ce ZCFG est similaire au précédent. Merci de vous référer aux commentaires de la section précédente pour plus d’informations.

Le Service JavaScript

Dans ce service, vous utiliserez le même code source (jusqu’à la ligne 19) que vous avez utilisé dans la section précédente. En effet, vous devez calculer le masque (Mask) comme vous l’avez fait avant puis calculer le tampon (Buffer) pour créer un trou dans le masque (à la ligne 22) pour exécuter le service Difference (des lignes 25 à 32).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 function BufferMask(conf,inputs,outputs){

   // Create all required ZOO.formats
   var fGML=new ZOO.Format.GML();
   var fGJ=new ZOO.Format.GeoJSON();

   // Read the input GML
   var inputData=fGML.read(inputs["InputData"]["value"]);

   // Compute Buffer
   var bufferResultAsJSON=Buffer(inputData,0.015);

   // Create the Buffer result BBOX
   var bbox = new ZOO.Bounds();
   var bounds=bufferResultAsJSON[0].geometry.getVertices();
   for(var t in bounds){
     bbox.extend(bounds[t]);
   }
   var finalG=bbox.toGeometry();

  // Compute Buffer standard buffer
  var bufferResultAsJSON=Buffer(inputData,0.0015);

  // Request Difference service using Buffer result and features in the BBOX
  var result=new ZOO.Feature(finalG,{"name": "Result1000"});
  var myProcess2 = new ZOO.Process(zoo_url,'DifferencePy');
  var myInputs2 = {
      InputEntity1: {
          type: 'complex',
          value: fGJ.write(finalG),
          mimeType: "application/json"
      },
      InputEntity2: {
          type: 'complex',
          value: fGJ.write(bufferResultAsJSON),
          mimeType: "application/json"
      }
  };
  var myOutputs2= {Result: {type: 'RawDataOutput',  mimeType: "application/json" } };
  var myExecuteResult4=myProcess2.Execute(myInputs2,myOutputs2);

   // Return the bbox
   var result=new ZOO.Feature(finalG,{"name": "Result1000"});
   return {
       result: ZOO.SERVICE_SUCCEEDED,
       outputs: { "Result": {mimeType: "application/json", value: myExecuteResult4 } }
   };

 }

Publier et utiliser votre service

Maintenant, vous pouvez publier votre service comme vous l’avez fait avant. Pour utiliser votre service, merci d’utiliser cet url.

Les Service BufferRequest

Dans cette section, nous allons créer un nouveau service : BufferRequest qui interrogera les POIs inclus dans le tampon (Buffer) autour d’un objet sélectionné [6]. Nous utiliserons la couche points1 servie en WFS par l’installation locale de MapServer. Vous pouvez voir dans la capture d’écran suivante, le résultat attendu :

_images/BufferRequest_Level_15_2014.png

Le ZCFG

Ouvrez le fichier nommé ~/jschains/BufferRequest.zcfg avec votre éditeur de texte favori et copiez / collez le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[BufferRequest]
 Title = Compute buffer request
 Abstract = Compute buffer request around a geometry
 processVersion = 1
 storeSupported = true
 statusSupported = true
 serviceProvider = foss4gws.js
 serviceType = JS
 <DataInputs>
  [InputData]
   Title = The feature
   Abstract = The feature to run the service with
   minOccurs = 1
   maxOccurs = 1
   <ComplexData>
    <Default>
    mimeType = text/xml
    encoding = utf-8
    </Default>
   </ComplexData>
 </DataInputs>
 <DataOutputs>
  [Result]
   Title = The resulting feature
   Abstract = The feature created by the service.
   <ComplexOutput>
     <Default>
     mimeType = application/json
     </Default>
   </ComplexOutput>
 </DataOutputs>

Le Service JavaScript

Comme dans le service précédent, nous allons calculer un tampon autour de l’objet en entrée. Mais ensuite nous allons interroger les POI disponibles dans l’étendue du tampon en utilisant une requête WFS pour les utiliser afin d’exécuter le service Intersection en utilisant le tampon initial. La requête WFS est utile pour limiter le nombre de points à utiliser avan d’interrogez le service Intersection.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function BufferRequest(conf,inputs,outputs){

  // Create all required ZOO.formats
  var fGJ=new ZOO.Format.GeoJSON();
  var fGML=new ZOO.Format.GML();

  // Read the input GML
  var inputData=fGML.read(inputs["InputData"]["value"]);

  // Compute Buffer
  var bufferResultAsJSON=Buffer(inputData,0.0015);

  // Create the Buffer result BBOX
  var bbox = new ZOO.Bounds();
  var bounds=bufferResultAsJSON[0].geometry.getVertices();
  for(var t in bounds){
    bbox.extend(bounds[t]);
  }

  // Request Intersection service using Buffer result and WFS request using the
  // BBOX
  var myProcess2 = new ZOO.Process(zoo_url,'Intersection');
  var req="&amp;SERVICE=WFS&amp;version=1.0.0&amp;request=GetFeature&amp;typename=poi1";
  req+="&amp;SRS=EPSG:4326&amp;BBOX=";
  var myInputs2 = {
    InputEntity1: {
      type: 'complex',
      value: fGJ.write(bufferResultAsJSON),
      mimeType: "application/json"
    },
    InputEntity2: {
      type: 'complex',
      xlink: mapserv_url+req+bbox.left+","+bbox.bottom+","+bbox.right+","+bbox.top,
      mimeType: "text/xml"
    }
  };
  var myOutputs2= {Result: { type: 'RawDataOutput', "mimeType": "application/json" } };
  var myExecuteResult4=myProcess2.Execute(myInputs2,myOutputs2);

  return {
    result: ZOO.SERVICE_SUCCEEDED,
    outputs: [ {name:"Result", mimeType: "application/json", value: myExecuteResult4} ]
  };

}

Warning

pour tirer avantage du système de cache du ZOO-Kernel, nous utilisons directement la requête WFS comme xlink:href plutôt que la valeur pour InputEntity2 (des lignes 31 à 34) et spécifions le mimeType text/xml (à la ligne 40). En effet, l’API ZOO n’utilise pas les mécanismes de cache interne.

Publier et utiliser votre service

Maintenant, vous pouvez publier votre service comme vous l’avez fait avant. Pour utiliser votre service, merci d’utiliser cet url.

Note

Vous pouvez cliquer sur “Buffer Request and Mask” pour obtenir le même résultat que celui présenté dans la capture d’écran initiale.

Ajouter Union dans la chaîne

Si vous consultez le lien suivant : http://localhost/zoo-demo-2016/routing.html vous devriez aussi pouvoir utiliser vos services depuis cette application.

Comme vous pouvez le voir dans la capture d’écran suivante, lors de l’utilisation du service Buffer en utilisant une collection d’objets contenant plus d’une géométrie, le résultat est composé de plusieurs géométries. Ainsi, l’exécution d’un service Buffer sur l’interface de routage se traduira par un tampon multiple:

_images/Buffer_Routing_Level_15_2014.png

Donc, pour obtenir le même résultat que celui que vous avez obtenu lors de la sélection d’une seule route, vous devez utiliser une Union de géométrie (entrée ou celle retournée par le service Buffer). Comme vous utilisez la ZOO-API JavaScript, vous pouvez simplement mettre à jour la fonction JavaScript Buffer que vous avez définie plus tôt, pour appeler d’abord l’Union de chaque géométrie disponible, dans une collection d’objets avant d’interroger (ou après avoir interrogé) le service Buffer. Heureusement, il y a déjà ce service Python disponible, son nom est UnionOne1, ainsi vous avez juste besoin de l’ajouter dans votre chaîne de service.

Voici le code final pour la fonction JavaScript de Buffer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Buffer(inputData,bDist){

  // Create all required ZOO.formats
  var fJ=new ZOO.Format.JSON();
  var fGJ=new ZOO.Format.GeoJSON();
  var fWPS=new ZOO.Format.WPS();

  // Call the UnionOne1 Service
  var myInputs0 = {
      InputPolygon: { type: 'complex', value: fGJ.write(inputData), mimeType: "application/json"},
      BufferDistance: {type: 'float', "value": bDist }
  };
  var myOutputs0= { Result: { type: 'RawDataOutput', "mimeType": "application/json" } };
  var myProcess0 = new ZOO.Process(zoo_url,'UnionOne1');
  var myExecuteResult0=myProcess0.Execute(myInputs0,myOutputs0);

  // Call the BufferPy Service
  var myInputs = {
      InputPolygon: { type: 'complex', value: myExecuteResult0, mimeType: "application/json"},
      BufferDistance: {type: 'float', "value": bDist }
  };
  var myOutputs= { Result: { type: 'RawDataOutput', "mimeType": "application/json" } };
  var myProcess = new ZOO.Process(zoo_url,'BufferPy');
  var myExecuteResult=myProcess.Execute(myInputs,myOutputs);

  return fGJ.read(myExecuteResult);

}

Conclusion

Après avoir compris comment les services d’opérations géométriques basiques fonctionnent, ici vous avez construit de nouveaux services JavaScript qui réutilisent ceux vu précédemment et les combinent de différentes façons. Ces services on été implémentés en utilisant l’API ZOO, composée par des fonctions C exposées par le ZOO-Kernel à l’environnement d’exécution des services JavaScript et des fichiers qui composent l’API-ZOO.

Footnotes

[1]La classe ZOO.Process utilise JSRequest (cf. ref). Vous aurez un exemple d’utilisation plus tard.
[2]Ainsi conf, inputs et outputs sont de simples objets JavaScript, similaires aux dictionnaires Python utilisés dans la section précédente.
[3]Comme conf, inputs et outputs.
[4]Vous pouvez également retourner un objet de configuration si vous obtenez des informations mises à jour depuis votre service JavaScript (tels que des cookies par exemple)
[5]En utilisant un des ZOO.formats disponibles, vous êtes également capable de supporter divers ComplexData à la fois en entrée et en sortie du service. Pour simplifier la présentation ici, vous utiliserez seulement ceux par défaut.
[6]Ainsi, dans le trou que vous avez créé dans la section précédente.