/src/MapServer/src/mapwfslayer.c
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * $Id$ |
3 | | * |
4 | | * Project: MapServer |
5 | | * Purpose: Implementation of WFS CONNECTIONTYPE - client to WFS servers |
6 | | * Author: Daniel Morissette, DM Solutions Group (morissette@dmsolutions.ca) |
7 | | * |
8 | | ********************************************************************** |
9 | | * Copyright (c) 2002, Daniel Morissette, DM Solutions Group Inc |
10 | | * |
11 | | * Permission is hereby granted, free of charge, to any person obtaining a |
12 | | * copy of this software and associated documentation files (the "Software"), |
13 | | * to deal in the Software without restriction, including without limitation |
14 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
15 | | * and/or sell copies of the Software, and to permit persons to whom the |
16 | | * Software is furnished to do so, subject to the following conditions: |
17 | | * |
18 | | * The above copyright notice and this permission notice shall be included in |
19 | | * all copies of this Software or works derived from this Software. |
20 | | * |
21 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
22 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
23 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
24 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
25 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
26 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
27 | | ****************************************************************************/ |
28 | | |
29 | | #include "mapserver.h" |
30 | | #include "maperror.h" |
31 | | #include "mapows.h" |
32 | | #include "mapproject.h" |
33 | | |
34 | | #include <time.h> |
35 | | #include <assert.h> |
36 | | |
37 | | #if defined(_WIN32) && !defined(__CYGWIN__) |
38 | | #include <process.h> |
39 | | #endif |
40 | | |
41 | | #define WFS_V_0_0_14 14 |
42 | | #define WFS_V_1_0_0 100 |
43 | | |
44 | | /*==================================================================== |
45 | | * Private (static) functions |
46 | | *====================================================================*/ |
47 | | |
48 | | #ifdef USE_WFS_LYR |
49 | | |
50 | | /************************************************************************/ |
51 | | /* msBuildRequestParms */ |
52 | | /* */ |
53 | | /* Build the params object based on the metadata */ |
54 | | /* information. This object will be used when building the Get */ |
55 | | /* and Post requsests. */ |
56 | | /* Note : Verify the connection string to extract some values */ |
57 | | /* for backward compatibility. (It is though deprecated). */ |
58 | | /* This will also set layer projection and compute BBOX in that */ |
59 | | /* projection. */ |
60 | | /* */ |
61 | | /************************************************************************/ |
62 | | static wfsParamsObj *msBuildRequestParams(mapObj *map, layerObj *lp, |
63 | | rectObj *bbox_ret) { |
64 | | wfsParamsObj *psParams = NULL; |
65 | | rectObj bbox; |
66 | | const char *pszTmp; |
67 | | char *pszVersion, *pszTypeName; |
68 | | |
69 | | if (!map || !lp || !bbox_ret) |
70 | | return NULL; |
71 | | |
72 | | if (lp->connection == NULL) |
73 | | return NULL; |
74 | | |
75 | | psParams = msWFSCreateParamsObj(); |
76 | | pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "version"); |
77 | | if (pszTmp) |
78 | | psParams->pszVersion = msStrdup(pszTmp); |
79 | | else { |
80 | | pszTmp = strstr(lp->connection, "VERSION="); |
81 | | if (!pszTmp) |
82 | | pszTmp = strstr(lp->connection, "version="); |
83 | | if (pszTmp) { |
84 | | pszVersion = strchr(pszTmp, '=') + 1; |
85 | | if (strncmp(pszVersion, "0.0.14", 6) == 0) |
86 | | psParams->pszVersion = msStrdup("0.0.14"); |
87 | | else if (strncmp(pszVersion, "1.0.0", 5) == 0) |
88 | | psParams->pszVersion = msStrdup("1.0.0"); |
89 | | } |
90 | | } |
91 | | |
92 | | /*the service is always set to WFS : see bug 1302 */ |
93 | | psParams->pszService = msStrdup("WFS"); |
94 | | |
95 | | /* |
96 | | pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "service"); |
97 | | if (pszTmp) |
98 | | psParams->pszService = msStrdup(pszTmp); |
99 | | else |
100 | | { |
101 | | pszTmp = strstr(lp->connection, "SERVICE="); |
102 | | if (!pszTmp) |
103 | | pszTmp = strstr(lp->connection, "service="); |
104 | | if (pszTmp) |
105 | | { |
106 | | pszService = strchr(pszTmp, '=')+1; |
107 | | if (strncmp(pszService, "WFS", 3) == 0) |
108 | | psParams->pszService = msStrdup("WFS"); |
109 | | } |
110 | | } |
111 | | */ |
112 | | |
113 | | pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "geometryname"); |
114 | | if (pszTmp) |
115 | | psParams->pszGeometryName = msStrdup(pszTmp); |
116 | | |
117 | | pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "typename"); |
118 | | if (pszTmp) |
119 | | psParams->pszTypeName = msStrdup(pszTmp); |
120 | | else { |
121 | | pszTmp = strstr(lp->connection, "TYPENAME="); |
122 | | if (!pszTmp) |
123 | | pszTmp = strstr(lp->connection, "typename="); |
124 | | if (pszTmp) { |
125 | | pszTypeName = strchr(pszTmp, '=') + 1; |
126 | | if (pszTypeName) { |
127 | | const int nLength = strlen(pszTypeName); |
128 | | if (nLength > 0) { |
129 | | int i = 0; |
130 | | for (; i < nLength; i++) { |
131 | | if (pszTypeName[i] == '&') |
132 | | break; |
133 | | } |
134 | | |
135 | | if (i < nLength) { |
136 | | char *pszTypeNameTmp = NULL; |
137 | | pszTypeNameTmp = msStrdup(pszTypeName); |
138 | | pszTypeNameTmp[i] = '\0'; |
139 | | psParams->pszTypeName = msStrdup(pszTypeNameTmp); |
140 | | free(pszTypeNameTmp); |
141 | | } else |
142 | | psParams->pszTypeName = msStrdup(pszTypeName); |
143 | | } |
144 | | } |
145 | | } |
146 | | } |
147 | | |
148 | | pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "filter"); |
149 | | if (pszTmp && strlen(pszTmp) > 0) { |
150 | | if (strstr(pszTmp, "<Filter>") != NULL || |
151 | | strstr(pszTmp, "<ogc:Filter") != NULL) |
152 | | psParams->pszFilter = msStrdup(pszTmp); |
153 | | else { |
154 | | psParams->pszFilter = msStringConcatenate( |
155 | | psParams->pszFilter, |
156 | | "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\">"); |
157 | | psParams->pszFilter = |
158 | | msStringConcatenate(psParams->pszFilter, (char *)pszTmp); |
159 | | psParams->pszFilter = |
160 | | msStringConcatenate(psParams->pszFilter, "</ogc:Filter>"); |
161 | | } |
162 | | } |
163 | | |
164 | | pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "maxfeatures"); |
165 | | if (pszTmp) |
166 | | psParams->nMaxFeatures = atoi(pszTmp); |
167 | | |
168 | | /* Request is always GetFeature; */ |
169 | | psParams->pszRequest = msStrdup("GetFeature"); |
170 | | |
171 | | /* ------------------------------------------------------------------ |
172 | | * Figure the SRS we'll use for the request. |
173 | | * - Fetch the map SRS (if it's EPSG) |
174 | | * - Check if map SRS is listed in layer wfs_srs metadata |
175 | | * - If map SRS is valid for this layer then use it |
176 | | * - Otherwise request layer in its default SRS and we'll reproject later |
177 | | * ------------------------------------------------------------------ */ |
178 | | |
179 | | /* __TODO__ WFS servers support only one SRS... need to decide how we'll */ |
180 | | /* handle this and document it well. */ |
181 | | /* It's likely that we'll simply reproject the BBOX to the layer's projection. |
182 | | */ |
183 | | |
184 | | /* ------------------------------------------------------------------ |
185 | | * Set layer SRS and reproject map extents to the layer's SRS |
186 | | * ------------------------------------------------------------------ */ |
187 | | #ifdef __TODO__ |
188 | | /* No need to set lp->proj if it's already set to the right EPSG code */ |
189 | | if ((pszTmp = msGetEPSGProj(&(lp->projection), NULL, MS_TRUE)) == NULL || |
190 | | strcasecmp(pszEPSG, pszTmp) != 0) { |
191 | | char szProj[20]; |
192 | | snprintf(szProj, sizeof(szProj), "init=epsg:%s", pszEPSG + 5); |
193 | | if (msLoadProjectionString(&(lp->projection), szProj) != 0) |
194 | | return NULL; |
195 | | } |
196 | | #endif |
197 | | |
198 | | bbox = map->extent; |
199 | | if (msProjectionsDiffer(&(map->projection), &(lp->projection))) { |
200 | | msProjectRect(&(map->projection), &(lp->projection), &bbox); |
201 | | } |
202 | | |
203 | | *bbox_ret = bbox; |
204 | | |
205 | | return psParams; |
206 | | } |
207 | | |
208 | | /********************************************************************** |
209 | | * msBuildWFSLayerPostRequest() |
210 | | * |
211 | | * Build a WFS GetFeature xml document for a Post Request. |
212 | | * |
213 | | * Returns a reference to a newly allocated string that should be freed |
214 | | * by the caller. |
215 | | **********************************************************************/ |
216 | | static char *msBuildWFSLayerPostRequest(rectObj *bbox, wfsParamsObj *psParams) { |
217 | | char *pszPostReq = NULL; |
218 | | char *pszFilter = NULL; |
219 | | char *pszGeometryName = "Geometry"; |
220 | | size_t bufferSize = 0; |
221 | | |
222 | | if (psParams->pszVersion == NULL || |
223 | | (strncmp(psParams->pszVersion, "0.0.14", 6) != 0 && |
224 | | strncmp(psParams->pszVersion, "1.0.0", 5)) != 0) { |
225 | | msSetError(MS_WFSCONNERR, |
226 | | "MapServer supports only WFS 1.0.0 or 0.0.14 (please verify the " |
227 | | "version metadata wfs_version).", |
228 | | "msBuildWFSLayerPostRequest()"); |
229 | | return NULL; |
230 | | } |
231 | | |
232 | | if (psParams->pszTypeName == NULL) { |
233 | | msSetError(MS_WFSCONNERR, "Metadata wfs_typename must be set in the layer", |
234 | | "msBuildWFSLayerPostRequest()"); |
235 | | return NULL; |
236 | | } |
237 | | |
238 | | if (psParams->pszGeometryName) { |
239 | | pszGeometryName = psParams->pszGeometryName; |
240 | | } |
241 | | |
242 | | if (psParams->pszFilter) |
243 | | pszFilter = psParams->pszFilter; |
244 | | else { |
245 | | bufferSize = 500; |
246 | | pszFilter = (char *)msSmallMalloc(bufferSize); |
247 | | snprintf(pszFilter, bufferSize, |
248 | | "<ogc:Filter>\n" |
249 | | "<ogc:BBOX>\n" |
250 | | "<ogc:PropertyName>%s</ogc:PropertyName>\n" |
251 | | "<gml:Box>\n" |
252 | | "<gml:coordinates>%f,%f %f,%f</gml:coordinates>\n" |
253 | | "</gml:Box>\n" |
254 | | "</ogc:BBOX>\n" |
255 | | "</ogc:Filter>", |
256 | | pszGeometryName, bbox->minx, bbox->miny, bbox->maxx, bbox->maxy); |
257 | | } |
258 | | |
259 | | bufferSize = strlen(pszFilter) + strlen(psParams->pszTypeName) + 500; |
260 | | pszPostReq = (char *)msSmallMalloc(bufferSize); |
261 | | if (psParams->nMaxFeatures > 0) |
262 | | snprintf(pszPostReq, bufferSize, |
263 | | "<?xml version=\"1.0\" ?>\n" |
264 | | "<wfs:GetFeature\n" |
265 | | "service=\"WFS\"\n" |
266 | | "version=\"1.0.0\"\n" |
267 | | "maxFeatures=\"%d\"\n" |
268 | | "outputFormat=\"GML2\"\n" |
269 | | "xmlns:wfs=\"http://www.opengis.net/wfs\" " |
270 | | "xmlns:ogc=\"http://www.opengis.net/ogc\" " |
271 | | "xsi:schemaLocation=\"http://www.opengis.net/wfs " |
272 | | "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd\" " |
273 | | "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " |
274 | | "xmlns:gml=\"http://www.opengis.net/gml\">\n" |
275 | | "<wfs:Query typeName=\"%s\">\n" |
276 | | "%s" |
277 | | "</wfs:Query>\n" |
278 | | "</wfs:GetFeature>\n", |
279 | | psParams->nMaxFeatures, psParams->pszTypeName, pszFilter); |
280 | | else |
281 | | snprintf(pszPostReq, bufferSize, |
282 | | "<?xml version=\"1.0\" ?>\n" |
283 | | "<wfs:GetFeature\n" |
284 | | "service=\"WFS\"\n" |
285 | | "version=\"1.0.0\"\n" |
286 | | "outputFormat=\"GML2\"\n" |
287 | | "xmlns:wfs=\"http://www.opengis.net/wfs\" " |
288 | | "xmlns:ogc=\"http://www.opengis.net/ogc\" " |
289 | | "xsi:schemaLocation=\"http://www.opengis.net/wfs " |
290 | | "http://schemas.opengis.net/wfs/1.0.0/wfs.xsd\" " |
291 | | "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " |
292 | | "xmlns:gml=\"http://www.opengis.net/gml\">\n" |
293 | | "<wfs:Query typeName=\"%s\">\n" |
294 | | "%s" |
295 | | "</wfs:Query>\n" |
296 | | "</wfs:GetFeature>\n", |
297 | | psParams->pszTypeName, pszFilter); |
298 | | if (psParams->pszFilter == NULL) |
299 | | free(pszFilter); |
300 | | |
301 | | return pszPostReq; |
302 | | } |
303 | | |
304 | | /********************************************************************** |
305 | | * msBuildWFSLayerGetURL() |
306 | | * |
307 | | * Build a WFS GetFeature URL for a Get Request. |
308 | | * |
309 | | * Returns a reference to a newly allocated string that should be freed |
310 | | * by the caller. |
311 | | **********************************************************************/ |
312 | | static char *msBuildWFSLayerGetURL(layerObj *lp, rectObj *bbox, |
313 | | wfsParamsObj *psParams) { |
314 | | char *pszURL = NULL, *pszOnlineResource = NULL; |
315 | | const char *pszTmp; |
316 | | char *pszVersion, *pszService, *pszTypename = NULL; |
317 | | int bVersionInConnection = 0; |
318 | | int bTypenameInConnection = 0; |
319 | | size_t bufferSize = 0; |
320 | | |
321 | | if (lp->connectiontype != MS_WFS || lp->connection == NULL) { |
322 | | msSetError(MS_WFSCONNERR, "Call supported only for CONNECTIONTYPE WFS", |
323 | | "msBuildWFSLayerGetURL()"); |
324 | | return NULL; |
325 | | } |
326 | | |
327 | | /* -------------------------------------------------------------------- */ |
328 | | /* Find out request version. Look first for the wfs_version */ |
329 | | /* metedata. If not available try to find out if the CONNECTION */ |
330 | | /* string contains the version. This last test is done for */ |
331 | | /* backward compatibility but is depericated. */ |
332 | | /* -------------------------------------------------------------------- */ |
333 | | pszVersion = psParams->pszVersion; |
334 | | if (!pszVersion) { |
335 | | if ((pszTmp = strstr(lp->connection, "VERSION=")) == NULL && |
336 | | (pszTmp = strstr(lp->connection, "version=")) == NULL) { |
337 | | msSetError(MS_WFSCONNERR, "Metadata wfs_version must be set in the layer", |
338 | | "msBuildWFSLayerGetURL()"); |
339 | | return NULL; |
340 | | } |
341 | | pszVersion = strchr(pszTmp, '=') + 1; |
342 | | bVersionInConnection = 1; |
343 | | } |
344 | | |
345 | | if (strncmp(pszVersion, "0.0.14", 6) != 0 && |
346 | | strncmp(pszVersion, "1.0.0", 5) != 0 && |
347 | | strncmp(pszVersion, "1.1", 3) != 0) { |
348 | | msSetError(MS_WFSCONNERR, |
349 | | "MapServer supports only WFS 1.1.0, 1.0.0 or 0.0.14 (please " |
350 | | "verify the version metadata wfs_version).", |
351 | | "msBuildWFSLayerGetURL()"); |
352 | | return NULL; |
353 | | } |
354 | | |
355 | | /* -------------------------------------------------------------------- */ |
356 | | /* Find out the service. It is always set to WFS in function */ |
357 | | /* msBuildRequestParms (check Bug 1302 for details). */ |
358 | | /* -------------------------------------------------------------------- */ |
359 | | pszService = psParams->pszService; |
360 | | |
361 | | /* -------------------------------------------------------------------- */ |
362 | | /* Find out the typename. Look first for the wfs_tyename */ |
363 | | /* metadata. If not available try to find out if the CONNECTION */ |
364 | | /* string contains it. This last test is done for */ |
365 | | /* backward compatibility but is depericated. */ |
366 | | /* -------------------------------------------------------------------- */ |
367 | | pszTypename = psParams->pszTypeName; |
368 | | if (!pszTypename) { |
369 | | if (strstr(lp->connection, "TYPENAME=") == NULL && |
370 | | strstr(lp->connection, "typename=") == NULL) { |
371 | | msSetError(MS_WFSCONNERR, |
372 | | "Metadata wfs_typename must be set in the layer", |
373 | | "msBuildWFSLayerGetURL()"); |
374 | | return NULL; |
375 | | } |
376 | | bTypenameInConnection = 1; |
377 | | } |
378 | | |
379 | | /* -------------------------------------------------------------------- |
380 | | * Build the request URL. |
381 | | * At this point we set only the following parameters for GetFeature: |
382 | | * REQUEST |
383 | | * BBOX |
384 | | * VERSION |
385 | | * SERVICE |
386 | | * TYPENAME |
387 | | * FILTER |
388 | | * MAXFEATURES |
389 | | * |
390 | | * For backward compatibility the user could also have in the connection |
391 | | * string the following parameters (but it is deprecated): |
392 | | * VERSION |
393 | | * SERVICE |
394 | | * TYPENAME |
395 | | * -------------------------------------------------------------------- */ |
396 | | /* Make sure we have a big enough buffer for the URL */ |
397 | | bufferSize = strlen(lp->connection) + 1024; |
398 | | pszURL = (char *)malloc(bufferSize); |
399 | | MS_CHECK_ALLOC(pszURL, bufferSize, NULL); |
400 | | |
401 | | /* __TODO__ We have to urlencode each value... especially the BBOX values */ |
402 | | /* because if they end up in exponent format (123e+06) the + will be seen */ |
403 | | /* as a space by the remote server. */ |
404 | | |
405 | | /* -------------------------------------------------------------------- */ |
406 | | /* build the URL, */ |
407 | | /* -------------------------------------------------------------------- */ |
408 | | /* make sure connection ends with "&" or "?" */ |
409 | | pszOnlineResource = msOWSTerminateOnlineResource(lp->connection); |
410 | | snprintf(pszURL, bufferSize, "%s", pszOnlineResource); |
411 | | msFree(pszOnlineResource); |
412 | | |
413 | | /* REQUEST */ |
414 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), |
415 | | "&REQUEST=GetFeature"); |
416 | | |
417 | | /* VERSION */ |
418 | | if (!bVersionInConnection) |
419 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), |
420 | | "&VERSION=%s", pszVersion); |
421 | | |
422 | | /* SERVICE */ |
423 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), "&SERVICE=%s", |
424 | | pszService); |
425 | | |
426 | | /* TYPENAME */ |
427 | | if (!bTypenameInConnection) |
428 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), |
429 | | "&TYPENAME=%s", pszTypename); |
430 | | |
431 | | /* -------------------------------------------------------------------- */ |
432 | | /* If the filter parameter is given in the wfs_filter metadata, */ |
433 | | /* we use it and do not send the BBOX parameter as they are */ |
434 | | /* mutually exclusive. */ |
435 | | /* -------------------------------------------------------------------- */ |
436 | | if (psParams->pszFilter) { |
437 | | char *encoded_filter = msEncodeUrl(psParams->pszFilter); |
438 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), "&FILTER=%s", |
439 | | encoded_filter); |
440 | | free(encoded_filter); |
441 | | } else { |
442 | | /* |
443 | | * take care about the axis order for WFS 1.1 |
444 | | */ |
445 | | char *projUrn; |
446 | | char *projEpsg; |
447 | | projUrn = msOWSGetProjURN(&(lp->projection), &(lp->metadata), "FO", 1); |
448 | | msOWSGetEPSGProj(&(lp->projection), &(lp->metadata), "FO", 1, &projEpsg); |
449 | | |
450 | | /* |
451 | | * WFS 1.1 supports including the SRS in the BBOX parameter, should |
452 | | * respect axis order in the BBOX and has a separate SRSNAME parameter for |
453 | | * the desired result SRS. |
454 | | * WFS 1.0 is always easting, northing, doesn't include the SRS as part of |
455 | | * the BBOX parameter and has no SRSNAME parameter: if we don't have a |
456 | | * URN then fallback to WFS 1.0 style */ |
457 | | if ((strncmp(pszVersion, "1.1", 3) == 0) && projUrn) { |
458 | | if (projEpsg && (strncmp(projEpsg, "EPSG:", 5) == 0) && |
459 | | msIsAxisInverted(atoi(projEpsg + 5))) { |
460 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), |
461 | | "&BBOX=%.15g,%.15g,%.15g,%.15g,%s&SRSNAME=%s", bbox->miny, |
462 | | bbox->minx, bbox->maxy, bbox->maxx, projUrn, projUrn); |
463 | | } else { |
464 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), |
465 | | "&BBOX=%.15g,%.15g,%.15g,%.15g,%s&SRSNAME=%s", bbox->minx, |
466 | | bbox->miny, bbox->maxx, bbox->maxy, projUrn, projUrn); |
467 | | } |
468 | | } else { |
469 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), |
470 | | "&BBOX=%.15g,%.15g,%.15g,%.15g", bbox->minx, bbox->miny, |
471 | | bbox->maxx, bbox->maxy); |
472 | | } |
473 | | |
474 | | msFree(projUrn); |
475 | | msFree(projEpsg); |
476 | | } |
477 | | |
478 | | if (psParams->nMaxFeatures > 0) |
479 | | snprintf(pszURL + strlen(pszURL), bufferSize - strlen(pszURL), |
480 | | "&MAXFEATURES=%d", psParams->nMaxFeatures); |
481 | | |
482 | | return pszURL; |
483 | | } |
484 | | |
485 | | /********************************************************************** |
486 | | * msWFSLayerInfo |
487 | | * |
488 | | **********************************************************************/ |
489 | | typedef struct ms_wfs_layer_info_t { |
490 | | char *pszGMLFilename; |
491 | | rectObj rect; /* set by WhichShapes */ |
492 | | char *pszGetUrl; |
493 | | int nStatus; /* HTTP status */ |
494 | | int bLayerHasValidGML; /* False until msWFSLayerWhichShapes() is called and |
495 | | determines the result GML is valid with features*/ |
496 | | } msWFSLayerInfo; |
497 | | |
498 | | /********************************************************************** |
499 | | * msAllocWFSLayerInfo() |
500 | | * |
501 | | **********************************************************************/ |
502 | | static msWFSLayerInfo *msAllocWFSLayerInfo(void) { |
503 | | msWFSLayerInfo *psInfo; |
504 | | |
505 | | psInfo = (msWFSLayerInfo *)calloc(1, sizeof(msWFSLayerInfo)); |
506 | | MS_CHECK_ALLOC(psInfo, sizeof(msWFSLayerInfo), NULL); |
507 | | |
508 | | psInfo->pszGMLFilename = NULL; |
509 | | psInfo->rect.minx = psInfo->rect.maxx = 0; |
510 | | psInfo->rect.miny = psInfo->rect.maxy = 0; |
511 | | psInfo->pszGetUrl = NULL; |
512 | | psInfo->nStatus = 0; |
513 | | |
514 | | return psInfo; |
515 | | } |
516 | | |
517 | | /********************************************************************** |
518 | | * msFreeWFSLayerInfo() |
519 | | * |
520 | | **********************************************************************/ |
521 | | static void msFreeWFSLayerInfo(msWFSLayerInfo *psInfo) { |
522 | | if (psInfo) { |
523 | | if (psInfo->pszGMLFilename) |
524 | | free(psInfo->pszGMLFilename); |
525 | | if (psInfo->pszGetUrl) |
526 | | free(psInfo->pszGetUrl); |
527 | | |
528 | | free(psInfo); |
529 | | } |
530 | | } |
531 | | |
532 | | #endif /* USE_WFS_LYR */ |
533 | | |
534 | | /*==================================================================== |
535 | | * Public functions |
536 | | *====================================================================*/ |
537 | | |
538 | | /********************************************************************** |
539 | | * msPrepareWFSLayerRequest() |
540 | | * |
541 | | **********************************************************************/ |
542 | | |
543 | | int msPrepareWFSLayerRequest(int nLayerId, mapObj *map, layerObj *lp, |
544 | 0 | httpRequestObj *pasReqInfo, int *numRequests) { |
545 | | #ifdef USE_WFS_LYR |
546 | | char *pszURL = NULL; |
547 | | const char *pszTmp; |
548 | | rectObj bbox; |
549 | | int nTimeout; |
550 | | int nStatus = MS_SUCCESS; |
551 | | msWFSLayerInfo *psInfo = NULL; |
552 | | int bPostRequest = 0; |
553 | | wfsParamsObj *psParams = NULL; |
554 | | char *pszHTTPCookieData = NULL; |
555 | | |
556 | | if (lp->connectiontype != MS_WFS || lp->connection == NULL) |
557 | | return MS_FAILURE; |
558 | | |
559 | | /* ------------------------------------------------------------------ |
560 | | * Build a params object that will be used by to build the request, |
561 | | this will also set layer projection and compute BBOX in that projection. |
562 | | * ------------------------------------------------------------------ */ |
563 | | psParams = msBuildRequestParams(map, lp, &bbox); |
564 | | if (!psParams) |
565 | | return MS_FAILURE; |
566 | | |
567 | | /* -------------------------------------------------------------------- */ |
568 | | /* Depending on the metadata wfs_request_method, build a Get or */ |
569 | | /* a Post URL. */ |
570 | | /* If it is a Get request the URL would contain all the parameters in*/ |
571 | | /* the string; */ |
572 | | /* If it is a Post request, the URL will only contain the */ |
573 | | /* connection string coming from the layer. */ |
574 | | /* -------------------------------------------------------------------- */ |
575 | | if ((pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "request_method")) != |
576 | | NULL) { |
577 | | if (strncmp(pszTmp, "GET", 3) == 0) { |
578 | | pszURL = msBuildWFSLayerGetURL(lp, &bbox, psParams); |
579 | | if (!pszURL) { |
580 | | /* an error was already reported. */ |
581 | | return MS_FAILURE; |
582 | | } |
583 | | } |
584 | | } |
585 | | /* else it is a post request and just get the connection string */ |
586 | | if (!pszURL) { |
587 | | bPostRequest = 1; |
588 | | pszURL = msStrdup(lp->connection); |
589 | | } |
590 | | |
591 | | /* ------------------------------------------------------------------ |
592 | | * check to see if a the metadata wfs_connectiontimeout is set. If it is |
593 | | * the case we will use it, else we use the default which is 30 seconds. |
594 | | * First check the metadata in the layer object and then in the map object. |
595 | | * ------------------------------------------------------------------ */ |
596 | | nTimeout = 30; /* Default is 30 seconds */ |
597 | | if ((pszTmp = msOWSLookupMetadata2(&(lp->metadata), &(map->web.metadata), |
598 | | "FO", "connectiontimeout")) != NULL) { |
599 | | nTimeout = atoi(pszTmp); |
600 | | } |
601 | | |
602 | | /*------------------------------------------------------------------ |
603 | | * Check to see if there's a HTTP Cookie to forward |
604 | | * If Cookie differ between the two connection, it's NOT OK to merge |
605 | | * the connection |
606 | | * ------------------------------------------------------------------ */ |
607 | | if ((pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "http_cookie")) != |
608 | | NULL) { |
609 | | if (strcasecmp(pszTmp, "forward") == 0) { |
610 | | pszTmp = msLookupHashTable(&(map->web.metadata), "http_cookie_data"); |
611 | | if (pszTmp != NULL) { |
612 | | pszHTTPCookieData = msStrdup(pszTmp); |
613 | | } |
614 | | } else { |
615 | | pszHTTPCookieData = msStrdup(pszTmp); |
616 | | } |
617 | | } else if ((pszTmp = msOWSLookupMetadata(&(map->web.metadata), "FO", |
618 | | "http_cookie")) != NULL) { |
619 | | if (strcasecmp(pszTmp, "forward") == 0) { |
620 | | pszTmp = msLookupHashTable(&(map->web.metadata), "http_cookie_data"); |
621 | | if (pszTmp != NULL) { |
622 | | pszHTTPCookieData = msStrdup(pszTmp); |
623 | | } |
624 | | } else { |
625 | | pszHTTPCookieData = msStrdup(pszTmp); |
626 | | } |
627 | | } |
628 | | |
629 | | /* ------------------------------------------------------------------ |
630 | | * If nLayerId == -1 then we need to figure it |
631 | | * ------------------------------------------------------------------ */ |
632 | | if (nLayerId == -1) { |
633 | | int iLayer; |
634 | | for (iLayer = 0; iLayer < map->numlayers; iLayer++) { |
635 | | if (GET_LAYER(map, iLayer) == lp) { |
636 | | nLayerId = iLayer; |
637 | | break; |
638 | | } |
639 | | } |
640 | | } |
641 | | |
642 | | /* ------------------------------------------------------------------ |
643 | | * Add a request to the array (already preallocated) |
644 | | * ------------------------------------------------------------------ */ |
645 | | pasReqInfo[(*numRequests)].nLayerId = nLayerId; |
646 | | pasReqInfo[(*numRequests)].pszGetUrl = pszURL; |
647 | | |
648 | | if (bPostRequest) { |
649 | | pasReqInfo[(*numRequests)].pszPostRequest = |
650 | | msBuildWFSLayerPostRequest(&bbox, psParams); |
651 | | pasReqInfo[(*numRequests)].pszPostContentType = msStrdup("text/xml"); |
652 | | } |
653 | | |
654 | | /* We'll store the remote server's response to a tmp file. */ |
655 | | pasReqInfo[(*numRequests)].pszOutputFile = |
656 | | msTmpFile(map, map->mappath, NULL, "tmp.gml"); |
657 | | |
658 | | /* TODO: Implement Caching of GML responses. There was an older caching |
659 | | * method, but it suffered from a race condition. See #3137. |
660 | | */ |
661 | | |
662 | | pasReqInfo[(*numRequests)].pszHTTPCookieData = pszHTTPCookieData; |
663 | | pszHTTPCookieData = NULL; |
664 | | pasReqInfo[(*numRequests)].nStatus = 0; |
665 | | pasReqInfo[(*numRequests)].nTimeout = nTimeout; |
666 | | pasReqInfo[(*numRequests)].bbox = bbox; |
667 | | pasReqInfo[(*numRequests)].debug = lp->debug; |
668 | | |
669 | | if (msHTTPAuthProxySetup(&(map->web.metadata), &(lp->metadata), pasReqInfo, |
670 | | *numRequests, map, "FO") != MS_SUCCESS) { |
671 | | msWFSFreeParamsObj(psParams); |
672 | | return MS_FAILURE; |
673 | | } |
674 | | |
675 | | /* ------------------------------------------------------------------ |
676 | | * Pre-Open the layer now, (i.e. alloc and fill msWFSLayerInfo inside |
677 | | * layer obj). Layer will be ready for use when the main mapserver |
678 | | * code calls msLayerOpen(). |
679 | | * ------------------------------------------------------------------ */ |
680 | | if (lp->wfslayerinfo != NULL) { |
681 | | psInfo = (msWFSLayerInfo *)(lp->wfslayerinfo); |
682 | | } else { |
683 | | lp->wfslayerinfo = psInfo = msAllocWFSLayerInfo(); |
684 | | } |
685 | | |
686 | | if (psInfo->pszGMLFilename) |
687 | | free(psInfo->pszGMLFilename); |
688 | | psInfo->pszGMLFilename = msStrdup(pasReqInfo[(*numRequests)].pszOutputFile); |
689 | | |
690 | | psInfo->rect = pasReqInfo[(*numRequests)].bbox; |
691 | | |
692 | | if (psInfo->pszGetUrl) |
693 | | free(psInfo->pszGetUrl); |
694 | | psInfo->pszGetUrl = msStrdup(pasReqInfo[(*numRequests)].pszGetUrl); |
695 | | |
696 | | psInfo->nStatus = 0; |
697 | | |
698 | | (*numRequests)++; |
699 | | |
700 | | msWFSFreeParamsObj(psParams); |
701 | | |
702 | | return nStatus; |
703 | | |
704 | | #else |
705 | | /* ------------------------------------------------------------------ |
706 | | * WFS CONNECTION Support not included... |
707 | | * ------------------------------------------------------------------ */ |
708 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
709 | 0 | "msPrepareWFSLayerRequest"); |
710 | 0 | return (MS_FAILURE); |
711 | |
|
712 | 0 | #endif /* USE_WFS_LYR */ |
713 | 0 | } |
714 | | |
715 | | /********************************************************************** |
716 | | * msWFSUpdateRequestInfo() |
717 | | * |
718 | | * This function is called after a WFS request has been completed so that |
719 | | * we can copy request result information from the httpRequestObj to the |
720 | | * msWFSLayerInfo struct. Things to copy here are the HTTP status, exceptions |
721 | | * information, mime type, etc. |
722 | | **********************************************************************/ |
723 | 0 | void msWFSUpdateRequestInfo(layerObj *lp, httpRequestObj *pasReqInfo) { |
724 | | #ifdef USE_WFS_LYR |
725 | | if (lp->wfslayerinfo) { |
726 | | msWFSLayerInfo *psInfo = NULL; |
727 | | |
728 | | psInfo = (msWFSLayerInfo *)(lp->wfslayerinfo); |
729 | | |
730 | | /* Copy request results infos to msWFSLayerInfo struct */ |
731 | | /* For now there is only nStatus, but we should eventually add */ |
732 | | /* mime type and WFS exceptions information. */ |
733 | | psInfo->nStatus = pasReqInfo->nStatus; |
734 | | } |
735 | | #else |
736 | | /* ------------------------------------------------------------------ |
737 | | * WFS CONNECTION Support not included... |
738 | | * ------------------------------------------------------------------ */ |
739 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
740 | 0 | "msWFSUpdateRequestInfo()"); |
741 | 0 | #endif /* USE_WFS_LYR */ |
742 | 0 | } |
743 | | |
744 | | /********************************************************************** |
745 | | * msWFSLayerOpen() |
746 | | * |
747 | | * WFS layers are just a special case of OGR connection. Only the open/close |
748 | | * methods differ since they have to download and maintain GML files in cache |
749 | | * but the rest is mapped directly to OGR function calls in maplayer.c |
750 | | * |
751 | | **********************************************************************/ |
752 | | |
753 | | int msWFSLayerOpen(layerObj *lp, const char *pszGMLFilename, |
754 | 0 | rectObj *defaultBBOX) { |
755 | | #ifdef USE_WFS_LYR |
756 | | int status = MS_SUCCESS; |
757 | | msWFSLayerInfo *psInfo = NULL; |
758 | | |
759 | | if (msCheckParentPointer(lp->map, "map") == MS_FAILURE) |
760 | | return MS_FAILURE; |
761 | | |
762 | | if (lp->wfslayerinfo != NULL) { |
763 | | psInfo = (msWFSLayerInfo *)lp->wfslayerinfo; |
764 | | |
765 | | /* Layer already opened. If explicit filename requested then check */ |
766 | | /* that file was already opened with the same filename. */ |
767 | | /* If no explicit filename requested then we'll try to reuse the */ |
768 | | /* previously opened layer... this will happen in a msDrawMap() call. */ |
769 | | if (pszGMLFilename == NULL || |
770 | | (psInfo->pszGMLFilename && |
771 | | strcmp(psInfo->pszGMLFilename, pszGMLFilename) == 0)) { |
772 | | if (lp->layerinfo == NULL) { |
773 | | if (msWFSLayerWhichShapes(lp, psInfo->rect, MS_FALSE) == |
774 | | MS_FAILURE) /* no access to context (draw vs. query) here, although |
775 | | I doubt it matters... */ |
776 | | return MS_FAILURE; |
777 | | } |
778 | | return MS_SUCCESS; /* Nothing to do... layer is already opened */ |
779 | | } else { |
780 | | /* Hmmm... should we produce a fatal error? */ |
781 | | /* For now we'll just close the layer and reopen it. */ |
782 | | if (lp->debug) |
783 | | msDebug("msWFSLayerOpen(): Layer already opened (%s)\n", |
784 | | lp->name ? lp->name : "(null)"); |
785 | | msWFSLayerClose(lp); |
786 | | } |
787 | | } |
788 | | |
789 | | /* ------------------------------------------------------------------ |
790 | | * Alloc and fill msWFSLayerInfo inside layer obj |
791 | | * ------------------------------------------------------------------ */ |
792 | | lp->wfslayerinfo = psInfo = msAllocWFSLayerInfo(); |
793 | | |
794 | | if (pszGMLFilename) |
795 | | psInfo->pszGMLFilename = msStrdup(pszGMLFilename); |
796 | | else { |
797 | | psInfo->pszGMLFilename = |
798 | | msTmpFile(lp->map, lp->map->mappath, NULL, "tmp.gml"); |
799 | | } |
800 | | |
801 | | if (defaultBBOX) { |
802 | | /* __TODO__ If new bbox differs from current one then we should */ |
803 | | /* invalidate current GML file in cache */ |
804 | | psInfo->rect = *defaultBBOX; |
805 | | } else { |
806 | | /* Use map bbox by default */ |
807 | | psInfo->rect = lp->map->extent; |
808 | | } |
809 | | |
810 | | /* We will call whichshapes() now and force downloading layer right */ |
811 | | /* away. This saves from having to call DescribeFeatureType and */ |
812 | | /* parsing the response (being lazy I guess) and anyway given the */ |
813 | | /* way we work with layers right now the bbox is unlikely to change */ |
814 | | /* between now and the time whichshapes() would have been called by */ |
815 | | /* the MapServer core. */ |
816 | | |
817 | | if ((lp->map->projection.numargs > 0) && (lp->projection.numargs > 0)) |
818 | | msProjectRect(&lp->map->projection, &lp->projection, |
819 | | &psInfo->rect); /* project the searchrect to source coords */ |
820 | | |
821 | | if (msWFSLayerWhichShapes(lp, psInfo->rect, MS_FALSE) == |
822 | | MS_FAILURE) /* no access to context (draw vs. query) here, although I |
823 | | doubt it matters... */ |
824 | | status = MS_FAILURE; |
825 | | |
826 | | return status; |
827 | | #else |
828 | | /* ------------------------------------------------------------------ |
829 | | * WFS CONNECTION Support not included... |
830 | | * ------------------------------------------------------------------ */ |
831 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
832 | 0 | "msWFSLayerOpen()"); |
833 | 0 | return (MS_FAILURE); |
834 | |
|
835 | 0 | #endif /* USE_WFS_LYR */ |
836 | 0 | } |
837 | | |
838 | | /********************************************************************** |
839 | | * msWFSLayerOpenVT() |
840 | | * |
841 | | * Overloaded version of msWFSLayerOpen for virtual table architecture |
842 | | **********************************************************************/ |
843 | 0 | int msWFSLayerOpenVT(layerObj *lp) { return msWFSLayerOpen(lp, NULL, NULL); } |
844 | | |
845 | | /********************************************************************** |
846 | | * msWFSLayerIsOpen() |
847 | | * |
848 | | * Returns MS_TRUE if layer is already open, MS_FALSE otherwise. |
849 | | * |
850 | | **********************************************************************/ |
851 | | |
852 | 63 | int msWFSLayerIsOpen(layerObj *lp) { |
853 | | #ifdef USE_WFS_LYR |
854 | | if (lp->wfslayerinfo != NULL) |
855 | | return MS_TRUE; |
856 | | |
857 | | return MS_FALSE; |
858 | | #else |
859 | | /* ------------------------------------------------------------------ |
860 | | * WFS CONNECTION Support not included... |
861 | | * ------------------------------------------------------------------ */ |
862 | 63 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
863 | 63 | "msWFSLayerIsOpen()"); |
864 | 63 | return (MS_FALSE); |
865 | | |
866 | 63 | #endif /* USE_WFS_LYR */ |
867 | 63 | } |
868 | | |
869 | | /********************************************************************** |
870 | | * msWFSLayerInitItemInfo() |
871 | | * |
872 | | **********************************************************************/ |
873 | | |
874 | 0 | int msWFSLayerInitItemInfo(layerObj *layer) { |
875 | 0 | (void)layer; |
876 | | /* Nothing to do here. OGR will do its own initialization when it */ |
877 | | /* opens the actual file. */ |
878 | | /* Note that we didn't implement our own msWFSLayerFreeItemInfo() */ |
879 | | /* so that the OGR one gets called. */ |
880 | 0 | return MS_SUCCESS; |
881 | 0 | } |
882 | | |
883 | | /********************************************************************** |
884 | | * msWFSLayerGetShape() |
885 | | * |
886 | | **********************************************************************/ |
887 | 0 | int msWFSLayerGetShape(layerObj *layer, shapeObj *shape, resultObj *record) { |
888 | | #ifdef USE_WFS_LYR |
889 | | msWFSLayerInfo *psInfo = NULL; |
890 | | |
891 | | if (layer != NULL && layer->wfslayerinfo != NULL) |
892 | | psInfo = (msWFSLayerInfo *)layer->wfslayerinfo; |
893 | | else { |
894 | | msSetError(MS_WFSERR, "Layer is not opened.", "msWFSLayerGetShape()"); |
895 | | return MS_FAILURE; |
896 | | } |
897 | | |
898 | | if (psInfo->bLayerHasValidGML) |
899 | | return msOGRLayerGetShape(layer, shape, record); |
900 | | else { |
901 | | /* Layer is successful, but there is no data to process */ |
902 | | msFreeShape(shape); |
903 | | shape->type = MS_SHAPE_NULL; |
904 | | return MS_FAILURE; |
905 | | } |
906 | | #else |
907 | | /* ------------------------------------------------------------------ |
908 | | * WFS CONNECTION Support not included... |
909 | | * ------------------------------------------------------------------ */ |
910 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
911 | 0 | "msWFSLayerGetShape()"); |
912 | 0 | return (MS_FAILURE); |
913 | 0 | #endif /* USE_WFS_LYR */ |
914 | 0 | } |
915 | | |
916 | | /********************************************************************** |
917 | | * msWFSLayerGetNextShape() |
918 | | * |
919 | | **********************************************************************/ |
920 | 0 | int msWFSLayerNextShape(layerObj *layer, shapeObj *shape) { |
921 | | #ifdef USE_WFS_LYR |
922 | | msWFSLayerInfo *psInfo = NULL; |
923 | | |
924 | | if (layer != NULL && layer->wfslayerinfo != NULL) |
925 | | psInfo = (msWFSLayerInfo *)layer->wfslayerinfo; |
926 | | else { |
927 | | msSetError(MS_WFSERR, "Layer is not opened.", "msWFSLayerNextShape()"); |
928 | | return MS_FAILURE; |
929 | | } |
930 | | |
931 | | if (psInfo->bLayerHasValidGML) |
932 | | return msOGRLayerNextShape(layer, shape); |
933 | | else { |
934 | | /* Layer is successful, but there is no data to process */ |
935 | | msFreeShape(shape); |
936 | | shape->type = MS_SHAPE_NULL; |
937 | | return MS_FAILURE; |
938 | | } |
939 | | #else |
940 | | /* ------------------------------------------------------------------ |
941 | | * WFS CONNECTION Support not included... |
942 | | * ------------------------------------------------------------------ */ |
943 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
944 | 0 | "msWFSLayerNextShape()"); |
945 | 0 | return (MS_FAILURE); |
946 | 0 | #endif /* USE_WFS_LYR */ |
947 | 0 | } |
948 | | |
949 | | /********************************************************************** |
950 | | * msWFSLayerGetExtent() |
951 | | * |
952 | | **********************************************************************/ |
953 | 0 | int msWFSLayerGetExtent(layerObj *layer, rectObj *extent) { |
954 | | #ifdef USE_WFS_LYR |
955 | | msWFSLayerInfo *psInfo = NULL; |
956 | | |
957 | | if (layer != NULL && layer->wfslayerinfo != NULL) |
958 | | psInfo = (msWFSLayerInfo *)layer->wfslayerinfo; |
959 | | else { |
960 | | msSetError(MS_WFSERR, "Layer is not opened.", "msWFSLayerGetExtent()"); |
961 | | return MS_FAILURE; |
962 | | } |
963 | | |
964 | | if (psInfo->bLayerHasValidGML) |
965 | | return msOGRLayerGetExtent(layer, extent); |
966 | | else { |
967 | | /* Layer is successful, but there is no data to process */ |
968 | | msSetError(MS_WFSERR, "Unable to get extents for this layer.", |
969 | | "msWFSLayerGetExtent()"); |
970 | | return MS_FAILURE; |
971 | | } |
972 | | #else |
973 | | /* ------------------------------------------------------------------ |
974 | | * WFS CONNECTION Support not included... |
975 | | * ------------------------------------------------------------------ */ |
976 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
977 | 0 | "msWFSLayerGetExtent()"); |
978 | 0 | return (MS_FAILURE); |
979 | 0 | #endif /* USE_WFS_LYR */ |
980 | 0 | } |
981 | | |
982 | | /********************************************************************** |
983 | | * msWFSLayerGetItems() |
984 | | * |
985 | | **********************************************************************/ |
986 | | |
987 | 0 | int msWFSLayerGetItems(layerObj *layer) { |
988 | | #ifdef USE_WFS_LYR |
989 | | /* For now this method simply lets OGR parse the GML and figure the */ |
990 | | /* schema itself. */ |
991 | | /* It could also be implemented to call DescribeFeatureType for */ |
992 | | /* this layer, but we don't need to do it so why waste resources? */ |
993 | | |
994 | | msWFSLayerInfo *psInfo = NULL; |
995 | | |
996 | | if (layer != NULL && layer->wfslayerinfo != NULL) |
997 | | psInfo = (msWFSLayerInfo *)layer->wfslayerinfo; |
998 | | else { |
999 | | msSetError(MS_WFSERR, "Layer is not opened.", "msWFSLayerGetItems()"); |
1000 | | return MS_FAILURE; |
1001 | | } |
1002 | | |
1003 | | if (psInfo->bLayerHasValidGML) |
1004 | | return msOGRLayerGetItems(layer); |
1005 | | else { |
1006 | | /* Layer is successful, but there is no data to process */ |
1007 | | layer->numitems = 0; |
1008 | | layer->items = NULL; |
1009 | | return MS_SUCCESS; |
1010 | | } |
1011 | | #else |
1012 | | /* ------------------------------------------------------------------ |
1013 | | * WFS CONNECTION Support not included... |
1014 | | * ------------------------------------------------------------------ */ |
1015 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
1016 | 0 | "msWFSLayerGetItems()"); |
1017 | 0 | return (MS_FAILURE); |
1018 | 0 | #endif /* USE_WFS_LYR */ |
1019 | 0 | } |
1020 | | |
1021 | | /********************************************************************** |
1022 | | * msWFSLayerWhichShapes() |
1023 | | * |
1024 | | **********************************************************************/ |
1025 | | |
1026 | 0 | int msWFSLayerWhichShapes(layerObj *lp, rectObj rect, int isQuery) { |
1027 | | #ifdef USE_WFS_LYR |
1028 | | msWFSLayerInfo *psInfo; |
1029 | | int status = MS_SUCCESS; |
1030 | | const char *pszTmp; |
1031 | | FILE *fp; |
1032 | | |
1033 | | if (msCheckParentPointer(lp->map, "map") == MS_FAILURE) |
1034 | | return MS_FAILURE; |
1035 | | |
1036 | | psInfo = (msWFSLayerInfo *)lp->wfslayerinfo; |
1037 | | |
1038 | | if (psInfo == NULL) { |
1039 | | msSetError(MS_WFSCONNERR, "Assertion failed: WFS layer not opened!!!", |
1040 | | "msWFSLayerWhichShapes()"); |
1041 | | return (MS_FAILURE); |
1042 | | } |
1043 | | |
1044 | | /* ------------------------------------------------------------------ |
1045 | | * Check if layer overlaps current view window (using wfs_latlonboundingbox) |
1046 | | * ------------------------------------------------------------------ */ |
1047 | | if ((pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", |
1048 | | "latlonboundingbox")) != NULL) { |
1049 | | char **tokens; |
1050 | | int n; |
1051 | | rectObj ext; |
1052 | | |
1053 | | tokens = msStringSplit(pszTmp, ' ', &n); |
1054 | | if (tokens == NULL || n != 4) { |
1055 | | msSetError(MS_WFSCONNERR, |
1056 | | "Wrong number of values in 'wfs_latlonboundingbox' metadata.", |
1057 | | "msWFSLayerWhichShapes()"); |
1058 | | return MS_FAILURE; |
1059 | | } |
1060 | | |
1061 | | ext.minx = atof(tokens[0]); |
1062 | | ext.miny = atof(tokens[1]); |
1063 | | ext.maxx = atof(tokens[2]); |
1064 | | ext.maxy = atof(tokens[3]); |
1065 | | |
1066 | | msFreeCharArray(tokens, n); |
1067 | | |
1068 | | /* Reproject latlonboundingbox to the selected SRS for the layer and */ |
1069 | | /* check if it overlaps the bbox that we calculated for the request */ |
1070 | | |
1071 | | msProjectRect(&(lp->map->latlon), &(lp->projection), &ext); |
1072 | | if (!msRectOverlap(&rect, &ext)) { |
1073 | | /* No overlap... nothing to do. If layer was never opened, go open it.*/ |
1074 | | if (lp->layerinfo) |
1075 | | return MS_DONE; /* No overlap. */ |
1076 | | } |
1077 | | } |
1078 | | |
1079 | | /* ------------------------------------------------------------------ |
1080 | | * __TODO__ If new bbox differs from current one then we should |
1081 | | * invalidate current GML file in cache |
1082 | | * ------------------------------------------------------------------ */ |
1083 | | psInfo->rect = rect; |
1084 | | |
1085 | | /* ------------------------------------------------------------------ |
1086 | | * If file not downloaded yet then do it now. |
1087 | | * ------------------------------------------------------------------ */ |
1088 | | if (psInfo->nStatus == 0) { |
1089 | | httpRequestObj asReqInfo[2]; |
1090 | | int numReq = 0; |
1091 | | |
1092 | | msHTTPInitRequestObj(asReqInfo, 2); |
1093 | | |
1094 | | if (msPrepareWFSLayerRequest(-1, lp->map, lp, asReqInfo, &numReq) == |
1095 | | MS_FAILURE || |
1096 | | msOWSExecuteRequests(asReqInfo, numReq, lp->map, MS_TRUE) == |
1097 | | MS_FAILURE) { |
1098 | | /* Delete tmp file... we don't want it to stick around. */ |
1099 | | unlink(asReqInfo[0].pszOutputFile); |
1100 | | return MS_FAILURE; |
1101 | | } |
1102 | | |
1103 | | if (psInfo->nStatus != asReqInfo[0].nStatus) { |
1104 | | /* For drawQuery, we may use a copy layer which needs to be updated */ |
1105 | | msWFSUpdateRequestInfo(lp, &(asReqInfo[0])); |
1106 | | } |
1107 | | |
1108 | | /* Cleanup */ |
1109 | | msHTTPFreeRequestObj(asReqInfo, numReq); |
1110 | | } |
1111 | | |
1112 | | if (!MS_HTTP_SUCCESS(psInfo->nStatus)) { |
1113 | | /* Delete tmp file... we don't want it to stick around. */ |
1114 | | unlink(psInfo->pszGMLFilename); |
1115 | | |
1116 | | msSetError(MS_WFSCONNERR, "Got HTTP status %d downloading WFS layer %s", |
1117 | | "msWFSLayerWhichShapes()", psInfo->nStatus, |
1118 | | lp->name ? lp->name : "(null)"); |
1119 | | return (MS_FAILURE); |
1120 | | } |
1121 | | |
1122 | | /* ------------------------------------------------------------------ |
1123 | | * Check that file is really GML... it could be an exception, or just junk. |
1124 | | * ------------------------------------------------------------------ */ |
1125 | | if ((fp = fopen(psInfo->pszGMLFilename, "r")) != NULL) { |
1126 | | char szHeader[2000]; |
1127 | | int nBytes = 0; |
1128 | | |
1129 | | nBytes = fread(szHeader, 1, sizeof(szHeader) - 1, fp); |
1130 | | fclose(fp); |
1131 | | |
1132 | | if (nBytes < 0) |
1133 | | nBytes = 0; |
1134 | | szHeader[nBytes] = '\0'; |
1135 | | |
1136 | | if (nBytes == 0) { |
1137 | | msSetError(MS_WFSCONNERR, "WFS request produced no oputput for layer %s.", |
1138 | | "msWFSLayerWhichShapes()", lp->name ? lp->name : "(null)"); |
1139 | | return (MS_FAILURE); |
1140 | | } |
1141 | | if (strstr(szHeader, "<WFS_Exception>") || |
1142 | | strstr(szHeader, "<ServiceExceptionReport>")) { |
1143 | | msOWSProcessException(lp, psInfo->pszGMLFilename, MS_WFSCONNERR, |
1144 | | "msWFSLayerWhichShapes()"); |
1145 | | return MS_FAILURE; |
1146 | | } else if (strstr(szHeader, "opengis.net/gml") && |
1147 | | strstr(szHeader, "featureMember>") == NULL) { |
1148 | | /* This looks like valid GML, but contains 0 features. */ |
1149 | | return MS_DONE; |
1150 | | } else if (strstr(szHeader, "opengis.net/gml") == NULL || |
1151 | | strstr(szHeader, "featureMember>") == NULL) { |
1152 | | /* This is probably just junk. */ |
1153 | | msSetError(MS_WFSCONNERR, |
1154 | | "WFS request produced unexpected output (junk?) for layer %s.", |
1155 | | "msWFSLayerWhichShapes()", lp->name ? lp->name : "(null)"); |
1156 | | return (MS_FAILURE); |
1157 | | } |
1158 | | |
1159 | | /* If we got this far, it must be a valid GML dataset... keep going */ |
1160 | | } |
1161 | | |
1162 | | /* ------------------------------------------------------------------ |
1163 | | * Open GML file using OGR. |
1164 | | * ------------------------------------------------------------------ */ |
1165 | | if ((status = msOGRLayerOpen(lp, psInfo->pszGMLFilename)) != MS_SUCCESS) |
1166 | | return status; |
1167 | | |
1168 | | status = msOGRLayerWhichShapes(lp, rect, isQuery); |
1169 | | |
1170 | | /* Mark that the OGR Layer is valid */ |
1171 | | psInfo->bLayerHasValidGML = MS_TRUE; |
1172 | | |
1173 | | return status; |
1174 | | #else |
1175 | | /* ------------------------------------------------------------------ |
1176 | | * WFS CONNECTION Support not included... |
1177 | | * ------------------------------------------------------------------ */ |
1178 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
1179 | 0 | "msWFSLayerWhichShapes()"); |
1180 | 0 | return (MS_FAILURE); |
1181 | |
|
1182 | 0 | #endif /* USE_WFS_LYR */ |
1183 | 0 | } |
1184 | | |
1185 | | /********************************************************************** |
1186 | | * msWFSLayerClose() |
1187 | | * |
1188 | | **********************************************************************/ |
1189 | | |
1190 | 0 | int msWFSLayerClose(layerObj *lp) { |
1191 | | #ifdef USE_WFS_LYR |
1192 | | |
1193 | | /* ------------------------------------------------------------------ |
1194 | | * Cleanup OGR connection |
1195 | | * ------------------------------------------------------------------ */ |
1196 | | if (lp->layerinfo) |
1197 | | msOGRLayerClose(lp); |
1198 | | |
1199 | | /* ------------------------------------------------------------------ |
1200 | | * Cleanup WFS connection info. |
1201 | | * __TODO__ For now we flush everything, but we should try to cache some stuff |
1202 | | * ------------------------------------------------------------------ */ |
1203 | | /* __TODO__ unlink() .gml file and OGR's schema file if they exist */ |
1204 | | /* unlink( */ |
1205 | | |
1206 | | msFreeWFSLayerInfo(lp->wfslayerinfo); |
1207 | | lp->wfslayerinfo = NULL; |
1208 | | |
1209 | | return MS_SUCCESS; |
1210 | | |
1211 | | #else |
1212 | | /* ------------------------------------------------------------------ |
1213 | | * WFS CONNECTION Support not included... |
1214 | | * ------------------------------------------------------------------ */ |
1215 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
1216 | 0 | "msWFSLayerClose()"); |
1217 | 0 | return (MS_FAILURE); |
1218 | |
|
1219 | 0 | #endif /* USE_WFS_LYR */ |
1220 | 0 | } |
1221 | | |
1222 | | /********************************************************************** |
1223 | | * msWFSExecuteGetFeature() |
1224 | | * Returns the temporary gml file name. User shpuld free the return string. |
1225 | | **********************************************************************/ |
1226 | 0 | char *msWFSExecuteGetFeature(layerObj *lp) { |
1227 | | #ifdef USE_WFS_LYR |
1228 | | char *gmltmpfile = NULL; |
1229 | | msWFSLayerInfo *psInfo = NULL; |
1230 | | |
1231 | | if (lp == NULL || lp->connectiontype != MS_WFS) |
1232 | | return NULL; |
1233 | | |
1234 | | msWFSLayerOpen(lp, NULL, NULL); |
1235 | | psInfo = (msWFSLayerInfo *)lp->wfslayerinfo; |
1236 | | if (psInfo && psInfo->pszGMLFilename) |
1237 | | gmltmpfile = msStrdup(psInfo->pszGMLFilename); |
1238 | | msWFSLayerClose(lp); |
1239 | | |
1240 | | return gmltmpfile; |
1241 | | |
1242 | | #else |
1243 | | /* ------------------------------------------------------------------ |
1244 | | * WFS CONNECTION Support not included... |
1245 | | * ------------------------------------------------------------------ */ |
1246 | 0 | msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", |
1247 | 0 | "msExecuteWFSGetFeature()"); |
1248 | 0 | return NULL; |
1249 | |
|
1250 | 0 | #endif /* USE_WFS_LYR */ |
1251 | 0 | } |
1252 | | |
1253 | 63 | int msWFSLayerInitializeVirtualTable(layerObj *layer) { |
1254 | 63 | assert(layer != NULL); |
1255 | 63 | assert(layer->vtable != NULL); |
1256 | | |
1257 | 63 | layer->vtable->LayerInitItemInfo = msWFSLayerInitItemInfo; |
1258 | 63 | layer->vtable->LayerFreeItemInfo = msOGRLayerFreeItemInfo; /* yes, OGR */ |
1259 | 63 | layer->vtable->LayerOpen = msWFSLayerOpenVT; |
1260 | 63 | layer->vtable->LayerIsOpen = msWFSLayerIsOpen; |
1261 | 63 | layer->vtable->LayerWhichShapes = msWFSLayerWhichShapes; |
1262 | 63 | layer->vtable->LayerNextShape = msWFSLayerNextShape; |
1263 | | /* layer->vtable->LayerResultsGetShape = msWFSLayerResultGetShape; */ |
1264 | | /* layer->vtable->LayerGetShapeCount, use default */ |
1265 | 63 | layer->vtable->LayerGetShape = msWFSLayerGetShape; |
1266 | 63 | layer->vtable->LayerClose = msWFSLayerClose; |
1267 | 63 | layer->vtable->LayerGetItems = msWFSLayerGetItems; |
1268 | 63 | layer->vtable->LayerGetExtent = msWFSLayerGetExtent; |
1269 | | /* layer->vtable->LayerGetAutoStyle, use default */ |
1270 | | /* layer->vtable->LayerApplyFilterToLayer, use default */ |
1271 | | /* layer->vtable->LayerCloseConnection, use default */ |
1272 | 63 | layer->vtable->LayerSetTimeFilter = msLayerMakePlainTimeFilter; |
1273 | | /* layer->vtable->LayerCreateItems, use default */ |
1274 | | /* layer->vtable->LayerGetNumFeatures, use default */ |
1275 | | /* layer->vtable->LayerGetAutoProjection, use default*/ |
1276 | | |
1277 | 63 | return MS_SUCCESS; |
1278 | 63 | } |