/src/gdal/frmts/wms/minidriver_mrf.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: WMS Client Mini Driver |
4 | | * Purpose: Implementation of Dataset and RasterBand classes for WMS |
5 | | * and other similar services. |
6 | | * Author: Lucian Plesea |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 2016, Lucian Plesea |
10 | | * |
11 | | * Copyright 2016 Esri |
12 | | * |
13 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
14 | | * you may not use this file except in compliance with the License. |
15 | | * You may obtain a copy of the License at |
16 | | * |
17 | | * http://www.apache.org/licenses/LICENSE-2.0 |
18 | | * |
19 | | * Unless required by applicable law or agreed to in writing, software |
20 | | * distributed under the License is distributed on an "AS IS" BASIS, |
21 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
22 | | * See the License for the specific language governing permissions and |
23 | | * limitations under the License. |
24 | | ****************************************************************************/ |
25 | | |
26 | | /* |
27 | | A WMS style minidriver that allows an MRF or an Esri bundle to be read from a |
28 | | URL, using one range request per tile All parameters have to be defined in the |
29 | | WMS file, especially for the MRF, so only simple MRF files work. For a bundle, |
30 | | the size is assumed to be 128 tiles of 256 pixels each, which is the standard |
31 | | size. |
32 | | */ |
33 | | |
34 | | #include "wmsdriver.h" |
35 | | #include "minidriver_mrf.h" |
36 | | |
37 | | using namespace WMSMiniDriver_MRF_ns; |
38 | | |
39 | | // Copied from frmts/mrf |
40 | | |
41 | | // A tile index record, 16 bytes, big endian |
42 | | typedef struct |
43 | | { |
44 | | GIntBig offset; |
45 | | GIntBig size; |
46 | | } MRFIdx; |
47 | | |
48 | | // Number of pages of size psz needed to hold n elements |
49 | | static inline int pcount(const int n, const int sz) |
50 | 0 | { |
51 | 0 | return 1 + (n - 1) / sz; |
52 | 0 | } |
53 | | |
54 | | // Returns a pagecount per dimension, .l will have the total number |
55 | | static inline const ILSize pcount(const ILSize &size, const ILSize &psz) |
56 | 0 | { |
57 | 0 | ILSize count; |
58 | 0 | count.x = pcount(size.x, psz.x); |
59 | 0 | count.y = pcount(size.y, psz.y); |
60 | 0 | count.z = pcount(size.z, psz.z); |
61 | 0 | count.c = pcount(size.c, psz.c); |
62 | 0 | count.l = static_cast<GIntBig>(count.x) * count.y * count.z * count.c; |
63 | 0 | return count; |
64 | 0 | } |
65 | | |
66 | | // End copied from frmts/mrf |
67 | | |
68 | | // pread_t adapter for VSIL |
69 | | static size_t pread_VSIL(void *user_data, void *buff, size_t count, |
70 | | off_t offset) |
71 | 0 | { |
72 | 0 | VSILFILE *fp = reinterpret_cast<VSILFILE *>(user_data); |
73 | 0 | VSIFSeekL(fp, offset, SEEK_SET); |
74 | 0 | return VSIFReadL(buff, 1, count, fp); |
75 | 0 | } |
76 | | |
77 | | // pread_t adapter for curl. We use the multi interface to get the same options |
78 | | static size_t pread_curl(void *user_data, void *buff, size_t count, |
79 | | off_t offset) |
80 | 0 | { |
81 | | // Use a copy of the provided request, which has the options and the URL |
82 | | // preset |
83 | 0 | WMSHTTPRequest &request = *(reinterpret_cast<WMSHTTPRequest *>(user_data)); |
84 | 0 | request.Range.Printf(CPL_FRMT_GUIB "-" CPL_FRMT_GUIB, |
85 | 0 | static_cast<GUIntBig>(offset), |
86 | 0 | static_cast<GUIntBig>(offset + count - 1)); |
87 | 0 | WMSHTTPInitializeRequest(&request); |
88 | 0 | if (WMSHTTPFetchMulti(&request) != CE_None) |
89 | 0 | { |
90 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
91 | 0 | "GDALWMS_MRF: failed to retrieve index data"); |
92 | 0 | return 0; |
93 | 0 | } |
94 | | |
95 | 0 | int success = (request.nStatus == 200) || |
96 | 0 | (!request.Range.empty() && request.nStatus == 206); |
97 | 0 | if (!success || request.pabyData == nullptr || request.nDataLen == 0) |
98 | 0 | { |
99 | 0 | CPLError(CE_Failure, CPLE_HttpResponse, |
100 | 0 | "GDALWMS: Unable to download data from %s", |
101 | 0 | request.URL.c_str()); |
102 | 0 | return 0; // Error flag |
103 | 0 | } |
104 | | |
105 | | // Might get less data than requested |
106 | 0 | if (request.nDataLen < count) |
107 | 0 | memset(buff, 0, count); |
108 | 0 | memcpy(buff, request.pabyData, request.nDataLen); |
109 | 0 | return request.nDataLen; |
110 | 0 | } |
111 | | |
112 | | SectorCache::SectorCache(void *user_data, pread_t fn, unsigned int size, |
113 | | unsigned int count) |
114 | 0 | : n(count + 2), m(size), reader(fn ? fn : pread_VSIL), |
115 | 0 | reader_data(user_data), last_used(nullptr) |
116 | 0 | { |
117 | 0 | } |
118 | | |
119 | | // Returns an in-memory offset to the byte at the given address, within a sector |
120 | | // Returns NULL if the sector can't be read |
121 | | void *SectorCache::data(size_t address) |
122 | 0 | { |
123 | 0 | for (size_t i = 0; i < store.size(); i++) |
124 | 0 | { |
125 | 0 | if (store[i].uid == address / m) |
126 | 0 | { |
127 | 0 | last_used = &store[i]; |
128 | 0 | return &(last_used->range[address % m]); |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | | // Not found, need a target sector to replace |
133 | 0 | Sector *target; |
134 | 0 | if (store.size() < m) |
135 | 0 | { // Create a new sector if there are slots available |
136 | 0 | store.resize(store.size() + 1); |
137 | 0 | target = &store.back(); |
138 | 0 | } |
139 | 0 | else |
140 | 0 | { // Choose a random one to replace, but not the last used, to avoid |
141 | | // thrashing |
142 | 0 | do |
143 | 0 | { |
144 | 0 | #ifndef __COVERITY__ |
145 | 0 | target = &(store[rand() % n]); |
146 | | #else |
147 | | target = &(store[0]); |
148 | | #endif |
149 | 0 | } while (target == last_used); |
150 | 0 | } |
151 | |
|
152 | 0 | target->range.resize(m); |
153 | 0 | if (reader(reader_data, &target->range[0], m, |
154 | 0 | static_cast<off_t>((address / m) * m))) |
155 | 0 | { // Success |
156 | 0 | target->uid = address / m; |
157 | 0 | last_used = target; |
158 | 0 | return &(last_used->range[address % m]); |
159 | 0 | } |
160 | | |
161 | | // Failure |
162 | | // If this is the last sector, it could be a new sector with invalid data, |
163 | | // so we remove it Otherwise, the previous content is still good |
164 | 0 | if (target == &store.back()) |
165 | 0 | store.pop_back(); |
166 | | // Signal invalid request |
167 | 0 | return nullptr; |
168 | 0 | } |
169 | | |
170 | | // Keep in sync with the type enum |
171 | | static const int ir_size[WMSMiniDriver_MRF::tEND] = {16, 8}; |
172 | | |
173 | | WMSMiniDriver_MRF::WMSMiniDriver_MRF() |
174 | 0 | : m_type(tMRF), fp(nullptr), m_request(nullptr), index_cache(nullptr) |
175 | 0 | { |
176 | 0 | } |
177 | | |
178 | | WMSMiniDriver_MRF::~WMSMiniDriver_MRF() |
179 | 0 | { |
180 | 0 | if (index_cache) |
181 | 0 | delete index_cache; |
182 | 0 | if (fp) |
183 | 0 | VSIFCloseL(fp); |
184 | 0 | delete m_request; |
185 | 0 | } |
186 | | |
187 | | CPLErr WMSMiniDriver_MRF::Initialize(CPLXMLNode *config, |
188 | | CPL_UNUSED char **papszOpenOptions) |
189 | 0 | { |
190 | | // This gets called before the rest of the WMS driver gets initialized |
191 | | // The MRF reader only works if all datawindow is defined within the WMS |
192 | | // file |
193 | |
|
194 | 0 | m_base_url = CPLGetXMLValue(config, "ServerURL", ""); |
195 | 0 | if (m_base_url.empty()) |
196 | 0 | { |
197 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
198 | 0 | "GDALWMS, MRF: ServerURL missing."); |
199 | 0 | return CE_Failure; |
200 | 0 | } |
201 | | |
202 | | // Index file location, in case it is different from the normal file name |
203 | 0 | m_idxname = CPLGetXMLValue(config, "index", ""); |
204 | |
|
205 | 0 | CPLString osType(CPLGetXMLValue(config, "type", "")); |
206 | |
|
207 | 0 | if (EQUAL(osType, "bundle")) |
208 | 0 | m_type = tBundle; |
209 | |
|
210 | 0 | if (m_type == tBundle) |
211 | 0 | { |
212 | 0 | m_parent_dataset->WMSSetDefaultOverviewCount(0); |
213 | 0 | m_parent_dataset->WMSSetDefaultTileCount(128, 128); |
214 | 0 | m_parent_dataset->WMSSetDefaultBlockSize(256, 256); |
215 | 0 | m_parent_dataset->WMSSetDefaultTileLevel(0); |
216 | 0 | m_parent_dataset->WMSSetNeedsDataWindow(FALSE); |
217 | 0 | offsets.push_back(64); |
218 | 0 | } |
219 | 0 | else |
220 | 0 | { // MRF |
221 | 0 | offsets.push_back(0); |
222 | 0 | } |
223 | |
|
224 | 0 | return CE_None; |
225 | 0 | } |
226 | | |
227 | | // Test for URL, things that curl can deal with while doing a range request |
228 | | // http and https should work, not sure about ftp or file |
229 | | int inline static is_url(const CPLString &value) |
230 | 0 | { |
231 | 0 | return (value.ifind("http://") == 0 || value.ifind("https://") == 0 || |
232 | 0 | value.ifind("ftp://") == 0 || value.ifind("file://") == 0); |
233 | 0 | } |
234 | | |
235 | | // Called after the dataset is initialized by the main WMS driver |
236 | | CPLErr WMSMiniDriver_MRF::EndInit() |
237 | 0 | { |
238 | 0 | int index_is_url = 1; |
239 | 0 | if (!m_idxname.empty()) |
240 | 0 | { // Provided, could be path or URL |
241 | 0 | if (!is_url(m_idxname)) |
242 | 0 | { |
243 | 0 | index_is_url = 0; |
244 | 0 | fp = VSIFOpenL(m_idxname, "rb"); |
245 | 0 | if (fp == nullptr) |
246 | 0 | { |
247 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Can't open index file %s", |
248 | 0 | m_idxname.c_str()); |
249 | 0 | return CE_Failure; |
250 | 0 | } |
251 | 0 | index_cache = new SectorCache(fp); |
252 | 0 | } |
253 | 0 | } |
254 | 0 | else |
255 | 0 | { // Not provided, change extension to .idx if we can, otherwise use the |
256 | | // same file |
257 | 0 | m_idxname = m_base_url; |
258 | 0 | } |
259 | | |
260 | 0 | if (index_is_url) |
261 | 0 | { // prepare a WMS request, the pread_curl will execute it repeatedly |
262 | 0 | m_request = new WMSHTTPRequest(); |
263 | 0 | m_request->URL = m_idxname; |
264 | 0 | m_request->options = m_parent_dataset->GetHTTPRequestOpts(); |
265 | 0 | index_cache = new SectorCache(m_request, pread_curl); |
266 | 0 | } |
267 | | |
268 | | // Set the level index offsets, assume MRF order since esri bundles don't |
269 | | // have overviews |
270 | 0 | ILSize size( |
271 | 0 | m_parent_dataset->GetRasterXSize(), m_parent_dataset->GetRasterYSize(), |
272 | 0 | 1, // Single slice for now |
273 | 0 | 1, // Ignore the c, only single or interleved data supported by WMS |
274 | 0 | m_parent_dataset->GetRasterBand(1)->GetOverviewCount()); |
275 | |
|
276 | 0 | int psx, psy; |
277 | 0 | m_parent_dataset->GetRasterBand(1)->GetBlockSize(&psx, &psy); |
278 | 0 | ILSize pagesize(psx, psy, 1, 1, 1); |
279 | |
|
280 | 0 | if (m_type == tBundle) |
281 | 0 | { // A bundle contains 128x128 pages, regadless of the raster size |
282 | 0 | size.x = psx * 128; |
283 | 0 | size.y = psy * 128; |
284 | 0 | } |
285 | |
|
286 | 0 | for (GIntBig l = size.l; l >= 0; l--) |
287 | 0 | { |
288 | 0 | ILSize pagecount = pcount(size, pagesize); |
289 | 0 | pages.push_back(pagecount); |
290 | 0 | if (l > 0) // Only for existing levels |
291 | 0 | offsets.push_back(offsets.back() + ir_size[m_type] * pagecount.l); |
292 | | |
293 | | // Sometimes this may be a 3 |
294 | 0 | size.x = pcount(size.x, 2); |
295 | 0 | size.y = pcount(size.y, 2); |
296 | 0 | } |
297 | |
|
298 | 0 | return CE_None; |
299 | 0 | } |
300 | | |
301 | | // Return -1 if error occurs |
302 | | size_t WMSMiniDriver_MRF::GetIndexAddress( |
303 | | const GDALWMSTiledImageRequestInfo &tiri) const |
304 | 0 | { |
305 | | // Bottom level is 0 |
306 | 0 | int l = -tiri.m_level; |
307 | 0 | if (l < 0 || l >= static_cast<int>(offsets.size())) |
308 | 0 | return ~static_cast<size_t>(0); // Indexing error |
309 | 0 | if (tiri.m_x >= pages[l].x || tiri.m_y >= pages[l].y) |
310 | 0 | return ~static_cast<size_t>(0); |
311 | 0 | return static_cast<size_t>(offsets[l] + (pages[l].x * tiri.m_y + tiri.m_x) * |
312 | 0 | ir_size[m_type]); |
313 | 0 | } |
314 | | |
315 | | // Signal errors and return error message |
316 | | CPLErr WMSMiniDriver_MRF::TiledImageRequest( |
317 | | WMSHTTPRequest &request, CPL_UNUSED const GDALWMSImageRequestInfo &iri, |
318 | | const GDALWMSTiledImageRequestInfo &tiri) |
319 | 0 | { |
320 | 0 | CPLString &url = request.URL; |
321 | 0 | url = m_base_url; |
322 | |
|
323 | 0 | size_t offset = GetIndexAddress(tiri); |
324 | 0 | if (offset == static_cast<size_t>(-1)) |
325 | 0 | { |
326 | 0 | request.Error = "Invalid level requested"; |
327 | 0 | return CE_Failure; |
328 | 0 | } |
329 | | |
330 | 0 | void *raw_index = index_cache->data(offset); |
331 | 0 | if (raw_index == nullptr) |
332 | 0 | { |
333 | 0 | request.Error = "Invalid indexing"; |
334 | 0 | return CE_Failure; |
335 | 0 | }; |
336 | | |
337 | | // Store the tile size and offset in this structure |
338 | 0 | MRFIdx idx; |
339 | |
|
340 | 0 | if (m_type == tMRF) |
341 | 0 | { |
342 | 0 | memcpy(&idx, raw_index, sizeof(idx)); |
343 | |
|
344 | 0 | #if defined(CPL_LSB) // raw index is MSB |
345 | 0 | idx.offset = CPL_SWAP64(idx.offset); |
346 | 0 | idx.size = CPL_SWAP64(idx.size); |
347 | 0 | #endif |
348 | 0 | } |
349 | 0 | else |
350 | 0 | { // Bundle |
351 | 0 | GIntBig bidx; |
352 | 0 | memcpy(&bidx, raw_index, sizeof(bidx)); |
353 | |
|
354 | | #if defined(CPL_MSB) // bundle index is LSB |
355 | | bidx = CPL_SWAP64(bidx); |
356 | | #endif |
357 | |
|
358 | 0 | idx.offset = bidx & ((1ULL << 40) - 1); |
359 | 0 | idx.size = bidx >> 40; |
360 | 0 | } |
361 | | |
362 | | // Set the range or flag it as missing |
363 | 0 | if (idx.size == 0) |
364 | 0 | request.Range = |
365 | 0 | "none"; // Signal that this block doesn't exist server-side |
366 | 0 | else |
367 | 0 | request.Range.Printf(CPL_FRMT_GUIB "-" CPL_FRMT_GUIB, idx.offset, |
368 | 0 | idx.offset + idx.size - 1); |
369 | |
|
370 | 0 | return CE_None; |
371 | 0 | } |