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