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

Last change on this file since 262 was 262, checked in by jmckenna, 13 years ago

fix formatting of workshop files

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

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