source: trunk/zoo-project/zoo-kernel/meta_sql.c

Last change on this file was 949, checked in by djay, 5 years ago

Prototype implementation of the OGC API - Processing and other simplifications

  • Property svn:keywords set to Id
File size: 18.7 KB
Line 
1/*
2 * Author : Gérald Fenoy
3 *
4 * Copyright 2017 GeoLabs SARL. All rights reserved.
5 *
6 * This work was supported by public funds received in the framework of GEOSUD,
7 * a project (ANR-10-EQPX-20) of the program "Investissements d'Avenir" managed
8 * by the French National Research Agency
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of this software and associated documentation files (the "Software"), to deal
12 * in the Software without restriction, including without limitation the rights
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 * copies of the Software, and to permit persons to whom the Software is
15 * furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 * THE SOFTWARE.
27 */
28
29#ifdef META_DB
30#include "ogr_api.h"
31#include "ogrsf_frmts.h"
32#include "ogr_p.h"
33#if GDAL_VERSION_MAJOR >= 2
34#include <gdal_priv.h>
35#endif
36
37#include "meta_sql.h"
38#include "sqlapi.h"
39#include "response_print.h"
40#ifdef USE_HPC
41#include "service_internal_hpc.h"
42#endif
43#define META_SERVICES_LIST_ALL \
44  "select id,identifier,title,abstract,service_type,service_provider,conf_id"\
45  " from ows_process"
46#define META_SERVICES_LIST_ALL_LENGTH strlen(META_SERVICES_LIST_ALL)
47
48#define META_SERVICES_KEYWORDS_FROM_PROCESS \
49  "SELECT keyword FROM CollectionDB.ows_Keywords where id in"\
50  " (SELECT keywords_id FROM CollectionDB.DescriptionsKeywordsAssignment"\
51  " where descriptions_id=%s) "
52#define META_SERVICES_KEYWORDS_FROM_PROCESS_LENGTH strlen(META_SERVICES_KEYWORDS_FROM_PROCESS)
53
54#define META_SERVICES_META_FROM_ANYTHING \
55  "SELECT title,role,href FROM CollectionDB.ows_Metadata where id in"\
56  " (SELECT metadata_id FROM CollectionDB.DescriptionsMetadataAssignment"\
57  " where descriptions_id=%s) "
58#define META_SERVICES_META_FROM_ANYTHING_LENGTH strlen(META_SERVICES_META_FROM_ANYTHING)
59
60#define META_SERVICES_AP_FROM_ANYTHING \
61  "SELECT id,title,role,href FROM CollectionDB.ows_AdditionalParameters where id in"\
62  " (SELECT additional_parameters_id FROM CollectionDB.DescriptionsAdditionalParametersAssignment"\
63  " where descriptions_id=%s) "
64#define META_SERVICES_AP_FROM_ANYTHING_LENGTH strlen(META_SERVICES_AP_FROM_ANYTHING)
65
66#define META_SERVICES_AP_FROM_AP \
67  "SELECT key,value FROM CollectionDB.ows_AdditionalParameter where additional_parameters_id =$q$%s$q$"
68#define META_SERVICES_AP_FROM_AP_LENGTH strlen(META_SERVICES_AP_FROM_AP)
69
70#define META_SERVICES_LIST_INPUTS_FROM_PROCESS                          \
71  "select id, identifier,title,abstract,min_occurs,max_occurs from CollectionDB.ows_Input where id in (SELECT input_id from CollectionDB.ProcessInputAssignment where process_id=%s) order by id"
72#define META_SERVICES_LIST_INPUTS_FROM_PROCESS_LENGTH strlen(META_SERVICES_LIST_INPUTS_FROM_PROCESS)
73
74#define META_SERVICES_LIST_INPUTS_FROM_INPUT                            \
75  "select id, identifier,title,abstract,min_occurs,max_occurs from CollectionDB.ows_Input where id in (SELECT child_input from CollectionDB.InputInputAssignment where parent_input=%s) order by id"
76#define META_SERVICES_LIST_INPUTS_FROM_INPUT_LENGTH strlen(META_SERVICES_LIST_INPUTS_FROM_INPUT)
77
78#define META_SERVICES_LIST_OUTPUTS_FROM_PROCESS \
79  "select id, identifier,title,abstract from CollectionDB.ows_Output where id in (SELECT output_id from CollectionDB.ProcessOutputAssignment where process_id=%s) order by id"
80#define META_SERVICES_LIST_OUTPUTS_FROM_PROCESS_LENGTH strlen(META_SERVICES_LIST_OUTPUTS_FROM_PROCESS)
81
82#define META_SERVICES_LIST_OUTPUTS_FROM_OUTPUT \
83  "select id, identifier,title,abstract from CollectionDB.ows_Output where id in (SELECT child_output from CollectionDB.OutputOutputAssignment where parent_output=%s) order by id"
84#define META_SERVICES_LIST_OUTPUTS_FROM_OUTPUT_LENGTH strlen(META_SERVICES_LIST_OUTPUTS_FROM_OUTPUT)
85
86#define META_SERVICES_LIST_LITERAL_FROM_IO \
87  "select (SELECT name as type FROM CollectionDB.PrimitiveDatatypes where CollectionDB.PrimitiveDatatypes.id=data_type_id),default_value,(SELECT uom from CollectionDB.PrimitiveUOM where id=CollectionDB.LiteralDataDomain.uom),translate(translate(ARRAY((SELECT allowed_Value from CollectionDB.AllowedValues where id in (SELECT allowed_value_id from CollectionDB.AllowedValuesAssignment where literal_data_domain_id=CollectionDB.LiteralDataDomain.id)))::varchar,'{',''),'}',''),def as allowedvalues from CollectionDB.LiteralDataDomain where id in (SELECT data_description_id from CollectionDB.%sDataDescriptionAssignment where %s_id = %s);"
88#define META_SERVICES_LIST_LITERAL_FROM_IO_LENGTH strlen(META_SERVICES_LIST_LITERAL_FROM_IO)
89
90#define META_SERVICES_LIST_FORMATS_FROM_IO \
91  "select mime_type,encoding,schema,maximum_megabytes,CASE WHEN use_mapserver THEN 'true' ELSE 'false' END, ms_styles, def from CollectionDB.ows_Format,CollectionDB.PrimitiveFormats where CollectionDB.ows_Format.primitive_format_id=CollectionDB.PrimitiveFormats.id and CollectionDB.ows_Format.id in (SELECT format_id from collectiondb.ows_datadescription where id in ( SELECT data_description_id from CollectionDB.%sDataDescriptionAssignment where %s_id = %s))"
92#define META_SERVICES_LIST_FORMATS_FROM_IO_LENGTH strlen(META_SERVICES_LIST_FORMATS_FROM_IO)
93
94/**
95 * Create a new iotype pointer using field names from an OGRFeature
96 *
97 * @param f the OGRFeature
98 * @param fields the fields names
99 * @return the iotype
100 */
101iotype* getIoType(OGRFeature* f,const char** fields){
102  iotype* io=(iotype*)malloc(IOTYPE_SIZE);
103  io->content=NULL;
104  io->next=NULL;
105  for(int i=0;i<6;i++){
106    if(fields[i]==NULL)
107      return io;
108    const char* tmpS=f->GetFieldAsString( i );
109    if(strlen(tmpS)>0){
110      if(io->content==NULL)
111        io->content=createMap(fields[i],tmpS);
112      else
113        addToMap(io->content,fields[i],tmpS);
114    }
115  }
116  return io;
117}
118
119/**
120 * Fill the AdditionalParameters map with the data extracted from metadb
121 *
122 * @param conf the main configuration maps
123 * @param ap the map to fill
124 * @param dref the description identifier
125 * @return the number of metadata field found
126 */
127int fillAdditionalParameters(maps* conf,map** ap,const char* dref){
128  int res=0;
129  char* ioQuery=(char*)malloc((META_SERVICES_AP_FROM_ANYTHING_LENGTH+strlen(dref)+1)*sizeof(char));
130  sprintf(ioQuery,META_SERVICES_AP_FROM_ANYTHING,dref);
131  OGRFeature  *meta = NULL;
132  OGRLayer *metas=fetchSql(conf,0,ioQuery);
133  free(ioQuery);
134  int cnt=0;
135  char fields[3][255]={
136    "title",
137    "role",
138    "href"
139  };
140  while( (meta = metas->GetNextFeature()) != NULL ){
141    int i=0;
142    for(i=0;i<3;i++){     
143      const char *tmp=meta->GetFieldAsString(i+1);
144      if(strlen(tmp)>0)
145        if(*ap==NULL){
146          (*ap)=createMap(fields[i],tmp);
147          addToMap(*ap,"fromDb","true");
148        }
149        else
150          setMapArray(*ap,fields[i],res,tmp);
151    }
152    char* apQuery=(char*)malloc((META_SERVICES_AP_FROM_AP_LENGTH+strlen(dref)+1)*sizeof(char));
153    sprintf(apQuery,META_SERVICES_AP_FROM_AP,meta->GetFieldAsString(0));
154    OGRFeature  *adp = NULL;
155    OGRLayer *adps=fetchSql(conf,0,apQuery);
156    free(apQuery);
157    while( (adp = adps->GetNextFeature()) != NULL ){
158      addToMap(*ap,adp->GetFieldAsString(0),adp->GetFieldAsString(1));
159      OGRFeature::DestroyFeature( adp );
160    }
161    cleanFetchSql(conf,0,adps);
162    res++;
163    OGRFeature::DestroyFeature( meta );
164  }
165  cleanFetchSql(conf,0,metas);
166  return res;
167}
168
169/**
170 * Fill the metadata map with the data extracted from metadb
171 *
172 * @param conf the main configuration maps
173 * @param metadata the map to fill
174 * @param dref the description identifier
175 * @return the number of metadata field found
176 */
177int fillMetadata(maps* conf,map** metadata,const char* dref){
178  int res=0;
179  char* ioQuery=(char*)malloc((META_SERVICES_META_FROM_ANYTHING_LENGTH+strlen(dref)+1)*sizeof(char));
180  sprintf(ioQuery,META_SERVICES_META_FROM_ANYTHING,dref);
181  OGRFeature  *meta = NULL;
182  OGRLayer *metas=fetchSql(conf,0,ioQuery);
183  free(ioQuery);
184  int cnt=0;
185  char fields[3][255]={
186    "title",
187    "role",
188    "href"
189  };
190  while( (meta = metas->GetNextFeature()) != NULL ){
191    int i=0;
192    for(i=0;i<3;i++){
193      const char *tmp=meta->GetFieldAsString(i);
194      if(strlen(tmp)>0)
195        if(*metadata==NULL)
196          *metadata=createMap(fields[i],tmp);
197        else
198          addToMap(*metadata,fields[i],tmp);
199    }
200    res++;
201    OGRFeature::DestroyFeature( meta );
202  }
203  cleanFetchSql(conf,0,metas);
204  return res;
205}
206
207/**
208 * Try to fill the default/supported map for the LiteralData with the data
209 * extracted from metadb.
210 *
211 * @param conf the main configuration maps
212 * @param in the element to fill default/supported
213 * @param input the OGRFeature corresponding to the input/output
214 * @param ltype the element type ("Input" or "Output")
215 * @return the number of default/supported definition found
216 */
217int fillLiteralData(maps* conf,elements* in,OGRFeature  *input,const char* ltype){
218  int res=0;
219  char* ioQuery=(char*)malloc((META_SERVICES_LIST_LITERAL_FROM_IO_LENGTH+(strlen(ltype)*2)+strlen(input->GetFieldAsString( 0 ))+1)*sizeof(char));
220  sprintf(ioQuery,META_SERVICES_LIST_LITERAL_FROM_IO,ltype,ltype,input->GetFieldAsString( 0 ));
221  OGRFeature  *io = NULL;
222  OGRLayer *ios=fetchSql(conf,0,ioQuery);
223  free(ioQuery);
224  int ioCnt=0;
225  const char* fields[5]={"dataType","value","uom","AllowedValues",NULL};
226  while( (io = ios->GetNextFeature()) != NULL ){
227    iotype* currentIoType;
228    if(strncmp(io->GetFieldAsString( 4 ),"1",1)==0){
229      in->defaults=getIoType(io,fields);
230    }else{
231      if(in->supported==NULL)
232        in->supported=getIoType(io,fields);
233      else{
234        iotype* p=in->supported;
235        while(p->next!=NULL){
236          p=p->next;
237        }
238        p->next=getIoType(io,fields);
239      }
240    }
241    in->format=strdup("LiteralData");
242    res++;
243    OGRFeature::DestroyFeature( io );
244  }
245  cleanFetchSql(conf,0,ios);
246  return res;
247}
248
249/**
250 * Try to fill the default/supported map for the ComplexData with the data
251 * extracted from metadb.
252 *
253 * @param conf the main configuration maps
254 * @param in the element to fill default/supported
255 * @param input the OGRFeature corresponding to the input/output
256 * @param ltype the element type ("Input" or "Output")
257 * @return the number of default/supported definition found
258 */
259int fillComplexData(maps* conf,elements* in,OGRFeature  *input,const char* ltype){
260  int res=0;
261  char* ioQuery=(char*)malloc((META_SERVICES_LIST_FORMATS_FROM_IO_LENGTH+(strlen(ltype)*2)+strlen(input->GetFieldAsString( 0 ))+1)*sizeof(char));
262  sprintf(ioQuery,META_SERVICES_LIST_FORMATS_FROM_IO,ltype,ltype,input->GetFieldAsString( 0 ));
263  OGRFeature  *io = NULL;
264  OGRLayer *ios=fetchSql(conf,0,ioQuery);
265  free(ioQuery);
266  int ioCnt=0;
267  const char* fields[6]={"mimeType","ecoding","schema","maximumMegabytes","useMapserver","msStyle"};
268  while( (io = ios->GetNextFeature()) != NULL ){
269    iotype* currentIoType;
270    if(strncmp(io->GetFieldAsString( 6 ),"1",1)==0){
271      in->defaults=getIoType(io,fields);
272    }else{
273      if(in->supported==NULL)
274        in->supported=getIoType(io,fields);
275      else{
276        iotype* p=in->supported;
277        while(p->next!=NULL){
278          p=p->next;
279        }
280        p->next=getIoType(io,fields);
281      }
282    }
283    in->format=strdup("ComplexData");
284    res++;
285    OGRFeature::DestroyFeature( io );
286  }
287  cleanFetchSql(conf,0,ios);
288  return res;
289}
290
291/**
292 * Extract input definition from metadb
293 *
294 * @param conf the main configuration maps
295 * @param input the OGRFeature pointing to an input
296 * @return a new elements* corresponding to the current input
297 */
298elements* extractInput(maps* conf,OGRFeature *input){
299  elements* res=createElements(input->GetFieldAsString( 1 ));
300  res->content=createMap("title",input->GetFieldAsString( 2 ));
301  addToMap(res->content,"abstract",input->GetFieldAsString( 3 ));
302  addToMap(res->content,"minOccurs",input->GetFieldAsString( 4 ));
303  addToMap(res->content,"maxOccurs",input->GetFieldAsString( 5 ));
304  // Extract metadata
305  fillMetadata(conf,&res->metadata,input->GetFieldAsString( 0 ));
306  res->additional_parameters=NULL;
307  fillAdditionalParameters(conf,&res->additional_parameters,input->GetFieldAsString( 0 ));
308  res->defaults=NULL;
309  res->supported=NULL;
310  res->child=NULL;
311  res->next=NULL;
312  // Extract iotypes
313  int ioCnt=fillLiteralData(conf,res,input,"Input");
314  if(ioCnt==0){
315    ioCnt=fillComplexData(conf,res,input,"Input");
316  }
317  if(ioCnt==0){
318    char* nestedInputsQuery=(char*)malloc((META_SERVICES_LIST_INPUTS_FROM_INPUT_LENGTH+strlen(input->GetFieldAsString( 0 ))+1)*sizeof(char));
319    sprintf(nestedInputsQuery,META_SERVICES_LIST_INPUTS_FROM_INPUT,input->GetFieldAsString( 0 ));
320    OGRFeature  *ninput = NULL;
321    OGRLayer *ninputs=fetchSql(conf,0,nestedInputsQuery);
322    free(nestedInputsQuery);
323    while( (ninput = ninputs->GetNextFeature()) != NULL ){
324      elements* nin=extractInput(conf,ninput);
325      addToElements(&res->child,nin);
326      freeElements(&nin);
327      free(nin);
328      OGRFeature::DestroyFeature( ninput );
329    }
330    cleanFetchSql(conf,0,ninputs);
331  }
332  return res;
333}
334
335/**
336 * Extract output definition from metadb
337 *
338 * @param conf the main configuration maps
339 * @param output the OGRFeature pointing to an output
340 * @return a new elements* corresponding to the current output
341 */
342elements* extractOutput(maps* conf,OGRFeature *output){
343  elements* res=createElements(output->GetFieldAsString( 1 ));
344  res->content=createMap("title",output->GetFieldAsString( 2 ));
345  addToMap(res->content,"abstract",output->GetFieldAsString( 3 ));
346  fillMetadata(conf,&res->metadata,output->GetFieldAsString( 0 ));
347  fillAdditionalParameters(conf,&res->additional_parameters,output->GetFieldAsString( 0 ));
348  int ioCnt=fillLiteralData(conf,res,output,"Output");
349  if(ioCnt==0)
350    ioCnt=fillComplexData(conf,res,output,"Output");
351  char* nestedOutputsQuery=(char*)malloc((META_SERVICES_LIST_OUTPUTS_FROM_OUTPUT_LENGTH+strlen(output->GetFieldAsString( 0 ))+1)*sizeof(char));
352  sprintf(nestedOutputsQuery,META_SERVICES_LIST_OUTPUTS_FROM_OUTPUT,output->GetFieldAsString( 0 ));
353  OGRFeature  *noutput = NULL;
354  OGRLayer *noutputs=fetchSql(conf,0,nestedOutputsQuery);
355  free(nestedOutputsQuery);
356  while( (noutput = noutputs->GetNextFeature()) != NULL ){
357    elements* nout=extractOutput(conf,noutput);
358    addToElements(&res->child,nout);
359    freeElements(&nout);
360    free(nout);
361    OGRFeature::DestroyFeature( noutput );
362  }
363  cleanFetchSql(conf,0,noutputs);
364  return res;
365}
366
367/**
368 * Extract service from metadb
369 *
370 * @param conf the main configuration maps
371 * @param serviceName the service name
372 * @param minimal 1 for minimal metadata extraction (GetCapabilities), 0 in other cases.
373 * @return a new service* corresponding to the service
374 */
375service* extractServiceFromDb(maps* conf,const char* serviceName,int minimal){
376  OGRFeature  *poFeature = NULL;
377  char* tmpQuery=(char*)malloc((META_SERVICES_LIST_ALL_LENGTH+strlen(serviceName)+21)*sizeof(char));
378  sprintf(tmpQuery,"%s WHERE identifier='%s'",META_SERVICES_LIST_ALL,serviceName); 
379  OGRLayer *res=fetchSql(conf,0,tmpQuery);
380  free(tmpQuery);
381  if(res!=NULL){
382    while( (poFeature = res->GetNextFeature()) != NULL ){
383      service* s = (service*) malloc(SERVICE_SIZE);
384      s->name = strdup(poFeature->GetFieldAsString( 1 ));
385      s->content = createMap("title",poFeature->GetFieldAsString( 2 ));
386      addToMap(s->content,"abstract",poFeature->GetFieldAsString( 3 ));
387      addToMap(s->content,"serviceType",poFeature->GetFieldAsString( 4 ));
388      addToMap(s->content,"serviceProvider",poFeature->GetFieldAsString( 5 ));
389      addToMap(s->content,"confId",poFeature->GetFieldAsString( 6 ));
390      addToMap(s->content,"fromDb","true");
391      s->metadata=NULL;
392      fillMetadata(conf,&s->metadata,poFeature->GetFieldAsString( 0 ));
393      s->additional_parameters=NULL;
394      fillAdditionalParameters(conf,&s->additional_parameters,poFeature->GetFieldAsString( 0 ));
395      s->inputs=NULL;
396      s->outputs=NULL;
397      if(minimal==1){
398        OGRFeature::DestroyFeature( poFeature );
399        cleanFetchSql(conf,0,res);
400        return s;
401      }
402      char* inputsQuery=(char*)malloc((META_SERVICES_LIST_INPUTS_FROM_PROCESS_LENGTH+strlen(poFeature->GetFieldAsString( 0 ))+1)*sizeof(char));
403      sprintf(inputsQuery,META_SERVICES_LIST_INPUTS_FROM_PROCESS,poFeature->GetFieldAsString( 0 ));
404      OGRFeature  *input = NULL;
405      OGRLayer *inputs=fetchSql(conf,0,inputsQuery);
406      free(inputsQuery);
407      while( (input = inputs->GetNextFeature()) != NULL ){
408        elements* in=extractInput(conf,input);
409        if(in!=NULL){
410          addToElements(&s->inputs,in);
411          freeElements(&in);
412          free(in);
413        }
414        OGRFeature::DestroyFeature( input );
415      }
416      cleanFetchSql(conf,0,inputs);
417      char* outputsQuery=(char*)malloc((META_SERVICES_LIST_OUTPUTS_FROM_PROCESS_LENGTH+strlen(poFeature->GetFieldAsString( 0 ))+1)*sizeof(char));
418      sprintf(outputsQuery,META_SERVICES_LIST_OUTPUTS_FROM_PROCESS,poFeature->GetFieldAsString( 0 ));
419      OGRFeature  *output = NULL;
420      OGRLayer *outputs=fetchSql(conf,0,outputsQuery);
421      free(outputsQuery);
422      s->outputs=NULL;
423      while( (output = outputs->GetNextFeature()) != NULL ){
424        elements* in=extractOutput(conf,output);
425        if(in!=NULL){
426          addToElements(&s->outputs,in);
427          freeElements(&in);
428          free(in);
429        }
430        OGRFeature::DestroyFeature( output );
431      }
432      cleanFetchSql(conf,0,outputs);
433      OGRFeature::DestroyFeature( poFeature );
434      cleanFetchSql(conf,0,res);
435      return s;
436    }
437  }
438  cleanFetchSql(conf,0,res);
439  return NULL;
440}
441
442/**
443 * Extract every service definitions from metadb
444 *
445 * @param reg the registry
446 * @param conf the main configuration maps
447 * @param n the node where to add the services found
448 * @param func the C function to call for each service found
449 * @param minimal 1 for minimal metadata extraction (GetCapabilities), 0 in other cases.
450 * @return the number of services found, -1 in case of failure
451 */
452int fetchServicesFromDb(registry* reg,maps* conf, void* doc0, void* n0,
453                        void (func) (registry *, maps *, void*, void*,
454                                     service *), int minimal ){
455  int result=0;
456  xmlDocPtr doc=(xmlDocPtr) doc0;
457  xmlNodePtr n=(xmlNodePtr) n0;
458  result=_init_sql(conf,"metadb");
459  if(getMapFromMaps(conf,"lenv","dbIssue")!=NULL || result < 0)
460    return -1;
461  // Fetch every services
462  OGRLayer *res=fetchSql(conf,0,META_SERVICES_LIST_ALL);
463  if(res!=NULL){
464    OGRFeature  *poFeature = NULL;
465    const char *tmp1;
466    poFeature = res->GetNextFeature();
467    while( poFeature != NULL ){
468      service* s=extractServiceFromDb(conf,poFeature->GetFieldAsString( 1 ),minimal);
469#ifdef USE_HPC
470      addNestedOutputs(&s);
471#endif
472      func(reg,conf,doc,n,s);
473      freeService(&s);
474      free(s);
475      OGRFeature::DestroyFeature( poFeature );
476      poFeature = res->GetNextFeature();
477      result++;
478    }
479    cleanFetchSql(conf,0,res);
480  }
481  return result;
482}
483
484#endif
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