Version 7 (modified by djay, 14 years ago) |
---|
Creating WPS compliant OGR based Web Services
Introduction
In this part, we are going to create a ZOO ServiceProvider? 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.
We 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 at the end of the workshop.
As 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.
Preparing ZOO metadata file
A 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:
[Boundary]
This 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 :
Title = Compute boundary. Abstract = Returns the boundary of the geometry on which the method is invoked.
Such metadata informations will be returned by a GetCapabilities? request.
You 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 :
processVersion = 1 storeSupported = true statusSupported = true
In the main section of the ZOO Service metadata file, you must also specify two important things:
- serviceProvider, which is the name of the C shared library containing the Service function or the Python module name.
- 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)
C ServiceProvider? Example :
serviceProvider=ogr_ws_service_provider.zo serviceType=C
In 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.
Python ServiceProvider? Example :
serviceProvider=ogr_ws_service_provider serviceType=Python
In this case, you will get an ogr_ws_service_provider.py file containing the Python code of your Boundary function.
In the main section you can also add any other metadata information, as the following:
<MetaData> Title = Demo </MetaData>
The 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.
The 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 aMetaData 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.
[InputPolygon] Title = Polygon to compute boundary Abstract = URI to a set of GML that describes the polygon. minOccurs = 1 maxOccurs = 1 <MetaData lang="en"> Test = My test </MetaData>
The 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.
<ComplexData> <Default> mimeType = text/xml encoding = UTF-8 </Default> <Supported> mimeType = application/json encoding = UTF-8 </Supported> </ComplexData>
Then, the same metadata information must be defined for the output of the Service, inside aDataOutputs block, as the following:
[Result] Title = The created geometry Abstract = The geometry containing the boundary of the geometry on which the method was invoked. <MetaData lang="en"> Title = Result </MetaData> <ComplexData> <Default> mimeType = application/json encoding = UTF-8 </Default> <Supported> mimeType = text/xml encoding = UTF-8 </Supported> </ComplexData>
A 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
Once 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 :
http://localhost/zoo/?Request=DescribeProcess&Service=WPS&Identifier=Boundary&version=1.0.0
The returned ProcessDescriptions? XML document should look like the following :
Please 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 :
A similar error message will be returned if you try to run your Python Service :
Implementing single geometry processes
In 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.
Your 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) and returns an integer value representing the status of execution (SERVICE_FAILED or SERVICE_SUCCEEDED) :
- conf : The main environment configuration (corresponding to the main.cfg content)
- inputs : The requested / default inputs
- outputs : The requested / default outputs
Boundary
C Version
As 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.
The 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 :
typedef struct maps{ char* name; struct map* content; struct maps* next; } maps;
The map included in the maps is also a simple linked list and is used to store Key Value Pair values. Amap is thus a couple of name and value and a pointer to the next element in the list. Here is the datatype definition you can find in the zoo-kernel/service.h file :
typedef struct map{ char* name; /* The key */ char* value; /* The value */ struct map* next; /* Next couple */ } map;
As 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.
This function takes three parameters listed bellow :
- m : a maps pointer representing the maps used to search the specific map
- name : a char* representing the name of the map you are searching for
- key : a specific key in the map named name
For example, the following syntax will be used to access the InputPolygon? value map of a maps named inputs, your C code should be :
map* tmp=getMapFromMaps(inputs,"InputPolygon","value");
Once you get the map, you can access the name or the value fields, using the following syntax :
tmp->name tmp->value
As 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 addToMap function once again defined in zoo- kernel/service.h. The addToMap function also takes three parameters :
- m : a map pointer you want to update,
- n : the name of the map you want to add or update the value,
- v : the value you want to set for this map.
Here is an example of how to add or edit the content map of a maps called outputs :
addToMap(outputs->content,"value","Hello from the C World !"); addToMap(outputs->content,"mimeType","text/plain"); addToMap(outputs->content,"encoding","UTF-8");
Please note that the addToMap 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.
This 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.
Here is an example of a maps used in Python language (this is a summarized version of the main configaration maps) :
main={ "main": { "encoding": "utf-8", "version": "1.0.0", "serverAddress": "http://www.zoo-project.org/zoo/", "lang": "fr-FR,en-CA" }, "identification": {"title": "The Zoo WPS Development Server", "abstract": "Development version of ZooWPS.", "fees": "None", "accessConstraints": "none", "keywords": "WPS,GIS,buffer" } }
As you know how to deal with maps and map, you are ready to code the first ZOO Service by using the OGR Boundary function.
As already said in introduction we will use the Geoserver 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, 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.
Here is the code of such a function :
OGRGeometryH createGeometryFromWFS(maps* conf,char* inputStr){ xmlInitParser(); xmlDocPtr doc = xmlParseMemory(inputStr,strlen(inputStr)); xmlChar *xmlbuff; int buffersize; xmlXPathContextPtr xpathCtx; xmlXPathObjectPtr xpathObj; char * xpathExpr="/*/*/*/*/*[local-name()='Polygon' or local-name()='MultiPolygon']"; xpathCtx = xmlXPathNewContext(doc); xpathObj = xmlXPathEvalExpression(BAD_CAST xpathExpr,xpathCtx); if(!xpathObj->nodesetval){ errorException(conf, "Unable to parse Input Polygon","InvalidParameterValue"); exit(0); } int size = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0; xmlDocPtr ndoc = xmlNewDoc(BAD_CAST "1.0"); for(int k=size-1;k>=0;k--){ xmlDocSetRootElement(ndoc, xpathObj->nodesetval->nodeTab[k]); } xmlDocDumpFormatMemory(ndoc, &xmlbuff, &buffersize, 1); char *tmp=strdup(strstr((char*)xmlbuff,"?>")+2); xmlXPathFreeObject(xpathObj); xmlXPathFreeContext(xpathCtx); xmlFree(xmlbuff); xmlFreeDoc(doc); xmlCleanupParser(); OGRGeometryH res=OGR_G_CreateFromGML(tmp); if(res==NULL){ map* tmp=createMap("text","Unable to call OGR_G_CreatFromGML"); addToMap(tmp,"code","NoApplicableCode"); exit(0); } else return res; }
The 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 :
- the main environment maps,
- a char* representing the error message to display,
- a char* representing the error code (as defined in the WPS specification – Table 62).
In other words, if the WFS response cannot be parsed properly, then you will return anExceptionReport document informing the client that a problem occured.
The 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 :
int Boundary(maps*& conf,maps*& inputs,maps*& outputs){ OGRGeometryH geometry,res; map* tmp=getMapFromMaps(inputs,"InputPolygon","value"); if(tmp==NULL) return SERVICE_FAILED; map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType"); if(strncmp(tmp1->value,"application/json",16)==0) geometry=OGR_G_CreateGeometryFromJson(tmp->value); else geometry=createGeometryFromWFS(conf,tmp->value); res=OGR_G_GetBoundary(geometry); tmp1=getMapFromMaps(outputs,"Result","mimeType"); if(strncmp(tmp1->value,"application/json",16)==0){ addToMap(outputs->content,"value",OGR_G_ExportToJson(res)); addToMap(outputs->content,"mimeType","text/plain"); } else{ addToMap(outputs->content,"value",OGR_G_ExportToGML(res)); } outputs->next=NULL; OGR_G_DestroyGeometry(geometry); OGR_G_DestroyGeometry(res); return SERVICE_SUCCEEDED; }
As you can see in the code above, the mimeType of the data inputs passed to our Service is first checked :
map* tmp1=getMapFromMaps(inputs,"InputPolygon","mimeType"); if(strncmp(tmp1->value,"application/json",16)==0) geometry=OGR_G_CreateGeometryFromJson(tmp->value); else geometry=createGeometryFromGML(conf,tmp->value);
Basicaly, if we get an input with a mimeType set to application/json, then we will use our OGR_G_CreateGeometryFromJson in other case, our createGeometryFromGML local function.
Please 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).
Once 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.
Please 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.
tmp1=getMapFromMaps(outputs,"Result","mimeType"); if(strncmp(tmp1->value,"application/json",16)==0){ addToMap(outputs->content,"value",OGR_G_ExportToJson(res)); addToMap(outputs->content,"mimeType","text/plain"); } else{ addToMap(outputs->content,"value",OGR_G_ExportToGML(res)); }
The 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 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.
For the need of the Shared Library, you have to put your code in a block declared asextern "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 :
#include "ogr_api.h" #include "service.h" extern "C" { #include <libxml/tree.h> #include <libxml/parser.h> #include <libxml/xpath.h> #include <libxml/xpathInternals.h> <YOUR SERVICE CODE AND OTHER UTILITIES FUNCTIONS> }
The 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:
g++ $CFLAGS -shared -fpic -o cgi-env/ServiceProvider.zo ./service.c $LDFLAGS
Please note that the CLFAGS and LDFLAGS environment variables values must be set before.
The 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.
If you follow the instructions to create your ZOO Services Provider main directory inzoo-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.
The full CFLAGS definition should look like this :
CFLAGS=`gdal-config --cflags` `xml2-config --clfags` -I../../zoo-kernel/
Once 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 env 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 :
LDFLAGS=`gdal-config --libs` `xml2-config --libs` ../../zoo-kernel/service_internal.o
Let'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 :
ZOO_SRC_ROOT=../../zoo-kernel/ CFLAGS=-I${ZOO_SRC_ROOT} `xml2-config --cflags` `gdal-config --cflags` LDFLAGS=`xml2-config --libs` `gdal-config --libs`${ZOO_SRC_ROOT}/service_internal.o cgi-env/ogr_ws_service_provider.zo: service.c g++ ${CFLAGS} -shared -fpic -o cgi-env/ogr_ws_service_provider.zo ./service.c $ {LDFLAGS} clean: rm -f cgi-env/ogr_ws_service_provider.zo
Using 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.
The metadata file and the ZOO Service Shared Object are now both located in the cgi-env directory. In order to deploy your new ServiceProvider?, 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:
sudo cp ./cgi-env/* /usr/lib/cgi-bin
You should now understand more clearly the meannings of the ZOO Service Provider source tree ! Thecgi- 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.
Please 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 :
install: sudo cp ./cgi-env/* /usr/lib/cgi-bin
Your ZOO Services Provider is now ready to use from an Execute request passed to ZOO Kernel.
Python Version
Attachments (10)
-
Practical introduction to ZOO - 4.png
(82.3 KB) -
added by djay 14 years ago.
Practical introduction to ZOO - 4.png
-
Practical introduction to ZOO - 5.png
(47.7 KB) -
added by djay 14 years ago.
Practical introduction to ZOO - 5.png
-
Practical introduction to ZOO - 6.png
(45.1 KB) -
added by djay 14 years ago.
Practical introduction to ZOO - 6.png
-
Practical introduction to ZOO - 7.png
(61.8 KB) -
added by djay 14 years ago.
Practical introduction to ZOO - 7.png
-
Practical introduction to ZOO - 8.png
(117.2 KB) -
added by djay 14 years ago.
Practical introduction to ZOO - 8.png
-
Capture d’écran 2010-11-02 à 03.15.44.png
(108.1 KB) -
added by djay 14 years ago.
Screenshot presenting thre XML response when storeExecuteResponse was set to true, GetStatus? is present and the rewriteUrl was set to call
-
Practical introduction to ZOO - 9.png
(115.9 KB) -
added by djay 14 years ago.
Screenshot
-
Practical introduction to ZOO - 10.png
(113.0 KB) -
added by djay 14 years ago.
Screenshot
-
Practical introduction to ZOO - 8.2.png
(84.9 KB) -
added by djay 14 years ago.
Practical introduction to ZOO - 8.png
-
screenshot-ZOO-asReference-attribute.png
(106.3 KB) -
added by djay 14 years ago.
Sample result when using the asReference attribute for ResponseDocument? key.
Download all attachments as: .zip