source: trunk/zoo-project/zoo-kernel/service_internal_otb.c @ 618

Last change on this file since 618 was 618, checked in by djay, 9 years ago

Fix for OTB support when input was passed by value rather than reference.

  • Property svn:keywords set to Id
File size: 18.1 KB
RevLine 
[580]1/*
[550]2 * Author : Gérald FENOY
3 *
4 * Copyright (c) 2015 GeoLabs SARL
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
[558]23 *
24 * See Ref: http://hg.orfeo-toolbox.org/OTB/ Copyright
25 * Some parts of this code are derived from ITK. See ITKCopyright.txt for
26 * details.
[550]27 */
28
29#include "service_internal_otb.h"
30
31using namespace otb::Wrapper;
32
[580]33/**
34 * The ZooWatcher list
35 */
[561]36WatcherListType m_WatcherList;
[580]37/**
38 * A pointer to the conf maps containing the main.cfg settings
39 */
[561]40maps* m_Conf;
41
[580]42/**
43 * The command to create a ZooWatcher and add it to the global m_WatcherList
44 */
[558]45class MyCommand : public itk::Command
46{
47 public:
48  itkNewMacro( MyCommand );
49 public:
50
[580]51  /**
52   * The method that defines the action to be taken by the command.
53   *
54   * @param caller an itk::Object pointer
55   * @param event an itk::EventObject pointer
56   */
[558]57  void Execute(itk::Object *caller, const itk::EventObject & event)
58  {
59    Execute( (const itk::Object *)caller, event);
60  }
61 
[580]62  /**
63   * The method that defines the action to be taken by the command.
64   * Create a new ZooWatcher instance then add it to the m_WatcherList.
65   *
66   * @param caller a const itk::Object pointer
67   * @param event an itk::EventObject pointer
68   * @see ZooWatcher,ZooWatcher::SetConf
69   */
70  void Execute(const itk::Object *caller, const itk::EventObject & event)
[558]71  {
72    const AddProcessToWatchEvent* eventToWatch = dynamic_cast< const AddProcessToWatchEvent*> ( &event );
73    std::string m_CurrentDescription = eventToWatch->GetProcessDescription();
74    ZooWatcher * watch = new ZooWatcher(eventToWatch->GetProcess(),
75                                        eventToWatch->GetProcessDescription());
76    watch->SetConf(&m_Conf);
77    m_WatcherList.push_back(watch);
78  }
79
80};
81
[580]82/**
83 * Replace all occurence of from by to in a str string
84 *
85 * @param str the string to transform
86 * @param from the string to replace
87 * @param to the string used as replacement
88 * @return the resulting string
89 */
[550]90std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) {
91    size_t start_pos = 0;
92    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
93        str.replace(start_pos, from.length(), to);
94        start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
95    }
96    return str;
97}
98
[580]99/**
100 * Load and run an OTB Application corresponding to the service by using inputs parameters.
101 * Define the m_Conf
102 *
103 * @param main_conf the conf maps containing the main.cfg settings
104 * @param request the map containing the HTTP request
105 * @param s the service structure
106 * @param real_inputs the maps containing the inputs
107 * @param real_outputs the maps containing the outputs
108 */
[550]109int zoo_otb_support(maps** main_conf,map* request,service* s,maps **real_inputs,maps **real_outputs){
110  maps* m=*main_conf;
111  maps* inputs=*real_inputs;
112  maps* outputs=*real_outputs;
[558]113  map* tmp0=getMapFromMaps(*main_conf,"lenv","cwd");
114  char *ntmp=tmp0->value;
[550]115  map* tmp=NULL;
116  int res=-1;
117
118  std::vector<std::string> list = ApplicationRegistry::GetAvailableApplications();
119  if (list.size() == 0){
120    map* tmps=createMap("text","No OTB Application found.");
121    addToMap(tmps,"code","InternalError");
122    printExceptionReportResponse(m,tmps);
123    freeMap(&tmps);
124    free(tmps);
125    res=-1;
126  }
127  else{
128    for (std::vector<std::string>::const_iterator it = list.begin(); it != list.end(); ++it){
129      if(s->name==*it){
130        Application::Pointer m_Application=ApplicationRegistry::CreateApplication(*it);
131        if (m_Application.IsNull()){
132          char tmpS[1024];
133          sprintf(tmpS, "The OTB Application %s cannot be loaded.", (*it).c_str());
134          map* tmps=createMap("text",tmpS);
135          addToMap(tmps,"code","InternalError");
136          printExceptionReportResponse(m,tmps);
137          freeMap(&tmps);
138          free(tmps);
139          res=-1;
140        }else{
[558]141          // Create Observer on AddProcessToWatchEvent
142          m_Conf=m;
143          MyCommand::Pointer myCommand = MyCommand::New();
144          m_Application->AddObserver(AddProcessToWatchEvent(), myCommand);
[550]145          char tmpS[1024];
146          const std::vector<std::string> appKeyList = m_Application->GetParametersKeys(true);
147          for (unsigned int i = 0; i < appKeyList.size(); i++){
148            const std::string paramKey(appKeyList[i]);
149            std::vector<std::string> values;
150            Parameter::Pointer param = m_Application->GetParameterByKey(paramKey);
151            ParameterType type = m_Application->GetParameterType(paramKey);
152            if (type != ParameterType_Group && paramKey!="inxml" && paramKey!="outxml"){
153              map* test=getMapFromMaps(inputs,paramKey.c_str(),"cache_file");
154              if(test==NULL){
155                test=getMapFromMaps(inputs,paramKey.c_str(),"inRequest");
[558]156                map* tmpPath=getMapFromMaps(m,"main","tmpPath");
157                map* tmpSid=getMapFromMaps(m,"lenv","usid");
158                char tmp[1024];
159                map* tmpVal=getMapFromMaps(outputs,paramKey.c_str(),"mimeType");
[618]160                maps* tmpMaps=getMaps(outputs,paramKey.c_str());
[550]161                if(test!=NULL && test->value!=NULL && strncasecmp(test->value,"true",4)==0){
162                  test=getMapFromMaps(inputs,paramKey.c_str(),"value");
163                  if(type == ParameterType_OutputImage){
164                    ImagePixelType outPixType = ImagePixelType_float;
165                    if (strncasecmp(test->value,"uint8",5)==0)
166                      outPixType = ImagePixelType_uint8;
167                    else if (strncasecmp(test->value,"int16",5)==0)
168                      outPixType = ImagePixelType_int16;
169                    else if (strncasecmp(test->value,"uint16",6)==0)
170                      outPixType = ImagePixelType_uint16;
171                    else if (strncasecmp(test->value,"int32",5)==0)
172                      outPixType = ImagePixelType_int32;
173                    else if (strncasecmp(test->value,"uint32",6)==0)
174                      outPixType = ImagePixelType_uint32;
175                    else if (strncasecmp(test->value,"double",6)==0)
176                      outPixType = ImagePixelType_double;
[576]177                    const char* ext="tiff";
[550]178                    if(tmpVal!=NULL){
179                      if(strncasecmp(tmpVal->value,"image/jp2",9)==0)
180                         ext="j2k";
181                      else
182                        if(strncasecmp(tmpVal->value,"image/png",9)==0)
183                         ext="png";
184                        else
185                          if(strncasecmp(tmpVal->value,"image/jpeg",10)==0)
186                            ext="jpeg";
187                    }
188                    sprintf(tmp,"%s/%s_%s.%s",tmpPath->value,s->name,tmpSid->value,ext);
189                    m_Application->SetParameterString(paramKey, tmp);
190                    setMapInMaps(inputs,paramKey.c_str(),"generated_file",tmp);
191                    dynamic_cast<OutputImageParameter *> (param.GetPointer())->SetPixelType(outPixType);
192                  }else{
[618]193                    map* tmpVal=getMapFromMaps(inputs,paramKey.c_str(),"mimeType");
194                    char file_ext[32];
195                    getFileExtension(tmpVal != NULL ? tmpVal->value : NULL, file_ext, 32);
196                    if(type == ParameterType_InputImageList){
197                      values.push_back(test->value);
198                      map* tmpLength=getMapFromMaps(inputs,paramKey.c_str(),"length");
199                      if(tmpLength!=NULL){
200                        int len=atoi(tmpLength->value);
201                        for(int k=1;k<len;k++){
202                          char *val=(char*)malloc((strlen(tmpPath->value)+strlen(s->name)+strlen(tmpSid->value)+strlen(file_ext)+13)*sizeof(char));
203                          sprintf(val,"%s/Input_%s_%s_%d.%s",tmpPath->value,s->name,tmpSid->value,k,file_ext);
204                          FILE* of=fopen(val,"wb");
205                          int length=0;
206                          map* tmpSize=getMap(test,"size");
207                          if(tmpSize!=NULL){
208                            length=atoi(tmpSize->value);
209                          }
210                          fwrite(test->value,sizeof(char),length,of);
211                          fclose(of);
212                          values.push_back(val);
213                          free(val);
214                        }
215                      }
216                      dynamic_cast<InputImageListParameter *> (param.GetPointer())->SetListFromFileName(values);
217                    }
218                    else
219                      if(type == ParameterType_InputVectorData || type == ParameterType_InputImage
220                           || type == ParameterType_ComplexInputImage || type == ParameterType_InputVectorData
221                           || type == ParameterType_InputFilename){
222                        map* tmpPath=getMapFromMaps(m,"main","tmpPath");
223                        map* tmpSid=getMapFromMaps(m,"lenv","sid");
224                        char tmp[1024];
225                        char* ext="json";
226                        if(tmpVal!=NULL){
227                          char *val=(char*)malloc((strlen(tmpPath->value)+strlen(s->name)+strlen(tmpSid->value)+strlen(file_ext)+10)*sizeof(char));
228                          sprintf(val,"%s/Input_%s_%s.%s",tmpPath->value,s->name,tmpSid->value,file_ext);
229                          FILE* of=fopen(val,"wb");
230                          int length=0;
231                          map* tmpSize=getMap(test,"size");
232                          if(tmpSize!=NULL){
233                            length=atoi(tmpSize->value);
234                          }
235                          fwrite(test->value,sizeof(char),length,of);
236                          fclose(of);
237
238                          if(strncasecmp(tmpVal->value,"application/zip",14)==0){
239
240                            char tmpName[1024];
241                            sprintf(tmpName,"/vsizip/%s",val);
242                            char **files=VSIReadDir(tmpName);
243                            int nFiles = CSLCount( files );
244                            char tmpSSName[1024];
245                            sprintf(tmpSSName,"%s/Input_%s_%s",tmpPath->value,s->name,tmpSid->value);
246                            mkdir(tmpSSName,0777);
247                           
248                            char tmpSName[1024];
249                            for(int kk=0;kk<nFiles;kk++){
250                              sprintf(tmpSName,"%s/%s",tmpName,files[kk]);
251                              VSILFILE* fmain=VSIFOpenL(tmpSName, "rb");
252                              if(fmain!=NULL){
253                                VSIFSeekL(fmain,0,SEEK_END);
254                                long count=VSIFTellL(fmain);
255                                VSIRewindL(fmain);
256                               
257                                char *content=(char*) malloc((count+1)*sizeof(char)); 
258                                VSIFReadL(content,1,count*sizeof(char),fmain);
259                               
260                                char tmpSSSName[1024];
261                                sprintf(tmpSSSName,"%s/%s",tmpSSName,files[kk]);
262                               
263                                FILE* fx=fopen(tmpSSSName, "wb");
264                                fwrite(content,1,count,fx);
265                                fclose(fx);
266                                VSIFCloseL(fmain);
267                                free(content);
268                                std::string test1(tmpSSSName);
269                                if(test1.find(".shp")!=std::string::npos){
270                                  setMapInMaps(inputs,paramKey.c_str(),"cache_file",tmpSSSName);
271                                  test=getMapFromMaps(inputs,paramKey.c_str(),"cache_file");
272                                }
273                              }
274                            }
275                            m_Application->SetParameterString(paramKey, test->value);
276                          }else{
277                            m_Application->SetParameterString(paramKey, val);
278                          }
279                          free(val);
280                        }
281                      }
282                      else
283                        if(test->value!=NULL)
284                          m_Application->SetParameterString(paramKey, test->value);
[550]285                  }
286
287                }else{
288                  if(type == ParameterType_OutputVectorData){
289                      char* ext="json";
290                      if(tmpVal!=NULL){
291                        if(strncasecmp(tmpVal->value,"text/xml",8)==0)
292                        ext="gml";
293                      else
294                        if(strncasecmp(tmpVal->value,"applicaton/json",15)==0)
295                          ext="json";
296                        else
297                          if(strncasecmp(tmpVal->value,"application/zip",14)==0)
298                            ext="shp";
299                          else
300                            if(strncasecmp(tmpVal->value,"application/vnd.google-earth.kml+xml",36)==0)
301                              ext="kml";
302                      }
303                      sprintf(tmp,"%s/%s_%s.%s",tmpPath->value,s->name,tmpSid->value,ext);
304                      m_Application->SetParameterString(paramKey, tmp);
305                      setMapInMaps(inputs,paramKey.c_str(),"generated_file",tmp);
306                  }
307                  else
308                    if(type == ParameterType_OutputFilename){
309                      char* ext="txt";
310                      if(tmpVal!=NULL){
311                        if(strncasecmp(tmpVal->value,"text/xml",8)==0)
312                          ext="xml";
313                        else
314                          if(strncasecmp(tmpVal->value,"text/csv",15)==0)
315                            ext="csv";
316                          else
317                            if(strncasecmp(tmpVal->value,"application/zip",14)==0)
318                              ext="shp";
319                            else
[564]320                              if(strncasecmp(tmpVal->value,"application/vnd.google-earth.kml+xml",36)==0)
321                                ext="kml";
322                              else
[565]323                                if(strncasecmp(tmpVal->value,"application/vnd.google-earth.kmz",32)==0){
[564]324                                  ext="kmz";
[565]325                                  sprintf(tmp,"%s/%s_%sxt.%s",tmpPath->value,s->name,tmpSid->value,ext);
326                                  m_Application->SetParameterString(paramKey, tmp);
327                                  setMapInMaps(outputs,paramKey.c_str(),"expected_generated_file",tmp);
328                                }
[564]329
[550]330                      }
331                      sprintf(tmp,"%s/%s_%s.%s",tmpPath->value,s->name,tmpSid->value,ext);
332                      m_Application->SetParameterString(paramKey, tmp);
333                      setMapInMaps(inputs,paramKey.c_str(),"generated_file",tmp);
334                    }
335
336                }
337              }else{
338                if(type == ParameterType_InputImageList){
339                  values.push_back(test->value);
340                  map* tmpPath=getMapFromMaps(inputs,paramKey.c_str(),"length");
341                  if(tmpPath!=NULL){
342                    int len=atoi(tmpPath->value);
343                    for(int k=1;k<len;k++){
[555]344                      char tmp[15];
[550]345                      sprintf(tmp,"cache_file_%d",k);
346                      map* tmpVal=getMapFromMaps(inputs,paramKey.c_str(),tmp);
347                      if(tmpVal!=NULL){
348                        values.push_back(tmpVal->value);
349                      }
350                    }
351                  }
352                  dynamic_cast<InputImageListParameter *> (param.GetPointer())->SetListFromFileName(values);
353                }
354                else
355                  if(type == ParameterType_InputVectorData || type == ParameterType_InputFilename){
356                    map* tmpPath=getMapFromMaps(m,"main","tmpPath");
357                    map* tmpSid=getMapFromMaps(m,"lenv","sid");
358                    char tmp[1024];
359                    map* tmpVal=getMapFromMaps(inputs,paramKey.c_str(),"mimeType");
360                    char* ext="json";
361                    if(tmpVal!=NULL){
362                      if(strncasecmp(tmpVal->value,"application/zip",14)==0){
363                        char tmpName[1024];
364                        symlink(test->value,ReplaceAll(test->value,".zca",".zip").c_str());
365                        sprintf(tmpName,"/vsizip/%s",ReplaceAll(test->value,".zca",".zip").c_str());
366                        char **files=VSIReadDir(tmpName);
367                        int nFiles = CSLCount( files );
368                        char tmpSSName[1024];
369                        sprintf(tmpSSName,"%s/Input_%s_%s",tmpPath->value,s->name,tmpSid->value);
370                        mkdir(tmpSSName,0777);
371                           
372                        char tmpSName[1024];
373                        for(int kk=0;kk<nFiles;kk++){
374                          sprintf(tmpSName,"%s/%s",tmpName,files[kk]);
375                          VSILFILE* fmain=VSIFOpenL(tmpSName, "rb");
376                          if(fmain!=NULL){
377                            VSIFSeekL(fmain,0,SEEK_END);
378                            long count=VSIFTellL(fmain);
379                            VSIRewindL(fmain);
380
381                            char *content=(char*) malloc((count+1)*sizeof(char)); 
382                            VSIFReadL(content,1,count*sizeof(char),fmain);
383                         
384                            char tmpSSSName[1024];
385                            sprintf(tmpSSSName,"%s/%s",tmpSSName,files[kk]);
386                           
387                            FILE* fx=fopen(tmpSSSName, "wb");
388                            fwrite(content,1,count,fx);
389                            fclose(fx);
390                            VSIFCloseL(fmain);
391                            free(content);
392                            std::string test1(tmpSSSName);
393                            if(test1.find(".shp")!=std::string::npos){
394                              setMapInMaps(inputs,paramKey.c_str(),"cache_file",tmpSSSName);
395                              test=getMapFromMaps(inputs,paramKey.c_str(),"cache_file");
396                            }
397                          }
398                        }
399                      }
400                    }
401                   
402                    m_Application->SetParameterString(paramKey, test->value);
403                  }
404                  else
405                    if(type == ParameterType_InputImage
406                       || type == ParameterType_ComplexInputImage || type == ParameterType_InputVectorData
407                       || type == ParameterType_InputFilename){
408                      m_Application->SetParameterString(paramKey, test->value);
[618]409                  }
[550]410              }
411            }
412            param->SetUserValue(true);
413            m_Application->UpdateParameters();
414          }
415
416          try{
417            if( m_Application->ExecuteAndWriteOutput() == 0 ){
418              std::vector< std::pair<std::string, std::string> > paramList;
419              paramList = m_Application->GetOutputParametersSumUp();
420              if(paramList.size()>0)
421                for( unsigned int i=0; i<paramList.size(); i++){
422                  setMapInMaps(outputs,paramList[i].first.c_str(),"value",paramList[i].second.c_str());
423                }
424              else{
425                const std::vector<std::string> appKeyList = m_Application->GetParametersKeys(true);
426                for (unsigned int i = 0; i < appKeyList.size(); i++){
427                  const std::string paramKey(appKeyList[i]);
428                  std::vector<std::string> values;
429                  Parameter::Pointer param = m_Application->GetParameterByKey(paramKey);
430                  ParameterType type = m_Application->GetParameterType(paramKey);
431                  if (type != ParameterType_Group && paramKey!="inxml" && paramKey!="outxml"
432                      && (type == ParameterType_OutputImage || type == ParameterType_OutputFilename
433                          || type == ParameterType_OutputVectorData ) ){
434                    if(type == ParameterType_OutputImage || type == ParameterType_OutputFilename || type == ParameterType_OutputVectorData){
435                      map* test=getMapFromMaps(outputs,paramKey.c_str(),"mimeType");
436                      if(test!=NULL && strncasecmp(test->value,"application/zip",15)==0){
437                       
438                        test=getMapFromMaps(inputs,paramKey.c_str(),"generated_file");
439                        char tmpName[1024];
440                        sprintf(tmpName,"/vsizip/%s",ReplaceAll(test->value,".shp",".zip").c_str());
441                        VSILFILE* fmain=VSIFOpenL(tmpName, "w");
442                        FILE * file;
443                        char *tmp;
444                        char tmpSName[1024];
445                        long count;
446                       
447                        char *exts[4];
448                        exts[0]=".shp";
449                        exts[1]=".shx";
450                        exts[2]=".dbf";
451                        exts[3]=".prj";
452                        for(int c=0;c<4;c++){
453                          sprintf(tmpSName,"%s/result%s",tmpName,exts[c]);
454                         
455                          file=fopen(ReplaceAll(test->value,".shp",exts[c]).c_str(),"rb");
456                          if(file!=NULL){
457                            fseek(file, 0, SEEK_END);
458                            count = ftell(file);
459                            rewind(file);
460                           
461                            tmp=(char*) malloc((count+1)*sizeof(char)); 
462                            fread(tmp,1,count*sizeof(char),file);
463                           
464                            VSILFILE* fx=VSIFOpenL(tmpSName, "wb");
465                            VSIFWriteL(tmp,1,count,fx);
466                            VSIFCloseL(fx);
467                            fclose(file);
468                            free(tmp);
469                          }
470                        }
471                       
472                        VSIFCloseL(fmain);
473                       
474                        FILE* file1=fopen(ReplaceAll(test->value,".shp",".zip").c_str(), "rb");
475                        fseek(file1, 0, SEEK_END);
476                        count=ftell(file1);
477                        rewind(file1);
478                       
479                        tmp=(char*) malloc((count+1)*sizeof(char)); 
480                        fread(tmp,1,count*sizeof(char),file1);
481                       
482                        file=fopen(ReplaceAll(test->value,".shp",".zip").c_str(),"wb");
483                        fwrite(tmp,1,count,file);
484                        fclose(file);
485                        free(tmp);
486                        fclose(file1);
487                        setMapInMaps(inputs,paramKey.c_str(),"generated_file",ReplaceAll(test->value,".shp",".zip").c_str());
488                      }
489                      test=getMapFromMaps(inputs,paramKey.c_str(),"generated_file");
490
491                      if(test!=NULL){
492                        setMapInMaps(outputs,paramKey.c_str(),"generated_file",test->value);
493                      }
494
495                    }
496                  }
497                }
498              }
499              res=3;
500              break;
501            }
502            else{
503              sprintf(tmpS, "The OTB Application %s cannot be run.", s->name);
504              setMapInMaps(m,"lenv","message",tmpS);
505              res=SERVICE_FAILED;
506            }
507          }
508          catch(std::exception& err){
509            setMapInMaps(m,"lenv","message",err.what());
510            return SERVICE_FAILED;
511           
512          }
513          catch(...){
514            setMapInMaps(m,"lenv","message","An unknown exception has been raised during application execution");
515            res=SERVICE_FAILED;
516          }
517          break;
518        }
519      }
520    }
521  }
[558]522
523  for (unsigned int i = 0; i < m_WatcherList.size(); i++){
524    m_WatcherList[i]->FreeConf();
525    delete m_WatcherList[i];
526    m_WatcherList[i] = NULL;
527  }
528  m_WatcherList.clear();
529
[550]530  return res;
531}
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