source: trunk/docs/workshop/2010/ogr_base_vect_ops.txt @ 256

Last change on this file since 256 was 256, checked in by djay, 13 years ago

Add the workshop 2010 material as Restructured text. Some issues with table of content and pdf production due to number of sub title level used in the workshop material.

File size: 57.3 KB
Line 
1.. _ogr_base_vect_ops:
2
3Creating OGR based Web Services
4##########################
5
6.. contents:: Table of Contents
7    :depth: 5
8    :backlinks: top
9
10Introduction
11*************
12
13In this part, we are going to create a ZOO ServicesProvider containing several Services based on the OGR C API or on the OGR Python module, which have also been placed in the ZOO installation on OSGeoLive. The intended goal is to use OGR and its GEOS based simple spatial functions as WPS Services.
14
15We will first start with the Boundary spatial function, which will be explained, codded and tested gradually as a ZOO Service. The same procedure will then be used to enable the Buffer, Centroid and Convex Hull functions. Once done, some multiple geometries processes such as Intersection, Union, Difference and Symetric Difference will be implemented through an `exercise <./exercise.html>`__ at the end of the workshop.
16
17As already said in the introduction, you have the choice to code your service in C or Python (or both!) during this workshop. Explanations will be based on the C part, but will be very helpful for those who will choose Python. Please decide according to your habits and preferences and tell your choice to the instructors. The results will be the same in both case.
18
19Preparing ZOO metadata file
20******************************
21
22A ZOO Service is a combination of a ZOO metadata file (``.zcfg``) and the runtime module for the corresponding implementation, which is commonly called ZOO Service Provider. We will first prepare a ``.zcfg`` file step-by-step. Please open your preferred text editor and edit a file named ``Boundary.zcfg`` in your ``/home/user/zoows/sources/zoo-services/ws_sp`` directory. First, you need to name the service between brackets at the top of the file, as the following
23
24::
25
26[Boundary]
27
28This name is very important, it is the name of the Service and so the name of the function defined in the Services Provider. A title and a brief abstract must then be added to inform clients on what the service can do:
29
30.. code-block:: guess
31
32    Title = Compute boundary.
33    Abstract = Returns the boundary of the geometry on which the method is invoked.
34
35Such metadata informations will be returned by a GetCapabilities request.
36
37You can also add other specific informations like the ``processVersion``. You can set if your ZOO Service can store its results, by setting the ``storeSupported`` parameter to true or false. You can also decide if the function can be run as a background task and inform on its current status, according to the ``statusSupported`` value :
38
39.. code-block:: guess
40
41    processVersion = 1
42    storeSupported = true
43    statusSupported = true
44
45In the main section of the ZOO Service metadata file, you must also specify two important things:
46
47  - ``serviceProvider``, which is the name of the C shared library containing the Service function or the Python module name.
48  - ``serviceType``, which defines the programming language to be used for the Service. (value can be C or Python depending on what language you have decided to use)
49
50C ServicesProvider Example :
51
52.. code-block:: guess
53
54    serviceProvider=ogr_ws_service_provider.zo
55    serviceType=C
56
57In this case you will get an ``ogr_ws_service_provider.zo`` shared library containing the Boundary function, placed in the same directory than ZOO Kernel.
58
59Python ServicesProvider Example :
60
61.. code-block:: guess
62
63    serviceProvider=ogr_ws_service_provider
64    serviceType=Python
65
66In this case, you will get an ``ogr_ws_service_provider.py`` file containing the Python code of your Boundary function.
67
68In the main section you can also add any other metadata information, as the following:
69
70.. code-block:: guess
71
72    <MetaData>
73        Title = Demo
74    </MetaData>
75
76The main metadata informations have been declared, so you can now define data input which will be used by the ZOO Service. You can define any input needed by the Service. Please note that you can request ZOO Kernel using more data input than defined in the ``.zcfg`` file without any problem, those values will be passed to your service without filtering. In the Boundary Service example, a single polygon will be used as input, the one on which to apply the Boundary function.
77
78The data input declarations are included in a DataInputs block. They use the same syntax as the Service itself and the input name is between brackets. You can also fill a title, an abstract and a MetaData section for the input. You must set values for the ``minOccurs`` and ``maxOccurs`` parameters, as they will inform ZOO Kernel which parameters are required to be able to run the Service function.
79
80.. code-block:: none
81
82    [InputPolygon]
83      Title = Polygon to compute boundary
84      Abstract = URI to a set of GML that describes the polygon.
85      minOccurs = 1
86      maxOccurs = 1
87      <MetaData>
88          Test = My test
89      </MetaData>
90
91
92The metadata defines what type of data the Service supports. In the Boundary example, the input polygon can be provided as a GML file or as a JSON string. Next step is thus to define the default and supported input formats. Both formats should be declared in a LitteralData or ComplexData block depending on their types. For this first example we will use ComplexData blocks only.
93
94.. code-block:: guess
95
96    <ComplexData>
97     <Default>
98       mimeType = text/xml
99       encoding = UTF-8
100     </Default>
101     <Supported>
102       mimeType = application/json
103       encoding = UTF-8
104     </Supported>
105    </ComplexData>
106
107
108Then, the same metadata information must be defined for the output of the Service, inside a DataOutputs block, as the following:
109
110.. code-block:: none
111
112    [Result]
113     Title = The created geometry
114     Abstract = The geometry containing the boundary of the geometry on which the method  was invoked.
115     <MetaData>
116       Title = Result
117     </MetaData>
118     <ComplexData>
119      <Default>
120       mimeType = application/json
121       encoding = UTF-8
122      </Default>
123      <Supported>
124       mimeType = text/xml
125       encoding = UTF-8
126      </Supported>
127     </ComplexData>
128
129A complete copy of this ``.zcfg`` file can be found at the following URL: http://zoo-project.org/trac/browser/trunk/zoo-services/ogr/base-vect-ops/cgi-env/Boundary.zcfg.
130
131
132Once the ZOO metadata file is modified, you have to copy it in the same directory than your ZOO Kernel (so in your case ``/usr/lib/cgi-bin``). Then you should be able to run the following request :
133
134http://localhost/zoo/?Request=DescribeProcess&Service=WPS&Identifier=Boundary&version=1.0.0
135
136The returned ProcessDescriptions XML document should look like the following :
137
138.. image:: ./images/Practical-introduction-to-ZOO-5.png
139   :width: 456px
140   :height: 157px
141   :align: center
142
143Please note that the GetCapabilities and DescribeProcess only need a ``.zcfg`` file to be completed. Simple, isn't it ? At this step, if you request ZOO Kernel for an Execute, you will get an ExceptionReport document as response, looking as the following :
144
145.. image:: ./images/Practical-introduction-to-ZOO-6.png
146   :width: 546px
147   :height: 80px
148   :align: center
149
150A similar error message will be returned if you try to run your Python Service :
151
152.. image:: ./images/Practical-introduction-to-ZOO-7.png
153   :width: 489px
154   :height: 87px
155   :align: center
156
157
158Implementing single geometry services
159*****************************************
160
161In order to learn the Services Provider creation and deployement step-by-step, we will first focus on creating a very simple one dedicated to the Boundary function. Similar procedure will then be used for the Buffer, Centroid and ConvexHull implementation.
162
163Your metadata is now ok, so you now must create the code of your Service. The most important thing you must be aware of when coding ZOO Services is that the function corresponding to your Service takes three parameters (internal maps datatype or  `Python dictionaries  <http://docs.python.org/tutorial/datastructures.html#dictionaries>`__) and returns an integer value representing the status of execution (SERVICE_FAILED or SERVICE_SUCCEEDED):
164
165  -  ``conf`` : The main environment configuration (corresponding to the ``main.cfg`` content)
166  - ``inputs`` : The requested / default inputs
167  - ``outputs`` : The requested / default outputs
168
169Boundary
170======
171
172C Version
173--------
174
175As explained before, ZOO Kernel will pass the parameters to your Service function in a specific datatype called maps. In order to code your Service in C language, you also need to learn how to access this datatype in read/write mode.
176
177The maps are simple map named linked list containing a name, a content map and a pointer to the next map in the list (or NULL if there is no more map in the list). Here is the datatype definition as you can find in the zoo-kernel/service.h file:
178
179.. code-block:: c
180
181    typedef struct maps{
182        char* name;
183        struct map* content;
184        struct maps* next;
185    } maps;
186
187The map included in the maps is also a simple linked list and is used to store Key Value Pair values. A map is thus a couple of name and value and a pointer to the next map in the list. Here is the datatype definition you can find in the zoo-kernel/service.h file:
188
189.. code-block:: guess
190
191    typedef struct map{
192        char* name;       /* The key */
193        char* value;      /* The value */
194        struct map* next; /* Next couple */
195    } map;
196
197
198As partially or fully filled datastructures will be passed by the ZOO Kernel to your Services, this means that you do not need to deal with maps creation but directly with existing map, in other words the content of each maps. The first function you need to know is getMapFromMaps (defined in the zoo-kernel/service.h file) which let you access to a specific map of a maps.
199
200This function takes three parameters listed bellow:
201
202  - ``m`` : a maps pointer representing the maps used to search the specific map
203  - ``name`` : a char* representing the name of the map you are searching for
204  - ``key`` : a specific key in the map named name
205
206For example, the following syntax will be used to access the InputPolygon value map of a maps named inputs, your C code should be:
207
208.. code-block:: guess
209
210    map* tmp=getMapFromMaps(inputs,"InputPolygon","value");
211
212Once you get the map, you can access the name or the value fields, using the following syntax :
213
214.. code-block:: guess
215
216    tmp->name
217    tmp->value
218
219As you know how to read and access the map fields from a maps, you can now learn how to write in such a datastructure. This is done by using the simple setMapInMaps function once again defined in zoo-kernel/service.h. The setMapInMaps function takes four parameters :
220
221  - ``m`` : a maps pointer you want to update,
222  - ``ns`` : the name of the maps you want you want to update,
223  - ``n`` : the name of the map you want to add or update the value,
224  - ``v`` : the value you want to set for this map.
225
226Here is an example of how to add or edit the values of some map in the Result maps from outputs :
227
228.. code-block:: guess
229
230    setMapInMaps(outputs,"Result","value","Hello from the C World !");
231    setMapInMaps(outputs,"Result","mimeType","text/plain");
232    setMapInMaps(outputs,"Result","encoding","UTF-8");
233
234
235Please note that the setMapInMaps function is able to create or update an existing map. Indeed, if a map called « value » allready exists, then its value will be updated automatically.
236
237Even if you will mainly use map from maps during this workshop, you can also add or update values in a map directly using the addToMap function defined in zoo-kernel/service.h. The addToMap function take three paramters :
238
239  - ``m`` : a map pointer you want to update,
240  - ``n`` : the name of the map you want to add or update the value,
241  - ``v`` : the value you want to set in this map.
242
243This datatype is really important cause it is used in every C based ZOO Services. It is also the same representation used in other languages but using their respectives datatypes. For Example in Python, the dictionaries datatype is used, so manipulation is much easier.
244
245Here is an example of the correspoding maps datatype used in Python language (this is a summarized version of the main configaration maps):
246
247.. code-block:: guess
248
249    main={
250      "main": {
251        "encoding": "utf-8",
252        "version": "1.0.0",
253        "serverAddress": "http://www.zoo-project.org/zoo/",
254        "lang": "fr-FR,en-CA"
255      },
256      "identification": {"title": "The Zoo WPS Development Server",
257        "abstract": "Development version of ZooWPS.",
258        "fees": "None",
259        "accessConstraints": "none",
260        "keywords": "WPS,GIS,buffer"
261      }
262    }
263
264As you know how to deal with maps and map, you are ready to code the first ZOO Service by using the OGR Boundary function.
265
266As already said in introduction we will use the MapServer WFS server available on OSGeoLive, so full WFS Response will be used as inputs values. As we will use the simple OGR Geometry functions like  `OGR_G_GetBoundary <http://www.gdal.org/ogr/ogr__api_8h.html#a797af4266c02846d52b9cf3207ef958>`__, only the Geometry object will be used rather than a full WFS Response. The first thing to do is to write a function which will extract the geometry definition from the full WFS Response. We will call it createGeometryFromWFS.
267
268Here is the code of such a function:
269
270.. code-block:: guess
271
272    OGRGeometryH createGeometryFromWFS(maps* conf,char* inputStr){
273      xmlInitParser();
274      xmlDocPtr doc = xmlParseMemory(inputStr,strlen(inputStr));
275      xmlChar *xmlbuff;
276      int buffersize;
277      xmlXPathContextPtr xpathCtx;
278      xmlXPathObjectPtr xpathObj;
279      char * xpathExpr="/*/*/*/*/*[local-name()='Polygon' or local-name()='MultiPolygon']";
280      xpathCtx = xmlXPathNewContext(doc);
281      xpathObj = xmlXPathEvalExpression(BAD_CAST xpathExpr,xpathCtx);
282      if(!xpathObj->nodesetval){
283        errorException(conf, "Unable to parse Input Polygon","InvalidParameterValue");
284        exit(0);
285      }
286      int size = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0;
287      xmlDocPtr ndoc = xmlNewDoc(BAD_CAST "1.0");
288      for(int k=size-1;k>=0;k--){
289        xmlDocSetRootElement(ndoc, xpathObj->nodesetval->nodeTab[k]);
290      }
291      xmlDocDumpFormatMemory(ndoc, &xmlbuff, &buffersize, 1);
292      char *tmp=strdup(strstr((char*)xmlbuff,"?>")+2);
293      xmlXPathFreeObject(xpathObj);
294      xmlXPathFreeContext(xpathCtx);
295      xmlFree(xmlbuff);
296      xmlFreeDoc(doc);
297      xmlCleanupParser();
298      OGRGeometryH res=OGR_G_CreateFromGML(tmp);
299      if(res==NULL){
300        errorException(conf, "Unable to call OGR_G_CreatFromGML","NoApplicableCode");
301        exit(0);
302      }
303      else
304        return res;
305    }
306
307
308The only thing we will focus on is the call to the errorException function used in the function body. This function is declared in the zoo-kernel/service_internal.h and defined in zoo-kernel/service_internal.c file. It takes three parameters as follow:
309
310  - the main environment maps,
311  - a char* representing the error message to display,
312  - a char* representing the error code (as defined in the WPS specification – Table 62).
313
314In other words, if the WFS response cannot be parsed properly, then you will return an ExceptionReport document informing the client that a problem occured.
315
316The function to extract the geometry object from a WFS Response is written, so you can now start defining the Boundary Service. Here is the full code for the Boundary Service:
317
318.. code-block:: guess
319
320    int Boundary(maps*& conf,maps*& inputs,maps*& outputs){
321      OGRGeometryH geometry,res;
322      map* tmp=getMapFromMaps(inputs,"InputPolygon","value");
323      if(tmp==NULL){
324        setMapInMaps(m,"lenv","message","Unable to parse InputPolygon");
325        return SERVICE_FAILED;
326      }
327      map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType");
328      if(strncmp(tmp1->value,"application/json",16)==0)
329        geometry=OGR_G_CreateGeometryFromJson(tmp->value);
330      else
331        geometry=createGeometryFromWFS(conf,tmp->value);
332      if(geometry==NULL){
333        setMapInMaps(m,"lenv","message","Unable to parse InputPolygon");
334        return SERVICE_FAILED;
335      }
336      res=OGR_G_GetBoundary(geometry);
337      tmp1=getMapFromMaps(outputs,"Result","mimeType");
338      if(strncmp(tmp1->value,"application/json",16)==0){
339        char *tmp=OGR_G_ExportToJson(res);
340        setMapInMaps(outputs,"Result","value",tmp);
341        setMapInMaps(outputs,"Result","mimeType","text/plain");
342        free(tmp);
343      }
344      else{
345        char *tmp=OGR_G_ExportToGML(res);
346        setMapInMaps(outputs,"Result","value",tmp);
347        free(tmp);
348      }
349      outputs->next=NULL;
350      OGR_G_DestroyGeometry(geometry);
351      OGR_G_DestroyGeometry(res);
352      return SERVICE_SUCCEEDED;
353    }
354
355As you can see in the code above, the mimeType of the data inputs passed to our Service is first checked:
356
357.. code-block:: guess
358
359    map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType");
360    if(strncmp(tmp1->value,"application/json",16)==0)
361      geometry=OGR_G_CreateGeometryFromJson(tmp->value);
362    else
363      geometry=createGeometryFromWFS(conf,tmp->value);
364
365Basicaly, if we get an input with a mimeType set to application/json, then we will use our ``OGR_G_CreateGeometryFromJson`` in other case, our ``createGeometryFromWFS`` local function.
366
367Please note that in some sense the data inputs are not really of the same kind. Indeed as we used directly ``OGR_G_CreateGeometryFromJson`` it means that the JSON string include only the geometry object and not the full GeoJSON string. Nevertheless, you can easily change this code to be able to use a full GeoJSON string, simply by creating a function which will extract the geometry object from the GeoJSON string (using the json-c library for instance, which is also used by the OGR GeoJSON Driver).
368
369Once you can access the input geometry object, you can use the  ``OGR_G_GetBoundary`` function and store the result in the res geometry variable. Then, you only have to store the value in the right format : GeoJSON per default or GML as we declared it as a supported output format.
370
371Please note that ZOO Kernel will give you pre-filled outputs values, so you will only have to fill the value for the key named value, even if in our example we override the mimeType using the text/plain value rather than the application/json (to show that we can also edit other fields of a map). Indeed, depending on the format requested by the client (or the default one) we will provide JSON or GML representation of the geometry.
372
373.. code-block:: guess
374
375      tmp1=getMapFromMaps(outputs,"Result","mimeType");
376      if(strncmp(tmp1->value,"application/json",16)==0){
377        char *tmp=OGR_G_ExportToJson(res);
378        setMapInMaps(outputs,"Result","value",tmp);
379        setMapInMaps(outputs,"Result","mimeType","text/plain");
380        free(tmp);
381      }
382      else{
383        char *tmp=OGR_G_ExportToGML(res);
384        setMapInMaps(outputs,"Result","value",tmp);
385        free(tmp);
386      }
387
388The Boundary ZOO Service is now implemented and you need to compile it to produce a Shared Library. As you just used functions defined in service.h (``getMapFromMaps``, ``setMapInMaps`` and ``addToMap``), you must include this file in your C code. The same requirement is needed to be able to use the ``errorException`` function declared in ``zoo-kernel/service_internal.h``, you also must link your service object file to the ``zoo-kernel/service_internal.o`` in order to use ``errorException`` on runtime. You must then include the required files to access the libxml2 and OGR C-API.
389
390For the need of the Shared Library, you have to put your code in a block declared as extern "C". The final Service code should be stored in a service.c file located in the root of the Services Provider directory (so in ``/home/zoows/sources/zoo-services/ws_sp``). It should look like this:
391
392.. code-block:: guess
393
394    #include "ogr_api.h"
395    #include "service.h"
396    extern "C" {
397    #include <libxml/tree.h>
398    #include <libxml/parser.h>
399    #include <libxml/xpath.h>
400    #include <libxml/xpathInternals.h>
401    <YOUR SERVICE CODE AND OTHER UTILITIES FUNCTIONS>
402    }
403
404The full source code of your Service is now ready and you must produce the corresponding Service Shared Object by compiling the code as a Shared Library. This can be done using the following command:
405
406.. code-block:: guess
407
408    g++ $CFLAGS -shared -fpic -o cgi-env/!ServicesProvider.zo ./service.c $LDFLAGS
409
410Please note that the ``CFLAGS`` and ``LDFLAGS`` environment variables values must be set before.
411
412The ``CFLAGS`` must contain all the requested paths to find included headers, so the path to the directories where the ``ogr_api.h``, ``libxml2`` directory, ``service.h`` and ``service_internal.h`` files are located. Thanks to the OSGeoLive environment, some of the provided tools can be used to retrieve those values : ``xml2-config`` and ``gdal-config``, both used with the ``--cflags`` argument. They will produce the desired paths for you.
413
414If you follow the instructions to create your ZOO Services Provider main directory in ``zoo-services``, then you should find the ZOO Kernel headers and source tree which is located in the ``../../zoo-kernel`` directory relatively to your current path (``/home/user/zoows/sources/zoo-services/ws_sp``). Note that you can also use a full path to the ``zoo-kernel`` directory but using relative path will let you move your sources tree somewhere else and keep your code compiling using exactly the same command line. So you must add a ``-I../../zoo-kernel`` to your ``CFLAGS`` to make the compiler able to find the ``service.h`` and ``service_internal.h`` files.
415
416The full ``CFLAGS`` definition should look like this:
417
418.. code-block:: guess
419
420    CFLAGS=`gdal-config --cflags` `xml2-config --clfags` -I../../zoo-kernel/
421
422Once you get the included paths correctly set in your ``CFLAGS`` , it is time to concentrate on the library we have to link against (defined in the ``LDFLAGS`` environment variable). In order to link against the gdal and libxml2 libraries, you can use the same tools than above using the ``--libs`` argument rather than ``--cflags``. The full ``LDFLAGS`` definition must look like this :
423
424.. code-block:: guess
425
426    LDFLAGS=`gdal-config --libs` `xml2-config --libs` ../../zoo-kernel/service_internal.o
427
428Let's now create a ``Makefile`` which will help you compiling your code over the time. Please write a short ``Makefile`` in the root of your ZOO Services Provider directory, containing the following lines:
429
430.. code-block:: guess
431
432    ZOO_SRC_ROOT=../../zoo-kernel/
433    CFLAGS=-I${ZOO_SRC_ROOT} `xml2-config --cflags` `gdal-config --cflags`
434    LDFLAGS=`xml2-config --libs` `gdal-config --libs`${ZOO_SRC_ROOT}/service_internal.o
435
436    cgi-env/ogr_ws_service_provider.zo: service.c
437        g++ ${CFLAGS} -shared -fpic -o cgi-env/ogr_ws_service_provider.zo ./service.c $ {LDFLAGS}
438    clean:
439        rm -f cgi-env/ogr_ws_service_provider.zo
440
441
442Using this ``Makefile``, you should be able to run ``make`` from your ZOO Service Provider main directory and to get the resulting ``ogr_ws_service_provider.zo`` file located in the ``cgi-env`` directory.
443
444The metadata file and the ZOO Service Shared Object are now both located in the ``cgi-env`` directory. In order to deploy your new ServicesProvider, you only have to copy the ZOO Service Shared Object and its corresponding metadata file in the directory where ZOO Kernel is located, so in ``/usr/lib/cgi-bin``. You must use a ``sudo`` command to achieve this task:
445
446.. code-block:: guess
447
448    sudo cp ./cgi-env/* /usr/lib/cgi-bin
449
450You should now understand more clearly the meannings of the ZOO Service Provider source tree ! The ``cgi-env`` directory will let you deploy your new Services or Services Provider in an easy way , simply by copying the whole cgi-env content in your ``cgi-bin`` directory.
451
452Please note that you can add the following lines to your ``Makefile`` to be able to type ``make install`` directly and to get your new Services Provider available for use from ZOO Kernel:
453
454.. code-block:: none
455
456    install:
457        sudo cp ./cgi-env/* /usr/lib/cgi-bin
458
459Your ZOO Services Provider is now ready to use from an Execute request passed to ZOO Kernel.
460
461Python Version
462------------
463
464For those using Python to implement their ZOO Services Provider, the full code to copy in ``ogr_ws_service_provider.py`` in ``cgi-env`` directory is shown bellow. Indeed, as Python is an interpreted language, you do not have to compile anything before deploying your service which makes the deployement step much easier:
465
466.. code-block:: guess
467
468    import osgeo.ogr
469    import libxml2
470
471    def createGeometryFromWFS(my_wfs_response):
472        doc=libxml2.parseMemory(my_wfs_response,len(my_wfs_response))
473        ctxt = doc.xpathNewContext()
474        res=ctxt.xpathEval("/*/*/*/*/*[local-name()='Polygon' or local- name()='MultiPolygon']")
475        for node in res:
476            geometry_as_string=node.serialize()
477            geometry=osgeo.ogr.CreateGeometryFromGML(geometry_as_string)
478            return geometry
479        return geometry
480
481    def Boundary(conf,inputs,outputs):
482        if inputs["InputPolygon"]["mimeType"]=="application/json":
483            geometry=osgeo.ogr.CreateGeometryFromJson(inputs["InputPolygon"]["value"])
484        else:
485            geometry=createGeometryFromWFS(inputs["InputPolygon"]["value"])
486        rgeom=geometry.GetBoundary()
487        if outputs["Result"]["mimeType"]=="application/json":
488            outputs["Result"]["value"]=rgeom.ExportToJson()
489            outputs["Result"]["mimeType"]="text/plain"
490        else:
491            outputs["Result"]["value"]=rgeom.ExportToGML()
492        geometry.Destroy()
493        rgeom.Destroy()
494        return 3
495
496We do not dicuss the functions body here as we already gave all the details before and the code was volontary made in a similar way.
497
498As done before, you only have to copy the ``cgi-env`` files into your ``cgi-bin`` directory:
499
500.. code-block:: guess
501
502    sudo cp ./cgi-env/* /usr/lib/cgi-bin
503
504A simple ``Makefile`` containing the install section can be written as the following :
505
506.. code-block:: none
507
508    install:
509        sudo cp ./cgi-env/* /usr/lib/cgi-bin/
510
511Finally, simply run make install from the ZOO Services Provider main directory, in order to deploy your ZOO Service Provider.
512
513
514Testing the Service using Execute Request
515----------------------------------
516
517The simple and unreadable way
518^^^^^^^^^^^^^^^^^^^^^^^^^
519
520Everybody should now get his own copy of the OGR Boundary Service stored as a ZOO Services Provider called ``ogr_ws_service_provider`` and deployed in the ZOO Kernel tree, so the following Execute request can be used to test the Service:
521
522`link <http://localhost/cgi-bin/zoo_loader.cgi?request=Execute&service=WPS&version=1.0.0&Identifier=Boundary&DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3Fmap%3D%2Fvar%2Fwww%2Fwfs.map%26SERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192>`__
523
524.. code-block:: guess
525
526    http://localhost/cgi-bin/zoo_loader.cgi?request=Execute&service=WPS&version=1.0.0&Identifier=Boundary&DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3Fmap%3D%2Fvar%2Fwww%2Fwfs.map%26SERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192
527
528As you can see in the url above, we use an URLEncoded WFS request to the MapServer WFS server available on OSGeoLive as a ``xlink:href`` key in the DataInputs KVP value, and set the ``InputPolygon`` value to Reference. The corresponding non encoded WFS request is as follow:
529
530::
531
532    http://localhost/cgi-bin/mapserv?map=/var/www/wfs.map&SERVICE=WFS&REQUEST=GetFeature&VERSION=1.0.0&typename=regions&SRS=EPSG:4326&featureid=regions.3192
533
534Please note that you can add ``lineage=true`` to the previous request if you need to get information about the input values used to run your Service. Furthermore, you may need to store the ExecuteResponse document of your ZOO Service to re-use it later. In this case you must add ``storeExecuteResponse=true`` to the previous request. Note that is an important thing as the behavior of ZOO Kernel is not exactly the same than when running without this parameter settled to true. Indeed, in such a request, ZOO Kernel will give you an ExecuteResponse document which will contain the attribute statusLocation, which inform the client where the ongoing status or the final ExecuteResponse will be located.
535
536Here is an example of what the ExecuteResponse would look like in case ``storeExecuteResponse`` was set to true in the request:
537
538.. image:: ./images/Practical-introduction-to-ZOO-7.png
539   :width: 610px
540   :height: 146px
541   :align: center
542
543Then, according to the statusLocation, you should get the ExecuteResponse as you get before using the previous request. Note that can be really useful to provide some caching system for a client application.
544
545You didn't specify any ResponseForm in the previous request, it is not requested and should return a ResponseDocument per default using the application/json mimeType as you defined in you zcfg file. Nevertheless, you can tell ZOO Kernel what kind of data you want to get in result of your query adding the attribute ``mimeType=text/xml`` to your ``ResponseDocument`` parameter. Adding this parameter to the previous request will give us the result as its GML representation :
546
547`link <http://localhost/cgi-bin/zoo_loader.cgi?request=Execute&service=WPS&version=1.0.0&Identifier=Boundary&DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3Fmap%3D%2Fvar%2Fwww%2Fwfs.map%26SERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192&ResponseDocument=Result@mimeType=text/xml>`__
548
549.. code-block:: guess
550
551    http://localhost/cgi-bin/zoo_loader.cgi?request=Execute&service=WPS&version=1.0.0&Identifier=Boundary&DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3Fmap%3D%2Fvar%2Fwww%2Fwfs.map%26SERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192&ResponseDocument=Result@mimeType=text/xml
552
553As defined by the WPS specifications, you can also ask for a ``RawDataOutput`` to get only the data without the full ``ResponseDocument``. To do that, you only have to replace the ``ResponseDocument`` of your request by ``RawDataOutput``, like in the following request :
554
555`link <http://localhost/cgi-bin/zoo_loader.cgi?request=Execute&service=WPS&version=1.0.0&Identifier=Boundary&DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3Fmap%3D%2Fvar%2Fwww%2Fwfs.map%26SERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192&RawDataOutput=Result@mimeType=application/json>`__
556
557.. code-block:: guess
558
559    http://localhost/cgi-bin/zoo_loader.cgi?request=Execute&service=WPS&version=1.0.0&Identifier=Boundary&DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3Fmap%3D%2Fvar%2Fwww%2Fwfs.map%26SERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192&RawDataOutput=Result@mimeType=application/json
560
561Please note that we go back to the default mimeType to directly obtain the JSON string as we will use this kind of request to develop our client application in the next section of this workshop.
562
563Now, you know how to ask ZOO Kernel to run service in background, ask for ``RawDataOutput`` specifying ``mimeType`` or any specific format to be returned by the Kernel. When you ask for ``ResponseDocument``, you can also specify to the ZOO Kernel that you want the result to be stored on the server side.
564
565To do such a thing, you have to set the attribute ``asReference`` as true and then the resulting ExecuteResponse will contain a Reference node including the href attribute to let you access the produced file. To be able to handle this, you have to add the extension parameter in your ``DataOutputs`` node in the corresponding ZCFG file.
566
567Here is a sample url which provide such a result:
568
569`link <http://localhost/cgi-bin/zoo_loader.cgi?request=Execute&service=WPS&version=1.0.0&Identifier=Boundary&DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3Fmap%3D%2Fvar%2Fwww%2Fwfs.map%26SERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192&ResponseDocument=Result@mimeType=application/json@asReference=true>`__
570
571.. code-block:: guess
572
573    http://localhost/cgi-bin/zoo_loader.cgi?request=Execute&service=WPS&version=1.0.0&Identifier=Boundary&DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3Fmap%3D%2Fvar%2Fwww%2Fwfs.map%26SERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192&ResponseDocument=Result@mimeType=application/json@asReference=true
574
575You can see bellow what kind of result can be expected :
576
577.. image:: ./images/screenshot-ZOO-asReference-attribute.png
578   :width: 620px
579   :height: 217px
580   :align: center
581
582Simplification and readability of request
583^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
584
585As you can see in the simple example we used since the begining of this workshop, it is sometimes hard to write the Execute requests using the GET method as it makes really long and complexe URLs. In the next requests examples, we will thus use the POST XML requests. First , here is the XML request corresponding to the previous Execute we used:
586
587.. code-block:: guess
588
589    <wps:Execute service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 ../wpsExecute_request.xsd">
590     <ows:Identifier>Boundary</ows:Identifier>
591     <wps:DataInputs>
592      <wps:Input>
593       <ows:Identifier>InputPolygon</ows:Identifier>
594       <ows:Title>Playground area</ows:Title>
595       <wps:Reference xlink:href="http://localhost/cgi-bin/mapserv?map=/var/www/wfs.map&amp;SERVICE=WFS&amp;REQUEST=GetFeature&amp;VERSION=1.0.0&amp;typename=regions&amp;SRS=EPSG:4326&amp;featureid=regions.3192"/>
596      </wps:Input>
597     </wps:DataInputs>
598     <wps:ResponseForm>
599      <wps:ResponseDocument>
600       <wps:Output>
601        <ows:Identifier>Result</ows:Identifier>
602        <ows:Title>Area serviced by playground.</ows:Title>
603        <ows:Abstract>Area within which most users of this playground will live.</ows:Abstract>
604       </wps:Output>
605      </wps:ResponseDocument>
606     </wps:ResponseForm>
607    </wps:Execute>
608
609In order to let you easily run the XML requests, a simple HTML form called ``test_services.html`` is available in your ``/var/www`` directory. You can access it using the following link :  http://localhost/test_services.html.
610
611Please open this page in your browser, simply fill the XML request content into the textarea field and click the « run using XML Request » submit button. You will get exactly the same result as when running your Service using the GET request. The screenshot above show the HTML form including the request and the ExecuteResponse document displayed in the iframe at the bottom of the page:
612
613.. image:: ./images/Practical-introduction-to-ZOO-8.png
614   :width: 573px
615   :height: 308px
616   :align: center
617
618The xlink:href value is used in the simplest way to deal with such data input. Obviously, you can also use a full JSON string of the geometry, as shown in the following XML Request example :
619
620.. code-block:: guess
621
622    <wps:Execute service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 ../wpsExecute_request.xsda">
623     <ows:Identifier>Boundary</ows:Identifier>
624     <wps:DataInputs>
625      <wps:Input>
626       <ows:Identifier>InputPolygon</ows:Identifier>
627       <wps:Data>
628        <wps:ComplexData mimeType="application/json">
629    { "type": "MultiPolygon", "coordinates": [ [ [ [ -105.998360, 31.393818 ], [ -106.212753, 31.478128 ], [ -106.383041, 31.733763 ], [ -106.538971, 31.786198 ], [ -106.614441, 31.817728 ], [ -105.769730, 31.170780 ], [ -105.998360, 31.393818 ] ] ], [ [ [ -94.913429, 29.257572 ], [ -94.767380, 29.342451 ], [ -94.748405, 29.319490 ], [ -95.105415, 29.096958 ], [ -94.913429, 29.257572 ] ] ] ] }
630        </wps:ComplexData>
631       </wps:Data>
632      </wps:Input>
633     </wps:DataInputs>
634     <wps:ResponseForm>
635      <wps:ResponseDocument>
636       <wps:Output>
637        <ows:Identifier>Result</ows:Identifier>
638        <ows:Title>Area serviced by playground.</ows:Title>
639        <ows:Abstract>Area within which most users of this playground will live.</ows:Abstract>
640       </wps:Output>
641      </wps:ResponseDocument>
642     </wps:ResponseForm>
643    </wps:Execute>
644 
645If everything went well, you should get the Boundary of the JSON geometry passed as argument, and so be sure that your Service support both GML and JSON as input data. Note that in the previous request, we added a ``mimeType`` attribute to the ``ComplexData`` node to specify that the input data is not in the default ``text/xml`` mimeType but passed as an ``application/json`` string directly. It is similar to add ``@mimeType=application/json`` as we discussed before.
646
647storeExecuteResponse parameter and GetStatus Service
648^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
649
650If you go in your local ``/home/user/zoows/sources/zoo-services/utils/status``, you'll find the code for a ServiceProvider which will provide the GetStatus service and the longProcess one. The last is a simple example to learn how to use the status variable from lenv section of the main configuration maps and the updateStatus function you have to call to take your status value into account. The main service provider is the GetStatus one, it is able to give you information about the current status value from a service running in background mode.
651
652You have to know that the ZOO Kernel will detect the presence of the GetStatus service and if it is available it will then return the link the corresponding Execute request.
653
654So now you will deploy the GetStatus and longProcess service on your local environment. As for each services, you shall be able to deploy the services simply by copying the cgi-env directory into your Apache ``cgi-bin`` directory. You can use the following command :
655
656.. code-block:: guess
657
658    sudo cp ~user/zoows/sources/zoo-services/utils/status/cgi-env/*{zcfg,zo} /usr/lib/cgi-bin
659
660For simple Services it is the right way to deploy Service Providers. But in this specific case you'll have also to add some special parameter in the main section of you main configuration file and to copy an xsl file used to replace on the fly in the ResponseDocument the percentCompleted attribute of the ProcessStarted node returned by the GetStatus service.
661
662So first edit you ``main.cfg`` file to add the following lines in your main section :
663
664.. code-block:: guess
665
666    rewriteUrl=call
667    dataPath=/var/www/data
668
669Here you define the path where the service is able to find the xsl file, specified in the dataPath parameter. You also tell the ZOO Kernel that you want to use the rewriteUrl we defined in the previous section.
670
671To finish your deployment, you'll have now to copy the xsl file in the defined dataPath directory. You can use the following command :
672
673.. code-block:: guess
674
675    cp ~/zoows/sources/zoo-services/utils/status/cgi-env/*xsl /var/www/data
676
677Now, if you run the following request to run the service longProcess :
678
679http://localhost/zoo/?request=Execute&service=WPS&version=1.0.0&Identifier=longProcess&DataInputs=&storeExecuteResponse=true
680
681You shall get the a XML document looking like the following:
682
683.. image:: ./images/Practical-introduction-to-ZOO-9.png
684   :width: 590px
685   :height: 155px
686   :align: center
687
688If you poll the statusLocation url provider in the answer you'll then be able to view the evolution of the percentCompleted attribute value growing, like you can see in the following screenshot.
689
690.. image:: ./images/Practical-introduction-to-ZOO-10.png
691   :width: 589px
692   :height: 146px
693   :align: center
694
695This won't be used during this workshop but can be useful for really time consuming services.
696
697
698Creating Services for other functions (ConvexHull and Centroid)
699========================================
700
701As the Boundary sample service code is available, you can now easily add ConvexHull and Centroid functions as they take exactly the same number of arguments : Only one geometry. The details for implementing and deploying the ConvexHull Service are provided bellow, and we will let you do the same thing for the Centroid one.
702
703C Version
704--------
705
706Please add first the following code to the service.c source code :
707
708.. code-block:: guess
709
710    int ConvexHull(maps*& conf,maps*& inputs,maps*& outputs){
711      OGRGeometryH geometry,res;
712      map* tmp=getMapFromMaps(inputs,"InputPolygon","value");
713      if(tmp==NULL){
714        setMapInMaps(conf,"lenv","message","Unable to fetch InputPolygon value.");
715        return SERVICE_FAILED;
716      }
717      map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType");
718      if(strncmp(tmp1->value,"application/json",16)==0)
719        geometry=OGR_G_CreateGeometryFromJson(tmp->value);
720      else
721        geometry=createGeometryFromWFS(conf,tmp->value);
722      if(geometry==NULL){
723        setMapInMaps(conf,"lenv","message","Unable to parse InputPolygon value.");
724        return SERVICE_FAILED;
725      }
726      res=OGR_G_ConvexHull(geometry);
727      tmp1=getMapFromMaps(outputs,"Result","mimeType");
728      if(strncmp(tmp1->value,"application/json",16)==0){
729        char* tmp=OGR_G_ExportToJson(res);
730        setMapInMaps(outputs,"Result","value",tmp);
731        setMapInMaps(outputs,"Result","mimeType","text/plain");
732        free(tmp);
733      }
734      else{
735        char* tmp=OGR_G_ExportToGML(res);
736        setMapInMaps(outputs,"Result","value",tmp);
737        free(tmp);
738      }
739      OGR_G_DestroyGeometry(geometry);
740      OGR_G_DestroyGeometry(res);
741      return SERVICE_SUCCEEDED;
742    }
743
744
745This new code is exactly the same as for the Boundary Service. The only thing we modified is the line where the  `OGR_G_ConvexHull <http://www.gdal.org/ogr/ogr__api_8h.html#7a93026cfae8ee6ce25546dba1b2df7d>`__ function is called (rather than the OGR_G_GetBoundary you used before). It is better to not copy and paste the whole function and find a more generic way to define your new Services as the function body will be the same in every case. The following generic function is proposed to make things simpler:
746
747.. code-block:: guess
748
749    int applyOne(maps*& conf,maps*& inputs,maps*& outputs,OGRGeometryH (*myFunc) (OGRGeometryH)){
750      OGRGeometryH geometry,res;
751      map* tmp=getMapFromMaps(inputs,"InputPolygon","value");
752      if(tmp==NULL){
753        setMapInMaps(conf,"lenv","message","Unable to fetch InputPolygon value.");
754        return SERVICE_FAILED;
755      }
756      map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType");
757      if(strncmp(tmp1->value,"application/json",16)==0)
758        geometry=OGR_G_CreateGeometryFromJson(tmp->value);
759      else
760        geometry=createGeometryFromWFS(conf,tmp->value);
761      if(geometry==NULL){
762        setMapInMaps(conf,"lenv","message","Unable to parse InputPolygon value.");
763        return SERVICE_FAILED;
764      }
765      res=(*myFunc)(geometry);
766      tmp1=getMapFromMaps(outputs,"Result","mimeType");
767      if(strncmp(tmp1->value,"application/json",16)==0){
768        char *tmp=OGR_G_ExportToJson(res);
769        setMapInMaps(outputs,"Result","value",tmp);
770        setMapInMaps(outputs,"Result","mimeType","text/plain");
771        free(tmp);
772      }
773      else{
774        char *tmp=OGR_G_ExportToGML(res);
775        setMapInMaps(outputs,"Result","value",tmp);
776        free(tmp);
777      }
778      outputs->next=NULL;
779      OGR_G_DestroyGeometry(geometry);
780      OGR_G_DestroyGeometry(res);
781      return SERVICE_SUCCEEDED;
782    }
783
784Then, a function pointer called myFunc rather than the full function name can be used. This way we can re-implement our Boundary Service this way:
785
786.. code-block:: guess
787
788    int Boundary(maps*& conf,maps*& inputs,maps*& outputs){
789      return applyOne(conf,inputs,outputs,&OGR_G_GetBoundary);
790    }
791
792Using this applyOne local function defined in the service.c source code, we can define other Services this way:
793
794.. code-block:: guess
795
796    int ConvexHull(maps*& conf,maps*& inputs,maps*& outputs){
797      return applyOne(conf,inputs,outputs,&OGR_G_ConvexHull);
798    }
799    int Centroid(maps*& conf,maps*& inputs,maps*& outputs){
800      return applyOne(conf,inputs,outputs,&MY_OGR_G_Centroid);
801    }
802
803The genericity of the applyOne function let you add two new Services in your ZOO Services Provider : ConvexHull and Centroid.
804
805Note that you should define MY_OGR_Centroid function before the Centroid one as  `OGR_G_Centroid <http://www.gdal.org/ogr/ogr__api_8h.html#23f5a19a81628af7f9cc59a37378cb2b>`__ don't return a geometry object but set the value to an already existing one and support only Polygon as input, so to ensure we use the ConvexHull for MultiPolygon. So please use the code bellow:
806
807.. code-block:: guess
808
809    OGRGeometryH MY_OGR_G_Centroid(OGRGeometryH hTarget){
810      OGRGeometryH res;
811      res=OGR_G_CreateGeometryFromJson("{\"type\": \"Point\", \"coordinates\": [0,0] }");
812      OGRwkbGeometryType gtype=OGR_G_GetGeometryType(hTarget);
813      if(gtype!=wkbPolygon){
814        hTarget=OGR_G_ConvexHull(hTarget);
815      }
816      OGR_G_Centroid(hTarget,res);
817      return res;
818    }
819
820To deploy your Services, you only have to copy the ``Boundary.zcfg`` metadata file from your cgi-env directory as ``ConvexHull.zcfg`` and ``Centroid.zcfg``. Then, you must rename the Service name on the first line to be able to run and test the Execute request in the same way you did before. You only have to set the Identifier value to ConvexHull or Centroid in your request depending on the Service you want to run.
821
822Note here that the GetCapabilities and DescribeProcess requests will return odd results as we didn't modified any metadata informations, you can edit the ``.zcfg`` files to set correct values. By the way it can be used for testing purpose, as the input and output get the same name and default/supported formats.
823
824Python Version
825------------
826
827.. code-block:: guess
828
829    def ConvexHull(conf,inputs,outputs):
830        if inputs["InputPolygon"]["mimeType"]=="application/json":
831            geometry=osgeo.ogr.CreateGeometryFromJson(inputs["InputPolygon"]["value"])
832        else:
833            geometry=createGeometryFromWFS(inputs["InputPolygon"]["value"])
834        rgeom=geometry.ConvexHull()
835        if outputs["Result"]["mimeType"]=="application/json":
836            outputs["Result"]["value"]=rgeom.ExportToJson()
837            outputs["Result"]["mimeType"]="text/plain"
838        else:
839            outputs["Result"]["value"]=rgeom.ExportToGML()
840        geometry.Destroy()
841        rgeom.Destroy()
842        return 3
843
844
845Once again, you can easily copy and paste the function for Boundary and simply modify the line where the Geometry method was called. Nevertheless, as we did for the C language we will give you a simple way to get things more generic.
846
847First of all, the first step which consists in extracting the InputPolygon Geometry as it will be used in the same way in each Service functions, so we will first create a function which will do that for us. The same thing can also be done for filling the output value, so we will define another function to do that automaticaly. Here is the code of this two functions (extractInputs and outputResult) :
848
849.. code-block:: guess
850
851    def extractInputs(obj):
852        if obj["mimeType"]=="application/json":
853            return osgeo.ogr.CreateGeometryFromJson(obj["value"])
854        else:
855            return createGeometryFromWFS(obj["value"])
856        return null
857
858    def outputResult(obj,geom):
859        if obj["mimeType"]=="application/json":
860            obj["value"]=geom.ExportToJson()
861            obj["mimeType"]="text/plain"
862        else:
863            obj["value"]=geom.ExportToGML()
864
865We can so minimize the code of the Boundary function to make it simplier using the following function definition :
866
867.. code-block:: guess
868
869    def Boundary(conf,inputs,outputs):
870        geometry=extractInputs(inputs["InputPolygon"])
871        rgeom=geometry.GetBoundary()
872        outputResult(outputs["Result"],rgeom)
873        geometry.Destroy()
874        rgeom.Destroy()
875        return 3
876
877Then definition of the ConvexHull and Centroid Services can be achieved using the following code:
878
879.. code-block:: guess
880
881    def ConvexHull(conf,inputs,outputs):
882        geometry=extractInputs(inputs["InputPolygon"])
883        rgeom=geometry.ConvexHull()
884        outputResult(outputs["Result"],rgeom)
885        geometry.Destroy()
886        rgeom.Destroy()
887        return 3
888
889    def Centroid(conf,inputs,outputs):
890        geometry=extractInputs(inputs["InputPolygon"])
891        if geometry.GetGeometryType()!=3:
892            geometry=geometry.ConvexHull()
893        rgeom=geometry.Centroid()
894        outputResult(outputs["Result"],rgeom)
895        geometry.Destroy()
896        rgeom.Destroy()
897        return 3
898 
899Note, that in Python you also need to use ConvexHull to deal with MultiPolygons.
900
901You must now copy the ``Boundary.zcfg`` file as we explained for the C version in ``ConvexHull.zcfg`` and ``Centroid.zcfg`` respectively and then, use make install command to re-deploy and test your Services Provider.
902
903Create the Buffer Service
904================
905
906We can now work on the Buffer Service, which takes more arguments than the other ones. Indeed, the code is a bit different from the one used to implement the Boundary, ConvexHull and Centroid Services.
907
908The Buffer service also takes an input geometry, but uses a BufferDistance parameter. It will also allow you to define LitteralData block as the BufferDistance will be simple integer value. The read access to such kind of input value is made using the same function as used before.
909
910C Version
911--------
912
913If you go back to the first Boundary Service source code, you should not find the following very complicated. Indeed, you simply have to add the access of the BufferDistance argument and modify the line whenthe  `OGR_G_Buffer <http://www.gdal.org/ogr/ogr__api_8h.html#1ca0bd5c0fcb4b1af3c3973e467b0ec0>`__ must be called (instead of OGR_G_GetBoundary). Here is the ful lcode :
914
915.. code-block:: guess
916
917    int Buffer(maps*& conf,maps*& inputs,maps*& outputs){
918      OGRGeometryH geometry,res;
919      map* tmp1=getMapFromMaps(inputs,"InputPolygon","value");
920      if(tmp==NULL){
921        setMapInMaps(conf,"lenv","message","Unable to fetch InputPolygon value.");
922        return SERVICE_FAILED;
923      }
924      map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType");
925      if(strncmp(tmp->value,"application/json",16)==0)
926        geometry=OGR_G_CreateGeometryFromJson(tmp->value);
927      else
928        geometry=createGeometryFromWFS(conf,tmp->value);
929      double bufferDistance=1;
930      tmp=getMapFromMaps(inputs,"BufferDistance","value");
931      if(tmp!=NULL)
932        bufferDistance=atof(tmp->value);
933      res=OGR_G_Buffer(geometry,bufferDistance,30);
934      tmp1=getMapFromMaps(outputs,"Result","mimeType");
935      if(strncmp(tmp1->value,"application/json",16)==0){
936        char *tmp=OGR_G_ExportToJson(res);
937        setMapInMaps(outputs,"Result","value",tmp);
938        setMapInMaps(outputs,"Result","mimeType","text/plain");
939        free(tmp);   
940      }
941      else{
942        char *tmp=OGR_G_ExportToGML(res);
943        setMapInMaps(outputs,"Result","value",tmp);
944        free(tmp);   
945      }
946      outputs->next=NULL;
947      OGR_G_DestroyGeometry(geometry);
948      OGR_G_DestroyGeometry(res);
949      return SERVICE_SUCCEEDED;
950    }
951
952The new code must be inserted in your service.c file and need to be recompiled and replace the older version of your ZOO Service Provider in the /usr/lib/cgi-bin/ directory. You must of course place the corresponding ZOO Metadata File in the same directory.
953
954As we explained before, ZOO Kernel is permissive in the sense that you can pass more arguments than defined in you zcfg file, so let's try using a copy of the ``Boundary.zcfg`` file renamed as ``Buffer.zcfg`` and containing the Buffer identifier. Then, please test your service using an Execute request as you did before. You will obtain the buffer result in a ResponseDocument.
955
956You may have noted that the above code check if a BufferDistance input was passed to the service. If not, we will use 1 as the default value, which explains why you do not have to use one more input to your previous queries.
957
958You can change the BufferDistance value used by your Service to compute Buffer of your geometry by adding it to the DataInputs value in your request. Note that using KVP syntaxe, each DataInputs are separated by a semicolon.
959
960So, the previous request:
961
962.. code-block:: guess
963
964    DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3FSERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192
965
966Can now be rewritten this way :
967
968.. code-block:: guess
969
970    DataInputs=InputPolygon=Reference@xlink:href=http%3A%2F%2Flocalhost%2Fcgi-bin%2Fmapserv%3FSERVICE%3DWFS%26REQUEST%3DGetFeature%26VERSION%3D1.0.0%26typename%3Dregions%26SRS%3DEPSG%3A4326%26FeatureID%3Dregions.3192;BufferDistance=2
971
972Setting BufferDistance value to 2 would give you a different result, then don't pass any other parameter as we defined 1 as the default value in the source code.
973
974Here you can find the same query in XML format to use from the  http://localhost/test_services.html HTML form :
975
976.. code-block:: guess
977
978    <wps:Execute service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 ../wpsExecute_request.xsda">
979     <ows:Identifier>Buffer</ows:Identifier>
980     <wps:DataInputs>
981      <wps:Input>
982       <ows:Identifier>InputPolygon</ows:Identifier>
983       <ows:Title>Playground area</ows:Title>
984       <wps:Reference xlink:href="http://localhost/cgi-bin/mapserv?map=/var/www/wfs.map&amp;SERVICE=WFS&amp;REQUEST=GetFeature&amp;VERSION=1.0.0&amp;typename=regions&amp;SRS=EPSG:4326&amp;featureid=regions.3192"/>
985      </wps:Input>
986      <wps:Input>
987       <ows:Identifier>BufferDistance</ows:Identifier>
988       <wps:Data>
989        <wps:LiteralData uom="degree">2</wps:LiteralData>
990       </wps:Data>
991      </wps:Input>
992     </wps:DataInputs>
993     <wps:ResponseForm>
994      <wps:ResponseDocument>
995       <wps:Output>
996        <ows:Identifier>Buffer</ows:Identifier>
997        <ows:Title>Area serviced by playground.</ows:Title>
998        <ows:Abstract>Area within which most users of this playground will live.</ows:Abstract>
999       </wps:Output>
1000      </wps:ResponseDocument>
1001     </wps:ResponseForm>
1002    </wps:Execute>
1003
1004Python Version
1005------------
1006
1007As we already defined the utility functions createGeometryFromWFS and outputResult, the code is as simple as this:
1008 
1009.. code-block:: guess
1010
1011    def Buffer(conf,inputs,outputs):
1012        geometry=extractInputs(inputs["InputPolygon"])
1013        try:
1014            bdist=int(inputs["BufferDistance"]["value"])
1015        except:
1016            bdist=10
1017        rgeom=geometry.Buffer(bdist)
1018        outputResult(outputs["Result"],rgeom)
1019        geometry.Destroy()
1020        rgeom.Destroy()
1021        return 3
1022
1023We simply added the use of inputs["BufferDistance"]["value"] as arguments of the Geometry instance Buffer method. Once you get this code added to your ogr_ws_service_provider.py file, simply copy it in the ZOO Kernel directory (or type make install from your ZOO Service Provider root directory). Note that you also need the ``Buffer.zcfg`` file detailled in the next section.
1024
1025The Buffer MetadataFile file
1026----------------------
1027
1028You must add BufferDistance to the Service Metadata File to let clients know that this Service supports this parameter. To do this, please copy your orginal ``Boundary.zcfg`` file as ``Buffer.zcfg`` and add the following lines to the DataInputs block :
1029
1030.. code-block:: none
1031
1032    [BufferDistance]
1033     Title = Buffer Distance
1034     Abstract = Distance to be used to calculate buffer.
1035     minOccurs = 0
1036     maxOccurs = 1
1037     <LiteralData>
1038      DataType = float
1039      <Default>
1040       uom = degree
1041       value = 10
1042      </Default>
1043      <Supported>
1044       uom = meter
1045      </Supported>
1046     </LiteralData>
1047
1048Note that as minOccurs is set to 0 which means that the input parameter is optional and don't have to be passed. You must know that ZOO Kernel will pass the default value to the Service function for an optional parameter with a default value set.
1049
1050You can get a full copy of the ``Buffer.zcfg`` file here :
1051
1052http://zoo-project.org/trac/browser/trunk/zoo-services/ogr/base-vect-ops/cgi-env/Buffer.zcfg
1053
1054You can now ask ZOO Kernel for GetCapabilities, DescribeProcess and Execute for the Buffer Service.
Note: See TracBrowser for help on using the repository browser.

Search

Context Navigation

ZOO Sponsors

http://www.zoo-project.org/trac/chrome/site/img/geolabs-logo.pnghttp://www.zoo-project.org/trac/chrome/site/img/neogeo-logo.png http://www.zoo-project.org/trac/chrome/site/img/apptech-logo.png http://www.zoo-project.org/trac/chrome/site/img/3liz-logo.png http://www.zoo-project.org/trac/chrome/site/img/gateway-logo.png

Become a sponsor !

Knowledge partners

http://www.zoo-project.org/trac/chrome/site/img/ocu-logo.png http://www.zoo-project.org/trac/chrome/site/img/gucas-logo.png http://www.zoo-project.org/trac/chrome/site/img/polimi-logo.png http://www.zoo-project.org/trac/chrome/site/img/fem-logo.png http://www.zoo-project.org/trac/chrome/site/img/supsi-logo.png http://www.zoo-project.org/trac/chrome/site/img/cumtb-logo.png

Become a knowledge partner

Related links

http://zoo-project.org/img/ogclogo.png http://zoo-project.org/img/osgeologo.png