source: branches/prototype-v0/zoo-project/zoo-kernel/meta_sql.c @ 854

Last change on this file since 854 was 854, checked in by djay, 6 years ago

HPC support update. Add inputs for create options in Gdal_Dem.

  • Property svn:keywords set to Id
File size: 18.2 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"\
45  " from ows_process"
46#define META_SERVICES_LIST_ALL_LENGTH 83
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 162
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 162
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 199
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 100
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 190
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 193
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 171
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 176
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 634
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 458
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,"fromDb","true");
390      s->metadata=NULL;
391      fillMetadata(conf,&s->metadata,poFeature->GetFieldAsString( 0 ));
392      s->additional_parameters=NULL;
393      fillAdditionalParameters(conf,&s->additional_parameters,poFeature->GetFieldAsString( 0 ));
394      s->inputs=NULL;
395      s->outputs=NULL;
396      if(minimal==1){
397        OGRFeature::DestroyFeature( poFeature );
398        cleanFetchSql(conf,0,res);
399        return s;
400      }
401      char* inputsQuery=(char*)malloc((META_SERVICES_LIST_INPUTS_FROM_PROCESS_LENGTH+strlen(poFeature->GetFieldAsString( 0 ))+1)*sizeof(char));
402      sprintf(inputsQuery,META_SERVICES_LIST_INPUTS_FROM_PROCESS,poFeature->GetFieldAsString( 0 ));
403      OGRFeature  *input = NULL;
404      OGRLayer *inputs=fetchSql(conf,0,inputsQuery);
405      free(inputsQuery);
406      while( (input = inputs->GetNextFeature()) != NULL ){
407        elements* in=extractInput(conf,input);
408        if(in!=NULL){
409          addToElements(&s->inputs,in);
410          freeElements(&in);
411          free(in);
412        }
413        OGRFeature::DestroyFeature( input );
414      }
415      cleanFetchSql(conf,0,inputs);
416      char* outputsQuery=(char*)malloc((META_SERVICES_LIST_OUTPUTS_FROM_PROCESS_LENGTH+strlen(poFeature->GetFieldAsString( 0 ))+1)*sizeof(char));
417      sprintf(outputsQuery,META_SERVICES_LIST_OUTPUTS_FROM_PROCESS,poFeature->GetFieldAsString( 0 ));
418      OGRFeature  *output = NULL;
419      OGRLayer *outputs=fetchSql(conf,0,outputsQuery);
420      free(outputsQuery);
421      s->outputs=NULL;
422      while( (output = outputs->GetNextFeature()) != NULL ){
423        elements* in=extractOutput(conf,output);
424        if(in!=NULL){
425          addToElements(&s->outputs,in);
426          freeElements(&in);
427          free(in);
428        }
429        OGRFeature::DestroyFeature( output );
430      }
431      cleanFetchSql(conf,0,outputs);
432      OGRFeature::DestroyFeature( poFeature );
433      cleanFetchSql(conf,0,res);
434      return s;
435    }
436  }
437  cleanFetchSql(conf,0,res);
438  return NULL;
439}
440
441/**
442 * Extract every service definitions from metadb
443 *
444 * @param reg the registry
445 * @param conf the main configuration maps
446 * @param n the node where to add the services found
447 * @param func the C function to call for each service found
448 * @param minimal 1 for minimal metadata extraction (GetCapabilities), 0 in other cases.
449 * @return the number of services found, -1 in case of failure
450 */
451int fetchServicesFromDb(registry* reg,maps* conf, xmlDocPtr doc, xmlNodePtr n,
452                        void (func) (registry *, maps *, xmlDocPtr, xmlNodePtr,
453                                     service *), int minimal ){
454  int result=0;
455  result=_init_sql(conf,"metadb");
456  if(getMapFromMaps(conf,"lenv","dbIssue")!=NULL || result < 0)
457    return -1;
458  // Fetch every services
459  OGRLayer *res=fetchSql(conf,0,META_SERVICES_LIST_ALL);
460  if(res!=NULL){
461    OGRFeature  *poFeature = NULL;
462    const char *tmp1;
463    poFeature = res->GetNextFeature();
464    while( poFeature != NULL ){
465      service* s=extractServiceFromDb(conf,poFeature->GetFieldAsString( 1 ),minimal);
466#ifdef USE_HPC
467      addNestedOutputs(&s);
468#endif
469      func(reg,conf,doc,n,s);
470      freeService(&s);
471      free(s);
472      OGRFeature::DestroyFeature( poFeature );
473      poFeature = res->GetNextFeature();
474      result++;
475    }
476    cleanFetchSql(conf,0,res);
477  }
478  return result;
479}
480
481#endif
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