/**********************************************************************
*
* Project: MapServer
* Purpose: Implementation of WFS CONNECTIONTYPE - client to WFS servers
* Author: Daniel Morissette, DM Solutions Group (morissette@dmsolutions.ca)
*
**********************************************************************
* Copyright (c) 2002, Daniel Morissette, DM Solutions Group Inc
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
**********************************************************************
*
* $Log: mapwfslayer.c,v $
* Revision 1.37 2006/05/19 20:53:27 dan
* Use lp->layerinfo for OGR connections (instead of ogrlayerinfo) (bug 331)
*
* Revision 1.36 2005/10/28 01:09:42 jani
* MS RFC 3: Layer vtable architecture (bug 1477)
*
* Revision 1.35 2005/05/12 18:41:54 assefa
* Use %f instead of %lf for bbox output : Bug 1239.
*
* Revision 1.34 2005/04/22 15:47:10 assefa
* Bug 1302: the wfs/ows_service parameter is not used any more. The
* service is always set to WFS for WFS layers.
*
* Revision 1.33 2005/04/21 14:39:47 dan
* Fixed typo in error messages (bug 1302)
*
* Revision 1.32 2005/02/18 03:06:48 dan
* Turned all C++ (//) comments into C comments (bug 1238)
*
* Revision 1.31 2005/02/14 04:33:32 sdlime
* Changed %.17g to %.15g for WMS/WFS server code.
*
* Revision 1.30 2005/01/28 06:16:54 sdlime
* Applied patch to make function prototypes ANSI C compliant. Thanks to Petter Reinholdtsen. This fixes but 1181.
*
* Revision 1.29 2004/11/25 06:19:05 dan
* Add trailing "?" or "&" to connection string when required in WFS
* client layers using GET method (bug 1082)
*
* Revision 1.28 2004/11/23 23:16:29 dan
* Fixed build error when WFS was not enabled (bug 1083)
*
* Revision 1.27 2004/11/16 21:57:49 dan
* Final pass at updating WMS/WFS client/server interfaces to lookup "ows_*"
* metadata in addition to default "wms_*"/"wfs_*" metadata (bug 568)
*
* Revision 1.26 2004/11/15 20:35:02 dan
* Added msLayerIsOpen() to all vector layer types (bug 1051)
*
* Revision 1.25 2004/11/12 17:05:01 assefa
* Use %.17g instrad of %f when output bbox values (Bug 678)
*
* Revision 1.24 2004/10/21 04:30:57 frank
* Added standardized headers. Added MS_CVSID().
*
* Revision 1.23 2004/07/13 20:39:37 dan
* Made msTmpFile() more robust using msBuildPath() to return absolute paths (bug 771)
*
* Revision 1.22 2004/06/22 20:55:21 sean
* Towards resolving issue 737 changed hashTableObj to a structure which contains a hashObj **items. Changed all hash table access functions to operate on the target table by reference. msFreeHashTable should not be used on the hashTableObj type members of mapserver structures, use msFreeHashItems instead.
*
* Revision 1.21 2004/04/27 16:16:11 assefa
* Use wfs_request_method instead of wms_request_method.
*
* Revision 1.20 2004/03/15 07:43:42 frank
* avoid warnings about usused variables when building without USE_WFS_LYR
*
* Revision 1.19 2004/03/11 22:45:39 dan
* Added pszPostContentType in httpRequestObj instead of using hardcoded
* text/html mime type for all post requests.
*
* Revision 1.18 2003/10/30 22:37:01 assefa
* Add function msWFSExecuteGetFeature on a wfs layer.
*
* Revision 1.17 2003/10/22 20:20:09 assefa
* Test if wfs_filter metada is empty.
*
* Revision 1.16 2003/09/19 21:54:19 assefa
* Add support fot the Post request.
*
* Revision 1.15 2003/09/10 19:53:19 assefa
* Use local hash function instead of md5.
*
* Revision 1.14 2003/09/10 03:52:53 assefa
* The and is now generated here intstead of
* comming from the wfs_filter metadata parameter.
*
* Revision 1.13 2003/09/04 17:47:15 assefa
* Add filterencoding tests.
*
* Revision 1.12 2003/03/26 20:24:38 dan
* Do not call msDebug() unless debug flag is turned on
*
* Revision 1.11 2003/02/19 14:19:02 frank
* cleanup warnings
*
* Revision 1.10 2002/12/19 07:35:53 dan
* Call msWFSLayerWhichShapes() inside open() to force downloading layer and
* enable msOGRLayerGetItems() to work for queries. i.e.WFS queries work now.
*
* Revision 1.9 2002/12/19 06:30:59 dan
* Enable caching WMS/WFS request using tmp filename built from URL
*
* Revision 1.8 2002/12/19 05:17:09 dan
* Report WFS exceptions, and do not fail on WFS requests returning 0 features
*
* Revision 1.7 2002/12/17 21:33:54 dan
* Enable following redirections with libcurl (requires libcurl 7.10.1+)
*
* Revision 1.6 2002/12/17 05:30:17 dan
* Fixed HTTP timeout value (in secs, not msecs) for WMS/WFS requests
*
* Revision 1.5 2002/12/17 04:48:25 dan
* Accept version 0.0.14 in addition to 1.0.0
*
* Revision 1.4 2002/12/16 20:35:00 dan
* Flush libwww and use libcurl instead for HTTP requests in WMS/WFS client
*
* Revision 1.3 2002/12/13 01:32:53 dan
* OOOpps.. lp->wfslayerinfo was not set in msWFSLayerOpen()
*
* Revision 1.2 2002/12/13 00:57:31 dan
* Modified WFS implementation to behave more as a real vector data source
*
* Revision 1.1 2002/10/09 02:29:03 dan
* Initial implementation of WFS client support.
*
**********************************************************************/
#include "map.h"
#include "maperror.h"
#include "mapows.h"
#include
#include
#if defined(_WIN32) && !defined(__CYGWIN__)
#include
#endif
MS_CVSID("$Id: mapwfslayer.c,v 1.37 2006/05/19 20:53:27 dan Exp $")
#define WFS_V_0_0_14 14
#define WFS_V_1_0_0 100
/*====================================================================
* Private (static) functions
*====================================================================*/
#ifdef USE_WFS_LYR
/************************************************************************/
/* msBuildRequestParms */
/* */
/* Build the params object based on the metadata */
/* information. This object will be used when building the Get */
/* and Post requsests. */
/* Note : Verify the connection string to extract some values */
/* for backward compatiblity. (It is though depricated). */
/* This will also set layer projection and compute BBOX in that */
/* projection. */
/* */
/************************************************************************/
static wfsParamsObj *msBuildRequestParams(mapObj *map, layerObj *lp,
rectObj *bbox_ret)
{
wfsParamsObj *psParams = NULL;
rectObj bbox;
const char *pszTmp;
int nLength, i = 0;
char *pszVersion, *pszTypeName = NULL;
if (!map || !lp || !bbox_ret)
return NULL;
if (lp->connection == NULL)
return NULL;
psParams = msWFSCreateParamsObj();
pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "version");
if (pszTmp)
psParams->pszVersion = strdup(pszTmp);
else
{
pszTmp = strstr(lp->connection, "VERSION=");
if (!pszTmp)
pszTmp = strstr(lp->connection, "version=");
if (pszTmp)
{
pszVersion = strchr(pszTmp, '=')+1;
if (strncmp(pszVersion, "0.0.14", 6) == 0)
psParams->pszVersion = strdup("0.0.14");
else if (strncmp(pszVersion, "1.0.0", 5) == 0)
psParams->pszVersion = strdup("1.0.0");
}
}
/*the service is always set to WFS : see bug 1302 */
psParams->pszService = strdup("WFS");
/*
pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "service");
if (pszTmp)
psParams->pszService = strdup(pszTmp);
else
{
pszTmp = strstr(lp->connection, "SERVICE=");
if (!pszTmp)
pszTmp = strstr(lp->connection, "service=");
if (pszTmp)
{
pszService = strchr(pszTmp, '=')+1;
if (strncmp(pszService, "WFS", 3) == 0)
psParams->pszService = strdup("WFS");
}
}
*/
pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "typename");
if (pszTmp)
psParams->pszTypeName = strdup(pszTmp);
else
{
pszTmp = strstr(lp->connection, "TYPENAME=");
if (!pszTmp)
pszTmp = strstr(lp->connection, "typename=");
if (pszTmp)
{
pszTypeName = strchr(pszTmp, '=')+1;
if (pszTypeName)
{
nLength = strlen(pszTypeName);
if (nLength > 0)
{
for (i=0; ipszTypeName = strdup(pszTypeNameTmp);
free(pszTypeNameTmp);
}
else
psParams->pszTypeName = strdup(pszTypeName);
}
}
}
}
pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "filter");
if (pszTmp && strlen(pszTmp) > 0)
{
psParams->pszFilter = malloc(sizeof(char)*(strlen(pszTmp)+17+1));
sprintf(psParams->pszFilter, "%s", pszTmp);
/* */
}
pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "maxfeatures");
if (pszTmp)
psParams->nMaxFeatures = atoi(pszTmp);
/* Request is always GetFeature; */
psParams->pszRequest = strdup("GetFeature");
/* ------------------------------------------------------------------
* Figure the SRS we'll use for the request.
* - Fetch the map SRS (if it's EPSG)
* - Check if map SRS is listed in layer wfs_srs metadata
* - If map SRS is valid for this layer then use it
* - Otherwise request layer in its default SRS and we'll reproject later
* ------------------------------------------------------------------ */
/* __TODO__ WFS servers support only one SRS... need to decide how we'll */
/* handle this and document it well. */
/* It's likely that we'll simply reproject the BBOX to teh layer's projection. */
/* ------------------------------------------------------------------
* Set layer SRS and reproject map extents to the layer's SRS
* ------------------------------------------------------------------ */
#ifdef __TODO__
/* No need to set lp->proj if it's already set to the right EPSG code */
if ((pszTmp = msGetEPSGProj(&(lp->projection), NULL, MS_TRUE)) == NULL ||
strcasecmp(pszEPSG, pszTmp) != 0)
{
char szProj[20];
sprintf(szProj, "init=epsg:%s", pszEPSG+5);
if (msLoadProjectionString(&(lp->projection), szProj) != 0)
return NULL;
}
#endif
bbox = map->extent;
if (msProjectionsDiffer(&(map->projection), &(lp->projection)))
{
msProjectRect(&(map->projection), &(lp->projection), &bbox);
}
if (bbox_ret != NULL)
*bbox_ret = bbox;
return psParams;
}
/**********************************************************************
* msBuildWFSLayerPostRequest()
*
* Build a WFS GetFeature xml document for a Post Request.
*
* Returns a reference to a newly allocated string that should be freed
* by the caller.
**********************************************************************/
static char *msBuildWFSLayerPostRequest(mapObj *map, layerObj *lp,
rectObj *bbox, wfsParamsObj *psParams)
{
char *pszPostReq = NULL;
char *pszFilter = NULL;
if (psParams->pszVersion == NULL ||
(strncmp(psParams->pszVersion, "0.0.14", 6) != 0 &&
strncmp(psParams->pszVersion, "1.0.0", 5)) != 0)
{
msSetError(MS_WFSCONNERR, "MapServer supports only WFS 1.0.0 or 0.0.14 (please verify the version metadata wfs_version).", "msBuildWFSLayerPostRequest()");
return NULL;
}
if (psParams->pszTypeName == NULL)
{
msSetError(MS_WFSCONNERR, "Metadata wfs_typename must be set in the layer", "msBuildWFSLayerPostRequest()");
return NULL;
}
if (psParams->pszFilter)
pszFilter = psParams->pszFilter;
else
{
pszFilter = (char *)malloc(sizeof(char)*500);
sprintf(pszFilter, "\n"
"\n"
"Geometry\n"
"\n"
"%f,%f %f,%f\n"
"\n"
"\n"
"",bbox->minx, bbox->miny, bbox->maxx, bbox->maxy);
}
pszPostReq = (char *)malloc(sizeof(char)*(strlen(pszFilter)+500));
if (psParams->nMaxFeatures > 0)
sprintf(pszPostReq, "\n"
"\n"
"\n"
"%s"
"\n"
"\n", psParams->nMaxFeatures, psParams->pszTypeName, pszFilter);
else
sprintf(pszPostReq, "\n"
"\n"
"\n"
"%s"
"\n"
"\n", psParams->pszTypeName, pszFilter);
if (psParams->pszFilter == NULL)
free(pszFilter);
return pszPostReq;
}
/**********************************************************************
* msBuildWFSLayerGetURL()
*
* Build a WFS GetFeature URL for a Get Request.
*
* Returns a reference to a newly allocated string that should be freed
* by the caller.
**********************************************************************/
static char *msBuildWFSLayerGetURL(mapObj *map, layerObj *lp, rectObj *bbox,
wfsParamsObj *psParams)
{
char *pszURL = NULL, *pszOnlineResource=NULL;
const char *pszTmp;
char *pszVersion, *pszService, *pszTypename = NULL;
int bVersionInConnection = 0, bServiceInConnection = 0;
int bTypenameInConnection = 0;
if (lp->connectiontype != MS_WFS || lp->connection == NULL)
{
msSetError(MS_WFSCONNERR, "Call supported only for CONNECTIONTYPE WFS",
"msBuildWFSLayerGetURL()");
return NULL;
}
/* -------------------------------------------------------------------- */
/* Find out request version. Look first for the wfs_version */
/* metedata. If not available try to find out if the CONNECTION */
/* string contains the version. This last test is done for */
/* backward compatiblity but is depericated. */
/* -------------------------------------------------------------------- */
pszVersion = psParams->pszVersion;
if (!pszVersion)
{
if ((pszTmp = strstr(lp->connection, "VERSION=")) == NULL &&
(pszTmp = strstr(lp->connection, "version=")) == NULL )
{
msSetError(MS_WFSCONNERR, "Metadata wfs_version must be set in the layer", "msBuildWFSLayerGetURL()");
return NULL;
}
pszVersion = strchr(pszTmp, '=')+1;
bVersionInConnection = 1;
}
if (strncmp(pszVersion, "0.0.14", 6) != 0 &&
strncmp(pszVersion, "1.0.0", 5) != 0 )
{
msSetError(MS_WFSCONNERR, "MapServer supports only WFS 1.0.0 or 0.0.14 (please verify the version metadata wfs_version).", "msBuildWFSLayerGetURL()");
return NULL;
}
/* -------------------------------------------------------------------- */
/* Find out the service. It is always set to WFS in function */
/* msBuildRequestParms (check Bug 1302 for details). */
/* -------------------------------------------------------------------- */
pszService = psParams->pszService;
/* -------------------------------------------------------------------- */
/* Find out the typename. Look first for the wfs_tyename */
/* metadata. If not available try to find out if the CONNECTION */
/* string contains it. This last test is done for */
/* backward compatiblity but is depericated. */
/* -------------------------------------------------------------------- */
pszTypename = psParams->pszTypeName;
if (!pszTypename)
{
if ((pszTmp = strstr(lp->connection, "TYPENAME=")) == NULL &&
(pszTmp = strstr(lp->connection, "typename=")) == NULL )
{
msSetError(MS_WFSCONNERR, "Metadata wfs_typename must be set in the layer", "msBuildWFSLayerGetURL()");
return NULL;
}
bTypenameInConnection = 1;
}
/* --------------------------------------------------------------------
* Build the request URL.
* At this point we set only the following parameters for GetFeature:
* REQUEST
* BBOX
* VERSION
* SERVICE
* TYPENAME
* FILTER
* MAXFEATURES
*
* For backward compatiblity the user could also have in the connection
* string the following parameters (but it is depricated):
* VERSION
* SERVICE
* TYPENAME
* -------------------------------------------------------------------- */
/* Make sure we have a big enough buffer for the URL */
if(!(pszURL = (char *)malloc((strlen(lp->connection)+1024)*sizeof(char))))
{
msSetError(MS_MEMERR, NULL, "msBuildWFSLayerGetURL()");
return NULL;
}
/* __TODO__ We have to urlencode each value... especially the BBOX values */
/* because if they end up in exponent format (123e+06) the + will be seen */
/* as a space by the remote server. */
/* -------------------------------------------------------------------- */
/* build the URL, */
/* -------------------------------------------------------------------- */
/* make sure connection ends with "&" or "?" */
pszOnlineResource = msOWSTerminateOnlineResource(lp->connection);
sprintf(pszURL, "%s", pszOnlineResource);
msFree(pszOnlineResource);
/* REQUEST */
sprintf(pszURL + strlen(pszURL), "&REQUEST=GetFeature");
/* VERSION */
if (!bVersionInConnection)
sprintf(pszURL + strlen(pszURL), "&VERSION=%s", pszVersion);
/* SERVICE */
if (!bServiceInConnection)
sprintf(pszURL + strlen(pszURL), "&SERVICE=%s", pszService);
/* TYPENAME */
if (!bTypenameInConnection)
sprintf(pszURL + strlen(pszURL), "&TYPENAME=%s", pszTypename);
/* -------------------------------------------------------------------- */
/* If the filter parameter is given in the wfs_filter metadata, */
/* we use it and do not send the BBOX paramter as they are */
/* mutually exclusive. */
/* -------------------------------------------------------------------- */
if (psParams->pszFilter)
{
sprintf(pszURL + strlen(pszURL), "&FILTER=%s",
msEncodeUrl(psParams->pszFilter));
}
else
sprintf(pszURL + strlen(pszURL),
"&BBOX=%.15g,%.15g,%.15g,%.15g",
bbox->minx, bbox->miny, bbox->maxx, bbox->maxy);
if (psParams->nMaxFeatures > 0)
sprintf(pszURL + strlen(pszURL), "&MAXFEATURES=%d", psParams->nMaxFeatures);
return pszURL;
}
/**********************************************************************
* msWFSLayerInfo
*
**********************************************************************/
typedef struct ms_wfs_layer_info_t
{
char *pszGMLFilename;
rectObj rect; /* set by WhichShapes */
char *pszGetUrl;
int nStatus; /* HTTP status */
int bLayerOpened; /* False until msWFSLayerOpen() is called*/
} msWFSLayerInfo;
/**********************************************************************
* msAllocWFSLayerInfo()
*
**********************************************************************/
static msWFSLayerInfo *msAllocWFSLayerInfo(void)
{
msWFSLayerInfo *psInfo;
psInfo = (msWFSLayerInfo*)calloc(1,sizeof(msWFSLayerInfo));
if (psInfo)
{
psInfo->pszGMLFilename = NULL;
psInfo->rect.minx = psInfo->rect.maxx = 0;
psInfo->rect.miny = psInfo->rect.maxy = 0;
psInfo->pszGetUrl = NULL;
psInfo->nStatus = 0;
psInfo->bLayerOpened = MS_FALSE;
}
else
{
msSetError(MS_MEMERR, NULL, "msAllocWFSLayerInfo()");
}
return psInfo;
}
/**********************************************************************
* msFreeWFSLayerInfo()
*
**********************************************************************/
static void msFreeWFSLayerInfo(msWFSLayerInfo *psInfo)
{
if (psInfo)
{
if (psInfo->pszGMLFilename)
free(psInfo->pszGMLFilename);
if (psInfo->pszGetUrl)
free(psInfo->pszGetUrl);
free(psInfo);
}
}
#endif /* USE_WFS_LYR */
/*====================================================================
* Public functions
*====================================================================*/
/**********************************************************************
* msPrepareWFSLayerRequest()
*
**********************************************************************/
int msPrepareWFSLayerRequest(int nLayerId, mapObj *map, layerObj *lp,
httpRequestObj *pasReqInfo, int *numRequests)
{
#ifdef USE_WFS_LYR
char *pszURL = NULL;
const char *pszTmp;
rectObj bbox;
int nTimeout;
int nStatus = MS_SUCCESS;
msWFSLayerInfo *psInfo = NULL;
char *pszHashFileName = NULL;
int bPostRequest = 0;
wfsParamsObj *psParams = NULL;
if (lp->connectiontype != MS_WFS || lp->connection == NULL)
return MS_FAILURE;
/* ------------------------------------------------------------------
* Build a params object that will be used by to build the request,
this will also set layer projection and compute BBOX in that projection.
* ------------------------------------------------------------------ */
psParams = msBuildRequestParams(map, lp, &bbox);
if (!psParams)
return MS_FAILURE;
/* -------------------------------------------------------------------- */
/* Depending on the metadata wfs_request_method, build a Get or */
/* a Post URL. */
/* If it is a Get request the URL would contain all the parameters in*/
/* the string; */
/* If it is a Post request, the URL will only contain the */
/* connection string comming from the layer. */
/* -------------------------------------------------------------------- */
if ((pszTmp = msOWSLookupMetadata(&(lp->metadata),
"FO", "request_method")) != NULL)
{
if (strncmp(pszTmp, "GET", 3) ==0)
{
pszURL = msBuildWFSLayerGetURL(map, lp, &bbox, psParams);
if (!pszURL)
{
/* an error was already reported. */
return MS_FAILURE;
}
}
}
/* else it is a post request and just get the connection string */
if (!pszURL)
{
bPostRequest = 1;
pszURL = strdup(lp->connection);
}
/* ------------------------------------------------------------------
* check to see if a the metadata wfs_connectiontimeout is set. If it is
* the case we will use it, else we use the default which is 30 seconds.
* First check the metadata in the layer object and then in the map object.
* ------------------------------------------------------------------ */
nTimeout = 30; /* Default is 30 seconds */
if ((pszTmp = msOWSLookupMetadata(&(lp->metadata),
"FO", "connectiontimeout")) != NULL)
{
nTimeout = atoi(pszTmp);
}
else if ((pszTmp = msOWSLookupMetadata(&(map->web.metadata),
"FO", "connectiontimeout")) != NULL)
{
nTimeout = atoi(pszTmp);
}
/* ------------------------------------------------------------------
* If nLayerId == -1 then we need to figure it
* ------------------------------------------------------------------ */
if (nLayerId == -1)
{
int iLayer;
for(iLayer=0; iLayer < map->numlayers; iLayer++)
{
if (&(map->layers[iLayer]) == lp)
{
nLayerId = iLayer;
break;
}
}
}
/* ------------------------------------------------------------------
* Add a request to the array (already preallocated)
* ------------------------------------------------------------------ */
pasReqInfo[(*numRequests)].nLayerId = nLayerId;
pasReqInfo[(*numRequests)].pszGetUrl = pszURL;
if (bPostRequest)
{
pasReqInfo[(*numRequests)].pszPostRequest =
msBuildWFSLayerPostRequest(map, lp, &bbox, psParams);
pasReqInfo[(*numRequests)].pszPostContentType =
strdup("text/xml");
}
/* We'll store the remote server's response to a tmp file. */
if (bPostRequest)
{
char *pszPostTmpName = NULL;
pszPostTmpName = (char *)malloc(sizeof(char)*(strlen(pszURL)+128));
sprintf(pszPostTmpName,"%s%ld%d",
pszURL, (long)time(NULL), (int)getpid());
pszHashFileName = msHashString(pszPostTmpName);
free(pszPostTmpName);
}
else
pszHashFileName = msHashString(pszURL);
pszURL = NULL;
pasReqInfo[(*numRequests)].pszOutputFile =
msOWSBuildURLFilename(map->web.imagepath,
pszHashFileName,".tmp.gml");
free(pszHashFileName);
pasReqInfo[(*numRequests)].nStatus = 0;
pasReqInfo[(*numRequests)].nTimeout = nTimeout;
pasReqInfo[(*numRequests)].bbox = bbox;
pasReqInfo[(*numRequests)].debug = lp->debug;
/* ------------------------------------------------------------------
* Pre-Open the layer now, (i.e. alloc and fill msWFSLayerInfo inside
* layer obj). Layer will be ready for use when the main mapserver
* code calls msLayerOpen().
* ------------------------------------------------------------------ */
if (lp->wfslayerinfo != NULL)
{
psInfo =(msWFSLayerInfo*)(lp->wfslayerinfo);
}
else
{
lp->wfslayerinfo = psInfo = msAllocWFSLayerInfo();
}
if (psInfo->pszGMLFilename)
free(psInfo->pszGMLFilename);
psInfo->pszGMLFilename=strdup(pasReqInfo[(*numRequests)].pszOutputFile);
psInfo->rect = pasReqInfo[(*numRequests)].bbox;
if (psInfo->pszGetUrl)
free(psInfo->pszGetUrl);
psInfo->pszGetUrl = strdup(pasReqInfo[(*numRequests)].pszGetUrl);
psInfo->nStatus = 0;
(*numRequests)++;
if (psParams)
{
msWFSFreeParamsObj(psParams);
psParams = NULL;
}
return nStatus;
#else
/* ------------------------------------------------------------------
* WFS CONNECTION Support not included...
* ------------------------------------------------------------------ */
msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.",
"msPrepareWFSLayerRequest");
return(MS_FAILURE);
#endif /* USE_WFS_LYR */
}
/**********************************************************************
* msWFSUpdateRequestInfo()
*
* This function is called after a WFS request has been completed so that
* we can copy request result information from the httpRequestObj to the
* msWFSLayerInfo struct. Things to copy here are the HTTP status, exceptions
* information, mime type, etc.
**********************************************************************/
void msWFSUpdateRequestInfo(layerObj *lp, httpRequestObj *pasReqInfo)
{
#ifdef USE_WFS_LYR
if (lp->wfslayerinfo)
{
msWFSLayerInfo *psInfo = NULL;
psInfo =(msWFSLayerInfo*)(lp->wfslayerinfo);
/* Copy request results infos to msWFSLayerInfo struct */
/* For now there is only nStatus, but we should eventually add */
/* mime type and WFS exceptions information. */
psInfo->nStatus = pasReqInfo->nStatus;
}
#else
/* ------------------------------------------------------------------
* WFS CONNECTION Support not included...
* ------------------------------------------------------------------ */
msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.",
"msWFSUpdateRequestInfo()");
#endif /* USE_WFS_LYR */
}
/**********************************************************************
* msWFSLayerOpen()
*
* WFS layers are just a special case of OGR connection. Only the open/close
* methods differ since they have to download and maintain GML files in cache
* but the rest is mapped directly to OGR function calls in maplayer.c
*
**********************************************************************/
int msWFSLayerOpen(layerObj *lp,
const char *pszGMLFilename, rectObj *defaultBBOX)
{
#ifdef USE_WFS_LYR
int status = MS_SUCCESS;
msWFSLayerInfo *psInfo = NULL;
if (lp->wfslayerinfo != NULL)
{
psInfo =(msWFSLayerInfo*)lp->wfslayerinfo;
/* Layer already opened. If explicit filename requested then check */
/* that file was already opened with the same filename. */
/* If no explicit filename requested then we'll try to reuse the */
/* previously opened layer... this will happen in a msDrawMap() call. */
if (pszGMLFilename == NULL ||
(psInfo->pszGMLFilename && pszGMLFilename &&
strcmp(psInfo->pszGMLFilename, pszGMLFilename) == 0) )
{
return MS_SUCCESS; /* Nothing to do... layer is already opened */
}
else
{
/* Hmmm... should we produce a fatal error? */
/* For now we'll just close the layer and reopen it. */
if (lp->debug)
msDebug("msWFSLayerOpen(): Layer already opened (%s)\n",
lp->name?lp->name:"(null)" );
msWFSLayerClose(lp);
}
}
/* ------------------------------------------------------------------
* Alloc and fill msWFSLayerInfo inside layer obj
* ------------------------------------------------------------------ */
lp->wfslayerinfo = psInfo = msAllocWFSLayerInfo();
if (pszGMLFilename)
psInfo->pszGMLFilename = strdup(pszGMLFilename);
else
{
if (lp->map->web.imagepath==NULL || strlen(lp->map->web.imagepath)==0)
{
msSetError(MS_WFSERR,
"WEB.IMAGEPATH must be set to use WFS client connections.",
"msPrepareWMSLayerRequest()");
return MS_FAILURE;
}
psInfo->pszGMLFilename = msTmpFile(lp->map->mappath,
lp->map->web.imagepath,
"tmp.gml");
}
if (defaultBBOX)
{
/* __TODO__ If new bbox differs from current one then we should */
/* invalidate current GML file in cache */
psInfo->rect = *defaultBBOX;
}
else
{
/* Use map bbox by default */
psInfo->rect = lp->map->extent;
}
/* We will call whichshapes() now and force downloading layer right */
/* away. This saves from having to call DescribeFeatureType and */
/* parsing the response (being lazy I guess) and anyway given the */
/* way we work with layers right now the bbox is unlikely to change */
/* between now and the time whichshapes() would have been called by */
/* the MapServer core. */
if (msWFSLayerWhichShapes(lp, psInfo->rect) == MS_FAILURE)
status = MS_FAILURE;
psInfo->bLayerOpened = MS_TRUE;
return status;
#else
/* ------------------------------------------------------------------
* WFS CONNECTION Support not included...
* ------------------------------------------------------------------ */
msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.",
"msWFSLayerOpen()");
return(MS_FAILURE);
#endif /* USE_WFS_LYR */
}
/**********************************************************************
* msWFSLayerOpenVT()
*
* Overloaded version of msWFSLayerOpen for virtual table architecture
**********************************************************************/
int
msWFSLayerOpenVT(layerObj *lp)
{
return msWFSLayerOpen(lp, NULL, NULL);
}
/**********************************************************************
* msWFSLayerIsOpen()
*
* Returns MS_TRUE if layer is already open, MS_FALSE otherwise.
*
**********************************************************************/
int msWFSLayerIsOpen(layerObj *lp)
{
#ifdef USE_WFS_LYR
if (lp->wfslayerinfo != NULL)
return MS_TRUE;
return MS_FALSE;
#else
/* ------------------------------------------------------------------
* WFS CONNECTION Support not included...
* ------------------------------------------------------------------ */
msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.",
"msWFSLayerIsOpen()");
return(MS_FALSE);
#endif /* USE_WFS_LYR */
}
/**********************************************************************
* msWFSLayerInitItemInfo()
*
**********************************************************************/
int msWFSLayerInitItemInfo(layerObj *layer)
{
/* Nothing to do here. OGR will do its own initialization when it */
/* opens the actual file. */
/* Note that we didn't implement our own msWFSLayerFreeItemInfo() */
/* so that the OGR one gets called. */
return MS_SUCCESS;
}
/**********************************************************************
* msWFSLayerGetItems()
*
**********************************************************************/
int msWFSLayerGetItems(layerObj *layer)
{
/* For now this method simply lets OGR parse the GML and figure the */
/* schema itself. */
/* It could also be implemented to call DescribeFeatureType for */
/* this layer, but we don't need to do it so why waste resources? */
return msOGRLayerGetItems(layer);
}
/**********************************************************************
* msWFSLayerWhichShapes()
*
**********************************************************************/
int msWFSLayerWhichShapes(layerObj *lp, rectObj rect)
{
#ifdef USE_WFS_LYR
msWFSLayerInfo *psInfo;
int status = MS_SUCCESS;
const char *pszTmp;
FILE *fp;
psInfo =(msWFSLayerInfo*)lp->wfslayerinfo;
if (psInfo == NULL)
{
msSetError(MS_WFSCONNERR, "Assertion failed: WFS layer not opened!!!",
"msWFSLayerWhichShapes()");
return(MS_FAILURE);
}
/* ------------------------------------------------------------------
* Check if layer overlaps current view window (using wfs_latlonboundingbox)
* ------------------------------------------------------------------ */
if ((pszTmp = msOWSLookupMetadata(&(lp->metadata),
"FO", "latlonboundingbox")) != NULL)
{
char **tokens;
int n;
rectObj ext;
tokens = split(pszTmp, ' ', &n);
if (tokens==NULL || n != 4) {
msSetError(MS_WFSCONNERR, "Wrong number of values in 'wfs_latlonboundingbox' metadata.",
"msWFSLayerWhichShapes()");
return MS_FAILURE;
}
ext.minx = atof(tokens[0]);
ext.miny = atof(tokens[1]);
ext.maxx = atof(tokens[2]);
ext.maxy = atof(tokens[3]);
msFreeCharArray(tokens, n);
/* Reproject latlonboundingbox to the selected SRS for the layer and */
/* check if it overlaps the bbox that we calculated for the request */
msProjectRect(&(lp->map->latlon), &(lp->projection), &ext);
if (!msRectOverlap(&rect, &ext))
{
/* No overlap... nothing to do */
return MS_DONE; /* No overlap. */
}
}
/* ------------------------------------------------------------------
* __TODO__ If new bbox differs from current one then we should
* invalidate current GML file in cache
* ------------------------------------------------------------------ */
psInfo->rect = rect;
/* ------------------------------------------------------------------
* If file not downloaded yet then do it now.
* ------------------------------------------------------------------ */
if (psInfo->nStatus == 0)
{
httpRequestObj asReqInfo[2];
int numReq = 0;
msHTTPInitRequestObj(asReqInfo, 2);
if ( msPrepareWFSLayerRequest(-1, lp->map, lp,
asReqInfo, &numReq) == MS_FAILURE ||
msOWSExecuteRequests(asReqInfo, numReq,
lp->map, MS_TRUE) == MS_FAILURE )
{
/* Delete tmp file... we don't want it to stick around. */
unlink(asReqInfo[0].pszOutputFile);
return MS_FAILURE;
}
/* Cleanup */
msHTTPFreeRequestObj(asReqInfo, numReq);
}
if ( !MS_HTTP_SUCCESS( psInfo->nStatus ) )
{
/* Delete tmp file... we don't want it to stick around. */
unlink(psInfo->pszGMLFilename);
msSetError(MS_WFSCONNERR,
"Got HTTP status %d downloading WFS layer %s",
"msWFSLayerWhichShapes()",
psInfo->nStatus, lp->name?lp->name:"(null)");
return(MS_FAILURE);
}
/* ------------------------------------------------------------------
* Check that file is really GML... it could be an exception, or just junk.
* ------------------------------------------------------------------ */
if ((fp = fopen(psInfo->pszGMLFilename, "r")) != NULL)
{
char szHeader[2000];
int nBytes = 0;
nBytes = fread( szHeader, 1, sizeof(szHeader)-1, fp );
fclose(fp);
if (nBytes < 0)
nBytes = 0;
szHeader[nBytes] = '\0';
if ( nBytes == 0 )
{
msSetError(MS_WFSCONNERR,
"WFS request produced no oputput for layer %s.",
"msWFSLayerWhichShapes()",
lp->name?lp->name:"(null)");
return(MS_FAILURE);
}
if ( strstr(szHeader, "") ||
strstr(szHeader, "") )
{
msOWSProcessException(lp, psInfo->pszGMLFilename,
MS_WFSCONNERR, "msWFSLayerWhichShapes()" );
return MS_FAILURE;
}
else if ( strstr(szHeader,"opengis.net/gml") &&
strstr(szHeader,"featureMember>") == NULL )
{
/* This looks like valid GML, but contains 0 features. */
return MS_DONE;
}
else if ( strstr(szHeader,"opengis.net/gml") == NULL ||
strstr(szHeader,"featureMember>") == NULL )
{
/* This is probably just junk. */
msSetError(MS_WFSCONNERR,
"WFS request produced unexpected output (junk?) for layer %s.",
"msWFSLayerWhichShapes()",
lp->name?lp->name:"(null)");
return(MS_FAILURE);
}
/* If we got this far, it must be a valid GML dataset... keep going */
}
/* ------------------------------------------------------------------
* Open GML file using OGR.
* ------------------------------------------------------------------ */
if ((status = msOGRLayerOpen(lp, psInfo->pszGMLFilename)) != MS_SUCCESS)
return status;
status = msOGRLayerWhichShapes(lp, rect);
return status;
#else
/* ------------------------------------------------------------------
* WFS CONNECTION Support not included...
* ------------------------------------------------------------------ */
msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.",
"msWFSLayerWhichShapes()");
return(MS_FAILURE);
#endif /* USE_WFS_LYR */
}
/**********************************************************************
* msWFSLayerClose()
*
**********************************************************************/
int msWFSLayerClose(layerObj *lp)
{
#ifdef USE_WFS_LYR
/* ------------------------------------------------------------------
* Cleanup OGR connection
* ------------------------------------------------------------------ */
if (lp->layerinfo)
msOGRLayerClose(lp);
/* ------------------------------------------------------------------
* Cleanup WFS connection info.
* __TODO__ For now we flush everything, but we should try to cache some stuff
* ------------------------------------------------------------------ */
/* __TODO__ unlink() .gml file and OGR's schema file if they exist */
/* unlink( */
msFreeWFSLayerInfo(lp->wfslayerinfo);
lp->wfslayerinfo = NULL;
return MS_SUCCESS;
#else
/* ------------------------------------------------------------------
* WFS CONNECTION Support not included...
* ------------------------------------------------------------------ */
msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.",
"msWFSLayerClose()");
return(MS_FAILURE);
#endif /* USE_WFS_LYR */
}
/**********************************************************************
* msWFSExecuteGetFeature()
* Returns the temporary gml file name. User shpuld free the return string.
**********************************************************************/
char *msWFSExecuteGetFeature(layerObj *lp)
{
#ifdef USE_WFS_LYR
char *gmltmpfile = NULL;
msWFSLayerInfo *psInfo = NULL;
if (lp == NULL || lp->connectiontype != MS_WFS)
return NULL;
msWFSLayerOpen(lp, NULL, NULL);
psInfo =(msWFSLayerInfo*)lp->wfslayerinfo;
if (psInfo && psInfo->pszGMLFilename)
gmltmpfile = strdup(psInfo->pszGMLFilename);
msWFSLayerClose(lp);
return gmltmpfile;
#else
/* ------------------------------------------------------------------
* WFS CONNECTION Support not included...
* ------------------------------------------------------------------ */
msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.",
"msExecuteWFSGetFeature()");
return NULL;
#endif /* USE_WFS_LYR */
}
int
msWFSLayerInitializeVirtualTable(layerObj *layer)
{
assert(layer != NULL);
assert(layer->vtable != NULL);
layer->vtable->LayerInitItemInfo = msWFSLayerInitItemInfo;
layer->vtable->LayerFreeItemInfo = msOGRLayerFreeItemInfo; /* yes, OGR */
layer->vtable->LayerOpen = msWFSLayerOpenVT;
layer->vtable->LayerIsOpen = msWFSLayerIsOpen;
layer->vtable->LayerWhichShapes = msWFSLayerWhichShapes;
layer->vtable->LayerNextShape = msOGRLayerNextShape; /* yes, OGR */
layer->vtable->LayerGetShape = msOGRLayerGetShape; /* yes, OGR */
layer->vtable->LayerClose = msWFSLayerClose;
layer->vtable->LayerGetItems = msWFSLayerGetItems;
layer->vtable->LayerGetExtent = msOGRLayerGetExtent; /* yes, OGR */
/* layer->vtable->LayerGetAutoStyle, use default */
/* layer->vtable->LayerApplyFilterToLayer, use default */
/* layer->vtable->LayerCloseConnection, use default */
layer->vtable->LayerSetTimeFilter = msLayerMakePlainTimeFilter;
/* layer->vtable->LayerCreateItems, use default */
/* layer->vtable->LayerGetNumFeatures, use default */
return MS_SUCCESS;
}