/src/gdal/frmts/wcs/wcsdataset110.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: WCS Client Driver |
4 | | * Purpose: Implementation of Dataset class for WCS 1.1. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2006, Frank Warmerdam |
9 | | * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com> |
10 | | * Copyright (c) 2017, Ari Jolma |
11 | | * Copyright (c) 2017, Finnish Environment Institute |
12 | | * |
13 | | * SPDX-License-Identifier: MIT |
14 | | ****************************************************************************/ |
15 | | |
16 | | #include "cpl_string.h" |
17 | | #include "cpl_minixml.h" |
18 | | #include "cpl_http.h" |
19 | | #include "gmlutils.h" |
20 | | #include "gdal_frmts.h" |
21 | | #include "gdal_pam.h" |
22 | | #include "ogr_spatialref.h" |
23 | | #include "gmlcoverage.h" |
24 | | |
25 | | #include <algorithm> |
26 | | |
27 | | #include "wcsdataset.h" |
28 | | #include "wcsutils.h" |
29 | | |
30 | | using namespace WCSUtils; |
31 | | |
32 | | /************************************************************************/ |
33 | | /* GetNativeExtent() */ |
34 | | /* */ |
35 | | /************************************************************************/ |
36 | | |
37 | | std::vector<double> WCSDataset110::GetNativeExtent(int nXOff, int nYOff, |
38 | | int nXSize, int nYSize, |
39 | | CPL_UNUSED int nBufXSize, |
40 | | CPL_UNUSED int nBufYSize) |
41 | 0 | { |
42 | 0 | std::vector<double> extent; |
43 | | |
44 | | // outer edges of outer pixels. |
45 | 0 | extent.push_back(m_gt.xorig + (nXOff)*m_gt.xscale); |
46 | 0 | extent.push_back(m_gt.yorig + (nYOff + nYSize) * m_gt.yscale); |
47 | 0 | extent.push_back(m_gt.xorig + (nXOff + nXSize) * m_gt.xscale); |
48 | 0 | extent.push_back(m_gt.yorig + (nYOff)*m_gt.yscale); |
49 | |
|
50 | 0 | bool no_shrink = CPLGetXMLBoolean(psService, "OuterExtents"); |
51 | | |
52 | | // WCS 1.1 extents are centers of outer pixels. |
53 | 0 | if (!no_shrink) |
54 | 0 | { |
55 | 0 | extent[2] -= m_gt.xscale * 0.5; |
56 | 0 | extent[0] += m_gt.xscale * 0.5; |
57 | 0 | extent[1] -= m_gt.yscale * 0.5; |
58 | 0 | extent[3] += m_gt.yscale * 0.5; |
59 | 0 | } |
60 | |
|
61 | 0 | double dfXStep, dfYStep; |
62 | |
|
63 | 0 | if (!no_shrink) |
64 | 0 | { |
65 | 0 | dfXStep = (nXSize / (double)nBufXSize) * m_gt.xscale; |
66 | 0 | dfYStep = (nYSize / (double)nBufYSize) * m_gt.yscale; |
67 | | // Carefully adjust bounds for pixel centered values at new |
68 | | // sampling density. |
69 | 0 | if (nBufXSize != nXSize || nBufYSize != nYSize) |
70 | 0 | { |
71 | 0 | extent[0] = nXOff * m_gt.xscale + m_gt.xorig + dfXStep * 0.5; |
72 | 0 | extent[2] = extent[0] + (nBufXSize - 1) * dfXStep; |
73 | |
|
74 | 0 | extent[3] = nYOff * m_gt.yscale + m_gt.yorig + dfYStep * 0.5; |
75 | 0 | extent[1] = extent[3] + (nBufYSize - 1) * dfYStep; |
76 | 0 | } |
77 | 0 | } |
78 | 0 | else |
79 | 0 | { |
80 | 0 | double adjust = |
81 | 0 | CPLAtof(CPLGetXMLValue(psService, "BufSizeAdjust", "0.0")); |
82 | 0 | dfXStep = (nXSize / ((double)nBufXSize + adjust)) * m_gt.xscale; |
83 | 0 | dfYStep = (nYSize / ((double)nBufYSize + adjust)) * m_gt.yscale; |
84 | 0 | } |
85 | |
|
86 | 0 | extent.push_back(dfXStep); |
87 | 0 | extent.push_back(dfYStep); |
88 | |
|
89 | 0 | return extent; |
90 | 0 | } |
91 | | |
92 | | /************************************************************************/ |
93 | | /* GetCoverageRequest() */ |
94 | | /* */ |
95 | | /************************************************************************/ |
96 | | |
97 | | std::string WCSDataset110::GetCoverageRequest(bool scaled, int /* nBufXSize */, |
98 | | int /* nBufYSize */, |
99 | | const std::vector<double> &extent, |
100 | | const std::string &osBandList) |
101 | 0 | { |
102 | 0 | CPLString osRequest; |
103 | | |
104 | | /* -------------------------------------------------------------------- */ |
105 | | /* URL encode strings that could have questionable characters. */ |
106 | | /* -------------------------------------------------------------------- */ |
107 | 0 | CPLString osCoverage = CPLGetXMLValue(psService, "CoverageName", ""); |
108 | |
|
109 | 0 | char *pszEncoded = CPLEscapeString(osCoverage, -1, CPLES_URL); |
110 | 0 | osCoverage = pszEncoded; |
111 | 0 | CPLFree(pszEncoded); |
112 | |
|
113 | 0 | CPLString osFormat = CPLGetXMLValue(psService, "PreferredFormat", ""); |
114 | |
|
115 | 0 | pszEncoded = CPLEscapeString(osFormat, -1, CPLES_URL); |
116 | 0 | osFormat = pszEncoded; |
117 | 0 | CPLFree(pszEncoded); |
118 | |
|
119 | 0 | CPLString osRangeSubset = CPLGetXMLValue(psService, "FieldName", ""); |
120 | | |
121 | | // todo: MapServer seems to require interpolation |
122 | |
|
123 | 0 | CPLString interpolation = CPLGetXMLValue(psService, "Interpolation", ""); |
124 | 0 | if (interpolation == "") |
125 | 0 | { |
126 | | // old undocumented key for interpolation in service |
127 | 0 | interpolation = CPLGetXMLValue(psService, "Resample", ""); |
128 | 0 | } |
129 | 0 | if (interpolation != "") |
130 | 0 | { |
131 | 0 | osRangeSubset += ":" + interpolation; |
132 | 0 | } |
133 | |
|
134 | 0 | if (osBandList != "") |
135 | 0 | { |
136 | 0 | if (osBandIdentifier != "") |
137 | 0 | { |
138 | 0 | osRangeSubset += CPLString().Printf( |
139 | 0 | "[%s[%s]]", osBandIdentifier.c_str(), osBandList.c_str()); |
140 | 0 | } |
141 | 0 | } |
142 | |
|
143 | 0 | osRangeSubset = "&RangeSubset=" + URLEncode(osRangeSubset); |
144 | |
|
145 | 0 | double bbox_0 = extent[0], // min X |
146 | 0 | bbox_1 = extent[1], // min Y |
147 | 0 | bbox_2 = extent[2], // max X |
148 | 0 | bbox_3 = extent[3]; // max Y |
149 | |
|
150 | 0 | if (axis_order_swap) |
151 | 0 | { |
152 | 0 | bbox_0 = extent[1]; // min Y |
153 | 0 | bbox_1 = extent[0]; // min X |
154 | 0 | bbox_2 = extent[3]; // max Y |
155 | 0 | bbox_3 = extent[2]; // max X |
156 | 0 | } |
157 | 0 | std::string request = CPLGetXMLValue(psService, "ServiceURL", ""); |
158 | 0 | request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS"); |
159 | 0 | request += CPLString().Printf( |
160 | 0 | "&VERSION=%s&REQUEST=GetCoverage&IDENTIFIER=%s" |
161 | 0 | "&FORMAT=%s&BOUNDINGBOX=%.15g,%.15g,%.15g,%.15g,%s%s", |
162 | 0 | CPLGetXMLValue(psService, "Version", ""), osCoverage.c_str(), |
163 | 0 | osFormat.c_str(), bbox_0, bbox_1, bbox_2, bbox_3, osCRS.c_str(), |
164 | 0 | osRangeSubset.c_str()); |
165 | 0 | double origin_1 = extent[0], // min X |
166 | 0 | origin_2 = extent[3], // max Y |
167 | 0 | offset_1 = extent[4], // dX |
168 | 0 | offset_2 = extent[5]; // dY |
169 | |
|
170 | 0 | if (axis_order_swap) |
171 | 0 | { |
172 | 0 | origin_1 = extent[3]; // max Y |
173 | 0 | origin_2 = extent[0]; // min X |
174 | 0 | offset_1 = extent[5]; // dY |
175 | 0 | offset_2 = extent[4]; // dX |
176 | 0 | } |
177 | 0 | CPLString offsets; |
178 | 0 | if (CPLGetXMLBoolean(psService, "OffsetsPositive")) |
179 | 0 | { |
180 | 0 | offset_1 = fabs(offset_1); |
181 | 0 | offset_2 = fabs(offset_2); |
182 | 0 | } |
183 | 0 | if (EQUAL(CPLGetXMLValue(psService, "NrOffsets", "4"), "2")) |
184 | 0 | { |
185 | 0 | offsets = CPLString().Printf("%.15g,%.15g", offset_1, offset_2); |
186 | 0 | } |
187 | 0 | else |
188 | 0 | { |
189 | 0 | if (axis_order_swap) |
190 | 0 | { |
191 | | // Only tested with GeoServer but this is the correct offset(?) |
192 | 0 | offsets = CPLString().Printf("0,%.15g,%.15g,0", offset_2, offset_1); |
193 | 0 | } |
194 | 0 | else |
195 | 0 | { |
196 | 0 | offsets = CPLString().Printf("%.15g,0,0,%.15g", offset_1, offset_2); |
197 | 0 | } |
198 | 0 | } |
199 | 0 | bool do_not_include = |
200 | 0 | CPLGetXMLBoolean(psService, "GridCRSOptional") && !scaled; |
201 | 0 | if (!do_not_include) |
202 | 0 | { |
203 | 0 | request += CPLString().Printf( |
204 | 0 | "&GridBaseCRS=%s" |
205 | 0 | "&GridCS=urn:ogc:def:cs:OGC:0.0:Grid2dSquareCS" |
206 | 0 | "&GridType=urn:ogc:def:method:WCS:1.1:2dGridIn2dCrs" |
207 | 0 | "&GridOrigin=%.15g,%.15g" |
208 | 0 | "&GridOffsets=%s", |
209 | 0 | osCRS.c_str(), origin_1, origin_2, offsets.c_str()); |
210 | 0 | } |
211 | 0 | CPLString extra = CPLGetXMLValue(psService, "Parameters", ""); |
212 | 0 | if (extra != "") |
213 | 0 | { |
214 | 0 | std::vector<std::string> pairs = Split(extra.c_str(), "&"); |
215 | 0 | for (unsigned int i = 0; i < pairs.size(); ++i) |
216 | 0 | { |
217 | 0 | std::vector<std::string> pair = Split(pairs[i].c_str(), "="); |
218 | 0 | request = |
219 | 0 | CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str()); |
220 | 0 | } |
221 | 0 | } |
222 | 0 | extra = CPLGetXMLValue(psService, "GetCoverageExtra", ""); |
223 | 0 | if (extra != "") |
224 | 0 | { |
225 | 0 | std::vector<std::string> pairs = Split(extra.c_str(), "&"); |
226 | 0 | for (unsigned int i = 0; i < pairs.size(); ++i) |
227 | 0 | { |
228 | 0 | std::vector<std::string> pair = Split(pairs[i].c_str(), "="); |
229 | 0 | request = |
230 | 0 | CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str()); |
231 | 0 | } |
232 | 0 | } |
233 | 0 | CPLDebug("WCS", "Requesting %s", request.c_str()); |
234 | 0 | return request; |
235 | 0 | } |
236 | | |
237 | | /************************************************************************/ |
238 | | /* DescribeCoverageRequest() */ |
239 | | /* */ |
240 | | /************************************************************************/ |
241 | | |
242 | | std::string WCSDataset110::DescribeCoverageRequest() |
243 | 0 | { |
244 | 0 | std::string request = CPLGetXMLValue(psService, "ServiceURL", ""); |
245 | 0 | request = CPLURLAddKVP(request.c_str(), "SERVICE", "WCS"); |
246 | 0 | request = CPLURLAddKVP(request.c_str(), "REQUEST", "DescribeCoverage"); |
247 | 0 | request = CPLURLAddKVP(request.c_str(), "VERSION", |
248 | 0 | CPLGetXMLValue(psService, "Version", "1.1.0")); |
249 | 0 | request = CPLURLAddKVP(request.c_str(), "IDENTIFIERS", |
250 | 0 | CPLGetXMLValue(psService, "CoverageName", "")); |
251 | 0 | CPLString extra = CPLGetXMLValue(psService, "Parameters", ""); |
252 | 0 | if (extra != "") |
253 | 0 | { |
254 | 0 | std::vector<std::string> pairs = Split(extra.c_str(), "&"); |
255 | 0 | for (unsigned int i = 0; i < pairs.size(); ++i) |
256 | 0 | { |
257 | 0 | std::vector<std::string> pair = Split(pairs[i].c_str(), "="); |
258 | 0 | request = |
259 | 0 | CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str()); |
260 | 0 | } |
261 | 0 | } |
262 | 0 | extra = CPLGetXMLValue(psService, "DescribeCoverageExtra", ""); |
263 | 0 | if (extra != "") |
264 | 0 | { |
265 | 0 | std::vector<std::string> pairs = Split(extra.c_str(), "&"); |
266 | 0 | for (unsigned int i = 0; i < pairs.size(); ++i) |
267 | 0 | { |
268 | 0 | std::vector<std::string> pair = Split(pairs[i].c_str(), "="); |
269 | 0 | request = |
270 | 0 | CPLURLAddKVP(request.c_str(), pair[0].c_str(), pair[1].c_str()); |
271 | 0 | } |
272 | 0 | } |
273 | 0 | return request; |
274 | 0 | } |
275 | | |
276 | | /************************************************************************/ |
277 | | /* CoverageOffering() */ |
278 | | /* */ |
279 | | /************************************************************************/ |
280 | | |
281 | | CPLXMLNode *WCSDataset110::CoverageOffering(CPLXMLNode *psDC) |
282 | 0 | { |
283 | 0 | return CPLGetXMLNode(psDC, "=CoverageDescriptions.CoverageDescription"); |
284 | 0 | } |
285 | | |
286 | | /************************************************************************/ |
287 | | /* ExtractGridInfo() */ |
288 | | /* */ |
289 | | /* Collect info about grid from describe coverage for WCS 1.1. */ |
290 | | /* */ |
291 | | /************************************************************************/ |
292 | | |
293 | | bool WCSDataset110::ExtractGridInfo() |
294 | | |
295 | 0 | { |
296 | 0 | CPLXMLNode *psCO = CPLGetXMLNode(psService, "CoverageDescription"); |
297 | |
|
298 | 0 | if (psCO == nullptr) |
299 | 0 | return false; |
300 | | |
301 | | /* -------------------------------------------------------------------- */ |
302 | | /* We need to strip off name spaces so it is easier to */ |
303 | | /* searchfor plain gml names. */ |
304 | | /* -------------------------------------------------------------------- */ |
305 | 0 | CPLStripXMLNamespace(psCO, nullptr, TRUE); |
306 | | |
307 | | /* -------------------------------------------------------------------- */ |
308 | | /* Verify we have a SpatialDomain and GridCRS. */ |
309 | | /* -------------------------------------------------------------------- */ |
310 | 0 | CPLXMLNode *psSD = CPLGetXMLNode(psCO, "Domain.SpatialDomain"); |
311 | 0 | CPLXMLNode *psGCRS = CPLGetXMLNode(psSD, "GridCRS"); |
312 | |
|
313 | 0 | if (psSD == nullptr || psGCRS == nullptr) |
314 | 0 | { |
315 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
316 | 0 | "Unable to find GridCRS in CoverageDescription,\n" |
317 | 0 | "unable to process WCS Coverage."); |
318 | 0 | return false; |
319 | 0 | } |
320 | | |
321 | | /* -------------------------------------------------------------------- */ |
322 | | /* Establish our coordinate system. */ |
323 | | /* This is needed before geometry since we may have axis order swap. */ |
324 | | /* -------------------------------------------------------------------- */ |
325 | 0 | CPLString crs = ParseCRS(psGCRS); |
326 | |
|
327 | 0 | if (crs.empty()) |
328 | 0 | { |
329 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
330 | 0 | "Unable to find GridCRS.GridBaseCRS"); |
331 | 0 | return false; |
332 | 0 | } |
333 | | |
334 | | // SetCRS should fail only if the CRS is really unknown to GDAL |
335 | 0 | if (!SetCRS(crs, true)) |
336 | 0 | { |
337 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
338 | 0 | "Unable to interpret GridBaseCRS '%s'.", crs.c_str()); |
339 | 0 | return false; |
340 | 0 | } |
341 | | |
342 | | /* -------------------------------------------------------------------- */ |
343 | | /* Collect size, origin, and offsets for SetGeometry() */ |
344 | | /* */ |
345 | | /* Extract Geotransform from GridCRS. */ |
346 | | /* */ |
347 | | /* -------------------------------------------------------------------- */ |
348 | 0 | const char *pszGridType = CPLGetXMLValue( |
349 | 0 | psGCRS, "GridType", "urn:ogc:def:method:WCS::2dSimpleGrid"); |
350 | 0 | bool swap = |
351 | 0 | axis_order_swap && !CPLGetXMLBoolean(psService, "NoGridAxisSwap"); |
352 | 0 | std::vector<double> origin = |
353 | 0 | Flist(Split(CPLGetXMLValue(psGCRS, "GridOrigin", ""), " ", swap)); |
354 | |
|
355 | 0 | std::vector<std::string> offset_1 = |
356 | 0 | Split(CPLGetXMLValue(psGCRS, "GridOffsets", ""), " "); |
357 | 0 | std::vector<std::string> offset_2; |
358 | 0 | size_t n = offset_1.size(); |
359 | 0 | if (n % 2 != 0) |
360 | 0 | { |
361 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
362 | 0 | "GridOffsets has incorrect amount of coefficients.\n" |
363 | 0 | "Unable to process WCS coverage."); |
364 | 0 | return false; |
365 | 0 | } |
366 | 0 | for (unsigned int i = 0; i < n / 2; ++i) |
367 | 0 | { |
368 | 0 | CPLString s = offset_1.back(); |
369 | 0 | offset_1.erase(offset_1.end() - 1); |
370 | 0 | #if defined(__GNUC__) |
371 | 0 | #pragma GCC diagnostic push |
372 | 0 | #pragma GCC diagnostic ignored "-Wnull-dereference" |
373 | 0 | #endif |
374 | 0 | offset_2.insert(offset_2.begin(), s); |
375 | 0 | #if defined(__GNUC__) |
376 | 0 | #pragma GCC diagnostic pop |
377 | 0 | #endif |
378 | 0 | } |
379 | 0 | std::vector<std::vector<double>> offsets; |
380 | 0 | if (swap) |
381 | 0 | { |
382 | 0 | offsets.push_back(Flist(offset_2)); |
383 | 0 | offsets.push_back(Flist(offset_1)); |
384 | 0 | } |
385 | 0 | else |
386 | 0 | { |
387 | 0 | offsets.push_back(Flist(offset_1)); |
388 | 0 | offsets.push_back(Flist(offset_2)); |
389 | 0 | } |
390 | |
|
391 | 0 | if (strstr(pszGridType, ":2dGridIn2dCrs") || |
392 | 0 | strstr(pszGridType, ":2dGridin2dCrs")) |
393 | 0 | { |
394 | 0 | if (!(offset_1.size() == 2 && origin.size() == 2)) |
395 | 0 | { |
396 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
397 | 0 | "2dGridIn2dCrs does not have expected GridOrigin or\n" |
398 | 0 | "GridOffsets values - unable to process WCS coverage."); |
399 | 0 | return false; |
400 | 0 | } |
401 | 0 | } |
402 | | |
403 | 0 | else if (strstr(pszGridType, ":2dGridIn3dCrs")) |
404 | 0 | { |
405 | 0 | if (!(offset_1.size() == 3 && origin.size() == 3)) |
406 | 0 | { |
407 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
408 | 0 | "2dGridIn3dCrs does not have expected GridOrigin or\n" |
409 | 0 | "GridOffsets values - unable to process WCS coverage."); |
410 | 0 | return false; |
411 | 0 | } |
412 | 0 | } |
413 | | |
414 | 0 | else if (strstr(pszGridType, ":2dSimpleGrid")) |
415 | 0 | { |
416 | 0 | if (!(offset_1.size() == 1 && origin.size() == 2)) |
417 | 0 | { |
418 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
419 | 0 | "2dSimpleGrid does not have expected GridOrigin or\n" |
420 | 0 | "GridOffsets values - unable to process WCS coverage."); |
421 | 0 | return false; |
422 | 0 | } |
423 | 0 | } |
424 | | |
425 | 0 | else |
426 | 0 | { |
427 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
428 | 0 | "Unrecognized GridCRS.GridType value '%s',\n" |
429 | 0 | "unable to process WCS coverage.", |
430 | 0 | pszGridType); |
431 | 0 | return false; |
432 | 0 | } |
433 | | |
434 | | /* -------------------------------------------------------------------- */ |
435 | | /* Search for an ImageCRS for raster size. */ |
436 | | /* -------------------------------------------------------------------- */ |
437 | 0 | std::vector<int> size; |
438 | 0 | CPLXMLNode *psNode; |
439 | |
|
440 | 0 | for (psNode = psSD->psChild; psNode != nullptr && size.size() == 0; |
441 | 0 | psNode = psNode->psNext) |
442 | 0 | { |
443 | 0 | if (psNode->eType != CXT_Element || |
444 | 0 | !EQUAL(psNode->pszValue, "BoundingBox")) |
445 | 0 | continue; |
446 | | |
447 | 0 | CPLString osBBCRS = ParseCRS(psNode); |
448 | 0 | if (strstr(osBBCRS, ":imageCRS")) |
449 | 0 | { |
450 | 0 | std::vector<std::string> bbox = ParseBoundingBox(psNode); |
451 | 0 | if (bbox.size() >= 2) |
452 | 0 | { |
453 | 0 | std::vector<int> low = Ilist(Split(bbox[0].c_str(), " "), 0, 2); |
454 | 0 | std::vector<int> high = |
455 | 0 | Ilist(Split(bbox[1].c_str(), " "), 0, 2); |
456 | 0 | if (low[0] == 0 && low[1] == 0) |
457 | 0 | { |
458 | 0 | size.push_back(high[0]); |
459 | 0 | size.push_back(high[1]); |
460 | 0 | } |
461 | 0 | } |
462 | 0 | } |
463 | 0 | } |
464 | | |
465 | | /* -------------------------------------------------------------------- */ |
466 | | /* Otherwise we search for a bounding box in our coordinate */ |
467 | | /* system and derive the size from that. */ |
468 | | /* -------------------------------------------------------------------- */ |
469 | 0 | for (psNode = psSD->psChild; psNode != nullptr && size.size() == 0; |
470 | 0 | psNode = psNode->psNext) |
471 | 0 | { |
472 | 0 | if (psNode->eType != CXT_Element || |
473 | 0 | !EQUAL(psNode->pszValue, "BoundingBox")) |
474 | 0 | continue; |
475 | | |
476 | 0 | CPLString osBBCRS = ParseCRS(psNode); |
477 | 0 | if (osBBCRS == osCRS) |
478 | 0 | { |
479 | 0 | std::vector<std::string> bbox = ParseBoundingBox(psNode); |
480 | 0 | bool not_rot = |
481 | 0 | (offsets[0].size() == 1 && offsets[1].size() == 1) || |
482 | 0 | ((swap && offsets[0][0] == 0.0 && offsets[1][1] == 0.0) || |
483 | 0 | (!swap && offsets[0][1] == 0.0 && offsets[1][0] == 0.0)); |
484 | 0 | if (bbox.size() >= 2 && not_rot) |
485 | 0 | { |
486 | 0 | std::vector<double> low = |
487 | 0 | Flist(Split(bbox[0].c_str(), " ", axis_order_swap), 0, 2); |
488 | 0 | std::vector<double> high = |
489 | 0 | Flist(Split(bbox[1].c_str(), " ", axis_order_swap), 0, 2); |
490 | 0 | double c1 = offsets[0][0]; |
491 | 0 | double c2 = |
492 | 0 | offsets[1].size() == 1 ? offsets[1][0] : offsets[1][1]; |
493 | 0 | size.push_back((int)((high[0] - low[0]) / c1 + 1.01)); |
494 | 0 | size.push_back((int)((high[1] - low[1]) / fabs(c2) + 1.01)); |
495 | 0 | } |
496 | 0 | } |
497 | 0 | } |
498 | |
|
499 | 0 | if (size.size() < 2) |
500 | 0 | { |
501 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
502 | 0 | "Could not determine the size of the grid."); |
503 | 0 | return false; |
504 | 0 | } |
505 | | |
506 | 0 | SetGeometry(size, origin, offsets); |
507 | | |
508 | | /* -------------------------------------------------------------------- */ |
509 | | /* Do we have a coordinate system override? */ |
510 | | /* -------------------------------------------------------------------- */ |
511 | 0 | const char *pszProjOverride = CPLGetXMLValue(psService, "SRS", nullptr); |
512 | |
|
513 | 0 | if (pszProjOverride) |
514 | 0 | { |
515 | 0 | if (m_oSRS.SetFromUserInput( |
516 | 0 | pszProjOverride, |
517 | 0 | OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) != |
518 | 0 | OGRERR_NONE) |
519 | 0 | { |
520 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
521 | 0 | "<SRS> element contents not parsable:\n%s", |
522 | 0 | pszProjOverride); |
523 | 0 | return false; |
524 | 0 | } |
525 | 0 | } |
526 | | |
527 | | /* -------------------------------------------------------------------- */ |
528 | | /* Pick a format type if we don't already have one selected. */ |
529 | | /* */ |
530 | | /* We will prefer anything that sounds like TIFF, otherwise */ |
531 | | /* falling back to the first supported format. Should we */ |
532 | | /* consider preferring the nativeFormat if available? */ |
533 | | /* -------------------------------------------------------------------- */ |
534 | 0 | if (CPLGetXMLValue(psService, "PreferredFormat", nullptr) == nullptr) |
535 | 0 | { |
536 | 0 | CPLString osPreferredFormat; |
537 | |
|
538 | 0 | for (psNode = psCO->psChild; psNode != nullptr; psNode = psNode->psNext) |
539 | 0 | { |
540 | 0 | if (psNode->eType == CXT_Element && |
541 | 0 | EQUAL(psNode->pszValue, "SupportedFormat") && psNode->psChild && |
542 | 0 | psNode->psChild->eType == CXT_Text) |
543 | 0 | { |
544 | 0 | if (osPreferredFormat.empty()) |
545 | 0 | osPreferredFormat = psNode->psChild->pszValue; |
546 | |
|
547 | 0 | if (strstr(psNode->psChild->pszValue, "tiff") != nullptr || |
548 | 0 | strstr(psNode->psChild->pszValue, "TIFF") != nullptr || |
549 | 0 | strstr(psNode->psChild->pszValue, "Tiff") != nullptr) |
550 | 0 | { |
551 | 0 | osPreferredFormat = psNode->psChild->pszValue; |
552 | 0 | break; |
553 | 0 | } |
554 | 0 | } |
555 | 0 | } |
556 | |
|
557 | 0 | if (!osPreferredFormat.empty()) |
558 | 0 | { |
559 | 0 | bServiceDirty = true; |
560 | 0 | CPLCreateXMLElementAndValue(psService, "PreferredFormat", |
561 | 0 | osPreferredFormat); |
562 | 0 | } |
563 | 0 | } |
564 | | |
565 | | /* -------------------------------------------------------------------- */ |
566 | | /* Try to identify a nodata value. For now we only support the */ |
567 | | /* singleValue mechanism. */ |
568 | | /* -------------------------------------------------------------------- */ |
569 | 0 | if (CPLGetXMLValue(psService, "NoDataValue", nullptr) == nullptr) |
570 | 0 | { |
571 | 0 | const char *pszSV = |
572 | 0 | CPLGetXMLValue(psCO, "Range.Field.NullValue", nullptr); |
573 | |
|
574 | 0 | if (pszSV != nullptr && (CPLAtof(pszSV) != 0.0 || *pszSV == DIGIT_ZERO)) |
575 | 0 | { |
576 | 0 | bServiceDirty = true; |
577 | 0 | CPLCreateXMLElementAndValue(psService, "NoDataValue", pszSV); |
578 | 0 | } |
579 | 0 | } |
580 | | |
581 | | /* -------------------------------------------------------------------- */ |
582 | | /* Grab the field name, if possible. */ |
583 | | /* -------------------------------------------------------------------- */ |
584 | 0 | if (CPLGetXMLValue(psService, "FieldName", nullptr) == nullptr) |
585 | 0 | { |
586 | 0 | CPLString osFieldName = |
587 | 0 | CPLGetXMLValue(psCO, "Range.Field.Identifier", ""); |
588 | |
|
589 | 0 | if (!osFieldName.empty()) |
590 | 0 | { |
591 | 0 | bServiceDirty = true; |
592 | 0 | CPLCreateXMLElementAndValue(psService, "FieldName", osFieldName); |
593 | 0 | } |
594 | 0 | else |
595 | 0 | { |
596 | 0 | CPLError( |
597 | 0 | CE_Failure, CPLE_AppDefined, |
598 | 0 | "Unable to find required Identifier name %s for Range Field.", |
599 | 0 | osCRS.c_str()); |
600 | 0 | return false; |
601 | 0 | } |
602 | 0 | } |
603 | | |
604 | | /* -------------------------------------------------------------------- */ |
605 | | /* Do we have a "Band" axis? If so try to grab the bandcount */ |
606 | | /* and data type from it. */ |
607 | | /* -------------------------------------------------------------------- */ |
608 | 0 | osBandIdentifier = CPLGetXMLValue(psService, "BandIdentifier", ""); |
609 | 0 | CPLXMLNode *psAxis = |
610 | 0 | CPLGetXMLNode(psService, "CoverageDescription.Range.Field.Axis"); |
611 | |
|
612 | 0 | if (osBandIdentifier.empty() && |
613 | 0 | (EQUAL(CPLGetXMLValue(psAxis, "Identifier", ""), "Band") || |
614 | 0 | EQUAL(CPLGetXMLValue(psAxis, "Identifier", ""), "Bands")) && |
615 | 0 | CPLGetXMLNode(psAxis, "AvailableKeys") != nullptr) |
616 | 0 | { |
617 | 0 | osBandIdentifier = CPLGetXMLValue(psAxis, "Identifier", ""); |
618 | | |
619 | | // verify keys are ascending starting at 1 |
620 | 0 | CPLXMLNode *psValues = CPLGetXMLNode(psAxis, "AvailableKeys"); |
621 | 0 | CPLXMLNode *psSV; |
622 | 0 | int iBand; |
623 | |
|
624 | 0 | for (psSV = psValues->psChild, iBand = 1; psSV != nullptr; |
625 | 0 | psSV = psSV->psNext, iBand++) |
626 | 0 | { |
627 | 0 | if (psSV->eType != CXT_Element || !EQUAL(psSV->pszValue, "Key") || |
628 | 0 | psSV->psChild == nullptr || psSV->psChild->eType != CXT_Text || |
629 | 0 | atoi(psSV->psChild->pszValue) != iBand) |
630 | 0 | { |
631 | 0 | osBandIdentifier = ""; |
632 | 0 | break; |
633 | 0 | } |
634 | 0 | } |
635 | |
|
636 | 0 | if (!osBandIdentifier.empty()) |
637 | 0 | { |
638 | 0 | if (CPLGetXMLValue(psService, "BandIdentifier", nullptr) == nullptr) |
639 | 0 | { |
640 | 0 | bServiceDirty = true; |
641 | 0 | CPLSetXMLValue(psService, "BandIdentifier", |
642 | 0 | osBandIdentifier.c_str()); |
643 | 0 | } |
644 | |
|
645 | 0 | if (CPLGetXMLValue(psService, "BandCount", nullptr) == nullptr) |
646 | 0 | { |
647 | 0 | bServiceDirty = true; |
648 | 0 | CPLSetXMLValue(psService, "BandCount", |
649 | 0 | CPLString().Printf("%d", iBand - 1)); |
650 | 0 | } |
651 | 0 | } |
652 | | |
653 | | // Is this an ESRI server returning a GDAL recognised data type? |
654 | 0 | CPLString osDataType = CPLGetXMLValue(psAxis, "DataType", ""); |
655 | 0 | if (GDALGetDataTypeByName(osDataType) != GDT_Unknown && |
656 | 0 | CPLGetXMLValue(psService, "BandType", nullptr) == nullptr) |
657 | 0 | { |
658 | 0 | bServiceDirty = true; |
659 | 0 | CPLCreateXMLElementAndValue(psService, "BandType", osDataType); |
660 | 0 | } |
661 | 0 | } |
662 | |
|
663 | 0 | return true; |
664 | 0 | } |
665 | | |
666 | | /************************************************************************/ |
667 | | /* ParseCapabilities() */ |
668 | | /************************************************************************/ |
669 | | |
670 | | CPLErr WCSDataset110::ParseCapabilities(CPLXMLNode *Capabilities, |
671 | | const std::string &url) |
672 | 0 | { |
673 | 0 | CPLStripXMLNamespace(Capabilities, nullptr, TRUE); |
674 | | |
675 | | // make sure this is a capabilities document |
676 | 0 | if (strcmp(Capabilities->pszValue, "Capabilities") != 0) |
677 | 0 | { |
678 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
679 | 0 | "Error in capabilities document.\n"); |
680 | 0 | return CE_Failure; |
681 | 0 | } |
682 | | |
683 | 0 | char **metadata = nullptr; |
684 | 0 | std::string path = "WCS_GLOBAL#"; |
685 | |
|
686 | 0 | CPLString key = path + "version"; |
687 | 0 | metadata = CSLSetNameValue(metadata, key, Version()); |
688 | |
|
689 | 0 | for (CPLXMLNode *node = Capabilities->psChild; node != nullptr; |
690 | 0 | node = node->psNext) |
691 | 0 | { |
692 | 0 | const char *attr = node->pszValue; |
693 | 0 | if (node->eType == CXT_Attribute && EQUAL(attr, "updateSequence")) |
694 | 0 | { |
695 | 0 | key = path + "updateSequence"; |
696 | 0 | CPLString value = CPLGetXMLValue(node, nullptr, ""); |
697 | 0 | metadata = CSLSetNameValue(metadata, key, value); |
698 | 0 | } |
699 | 0 | } |
700 | | |
701 | | // identification metadata |
702 | 0 | std::string path2 = path; |
703 | 0 | CPLXMLNode *service = AddSimpleMetaData( |
704 | 0 | &metadata, Capabilities, path2, "ServiceIdentification", |
705 | 0 | {"Title", "Abstract", "Fees", "AccessConstraints"}); |
706 | 0 | CPLString kw = GetKeywords(service, "Keywords", "Keyword"); |
707 | 0 | if (kw != "") |
708 | 0 | { |
709 | 0 | CPLString name = path + "Keywords"; |
710 | 0 | metadata = CSLSetNameValue(metadata, name, kw); |
711 | 0 | } |
712 | 0 | CPLString profiles = GetKeywords(service, "", "Profile"); |
713 | 0 | if (profiles != "") |
714 | 0 | { |
715 | 0 | CPLString name = path + "Profiles"; |
716 | 0 | metadata = CSLSetNameValue(metadata, name, profiles); |
717 | 0 | } |
718 | | |
719 | | // provider metadata |
720 | 0 | path2 = path; |
721 | 0 | CPLXMLNode *provider = AddSimpleMetaData( |
722 | 0 | &metadata, Capabilities, path2, "ServiceProvider", {"ProviderName"}); |
723 | 0 | if (provider) |
724 | 0 | { |
725 | 0 | CPLXMLNode *site = CPLGetXMLNode(provider, "ProviderSite"); |
726 | 0 | if (site) |
727 | 0 | { |
728 | 0 | std::string path3 = path2 + "ProviderSite"; |
729 | 0 | CPLString value = |
730 | 0 | CPLGetXMLValue(CPLGetXMLNode(site, "href"), nullptr, ""); |
731 | 0 | metadata = CSLSetNameValue(metadata, path3.c_str(), value); |
732 | 0 | } |
733 | 0 | std::string path3 = std::move(path2); |
734 | 0 | CPLXMLNode *contact = |
735 | 0 | AddSimpleMetaData(&metadata, provider, path3, "ServiceContact", |
736 | 0 | {"IndividualName", "PositionName", "Role"}); |
737 | 0 | if (contact) |
738 | 0 | { |
739 | 0 | std::string path4 = std::move(path3); |
740 | 0 | CPLXMLNode *info = |
741 | 0 | AddSimpleMetaData(&metadata, contact, path4, "ContactInfo", |
742 | 0 | {"HoursOfService", "ContactInstructions"}); |
743 | 0 | if (info) |
744 | 0 | { |
745 | 0 | std::string path5 = path4; |
746 | 0 | std::string path6 = path4; |
747 | 0 | AddSimpleMetaData(&metadata, info, path5, "Address", |
748 | 0 | {"DeliveryPoint", "City", |
749 | 0 | "AdministrativeArea", "PostalCode", |
750 | 0 | "Country", "ElectronicMailAddress"}); |
751 | 0 | AddSimpleMetaData(&metadata, info, path6, "Phone", |
752 | 0 | {"Voice", "Facsimile"}); |
753 | 0 | CPL_IGNORE_RET_VAL(path4); |
754 | 0 | } |
755 | 0 | } |
756 | 0 | } |
757 | | |
758 | | // operations metadata |
759 | 0 | CPLString DescribeCoverageURL = ""; |
760 | 0 | CPLXMLNode *service2 = CPLGetXMLNode(Capabilities, "OperationsMetadata"); |
761 | 0 | if (service2) |
762 | 0 | { |
763 | 0 | for (CPLXMLNode *operation = service2->psChild; operation != nullptr; |
764 | 0 | operation = operation->psNext) |
765 | 0 | { |
766 | 0 | if (operation->eType != CXT_Element || |
767 | 0 | !EQUAL(operation->pszValue, "Operation")) |
768 | 0 | { |
769 | 0 | continue; |
770 | 0 | } |
771 | 0 | if (EQUAL(CPLGetXMLValue(CPLGetXMLNode(operation, "name"), nullptr, |
772 | 0 | ""), |
773 | 0 | "DescribeCoverage")) |
774 | 0 | { |
775 | 0 | DescribeCoverageURL = CPLGetXMLValue( |
776 | 0 | CPLGetXMLNode(CPLSearchXMLNode(operation, "Get"), "href"), |
777 | 0 | nullptr, ""); |
778 | 0 | } |
779 | 0 | } |
780 | 0 | } |
781 | | // if DescribeCoverageURL looks wrong, we change it |
782 | 0 | if (DescribeCoverageURL.find("localhost") != std::string::npos) |
783 | 0 | { |
784 | 0 | DescribeCoverageURL = URLRemoveKey(url.c_str(), "request"); |
785 | 0 | } |
786 | | |
787 | | // service metadata (in 2.0) |
788 | 0 | CPLString ext = "ServiceMetadata"; |
789 | 0 | CPLString formats = GetKeywords(Capabilities, ext, "formatSupported"); |
790 | 0 | if (formats != "") |
791 | 0 | { |
792 | 0 | CPLString name = path + "formatSupported"; |
793 | 0 | metadata = CSLSetNameValue(metadata, name, formats); |
794 | 0 | } |
795 | | // wcs:Extensions: interpolation, CRS, others? |
796 | 0 | ext += ".Extension"; |
797 | 0 | CPLString interpolation = |
798 | 0 | GetKeywords(Capabilities, ext, "interpolationSupported"); |
799 | 0 | if (interpolation == "") |
800 | 0 | { |
801 | 0 | interpolation = |
802 | 0 | GetKeywords(Capabilities, ext + ".InterpolationMetadata", |
803 | 0 | "InterpolationSupported"); |
804 | 0 | } |
805 | 0 | if (interpolation != "") |
806 | 0 | { |
807 | 0 | CPLString name = path + "InterpolationSupported"; |
808 | 0 | metadata = CSLSetNameValue(metadata, name, interpolation); |
809 | 0 | } |
810 | 0 | CPLString crs = GetKeywords(Capabilities, ext, "crsSupported"); |
811 | 0 | if (crs == "") |
812 | 0 | { |
813 | 0 | crs = GetKeywords(Capabilities, ext + ".CrsMetadata", "crsSupported"); |
814 | 0 | } |
815 | 0 | if (crs != "") |
816 | 0 | { |
817 | 0 | CPLString name = path + "crsSupported"; |
818 | 0 | metadata = CSLSetNameValue(metadata, name, crs); |
819 | 0 | } |
820 | |
|
821 | 0 | this->SetMetadata(metadata, ""); |
822 | 0 | CSLDestroy(metadata); |
823 | 0 | metadata = nullptr; |
824 | | |
825 | | // contents metadata |
826 | 0 | CPLXMLNode *contents = CPLGetXMLNode(Capabilities, "Contents"); |
827 | 0 | if (contents) |
828 | 0 | { |
829 | 0 | int index = 1; |
830 | 0 | for (CPLXMLNode *summary = contents->psChild; summary != nullptr; |
831 | 0 | summary = summary->psNext) |
832 | 0 | { |
833 | 0 | if (summary->eType != CXT_Element || |
834 | 0 | !EQUAL(summary->pszValue, "CoverageSummary")) |
835 | 0 | { |
836 | 0 | continue; |
837 | 0 | } |
838 | 0 | CPLString path3; |
839 | 0 | path3.Printf("SUBDATASET_%d_", index); |
840 | 0 | index += 1; |
841 | | |
842 | | // the name and description of the subdataset: |
843 | | // GDAL Data Model: |
844 | | // The value of the _NAME is a string that can be passed to |
845 | | // GDALOpen() to access the file. |
846 | |
|
847 | 0 | CPLString key2 = path3 + "NAME"; |
848 | |
|
849 | 0 | CPLString name = DescribeCoverageURL; |
850 | 0 | name = CPLURLAddKVP(name, "version", this->Version()); |
851 | |
|
852 | 0 | CPLXMLNode *node = CPLGetXMLNode(summary, "CoverageId"); |
853 | 0 | std::string id; |
854 | 0 | if (node) |
855 | 0 | { |
856 | 0 | id = CPLGetXMLValue(node, nullptr, ""); |
857 | 0 | } |
858 | 0 | else |
859 | 0 | { |
860 | 0 | node = CPLGetXMLNode(summary, "Identifier"); |
861 | 0 | if (node) |
862 | 0 | { |
863 | 0 | id = CPLGetXMLValue(node, nullptr, ""); |
864 | 0 | } |
865 | 0 | else |
866 | 0 | { |
867 | | // todo: maybe not an error since CoverageSummary may be |
868 | | // within CoverageSummary (07-067r5 Fig4) |
869 | 0 | CSLDestroy(metadata); |
870 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
871 | 0 | "Error in capabilities document.\n"); |
872 | 0 | return CE_Failure; |
873 | 0 | } |
874 | 0 | } |
875 | 0 | name = CPLURLAddKVP(name, "coverage", id.c_str()); |
876 | 0 | name = "WCS:" + name; |
877 | 0 | metadata = CSLSetNameValue(metadata, key2, name); |
878 | |
|
879 | 0 | key2 = path3 + "DESC"; |
880 | |
|
881 | 0 | node = CPLGetXMLNode(summary, "Title"); |
882 | 0 | if (node) |
883 | 0 | { |
884 | 0 | metadata = CSLSetNameValue(metadata, key2, |
885 | 0 | CPLGetXMLValue(node, nullptr, "")); |
886 | 0 | } |
887 | 0 | else |
888 | 0 | { |
889 | 0 | metadata = CSLSetNameValue(metadata, key2, id.c_str()); |
890 | 0 | } |
891 | | |
892 | | // todo: compose global bounding box from WGS84BoundingBox and |
893 | | // BoundingBox |
894 | | |
895 | | // further subdataset (coverage) parameters are parsed in |
896 | | // ParseCoverageCapabilities |
897 | 0 | } |
898 | 0 | } |
899 | 0 | this->SetMetadata(metadata, "SUBDATASETS"); |
900 | 0 | CSLDestroy(metadata); |
901 | 0 | return CE_None; |
902 | 0 | } |
903 | | |
904 | | void WCSDataset110::ParseCoverageCapabilities(CPLXMLNode *capabilities, |
905 | | const std::string &coverage, |
906 | | CPLXMLNode *metadata) |
907 | 0 | { |
908 | 0 | CPLStripXMLNamespace(capabilities, nullptr, TRUE); |
909 | 0 | CPLXMLNode *contents = CPLGetXMLNode(capabilities, "Contents"); |
910 | 0 | if (contents) |
911 | 0 | { |
912 | 0 | for (CPLXMLNode *summary = contents->psChild; summary != nullptr; |
913 | 0 | summary = summary->psNext) |
914 | 0 | { |
915 | 0 | if (summary->eType != CXT_Element || |
916 | 0 | !EQUAL(summary->pszValue, "CoverageSummary")) |
917 | 0 | { |
918 | 0 | continue; |
919 | 0 | } |
920 | 0 | CPLXMLNode *node = CPLGetXMLNode(summary, "CoverageId"); |
921 | 0 | CPLString id; |
922 | 0 | if (node) |
923 | 0 | { |
924 | 0 | id = CPLGetXMLValue(node, nullptr, ""); |
925 | 0 | } |
926 | 0 | else |
927 | 0 | { |
928 | 0 | node = CPLGetXMLNode(summary, "Identifier"); |
929 | 0 | if (node) |
930 | 0 | { |
931 | 0 | id = CPLGetXMLValue(node, nullptr, ""); |
932 | 0 | } |
933 | 0 | else |
934 | 0 | { |
935 | 0 | id = ""; |
936 | 0 | } |
937 | 0 | } |
938 | 0 | if (id != coverage) |
939 | 0 | { |
940 | 0 | continue; |
941 | 0 | } |
942 | | |
943 | | // Description |
944 | | // todo: there could be Title and Abstract for each supported |
945 | | // language |
946 | 0 | XMLCopyMetadata(summary, metadata, "Title"); |
947 | 0 | XMLCopyMetadata(summary, metadata, "Abstract"); |
948 | | |
949 | | // 2.0.1 stuff |
950 | 0 | XMLCopyMetadata(summary, metadata, "CoverageSubtype"); |
951 | | |
952 | | // Keywords |
953 | 0 | CPLString kw = GetKeywords(summary, "Keywords", "Keyword"); |
954 | 0 | CPLAddXMLAttributeAndValue( |
955 | 0 | CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key", |
956 | 0 | "Keywords"); |
957 | | |
958 | | // WCSContents |
959 | 0 | const char *tags[] = {"SupportedCRS", "SupportedFormat", |
960 | 0 | "OtherSource"}; |
961 | 0 | for (unsigned int i = 0; i < CPL_ARRAYSIZE(tags); i++) |
962 | 0 | { |
963 | 0 | kw = GetKeywords(summary, "", tags[i]); |
964 | 0 | CPLAddXMLAttributeAndValue( |
965 | 0 | CPLCreateXMLElementAndValue(metadata, "MDI", kw), "key", |
966 | 0 | tags[i]); |
967 | 0 | } |
968 | | |
969 | | // skipping WGS84BoundingBox, BoundingBox, Metadata, Extension |
970 | | // since those we'll get from coverage description |
971 | 0 | } |
972 | 0 | } |
973 | 0 | } |