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

Last change on this file since 518 was 492, checked in by djay, 10 years ago

Inputs passed by reference downloaded in parallel. Conform behavior for DescribeProcess? when the Identifier was not found.

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