LCOV - code coverage report
Current view: top level - frmts/wms - minidriver_mrf.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 0 147 0.0 %
Date: 2025-07-13 09:04:35 Functions: 0 14 0.0 %

          Line data    Source code
       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           0 : static inline int pcount(const int n, const int sz)
      50             : {
      51           0 :     return 1 + (n - 1) / sz;
      52             : }
      53             : 
      54             : // Returns a pagecount per dimension, .l will have the total number
      55           0 : static inline const ILSize pcount(const ILSize &size, const ILSize &psz)
      56             : {
      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             : }
      65             : 
      66             : // End copied from frmts/mrf
      67             : 
      68             : // pread_t adapter for VSIL
      69           0 : static size_t pread_VSIL(void *user_data, void *buff, size_t count,
      70             :                          off_t offset)
      71             : {
      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             : }
      76             : 
      77             : // pread_t adapter for curl.  We use the multi interface to get the same options
      78           0 : static size_t pread_curl(void *user_data, void *buff, size_t count,
      79             :                          off_t offset)
      80             : {
      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             :     request.Range.Printf(CPL_FRMT_GUIB "-" CPL_FRMT_GUIB,
      85             :                          static_cast<GUIntBig>(offset),
      86           0 :                          static_cast<GUIntBig>(offset + count - 1));
      87           0 :     WMSHTTPInitializeRequest(&request);
      88           0 :     if (WMSHTTPFetchMulti(&request) != CE_None)
      89             :     {
      90           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      91             :                  "GDALWMS_MRF: failed to retrieve index data");
      92           0 :         return 0;
      93             :     }
      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             :     {
      99           0 :         CPLError(CE_Failure, CPLE_HttpResponse,
     100             :                  "GDALWMS: Unable to download data from %s",
     101             :                  request.URL.c_str());
     102           0 :         return 0;  // Error flag
     103             :     }
     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             : }
     111             : 
     112           0 : SectorCache::SectorCache(void *user_data, pread_t fn, unsigned int size,
     113           0 :                          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             : {
     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           0 : void *SectorCache::data(size_t address)
     122             : {
     123           0 :     for (size_t i = 0; i < store.size(); i++)
     124             :     {
     125           0 :         if (store[i].uid == address / m)
     126             :         {
     127           0 :             last_used = &store[i];
     128           0 :             return &(last_used->range[address % m]);
     129             :         }
     130             :     }
     131             : 
     132             :     // Not found, need a target sector to replace
     133             :     Sector *target;
     134           0 :     if (store.size() < m)
     135             :     {  // Create a new sector if there are slots available
     136           0 :         store.resize(store.size() + 1);
     137           0 :         target = &store.back();
     138             :     }
     139             :     else
     140             :     {  // Choose a random one to replace, but not the last used, to avoid
     141             :         // thrashing
     142           0 :         do
     143             :         {
     144             : #ifndef __COVERITY__
     145           0 :             target = &(store[rand() % n]);
     146             : #else
     147             :             target = &(store[0]);
     148             : #endif
     149           0 :         } while (target == last_used);
     150             :     }
     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             :     {  // Success
     156           0 :         target->uid = address / m;
     157           0 :         last_used = target;
     158           0 :         return &(last_used->range[address % m]);
     159             :     }
     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             : }
     169             : 
     170             : // Keep in sync with the type enum
     171             : static const int ir_size[WMSMiniDriver_MRF::tEND] = {16, 8};
     172             : 
     173           0 : WMSMiniDriver_MRF::WMSMiniDriver_MRF()
     174           0 :     : m_type(tMRF), fp(nullptr), m_request(nullptr), index_cache(nullptr)
     175             : {
     176           0 : }
     177             : 
     178           0 : WMSMiniDriver_MRF::~WMSMiniDriver_MRF()
     179             : {
     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           0 : CPLErr WMSMiniDriver_MRF::Initialize(CPLXMLNode *config,
     188             :                                      CPL_UNUSED char **papszOpenOptions)
     189             : {
     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             :     {
     197           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     198             :                  "GDALWMS, MRF: ServerURL missing.");
     199           0 :         return CE_Failure;
     200             :     }
     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             :     {
     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             :     }
     219             :     else
     220             :     {  // MRF
     221           0 :         offsets.push_back(0);
     222             :     }
     223             : 
     224           0 :     return CE_None;
     225             : }
     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           0 : int inline static is_url(const CPLString &value)
     230             : {
     231           0 :     return (value.ifind("http://") == 0 || value.ifind("https://") == 0 ||
     232           0 :             value.ifind("ftp://") == 0 || value.ifind("file://") == 0);
     233             : }
     234             : 
     235             : // Called after the dataset is initialized by the main WMS driver
     236           0 : CPLErr WMSMiniDriver_MRF::EndInit()
     237             : {
     238           0 :     int index_is_url = 1;
     239           0 :     if (!m_idxname.empty())
     240             :     {  // Provided, could be path or URL
     241           0 :         if (!is_url(m_idxname))
     242             :         {
     243           0 :             index_is_url = 0;
     244           0 :             fp = VSIFOpenL(m_idxname, "rb");
     245           0 :             if (fp == nullptr)
     246             :             {
     247           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Can't open index file %s",
     248             :                          m_idxname.c_str());
     249           0 :                 return CE_Failure;
     250             :             }
     251           0 :             index_cache = new SectorCache(fp);
     252             :         }
     253             :     }
     254             :     else
     255             :     {  // Not provided, change extension to .idx if we can, otherwise use the
     256             :         // same file
     257           0 :         m_idxname = m_base_url;
     258             :     }
     259             : 
     260           0 :     if (index_is_url)
     261             :     {  // 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             :     }
     267             : 
     268             :     // Set the level index offsets, assume MRF order since esri bundles don't
     269             :     // have overviews
     270             :     ILSize size(
     271           0 :         m_parent_dataset->GetRasterXSize(), m_parent_dataset->GetRasterYSize(),
     272             :         1,  // Single slice for now
     273             :         1,  // Ignore the c, only single or interleved data supported by WMS
     274           0 :         m_parent_dataset->GetRasterBand(1)->GetOverviewCount());
     275             : 
     276             :     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             :     {  // A bundle contains 128x128 pages, regadless of the raster size
     282           0 :         size.x = psx * 128;
     283           0 :         size.y = psy * 128;
     284             :     }
     285             : 
     286           0 :     for (GIntBig l = size.l; l >= 0; l--)
     287             :     {
     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             :     }
     297             : 
     298           0 :     return CE_None;
     299             : }
     300             : 
     301             : // Return -1 if error occurs
     302           0 : size_t WMSMiniDriver_MRF::GetIndexAddress(
     303             :     const GDALWMSTiledImageRequestInfo &tiri) const
     304             : {
     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             : }
     314             : 
     315             : // Signal errors and return error message
     316           0 : CPLErr WMSMiniDriver_MRF::TiledImageRequest(
     317             :     WMSHTTPRequest &request, CPL_UNUSED const GDALWMSImageRequestInfo &iri,
     318             :     const GDALWMSTiledImageRequestInfo &tiri)
     319             : {
     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             :     {
     326           0 :         request.Error = "Invalid level requested";
     327           0 :         return CE_Failure;
     328             :     }
     329             : 
     330           0 :     void *raw_index = index_cache->data(offset);
     331           0 :     if (raw_index == nullptr)
     332             :     {
     333           0 :         request.Error = "Invalid indexing";
     334           0 :         return CE_Failure;
     335             :     };
     336             : 
     337             :     // Store the tile size and offset in this structure
     338             :     MRFIdx idx;
     339             : 
     340           0 :     if (m_type == tMRF)
     341             :     {
     342           0 :         memcpy(&idx, raw_index, sizeof(idx));
     343             : 
     344             : #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             : #endif
     348             :     }
     349             :     else
     350             :     {  // Bundle
     351             :         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             :     }
     361             : 
     362             :     // Set the range or flag it as missing
     363           0 :     if (idx.size == 0)
     364             :         request.Range =
     365           0 :             "none";  // Signal that this block doesn't exist server-side
     366             :     else
     367             :         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             : }

Generated by: LCOV version 1.14