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: 2024-11-21 22:18:42 Functions: 0 14 0.0 %

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

Generated by: LCOV version 1.14