source: trunk/zoo-project/zoo-kernel/service_internal_ms.c @ 453

Last change on this file since 453 was 453, checked in by djay, 7 years ago

Add the optional Ruby Language Support to the ZOO-Kernel with an API similar to the Python ZOO-API. Small rewrite of Python support. Fix issue #86 and #87. Add usid in [lenv] section, this value is used to generate an unique identifier based on time and the process identifier. This usid is now used to name the stored result or the mapfile generated. Remove *some* warning messages displayed at compilation time.

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-csrc
File size: 34.2 KB
Line 
1/**
2 * Author : Gérald FENOY
3 *
4 *  Copyright 2010-2011 Fondazione Edmund Mach. All rights reserved.
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.
23 */
24
25#ifdef USE_MS
26#ifndef WIN32
27#define CLASS class
28#else
29#define CLASS _class
30#endif
31#include "service_internal_ms.h"
32
33/**
34 * Map composed by a main.cfg maps name as key and the corresponding
35 * MapServer Mafile Metadata name to use
36 * see doc from here :
37 *  - http://mapserver.org/ogc/wms_server.html
38 *  - http://mapserver.org/ogc/wfs_server.html
39 *  - http://mapserver.org/ogc/wcs_server.html
40 */
41map* getCorrespondance(){
42  map* res=createMap("encoding","ows_encoding");
43  addToMap(res,"abstract","ows_abstract");
44  addToMap(res,"title","ows_title");
45  addToMap(res,"keywords","ows_keywordlist");
46  addToMap(res,"fees","ows_fees");
47  addToMap(res,"accessConstraints","ows_accessconstraints");
48  addToMap(res,"providerName","ows_attribution_title");
49  addToMap(res,"providerSite","ows_service_onlineresource");
50  addToMap(res,"individualName","ows_contactperson");
51  addToMap(res,"positionName","ows_contactposition");
52  addToMap(res,"providerName","ows_contactorganization");
53  addToMap(res,"role","ows_role");
54  addToMap(res,"addressType","ows_addresstype");
55  addToMap(res,"addressCity","ows_city");
56  addToMap(res,"addressDeliveryPoint","ows_address");
57  addToMap(res,"addressPostalCode","ows_postcode");
58  addToMap(res,"addressAdministrativeArea","ows_stateorprovince");
59  addToMap(res,"addressCountry","ows_country");
60  addToMap(res,"phoneVoice","ows_contactvoicetelephone");
61  addToMap(res,"phoneFacsimile","ows_contactfacsimiletelephone");
62  addToMap(res,"addressElectronicMailAddress","ows_contactelectronicmailaddress");
63  // Missing Madatory Informations
64  addToMap(res,"hoursOfService","ows_hoursofservice");
65  addToMap(res,"contactInstructions","ows_contactinstructions");
66  return res;
67}
68
69void setMapSize(maps* output,double minx,double miny,double maxx,double maxy){
70  double maxWidth=640;
71  double maxHeight=480;
72  double deltaX=maxx-minx;
73  double deltaY=maxy-miny;
74  double qWidth;
75  qWidth=maxWidth/deltaX;
76  double qHeight;
77  qHeight=maxHeight/deltaY;
78#ifdef DEBUGMS
79  fprintf(stderr,"deltaX : %.15f \ndeltaY : %.15f\n",deltaX,deltaY);
80  fprintf(stderr,"qWidth : %.15f \nqHeight : %.15f\n",qWidth,qHeight);
81#endif
82
83  double width=deltaX*qWidth;
84  double height=height=deltaY*qWidth;
85  if(deltaX<deltaY){
86    width=deltaX*qHeight;
87    height=deltaY*qHeight;
88  }
89  if(height<0)
90    height=-height;
91  if(width<0)
92    width=-width;
93  char sWidth[1024];
94  char sHeight[1024];
95  sprintf(sWidth,"%.3f",width);
96  sprintf(sHeight,"%.3f",height);
97#ifdef DEBUGMS
98  fprintf(stderr,"sWidth : %.15f \nsHeight : %.15f\n",sWidth,sHeight);
99#endif
100  if(output!=NULL){
101    addToMap(output->content,"width",sWidth);
102    addToMap(output->content,"height",sHeight);
103  }
104}
105
106void setReferenceUrl(maps* m,maps* tmpI){
107  outputMapfile(m,tmpI);
108  map *msUrl=getMapFromMaps(m,"main","mapserverAddress");
109  map *msOgcVersion=getMapFromMaps(m,"main","msOgcVersion");
110  map *dataPath=getMapFromMaps(m,"main","dataPath");
111  map *sid=getMapFromMaps(m,"lenv","usid");
112  map* format=getMap(tmpI->content,"mimeType");
113  map* rformat=getMap(tmpI->content,"requestedMimeType");
114  map* width=getMap(tmpI->content,"width");
115  map* height=getMap(tmpI->content,"height");
116  map* protoMap=getMap(tmpI->content,"msOgc");
117  map* versionMap=getMap(tmpI->content,"msOgcVersion");
118  char options[3][5][25]={
119    {"WMS","1.3.0","GetMap","layers=%s","wms_extent"},
120    {"WFS","1.1.0","GetFeature","typename=%s","wcs_extent"},
121    {"WCS","1.1.0","GetCoverage","coverage=%s","wcs_extent"}
122  };
123  int proto=0;
124  if(rformat==NULL){
125    rformat=getMap(tmpI->content,"mimeType");
126  }
127  if(strncasecmp(rformat->value,"text/xml",8)==0)
128    proto=1;
129  if(strncasecmp(rformat->value,"image/tiff",10)==0)
130    proto=2;
131  if(protoMap!=NULL)
132    if(strncasecmp(protoMap->value,"WMS",3)==0)
133      proto=0;
134    else if(strncasecmp(protoMap->value,"WFS",3)==0)
135      proto=1;
136    else 
137      proto=2;
138 
139  char *protoVersion=options[proto][1];
140  if(proto==1){
141    if(msOgcVersion!=NULL)
142      protoVersion=msOgcVersion->value;
143    if(versionMap!=NULL)
144      protoVersion=versionMap->value;
145  }
146
147  map* extent=getMap(tmpI->content,options[proto][4]);
148  map* crs=getMap(tmpI->content,"crs");
149  int hasCRS=1;
150  if(crs==NULL){
151    crs=getMapFromMaps(m,"main","crs");
152    if(crs==NULL){
153      crs=createMap("crs","epsg:4326");
154      hasCRS=0;
155    }
156  }
157  char layers[128];
158  sprintf(layers,options[proto][3],tmpI->name);
159
160  char* webService_url=(char*)malloc((strlen(msUrl->value)+strlen(format->value)+strlen(tmpI->name)+strlen(width->value)+strlen(height->value)+strlen(extent->value)+256)*sizeof(char));
161
162  if(proto>0){
163    sprintf(webService_url,
164            "%s?map=%s/%s_%s.map&request=%s&service=%s&version=%s&%s&format=%s&bbox=%s&crs=%s",
165            msUrl->value,
166            dataPath->value,
167            tmpI->name,
168            sid->value,
169            options[proto][2],
170            options[proto][0],
171            protoVersion,
172            layers,
173            rformat->value,
174            extent->value,
175            crs->value
176            );
177  }
178  else{
179    sprintf(webService_url,
180            "%s?map=%s/%s_%s.map&request=%s&service=%s&version=%s&%s&width=%s&height=%s&format=%s&bbox=%s&crs=%s",
181            msUrl->value,
182            dataPath->value,
183            tmpI->name,
184            sid->value,
185            options[proto][2],
186            options[proto][0],
187            protoVersion,
188            layers,
189            width->value,
190            height->value,
191            rformat->value,
192            extent->value,
193            crs->value
194            );
195  }
196  if(hasCRS==0){
197    freeMap(&crs);
198    free(crs);
199  }
200  addToMap(tmpI->content,"Reference",webService_url);
201
202}
203
204/**
205 * Set projection using Authority Code and Name if available or fallback to
206 * proj4 definition if available or fallback to default EPSG:4326
207 */
208void setSrsInformations(maps* output,mapObj* m,layerObj* myLayer,
209                        char* pszProjection){
210  OGRSpatialReferenceH  hSRS;
211  map* msSrs=NULL;
212  hSRS = OSRNewSpatialReference(NULL);
213  if( pszProjection!=NULL && strlen(pszProjection)>1){
214    if(OSRImportFromWkt( hSRS, &pszProjection ) == CE_None ){
215      char *proj4Str=NULL;
216      if(OSRGetAuthorityName(hSRS,NULL)!=NULL && 
217         OSRGetAuthorityCode(hSRS,NULL)!=NULL){
218        char tmpSrs[20];
219        sprintf(tmpSrs,"%s:%s",
220                OSRGetAuthorityName(hSRS,NULL),OSRGetAuthorityCode(hSRS,NULL));
221        msLoadProjectionStringEPSG(&m->projection,tmpSrs);
222        msLoadProjectionStringEPSG(&myLayer->projection,tmpSrs);
223       
224        char tmpSrss[256];
225        sprintf(tmpSrss,"EPSG:4326 EPSG:900913 %s",tmpSrs);
226       
227        msInsertHashTable(&(m->web.metadata), "ows_srs", tmpSrss);
228        msInsertHashTable(&(myLayer->metadata), "ows_srs", tmpSrss);
229       
230#ifdef DEBUGMS
231        fprintf(stderr,"isGeo %b\n\n",OSRIsGeographic(hSRS)==TRUE);
232#endif
233        if(output!=NULL){
234          if(OSRIsGeographic(hSRS)==TRUE)
235            addToMap(output->content,"crs_isGeographic","true");
236          else
237            addToMap(output->content,"crs_isGeographic","false");
238          addToMap(output->content,"crs",tmpSrs);
239        }
240      }
241      else{
242        OSRExportToProj4(hSRS,&proj4Str);
243        fprintf(stderr,"Debug WKT: %s \n",proj4Str);
244        if(proj4Str!=NULL){
245#ifdef DEBUGMS
246          fprintf(stderr,"PROJ (%s)\n",proj4Str);
247#endif
248          msLoadProjectionString(&(m->projection),proj4Str);
249          msLoadProjectionString(&(myLayer->projection),proj4Str);
250          if(output!=NULL){ 
251            if(OSRIsGeographic(hSRS)==TRUE)
252              addToMap(output->content,"crs_isGeographic","true");
253            else
254              addToMap(output->content,"crs_isGeographic","false");
255          }
256        }
257        else{
258          msLoadProjectionStringEPSG(&m->projection,"EPSG:4326");
259          msLoadProjectionStringEPSG(&myLayer->projection,"EPSG:4326");
260          if(output!=NULL){
261            addToMap(output->content,"crs_isGeographic","true");
262          }
263        }
264        if(output!=NULL){
265          addToMap(output->content,"crs","EPSG:4326");
266          addToMap(output->content,"real_extent","true");
267        }
268        msInsertHashTable(&(m->web.metadata),"ows_srs", "EPSG:4326 EPSG:900913");
269        msInsertHashTable(&(myLayer->metadata),"ows_srs","EPSG:4326 EPSG:900913");
270      }
271    }
272  }
273  else{
274    if(output!=NULL){
275      msSrs=getMap(output->content,"msSrs");
276    }
277    if(msSrs!=NULL){
278      msLoadProjectionStringEPSG(&m->projection,msSrs->value);
279      msLoadProjectionStringEPSG(&myLayer->projection,msSrs->value);
280      char tmpSrs[128];
281      sprintf(tmpSrs,"%s EPSG:4326 EPSG:900913",msSrs);
282      msInsertHashTable(&(m->web.metadata),"ows_srs",tmpSrs);
283      msInsertHashTable(&(myLayer->metadata),"ows_srs",tmpSrs);
284    }else{
285      msLoadProjectionStringEPSG(&m->projection,"EPSG:4326");
286      msLoadProjectionStringEPSG(&myLayer->projection,"EPSG:4326");
287      msInsertHashTable(&(m->web.metadata),"ows_srs","EPSG:4326 EPSG:900913");
288      msInsertHashTable(&(myLayer->metadata),"ows_srs","EPSG:4326 EPSG:900913");
289    }
290    if(output!=NULL){
291      addToMap(output->content,"crs",msSrs->value);
292      addToMap(output->content,"crs_isGeographic","true");
293    }
294  }
295
296  OSRDestroySpatialReference( hSRS );
297}
298
299void setMsExtent(maps* output,mapObj* m,layerObj* myLayer,
300                 double minX,double minY,double maxX,double maxY){
301  msMapSetExtent(m,minX,minY,maxX,maxY);
302#ifdef DEBUGMS
303  fprintf(stderr,"Extent %.15f %.15f %.15f %.15f\n",minX,minY,maxX,maxY);
304#endif
305  char tmpExtent[1024];
306  sprintf(tmpExtent,"%.15f %.15f %.15f %.15f",minX,minY,maxX,maxY);
307#ifdef DEBUGMS
308  fprintf(stderr,"Extent %s\n",tmpExtent);
309#endif
310  msInsertHashTable(&(myLayer->metadata), "ows_extent", tmpExtent);
311 
312  if(output!=NULL){
313    map* test=getMap(output->content,"real_extent");
314    if(test!=NULL){
315      pointObj min, max;
316      projectionObj tempSrs;
317      min.x = m->extent.minx;
318      min.y = m->extent.miny;
319      max.x = m->extent.maxx;
320      max.y = m->extent.maxy;
321      char tmpSrsStr[1024];
322      msInitProjection(&tempSrs);
323      msLoadProjectionStringEPSG(&tempSrs,"EPSG:4326");
324
325      msProjectPoint(&(m->projection),&tempSrs,&min);
326      msProjectPoint(&m->projection,&tempSrs,&max);
327     
328      sprintf(tmpExtent,"%.3f,%.3f,%.3f,%.3f",min.y,min.x,max.y,max.x);
329      map* isGeo=getMap(output->content,"crs_isGeographic");
330#ifdef DEBUGMS
331      fprintf(stderr,"isGeo = %s\n",isGeo->value);
332#endif
333      if(isGeo!=NULL && strcasecmp("true",isGeo->value)==0)
334        sprintf(tmpExtent,"%f,%f,%f,%f", minY,minX, maxY, maxX);
335      addToMap(output->content,"wms_extent",tmpExtent);
336      sprintf(tmpSrsStr,"%.3f,%.3f,%.3f,%.3f",min.x,min.y,max.x,max.y);
337      addToMap(output->content,"wcs_extent",tmpExtent);
338    }else{
339      sprintf(tmpExtent,"%f,%f,%f,%f",minX, minY, maxX, maxY);
340      map* isGeo=getMap(output->content,"crs_isGeographic");
341      if(isGeo!=NULL){
342#ifdef DEBUGMS
343        fprintf(stderr,"isGeo = %s\n",isGeo->value);
344#endif
345        if(isGeo!=NULL && strcasecmp("true",isGeo->value)==0)
346          sprintf(tmpExtent,"%f,%f,%f,%f", minY,minX, maxY, maxX);
347      }
348      addToMap(output->content,"wms_extent",tmpExtent); 
349      sprintf(tmpExtent,"%.3f,%.3f,%.3f,%.3f",minX,minY,maxX,maxY);
350      addToMap(output->content,"wcs_extent",tmpExtent);
351    }
352  }
353
354  setMapSize(output,minX,minY,maxX,maxY);
355}
356
357int tryOgr(maps* conf,maps* output,mapObj* m){
358
359  map* tmpMap=getMap(output->content,"storage");
360  char *pszDataSource=tmpMap->value;
361
362  /**
363   * Try to open the DataSource using OGR
364   */
365  OGRRegisterAll();
366  /**
367   * Try to load the file as ZIP
368   */
369
370  OGRDataSourceH poDS1 = NULL;
371  OGRSFDriverH *poDriver1 = NULL;
372  char *dsName=(char*)malloc((8+strlen(pszDataSource)+1)*sizeof(char));
373  char *odsName=zStrdup(pszDataSource);
374  char *sdsName=zStrdup(pszDataSource);
375  char *demo=strstr(odsName,".");
376  sdsName[strlen(sdsName)-(strlen(demo)-1)]='d';
377  sdsName[strlen(sdsName)-(strlen(demo)-2)]='i';
378  sdsName[strlen(sdsName)-(strlen(demo)-3)]='r';
379  sdsName[strlen(sdsName)-(strlen(demo)-4)]=0;
380
381  odsName[strlen(odsName)-(strlen(demo)-1)]='z';
382  odsName[strlen(odsName)-(strlen(demo)-2)]='i';
383  odsName[strlen(odsName)-(strlen(demo)-3)]='p';
384  odsName[strlen(odsName)-(strlen(demo)-4)]=0;
385  sprintf(dsName,"/vsizip/%s",odsName);
386
387#ifdef DEBUGMS
388  fprintf(stderr,"Try loading %s, %s, %s\n",dsName,odsName,dsName);
389#endif
390
391  FILE* file = fopen(pszDataSource, "rb");
392  FILE* fileZ = fopen(odsName, "wb");
393  fseek(file, 0, SEEK_END);
394  unsigned long fileLen=ftell(file);
395  fseek(file, 0, SEEK_SET);
396  char *buffer=(char *)malloc(fileLen+1);
397  fread(buffer, fileLen, 1, file);
398  fwrite(buffer,fileLen, 1, fileZ);
399  fclose(file);
400  fclose(fileZ);
401  free(buffer);
402#ifdef DEBUGMS
403  fprintf(stderr,"Try loading %s",dsName);
404#endif
405  poDS1 = OGROpen( dsName, FALSE, poDriver1 );
406  if( poDS1 == NULL ){
407    fprintf(stderr,"Unable to access the DataSource as ZIP File\n");
408    setMapInMaps(conf,"lenv","message","Unable to open datasource in read only mode");
409    OGR_DS_Destroy(poDS1);
410  }else{
411#ifdef DEBUGMS
412    fprintf(stderr,"The DataSource is a  ZIP File\n");
413#endif
414    char** demo=VSIReadDir(dsName);
415    int i=0;
416    zMkdir(sdsName
417#ifndef WIN32
418                ,S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
419#endif
420                );
421    while(demo[i]!=NULL){
422#ifdef DEBUGMS
423      fprintf(stderr,"ZIP File content : %s\n",demo[i]);
424#endif
425      char *tmpDs=(char*)malloc((strlen(dsName)+strlen(demo[i])+2)*sizeof(char));
426      sprintf(tmpDs,"%s/%s",dsName,demo[i]);
427      fprintf(stderr,"read : %s\n",tmpDs);
428     
429      VSILFILE* vsif=VSIFOpenL(tmpDs,"rb");
430#ifdef DEBUGMS
431      fprintf(stderr,"open : %s\n",tmpDs);
432#endif
433      VSIFSeekL(vsif,0,SEEK_END);
434      vsi_l_offset size=VSIFTellL(vsif);
435#ifdef DEBUGMS
436      fprintf(stderr,"size : %d\n",size);
437#endif
438      VSIFSeekL(vsif,0,SEEK_SET);
439      char *vsifcontent=(char*) malloc(((int)size+1)*sizeof(char));
440      VSIFReadL(vsifcontent,1,(size_t)size,vsif);
441      char *fpath=(char*) malloc((strlen(sdsName)+strlen(demo[1])+2)*sizeof(char));
442      sprintf(fpath,"%s/%s",sdsName,demo[i]);
443      int f=zOpen(fpath,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
444      zWrite(f,vsifcontent,(int)size);
445      close(f);
446      chmod(fpath,S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH);
447      char* tmpP=strstr(fpath,".shp");
448      if(tmpP==NULL)
449        tmpP=strstr(fpath,".SHP");
450      if(tmpP!=NULL){
451#ifdef DEBUGMS
452        fprintf(stderr,"*** DEBUG %s\n",strstr(tmpP,"."));
453#endif
454        if( strcmp(tmpP,".shp")==0 || strcmp(tmpP,".SHP")==0 ){
455          tmpMap=getMap(output->content,"storage");
456          free(tmpMap->value);
457          tmpMap->value=(char*) malloc((strlen(fpath)+1)*sizeof(char));
458          sprintf(tmpMap->value,"%s",fpath);
459          pszDataSource=tmpMap->value;
460#ifdef DEBUGMS
461          fprintf(stderr,"*** DEBUG %s\n",pszDataSource);
462#endif
463        }
464      }
465      VSIFCloseL(vsif);
466      i++;
467    }
468
469  }
470
471  OGRDataSourceH poDS = NULL;
472  OGRSFDriverH *poDriver = NULL;
473  poDS = OGROpen( pszDataSource, FALSE, poDriver );
474  if( poDS == NULL ){
475#ifdef DEBUGMS
476    fprintf(stderr,"Unable to access the DataSource %s\n",pszDataSource);
477#endif
478    setMapInMaps(conf,"lenv","message","Unable to open datasource in read only mode");
479    OGR_DS_Destroy(poDS);
480    OGRCleanupAll();
481#ifdef DEBUGMS
482    fprintf(stderr,"Unable to access the DataSource, exit! \n"); 
483#endif
484    return -1;
485  }
486
487  int iLayer = 0;
488  for( iLayer=0; iLayer < OGR_DS_GetLayerCount(poDS); iLayer++ ){
489    OGRLayerH poLayer = OGR_DS_GetLayer(poDS,iLayer);
490
491    if( poLayer == NULL ){
492#ifdef DEBUGMS
493      fprintf(stderr,"Unable to access the DataSource Layer \n");
494#endif
495      setMapInMaps(conf,"lenv","message","Unable to open datasource in read only mode");
496      return -1;
497    }
498
499    /**
500     * Add a new layer set name, data
501     */
502    if(msGrowMapLayers(m)==NULL){
503      return -1;
504    }
505    if(initLayer((m->layers[m->numlayers]), m) == -1){
506      return -1;
507    }
508
509    layerObj* myLayer=m->layers[m->numlayers];
510#ifdef DEBUGMS
511    dumpMaps(output);
512#endif
513    myLayer->name = zStrdup(output->name);
514    myLayer->tileitem=NULL;
515    myLayer->data = zStrdup(OGR_L_GetName(poLayer));
516    myLayer->connection = zStrdup(pszDataSource);
517    myLayer->index = m->numlayers;
518    myLayer->dump = MS_TRUE;
519    myLayer->status = MS_ON;
520    msConnectLayer(myLayer,MS_OGR,pszDataSource);
521
522    /**
523     * Detect the Geometry Type or use Polygon
524     */
525    if(OGR_L_GetGeomType(poLayer) != wkbUnknown){
526      switch(OGR_L_GetGeomType(poLayer)){
527      case wkbPoint:
528      case wkbMultiPoint:
529      case wkbPoint25D:
530      case wkbMultiPoint25D:
531#ifdef DEBUGMS
532        fprintf(stderr,"POINT DataSource Layer \n");
533#endif
534        myLayer->type = MS_LAYER_POINT;
535        break;
536      case wkbLineString :
537      case wkbMultiLineString :
538      case wkbLineString25D:
539      case wkbMultiLineString25D:
540#ifdef DEBUGMS
541        fprintf(stderr,"LINE DataSource Layer \n");
542#endif
543        myLayer->type = MS_LAYER_LINE;
544        break;
545      case wkbPolygon:
546      case wkbMultiPolygon:
547      case wkbPolygon25D:
548      case wkbMultiPolygon25D:
549#ifdef DEBUGMS
550        fprintf(stderr,"POLYGON DataSource Layer \n");
551#endif
552        myLayer->type = MS_LAYER_POLYGON;
553        break;
554      default:
555        myLayer->type = MS_LAYER_POLYGON;
556        break;
557      }
558    }else
559      myLayer->type = MS_LAYER_POLYGON;
560
561    /**
562     * Detect spatial reference or use WGS84
563     */
564    OGRSpatialReferenceH srs=OGR_L_GetSpatialRef(poLayer);
565    if(srs!=NULL){
566      char *wkt=NULL;
567      OSRExportToWkt(srs,&wkt);
568      setSrsInformations(output,m,myLayer,wkt);
569    }
570    else{
571      addToMap(output->content,"crs","EPSG:4326");
572      addToMap(output->content,"crs_isGeographic","true");
573      msLoadProjectionStringEPSG(&m->projection,"EPSG:4326");
574      msInsertHashTable(&(m->web.metadata), "ows_srs", "EPSG:4326 EPSG:900913");
575      msInsertHashTable(&(myLayer->metadata), "ows_srs", "EPSG:4326 EPSG:900913");
576    }
577
578    map* crs=getMap(output->content,"crs");
579    map* isGeo=getMap(output->content,"crs_isGeographic");
580
581    OGREnvelope oExt;
582    if (OGR_L_GetExtent(poLayer,&oExt, TRUE) == OGRERR_NONE){
583      setMsExtent(output,m,myLayer,oExt.MinX, oExt.MinY, oExt.MaxX, oExt.MaxY);
584    }
585 
586    /**
587     * Detect the FID column or use the first attribute field as FID
588     */
589    char *fid=(char*)OGR_L_GetFIDColumn(poLayer);
590    if(strlen(fid)==0){
591      OGRFeatureDefnH def=OGR_L_GetLayerDefn(poLayer);
592      int fIndex=0;
593      for(fIndex=0;fIndex<OGR_FD_GetFieldCount(def);fIndex++){
594        OGRFieldDefnH fdef=OGR_FD_GetFieldDefn(def,fIndex);
595        fid=(char*)OGR_Fld_GetNameRef(fdef);
596        break;
597      }
598    }
599    msInsertHashTable(&(myLayer->metadata), "gml_featureid", fid);
600    msInsertHashTable(&(myLayer->metadata), "gml_include_items", "all");
601    msInsertHashTable(&(myLayer->metadata), "ows_name", output->name);
602    map* tmpMap=getMap(output->content,"title");
603    if(tmpMap!=NULL)
604      msInsertHashTable(&(myLayer->metadata), "ows_title", tmpMap->value);
605    else
606      msInsertHashTable(&(myLayer->metadata), "ows_title", "Default Title");
607   
608    if(msGrowLayerClasses(myLayer) == NULL)
609      return -1;
610    if(initClass((myLayer->CLASS[myLayer->numclasses])) == -1)
611      return -1;
612    myLayer->CLASS[myLayer->numclasses]->type = myLayer->type;
613    if(msGrowClassStyles(myLayer->CLASS[myLayer->numclasses]) == NULL)
614      return -1;
615    if(initStyle(myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]) == -1)
616      return -1;
617    /**
618     * Apply msStyle else fallback to the default style
619     */
620    tmpMap=getMap(output->content,"msStyle");
621    if(tmpMap!=NULL)
622      msUpdateStyleFromString(myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles],tmpMap->value,0);
623    else{
624      /**
625       * Set style
626       */
627      myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->color.red=125;
628      myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->color.green=125;
629      myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->color.blue=255;
630      myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->outlinecolor.red=80;
631      myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->outlinecolor.green=80;
632      myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->outlinecolor.blue=80;
633     
634      /**
635       * Set specific style depending on type
636       */
637      if(myLayer->type == MS_LAYER_POLYGON)
638        myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->width=3;
639      if(myLayer->type == MS_LAYER_LINE){
640        myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->width=3;
641        myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->outlinewidth=1.5;
642      }
643      if(myLayer->type == MS_LAYER_POINT){
644        myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->symbol=1;
645        myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->size=15;
646      }
647     
648    }
649    myLayer->CLASS[myLayer->numclasses]->numstyles++;
650    myLayer->numclasses++;
651   
652    m->layerorder[m->numlayers] = m->numlayers;
653    m->numlayers++;
654
655  }
656
657  OGR_DS_Destroy(poDS);
658  OGRCleanupAll();
659
660  return 1;
661}
662
663
664int tryGdal(maps* conf,maps* output,mapObj* m){
665  map* tmpMap=getMap(output->content,"storage");
666  char *pszFilename=tmpMap->value;
667  GDALDatasetH hDataset;
668  GDALRasterBandH hBand;
669  double adfGeoTransform[6];
670  int i, iBand;
671 
672  /**
673   * Try to open the DataSource using GDAL
674   */
675  GDALAllRegister();
676  hDataset = GDALOpen( pszFilename, GA_ReadOnly );
677  if( hDataset == NULL ){
678#ifdef DEBUGMS
679    fprintf(stderr,"Unable to access the DataSource %s \n",pszFilename);
680#endif
681    setMapInMaps(conf,"lenv","message","gdalinfo failed - unable to open");
682    GDALDestroyDriverManager();
683    return -1;
684  }
685#ifdef DEBUGMS
686  fprintf(stderr,"Accessing the DataSource %s %d\n",pszFilename,__LINE__);
687#endif
688
689  /**
690   * Add a new layer set name, data
691   */
692  if(msGrowMapLayers(m)==NULL){
693    return -1;
694  }
695  if(initLayer((m->layers[m->numlayers]), m) == -1){
696    return -1;
697  }
698  m->layers[m->numlayers]->index=m->numlayers;
699
700  layerObj* myLayer=m->layers[m->numlayers];
701  myLayer->name = zStrdup(output->name);
702  myLayer->tileitem=NULL;
703  myLayer->data = zStrdup(pszFilename);
704  myLayer->index = m->numlayers;
705  myLayer->dump = MS_TRUE;
706  myLayer->status = MS_ON;
707  myLayer->type = MS_LAYER_RASTER;
708
709  char *title=output->name;
710  tmpMap=getMap(output->content,"title");
711  if(tmpMap!=NULL)
712    title=tmpMap->value;
713  char *abstract=output->name;
714  tmpMap=getMap(output->content,"abstract");
715  if(tmpMap!=NULL)
716    abstract=tmpMap->value;
717
718  msInsertHashTable(&(myLayer->metadata), "ows_label", title);
719  msInsertHashTable(&(myLayer->metadata), "ows_title", title);
720  msInsertHashTable(&(myLayer->metadata), "ows_abstract", abstract);
721  msInsertHashTable(&(myLayer->metadata), "ows_rangeset_name", output->name);
722  msInsertHashTable(&(myLayer->metadata), "ows_rangeset_label", title);
723
724  /**
725   * Set Map Size to the raster size
726   */
727  m->width=GDALGetRasterXSize( hDataset );
728  m->height=GDALGetRasterYSize( hDataset );
729 
730  /**
731   * Set projection using Authority Code and Name if available or fallback to
732   * proj4 definition if available or fallback to default EPSG:4326
733   */
734  const char *tRef=GDALGetProjectionRef( hDataset );
735  if( tRef != NULL && strlen(tRef)>0 ){
736    char *pszProjection;
737    pszProjection = (char *) GDALGetProjectionRef( hDataset );
738    //#ifdef DEBUGMS
739    fprintf(stderr,"Accessing the DataSource %s\n",GDALGetProjectionRef( hDataset ));
740    //#endif
741    setSrsInformations(output,m,myLayer,pszProjection);
742  }else{
743    fprintf(stderr,"NO SRS FOUND ! %s\n",GDALGetProjectionRef( hDataset ));   
744  }
745
746
747  /**
748   * Set extent
749   */
750  if( GDALGetGeoTransform( hDataset, adfGeoTransform ) == CE_None ){
751    if( adfGeoTransform[2] == 0.0 && adfGeoTransform[4] == 0.0 ){
752
753      double minX = adfGeoTransform[0]
754        + adfGeoTransform[2] * GDALGetRasterYSize(hDataset);
755      double minY = adfGeoTransform[3]
756        + adfGeoTransform[5] * GDALGetRasterYSize(hDataset);
757
758      double maxX = adfGeoTransform[0]
759        + adfGeoTransform[1] * GDALGetRasterXSize(hDataset);
760      double maxY = adfGeoTransform[3]
761        + adfGeoTransform[4] * GDALGetRasterXSize(hDataset);
762
763       setMsExtent(output,m,myLayer,minX,minY,maxX,maxY);
764
765    }
766  }
767
768  /**
769   * Extract information about available bands to set the bandcount and the
770   * processing directive
771   */
772  char nBands[2];
773  int nBandsI=GDALGetRasterCount( hDataset );
774  sprintf(nBands,"%d",GDALGetRasterCount( hDataset ));
775  msInsertHashTable(&(myLayer->metadata), "ows_bandcount", nBands);
776  if(nBandsI>=3)
777    msLayerAddProcessing(myLayer,"BANDS=1,2,3");
778  else if(nBandsI>=2)
779    msLayerAddProcessing(myLayer,"BANDS=1,2");
780  else
781    msLayerAddProcessing(myLayer,"BANDS=1");
782
783  /**
784   * Name available Bands
785   */
786  char lBands[6];
787  char *nameBands=NULL;
788  for( iBand = 0; iBand < nBandsI; iBand++ ){
789    sprintf(lBands,"Band%d",iBand+1);
790    if(nameBands==NULL){
791      nameBands=(char*)malloc((strlen(lBands)+1)*sizeof(char));
792      sprintf(nameBands,"%s",lBands);
793    }else{
794      if(iBand<4){
795        char *tmpS=zStrdup(nameBands);
796        nameBands=(char*)realloc(nameBands,(strlen(nameBands)+strlen(lBands)+1)*sizeof(char));
797        sprintf(nameBands,"%s %s",tmpS,lBands);
798        free(tmpS);
799      }
800    }
801  }
802  msInsertHashTable(&(myLayer->metadata), "ows_bandnames", nameBands);
803 
804  /**
805   * Loops over metadata informations to setup specific informations
806   */
807  for( iBand = 0; iBand < nBandsI; iBand++ ){
808    //int         bGotNodata;//, bSuccess;
809    double      adfCMinMax[2]/*, dfNoData*/;
810    //int         nBlockXSize, nBlockYSize, nMaskFlags;
811    //double      /*dfMean, dfStdDev*/;
812    hBand = GDALGetRasterBand( hDataset, iBand+1 );
813
814    CPLErrorReset();
815    GDALComputeRasterMinMax( hBand, FALSE, adfCMinMax );
816    char tmpN[21];
817    sprintf(tmpN,"Band%d",iBand+1);
818    if (CPLGetLastErrorType() == CE_None){
819      char tmpMm[100];
820      sprintf(tmpMm,"%.3f %.3f",adfCMinMax[0],adfCMinMax[1]);
821      char tmpI[21];
822      sprintf(tmpI,"%s_interval",tmpN);
823      msInsertHashTable(&(myLayer->metadata), tmpI, tmpMm);
824
825      map* test=getMap(output->content,"msClassify");
826      if(test!=NULL && strncasecmp(test->value,"true",4)==0){
827        /**
828         * Classify one band raster pixel value using regular interval
829         */
830        int _tmpColors[10][3]={
831          {102,153,204},
832          {51,102,153},
833          {102,102,204},
834          {51,204,0},
835          {153,255,102},
836          {204,255,102},
837          {102,204,153},
838          {255,69,64},
839          {255,192,115},
840          {255,201,115}
841        };
842         
843        if(nBandsI==1){
844          double delta=adfCMinMax[1]-adfCMinMax[0];
845          double interval=delta/10;
846          double cstep=adfCMinMax[0];
847          for(i=0;i<10;i++){
848            /**
849             * Create a new class
850             */
851            if(msGrowLayerClasses(myLayer) == NULL)
852              return -1;
853            if(initClass((myLayer->CLASS[myLayer->numclasses])) == -1)
854              return -1;
855            myLayer->CLASS[myLayer->numclasses]->type = myLayer->type;
856            if(msGrowClassStyles(myLayer->CLASS[myLayer->numclasses]) == NULL)
857              return -1;
858            if(initStyle(myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]) == -1)
859              return -1;
860           
861            /**
862             * Set class name
863             */
864            char className[7];
865            sprintf(className,"class%d",i);
866            myLayer->CLASS[myLayer->numclasses]->name=zStrdup(className);
867           
868            /**
869             * Set expression
870             */
871            char expression[1024];
872            if(i+1<10)
873              sprintf(expression,"([pixel]>=%.3f AND [pixel]<%.3f)",cstep,cstep+interval);
874            else
875              sprintf(expression,"([pixel]>=%.3f AND [pixel]<=%.3f)",cstep,cstep+interval);
876            msLoadExpressionString(&myLayer->CLASS[myLayer->numclasses]->expression,expression);
877           
878            /**
879             * Set color
880             */
881            myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->color.red=_tmpColors[i][0];
882            myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->color.green=_tmpColors[i][1];
883            myLayer->CLASS[myLayer->numclasses]->styles[myLayer->CLASS[myLayer->numclasses]->numstyles]->color.blue=_tmpColors[i][2];
884            cstep+=interval;
885            myLayer->CLASS[myLayer->numclasses]->numstyles++;
886            myLayer->numclasses++;
887           
888          }
889         
890          char tmpMm[100];
891          sprintf(tmpMm,"%.3f %.3f",adfCMinMax[0],adfCMinMax[1]);
892         
893        }
894      }
895    }
896    if( strlen(GDALGetRasterUnitType(hBand)) > 0 ){
897      char tmpU[21];
898      sprintf(tmpU,"%s_band_uom",tmpN);
899      msInsertHashTable(&(myLayer->metadata), tmpU, GDALGetRasterUnitType(hBand));
900    }
901
902  }
903
904  m->layerorder[m->numlayers] = m->numlayers;
905  m->numlayers++;
906  GDALClose( hDataset );
907  GDALDestroyDriverManager();
908  CPLCleanupTLS();
909  return 1;
910}
911
912/**
913 * Create a MapFile for WMS, WFS or WCS Service output
914 */
915void outputMapfile(maps* conf,maps* outputs){
916
917  /**
918   * Firs store the value on disk
919   */
920  map* mime=getMap(outputs->content,"mimeType");
921  char *ext="data";
922  if(mime!=NULL)
923    if(strncasecmp(mime->value,"application/json",16)==0)
924      ext="json";
925 
926  map* tmpMap=getMapFromMaps(conf,"main","dataPath");
927  map* sidMap=getMapFromMaps(conf,"lenv","usid");
928  char *pszDataSource=(char*)malloc((strlen(tmpMap->value)+strlen(sidMap->value)+strlen(outputs->name)+17)*sizeof(char));
929  sprintf(pszDataSource,"%s/ZOO_DATA_%s_%s.%s",tmpMap->value,outputs->name,sidMap->value,ext);
930  int f=open(pszDataSource,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
931  map* sizeMap=getMap(outputs->content,"size");
932  map* vData=getMap(outputs->content,"value");
933  if(sizeMap!=NULL){
934    write(f,vData->value,atoi(sizeMap->value)*sizeof(char));
935  }
936  else{
937    write(f,vData->value,strlen(vData->value)*sizeof(char));
938  }
939  close(f);
940  //exit(-1);
941  addToMap(outputs->content,"storage",pszDataSource);
942
943  /*
944   * Create an empty map, set name, default size and extent
945   */
946  mapObj *myMap=msNewMapObj();
947  free(myMap->name);
948  myMap->name=zStrdup("ZOO-Project_WXS_Server");
949  msMapSetSize(myMap,2048,2048);
950  msMapSetExtent(myMap,-1,-1,1,1);
951 
952  /*
953   * Set imagepath and imageurl using tmpPath and tmpUrl from main.cfg
954   */
955  map *tmp1=getMapFromMaps(conf,"main","tmpPath");
956  myMap->web.imagepath=zStrdup(tmp1->value);
957  tmp1=getMapFromMaps(conf,"main","tmpUrl");
958  myMap->web.imageurl=zStrdup(tmp1->value);
959 
960  /*
961   * Define supported output formats
962   */
963  outputFormatObj *o1=msCreateDefaultOutputFormat(NULL,"AGG/PNG","png");
964  o1->imagemode=MS_IMAGEMODE_RGBA;
965  o1->transparent=MS_TRUE;
966  o1->inmapfile=MS_TRUE;
967  msAppendOutputFormat(myMap,msCloneOutputFormat(o1));
968  msFreeOutputFormat(o1);
969
970#ifdef USE_KML
971  outputFormatObj *o2=msCreateDefaultOutputFormat(NULL,"KML","kml");
972  o2->inmapfile=MS_TRUE; 
973  msAppendOutputFormat(myMap,msCloneOutputFormat(o2));
974  msFreeOutputFormat(o2);
975#endif
976
977  outputFormatObj *o3=msCreateDefaultOutputFormat(NULL,"GDAL/GTiff","tiff");
978  if(!o3)
979    fprintf(stderr,"Unable to initialize GDAL driver !\n");
980  else{
981    o3->imagemode=MS_IMAGEMODE_BYTE;
982    o3->inmapfile=MS_TRUE; 
983    msAppendOutputFormat(myMap,msCloneOutputFormat(o3));
984    msFreeOutputFormat(o3);
985  }
986
987  outputFormatObj *o4=msCreateDefaultOutputFormat(NULL,"GDAL/AAIGRID","grd");
988  if(!o4)
989    fprintf(stderr,"Unable to initialize GDAL driver !\n");
990  else{
991    o4->imagemode=MS_IMAGEMODE_INT16;
992    o4->inmapfile=MS_TRUE; 
993    msAppendOutputFormat(myMap,msCloneOutputFormat(o4));
994    msFreeOutputFormat(o4);
995  }
996
997#ifdef USE_CAIRO
998  outputFormatObj *o5=msCreateDefaultOutputFormat(NULL,"CAIRO/PNG","cairopng");
999  if(!o5)
1000    fprintf(stderr,"Unable to initialize CAIRO driver !\n");
1001  else{
1002    o5->imagemode=MS_IMAGEMODE_RGBA;
1003    o5->transparent=MS_TRUE;
1004    o5->inmapfile=MS_TRUE;
1005    msAppendOutputFormat(myMap,msCloneOutputFormat(o5));
1006    msFreeOutputFormat(o5);
1007  }
1008#endif
1009
1010  /*
1011   * Set default projection to EPSG:4326
1012   */
1013  msLoadProjectionStringEPSG(&myMap->projection,"EPSG:4326");
1014  myMap->transparent=1;
1015
1016  /**
1017   * Set metadata extracted from main.cfg file maps
1018   */
1019  maps* cursor=conf;
1020  map* correspondance=getCorrespondance();
1021  while(cursor!=NULL){
1022    map* _cursor=cursor->content;
1023    map* vMap;
1024    while(_cursor!=NULL){
1025      if((vMap=getMap(correspondance,_cursor->name))!=NULL){
1026        if (msInsertHashTable(&(myMap->web.metadata), vMap->value, _cursor->value) == NULL){
1027#ifdef DEBUGMS
1028          fprintf(stderr,"Unable to add metadata");
1029#endif
1030          return;
1031        }
1032      }
1033      _cursor=_cursor->next;
1034    }
1035    cursor=cursor->next;
1036  }
1037
1038  /*
1039   * Set mapserver PROJ_LIB/GDAL_DATA or any other config parameter from
1040   * the main.cfg [mapserver] section if any
1041   */
1042  maps *tmp3=getMaps(conf,"mapserver");
1043  if(tmp3!=NULL){
1044    map* tmp4=tmp3->content;
1045    while(tmp4!=NULL){
1046      msSetConfigOption(myMap,tmp4->name,tmp4->value);
1047      tmp4=tmp4->next;
1048    }
1049  }
1050
1051  /**
1052   * Set a ows_rootlayer_title, 
1053   */
1054  if (msInsertHashTable(&(myMap->web.metadata), "ows_rootlayer_name", "ZOO_Project_Layer") == NULL){
1055#ifdef DEBUGMS
1056    fprintf(stderr,"Unable to add metadata");
1057#endif
1058    return;
1059  }
1060  if (msInsertHashTable(&(myMap->web.metadata), "ows_rootlayer_title", "ZOO_Project_Layer") == NULL){
1061#ifdef DEBUGMS
1062    fprintf(stderr,"Unable to add metadata");
1063#endif
1064    return;
1065  }
1066
1067  /**
1068   * Enable all the WXS requests using ows_enable_request
1069   * see http://mapserver.org/trunk/development/rfc/ms-rfc-67.html
1070   */
1071  if (msInsertHashTable(&(myMap->web.metadata), "ows_enable_request", "*") == NULL){
1072#ifdef DEBUGMS
1073    fprintf(stderr,"Unable to add metadata");
1074#endif
1075    return;
1076  }
1077  msInsertHashTable(&(myMap->web.metadata), "ows_srs", "EPSG:4326");
1078
1079  if(tryOgr(conf,outputs,myMap)<0)
1080    if(tryGdal(conf,outputs,myMap)<0)
1081      return ;
1082
1083  tmp1=getMapFromMaps(conf,"main","dataPath");
1084  char *tmpPath=(char*)malloc((13+strlen(tmp1->value))*sizeof(char));
1085  sprintf(tmpPath,"%s/symbols.sym",tmp1->value);
1086  msInitSymbolSet(&myMap->symbolset);
1087  myMap->symbolset.filename=zStrdup(tmpPath);
1088  free(tmpPath);
1089
1090  map* sid=getMapFromMaps(conf,"lenv","usid");
1091  char *mapPath=
1092    (char*)malloc((16+strlen(outputs->name)+strlen(tmp1->value))*sizeof(char));
1093  sprintf(mapPath,"%s/%s_%s.map",tmp1->value,outputs->name,sid->value);
1094  msSaveMap(myMap,mapPath);
1095  msFreeMap(myMap);
1096}
1097
1098#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