LCOV - code coverage report
Current view: top level - frmts/kmlsuperoverlay - kmlsuperoverlaydataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1287 1467 87.7 %
Date: 2025-01-18 12:42:00 Functions: 47 47 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  KmlSuperOverlay
       4             :  * Purpose:  Implements write support for KML superoverlay - KMZ.
       5             :  * Author:   Harsh Govind, harsh.govind@spadac.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010, SPADAC Inc. <harsh.govind@spadac.com>
       9             :  * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "kmlsuperoverlaydataset.h"
      16             : 
      17             : #include <array>
      18             : #include <cmath>
      19             : #include <cstring>
      20             : #include <algorithm>
      21             : #include <fstream>
      22             : #include <iostream>
      23             : #include <sstream>
      24             : 
      25             : #include "cpl_conv.h"
      26             : #include "cpl_error.h"
      27             : #include "cpl_string.h"
      28             : #include "cpl_vsi.h"
      29             : #include "gdal_frmts.h"
      30             : #include "ogr_spatialref.h"
      31             : #include "../vrt/gdal_vrt.h"
      32             : #include "../vrt/vrtdataset.h"
      33             : 
      34             : /************************************************************************/
      35             : /*                           GenerateTiles()                            */
      36             : /************************************************************************/
      37          75 : static void GenerateTiles(const std::string &filename, CPL_UNUSED int zoom,
      38             :                           int rxsize, int rysize, CPL_UNUSED int ix,
      39             :                           CPL_UNUSED int iy, int rx, int ry, int dxsize,
      40             :                           int dysize, int bands, GDALDataset *poSrcDs,
      41             :                           GDALDriver *poOutputTileDriver,
      42             :                           GDALDriver *poMemDriver, bool isJpegDriver)
      43             : {
      44          75 :     GDALRasterBand *alphaBand = nullptr;
      45             : 
      46         150 :     std::vector<GByte> abyScanline(dxsize);
      47         150 :     std::vector<bool> hadnoData(dxsize);
      48             : 
      49          75 :     if (isJpegDriver && bands == 4)
      50          41 :         bands = 3;
      51             : 
      52             :     auto poTmpDataset = std::unique_ptr<GDALDataset>(
      53         150 :         poMemDriver->Create("", dxsize, dysize, bands, GDT_Byte, nullptr));
      54             : 
      55          75 :     if (!isJpegDriver)  // Jpeg dataset only has one or three bands
      56             :     {
      57          12 :         if (bands < 4)  // add transparency to files with one band or three
      58             :                         // bands
      59             :         {
      60           7 :             poTmpDataset->AddBand(GDT_Byte);
      61             :             alphaBand =
      62           7 :                 poTmpDataset->GetRasterBand(poTmpDataset->GetRasterCount());
      63             :         }
      64             :     }
      65             : 
      66          75 :     const int rowOffset = rysize / dysize;
      67          75 :     const int loopCount = rysize / rowOffset;
      68        7045 :     for (int row = 0; row < loopCount; row++)
      69             :     {
      70        6970 :         if (!isJpegDriver)
      71             :         {
      72      643640 :             for (int i = 0; i < dxsize; i++)
      73             :             {
      74      641920 :                 hadnoData[i] = false;
      75             :             }
      76             :         }
      77             : 
      78       22860 :         for (int band = 1; band <= bands; band++)
      79             :         {
      80       15890 :             GDALRasterBand *poBand = poSrcDs->GetRasterBand(band);
      81       15890 :             int hasNoData = 0;
      82       15890 :             const double noDataValue = poBand->GetNoDataValue(&hasNoData);
      83             : 
      84       15890 :             int yOffset = ry + row * rowOffset;
      85       15890 :             CPLErr errTest = poBand->RasterIO(
      86       15890 :                 GF_Read, rx, yOffset, rxsize, rowOffset, abyScanline.data(),
      87             :                 dxsize, 1, GDT_Byte, 0, 0, nullptr);
      88             : 
      89       15890 :             const bool bReadFailed = (errTest == CE_Failure);
      90       15890 :             if (bReadFailed)
      91             :             {
      92           0 :                 hasNoData = 1;
      93             :             }
      94             : 
      95             :             // fill the true or false for hadnoData array if the source data has
      96             :             // nodata value
      97       15890 :             if (!isJpegDriver)
      98             :             {
      99        5480 :                 if (hasNoData == 1)
     100             :                 {
     101           0 :                     for (int j = 0; j < dxsize; j++)
     102             :                     {
     103           0 :                         double v = abyScanline[j];
     104           0 :                         if (v == noDataValue || bReadFailed)
     105             :                         {
     106           0 :                             hadnoData[j] = true;
     107             :                         }
     108             :                     }
     109             :                 }
     110             :             }
     111             : 
     112       15890 :             if (!bReadFailed)
     113             :             {
     114       15890 :                 GDALRasterBand *poBandtmp = poTmpDataset->GetRasterBand(band);
     115       15890 :                 CPL_IGNORE_RET_VAL(poBandtmp->RasterIO(
     116       15890 :                     GF_Write, 0, row, dxsize, 1, abyScanline.data(), dxsize, 1,
     117             :                     GDT_Byte, 0, 0, nullptr));
     118             :             }
     119             :         }
     120             : 
     121             :         // fill the values for alpha band
     122        6970 :         if (!isJpegDriver)
     123             :         {
     124        1720 :             if (alphaBand)
     125             :             {
     126      561400 :                 for (int i = 0; i < dxsize; i++)
     127             :                 {
     128      560000 :                     if (hadnoData[i])
     129             :                     {
     130           0 :                         abyScanline[i] = 0;
     131             :                     }
     132             :                     else
     133             :                     {
     134      560000 :                         abyScanline[i] = 255;
     135             :                     }
     136             :                 }
     137             : 
     138        1400 :                 CPL_IGNORE_RET_VAL(alphaBand->RasterIO(
     139        1400 :                     GF_Write, 0, row, dxsize, 1, abyScanline.data(), dxsize, 1,
     140             :                     GDT_Byte, 0, 0, nullptr));
     141             :             }
     142             :         }
     143             :     }
     144             : 
     145          75 :     CPLConfigOptionSetter oSetter("GDAL_OPEN_AFTER_COPY", "NO", false);
     146             :     /* to prevent CreateCopy() from calling QuietDelete() */
     147          75 :     const char *const apszOptions[] = {"@QUIET_DELETE_ON_CREATE_COPY=NO",
     148             :                                        nullptr};
     149          75 :     std::unique_ptr<GDALDataset>(
     150             :         poOutputTileDriver->CreateCopy(filename.c_str(), poTmpDataset.get(),
     151             :                                        FALSE, apszOptions, nullptr, nullptr));
     152          75 : }
     153             : 
     154             : /************************************************************************/
     155             : /*                          GenerateRootKml()                           */
     156             : /************************************************************************/
     157             : 
     158          19 : static int GenerateRootKml(const char *filename, const char *kmlfilename,
     159             :                            double north, double south, double east, double west,
     160             :                            int tilesize, const char *pszOverlayName,
     161             :                            const char *pszOverlayDescription)
     162             : {
     163          19 :     VSILFILE *fp = VSIFOpenL(filename, "wb");
     164          19 :     if (fp == nullptr)
     165             :     {
     166           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s", filename);
     167           0 :         return FALSE;
     168             :     }
     169          19 :     int minlodpixels = tilesize / 2;
     170             : 
     171             :     const std::string osOverlayName = pszOverlayName
     172             :                                           ? std::string(pszOverlayName)
     173          19 :                                           : CPLGetBasenameSafe(kmlfilename);
     174             : 
     175             :     // If we have not written any features yet, output the layer's schema.
     176          19 :     VSIFPrintfL(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
     177          19 :     VSIFPrintfL(fp, "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n");
     178          19 :     VSIFPrintfL(fp, "\t<Document>\n");
     179          19 :     char *pszEncoded = CPLEscapeString(osOverlayName.c_str(), -1, CPLES_XML);
     180          19 :     VSIFPrintfL(fp, "\t\t<name>%s</name>\n", pszEncoded);
     181          19 :     CPLFree(pszEncoded);
     182          19 :     if (pszOverlayDescription == nullptr)
     183             :     {
     184          18 :         VSIFPrintfL(fp, "\t\t<description></description>\n");
     185             :     }
     186             :     else
     187             :     {
     188           1 :         pszEncoded = CPLEscapeString(pszOverlayDescription, -1, CPLES_XML);
     189           1 :         VSIFPrintfL(fp, "\t\t<description>%s</description>\n", pszEncoded);
     190           1 :         CPLFree(pszEncoded);
     191             :     }
     192          19 :     VSIFPrintfL(fp, "\t\t<styleUrl>#hideChildrenStyle</styleUrl>\n");
     193          19 :     VSIFPrintfL(fp, "\t\t<Style id=\"hideChildrenStyle\">\n");
     194          19 :     VSIFPrintfL(fp, "\t\t\t<ListStyle id=\"hideChildren\">\n");
     195          19 :     VSIFPrintfL(fp, "\t\t\t\t<listItemType>checkHideChildren</listItemType>\n");
     196          19 :     VSIFPrintfL(fp, "\t\t\t</ListStyle>\n");
     197          19 :     VSIFPrintfL(fp, "\t\t</Style>\n");
     198             :     /*VSIFPrintfL(fp, "\t\t<Region>\n");
     199             :     VSIFPrintfL(fp, "\t\t\t<LatLonAltBox>\n");
     200             :     VSIFPrintfL(fp, "\t\t\t\t<north>%f</north>\n", north);
     201             :     VSIFPrintfL(fp, "\t\t\t\t<south>%f</south>\n", south);
     202             :     VSIFPrintfL(fp, "\t\t\t\t<east>%f</east>\n", east);
     203             :     VSIFPrintfL(fp, "\t\t\t\t<west>%f</west>\n", west);
     204             :     VSIFPrintfL(fp, "\t\t\t</LatLonAltBox>\n");
     205             :     VSIFPrintfL(fp, "\t\t</Region>\n");*/
     206          19 :     VSIFPrintfL(fp, "\t\t<NetworkLink>\n");
     207          19 :     VSIFPrintfL(fp, "\t\t\t<open>1</open>\n");
     208          19 :     VSIFPrintfL(fp, "\t\t\t<Region>\n");
     209          19 :     VSIFPrintfL(fp, "\t\t\t\t<LatLonAltBox>\n");
     210          19 :     VSIFPrintfL(fp, "\t\t\t\t\t<north>%f</north>\n", north);
     211          19 :     VSIFPrintfL(fp, "\t\t\t\t\t<south>%f</south>\n", south);
     212          19 :     VSIFPrintfL(fp, "\t\t\t\t\t<east>%f</east>\n", east);
     213          19 :     VSIFPrintfL(fp, "\t\t\t\t\t<west>%f</west>\n", west);
     214          19 :     VSIFPrintfL(fp, "\t\t\t\t</LatLonAltBox>\n");
     215          19 :     VSIFPrintfL(fp, "\t\t\t\t<Lod>\n");
     216          19 :     VSIFPrintfL(fp, "\t\t\t\t\t<minLodPixels>%d</minLodPixels>\n",
     217             :                 minlodpixels);
     218          19 :     VSIFPrintfL(fp, "\t\t\t\t\t<maxLodPixels>-1</maxLodPixels>\n");
     219          19 :     VSIFPrintfL(fp, "\t\t\t\t</Lod>\n");
     220          19 :     VSIFPrintfL(fp, "\t\t\t</Region>\n");
     221          19 :     VSIFPrintfL(fp, "\t\t\t<Link>\n");
     222          19 :     VSIFPrintfL(fp, "\t\t\t\t<href>0/0/0.kml</href>\n");
     223          19 :     VSIFPrintfL(fp, "\t\t\t\t<viewRefreshMode>onRegion</viewRefreshMode>\n");
     224          19 :     VSIFPrintfL(fp, "\t\t\t</Link>\n");
     225          19 :     VSIFPrintfL(fp, "\t\t</NetworkLink>\n");
     226          19 :     VSIFPrintfL(fp, "\t</Document>\n");
     227          19 :     VSIFPrintfL(fp, "</kml>\n");
     228             : 
     229          19 :     VSIFCloseL(fp);
     230          19 :     return TRUE;
     231             : }
     232             : 
     233             : /************************************************************************/
     234             : /*                          GenerateChildKml()                          */
     235             : /************************************************************************/
     236             : 
     237          75 : static int GenerateChildKml(
     238             :     const std::string &filename, int zoom, int ix, int iy, double zoomxpixel,
     239             :     double zoomypixel, int dxsize, int dysize, double south, double west,
     240             :     int xsize, int ysize, int maxzoom, OGRCoordinateTransformation *poTransform,
     241             :     const std::string &fileExt, bool fixAntiMeridian, const char *pszAltitude,
     242             :     const char *pszAltitudeMode,
     243             :     const std::vector<std::pair<std::pair<int, int>, bool>> &childTiles)
     244             : {
     245          75 :     double tnorth = south + zoomypixel * ((iy + 1) * dysize);
     246          75 :     double tsouth = south + zoomypixel * (iy * dysize);
     247          75 :     double teast = west + zoomxpixel * ((ix + 1) * dxsize);
     248          75 :     double twest = west + zoomxpixel * ix * dxsize;
     249             : 
     250          75 :     double upperleftT = twest;
     251          75 :     double lowerleftT = twest;
     252             : 
     253          75 :     double rightbottomT = tsouth;
     254          75 :     double leftbottomT = tsouth;
     255             : 
     256          75 :     double lefttopT = tnorth;
     257          75 :     double righttopT = tnorth;
     258             : 
     259          75 :     double lowerrightT = teast;
     260          75 :     double upperrightT = teast;
     261             : 
     262          75 :     if (poTransform)
     263             :     {
     264          10 :         poTransform->Transform(1, &twest, &tsouth);
     265          10 :         poTransform->Transform(1, &teast, &tnorth);
     266             : 
     267          10 :         poTransform->Transform(1, &upperleftT, &lefttopT);
     268          10 :         poTransform->Transform(1, &upperrightT, &righttopT);
     269          10 :         poTransform->Transform(1, &lowerrightT, &rightbottomT);
     270          10 :         poTransform->Transform(1, &lowerleftT, &leftbottomT);
     271             :     }
     272             : 
     273          75 :     if (fixAntiMeridian && teast < twest)
     274             :     {
     275           3 :         teast += 360;
     276           3 :         lowerrightT += 360;
     277           3 :         upperrightT += 360;
     278             :     }
     279             : 
     280         150 :     std::vector<int> xchildren;
     281         150 :     std::vector<int> ychildern;
     282             : 
     283          75 :     int minLodPixels = 128;
     284          75 :     if (zoom == 0)
     285             :     {
     286          19 :         minLodPixels = 1;
     287             :     }
     288             : 
     289          75 :     int maxLodPix = -1;
     290          75 :     if (zoom < maxzoom)
     291             :     {
     292          16 :         double zareasize = pow(2.0, (maxzoom - zoom - 1)) * dxsize;
     293          16 :         double zareasize1 = pow(2.0, (maxzoom - zoom - 1)) * dysize;
     294          16 :         xchildren.push_back(ix * 2);
     295          16 :         int tmp = ix * 2 + 1;
     296          16 :         int tmp1 = static_cast<int>(ceil(xsize / zareasize));
     297          16 :         if (tmp < tmp1)
     298             :         {
     299          16 :             xchildren.push_back(ix * 2 + 1);
     300             :         }
     301          16 :         ychildern.push_back(iy * 2);
     302          16 :         tmp = iy * 2 + 1;
     303          16 :         tmp1 = static_cast<int>(ceil(ysize / zareasize1));
     304          16 :         if (tmp < tmp1)
     305             :         {
     306          16 :             ychildern.push_back(iy * 2 + 1);
     307             :         }
     308          16 :         maxLodPix = 2048;
     309             : 
     310          16 :         bool hasChildKML = false;
     311          60 :         for (const auto &kv : childTiles)
     312             :         {
     313          49 :             if (kv.second)
     314             :             {
     315           5 :                 hasChildKML = true;
     316           5 :                 break;
     317             :             }
     318             :         }
     319          16 :         if (!hasChildKML)
     320             :         {
     321             :             // no child KML files, so don't expire this one at any zoom.
     322          11 :             maxLodPix = -1;
     323             :         }
     324             :     }
     325             : 
     326          75 :     VSILFILE *fp = VSIFOpenL(filename.c_str(), "wb");
     327          75 :     if (fp == nullptr)
     328             :     {
     329           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s",
     330             :                  filename.c_str());
     331           0 :         return FALSE;
     332             :     }
     333             : 
     334          75 :     VSIFPrintfL(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
     335          75 :     VSIFPrintfL(fp, "<kml xmlns=\"http://www.opengis.net/kml/2.2\" "
     336             :                     "xmlns:gx=\"http://www.google.com/kml/ext/2.2\">\n");
     337          75 :     VSIFPrintfL(fp, "\t<Document>\n");
     338          75 :     VSIFPrintfL(fp, "\t\t<name>%d/%d/%d.kml</name>\n", zoom, ix, iy);
     339          75 :     VSIFPrintfL(fp, "\t\t<styleUrl>#hideChildrenStyle</styleUrl>\n");
     340          75 :     VSIFPrintfL(fp, "\t\t<Style id=\"hideChildrenStyle\">\n");
     341          75 :     VSIFPrintfL(fp, "\t\t\t<ListStyle id=\"hideChildren\">\n");
     342          75 :     VSIFPrintfL(fp, "\t\t\t\t<listItemType>checkHideChildren</listItemType>\n");
     343          75 :     VSIFPrintfL(fp, "\t\t\t</ListStyle>\n");
     344          75 :     VSIFPrintfL(fp, "\t\t</Style>\n");
     345          75 :     VSIFPrintfL(fp, "\t\t<Region>\n");
     346          75 :     VSIFPrintfL(fp, "\t\t\t<LatLonAltBox>\n");
     347          75 :     VSIFPrintfL(fp, "\t\t\t\t<north>%f</north>\n", tnorth);
     348          75 :     VSIFPrintfL(fp, "\t\t\t\t<south>%f</south>\n", tsouth);
     349          75 :     VSIFPrintfL(fp, "\t\t\t\t<east>%f</east>\n", teast);
     350          75 :     VSIFPrintfL(fp, "\t\t\t\t<west>%f</west>\n", twest);
     351          75 :     VSIFPrintfL(fp, "\t\t\t</LatLonAltBox>\n");
     352          75 :     VSIFPrintfL(fp, "\t\t\t<Lod>\n");
     353          75 :     VSIFPrintfL(fp, "\t\t\t\t<minLodPixels>%d</minLodPixels>\n", minLodPixels);
     354          75 :     VSIFPrintfL(fp, "\t\t\t\t<maxLodPixels>%d</maxLodPixels>\n", maxLodPix);
     355          75 :     VSIFPrintfL(fp, "\t\t\t</Lod>\n");
     356          75 :     VSIFPrintfL(fp, "\t\t</Region>\n");
     357          75 :     VSIFPrintfL(fp, "\t\t<GroundOverlay>\n");
     358          75 :     VSIFPrintfL(fp, "\t\t\t<drawOrder>%d</drawOrder>\n", zoom);
     359          75 :     VSIFPrintfL(fp, "\t\t\t<Icon>\n");
     360          75 :     VSIFPrintfL(fp, "\t\t\t\t<href>%d%s</href>\n", iy, fileExt.c_str());
     361          75 :     VSIFPrintfL(fp, "\t\t\t</Icon>\n");
     362             : 
     363          75 :     if (pszAltitude != nullptr)
     364             :     {
     365           5 :         VSIFPrintfL(fp, "\t\t\t<altitude>%s</altitude>\n", pszAltitude);
     366             :     }
     367          75 :     if (pszAltitudeMode != nullptr &&
     368           5 :         (strcmp(pszAltitudeMode, "clampToGround") == 0 ||
     369           5 :          strcmp(pszAltitudeMode, "absolute") == 0))
     370             :     {
     371           5 :         VSIFPrintfL(fp, "\t\t\t<altitudeMode>%s</altitudeMode>\n",
     372             :                     pszAltitudeMode);
     373             :     }
     374          70 :     else if (pszAltitudeMode != nullptr &&
     375           0 :              (strcmp(pszAltitudeMode, "relativeToSeaFloor") == 0 ||
     376           0 :               strcmp(pszAltitudeMode, "clampToSeaFloor") == 0))
     377             :     {
     378           0 :         VSIFPrintfL(fp, "\t\t\t<gx:altitudeMode>%s</gx:altitudeMode>\n",
     379             :                     pszAltitudeMode);
     380             :     }
     381             : 
     382             :     /* When possible, use <LatLonBox>. I've noticed otherwise that */
     383             :     /* if using <gx:LatLonQuad> with extents of the size of a country or */
     384             :     /* continent, the overlay is really bad placed in GoogleEarth */
     385          75 :     if (lowerleftT == upperleftT && lowerrightT == upperrightT &&
     386          70 :         leftbottomT == rightbottomT && righttopT == lefttopT)
     387             :     {
     388          70 :         VSIFPrintfL(fp, "\t\t\t<LatLonBox>\n");
     389          70 :         VSIFPrintfL(fp, "\t\t\t\t<north>%f</north>\n", tnorth);
     390          70 :         VSIFPrintfL(fp, "\t\t\t\t<south>%f</south>\n", tsouth);
     391          70 :         VSIFPrintfL(fp, "\t\t\t\t<east>%f</east>\n", teast);
     392          70 :         VSIFPrintfL(fp, "\t\t\t\t<west>%f</west>\n", twest);
     393          70 :         VSIFPrintfL(fp, "\t\t\t</LatLonBox>\n");
     394             :     }
     395             :     else
     396             :     {
     397           5 :         VSIFPrintfL(fp, "\t\t\t<gx:LatLonQuad>\n");
     398           5 :         VSIFPrintfL(fp, "\t\t\t\t<coordinates>\n");
     399           5 :         VSIFPrintfL(fp, "\t\t\t\t\t%f,%f,0\n", lowerleftT, leftbottomT);
     400           5 :         VSIFPrintfL(fp, "\t\t\t\t\t%f,%f,0\n", lowerrightT, rightbottomT);
     401           5 :         VSIFPrintfL(fp, "\t\t\t\t\t%f,%f,0\n", upperrightT, righttopT);
     402           5 :         VSIFPrintfL(fp, "\t\t\t\t\t%f,%f,0\n", upperleftT, lefttopT);
     403           5 :         VSIFPrintfL(fp, "\t\t\t\t</coordinates>\n");
     404           5 :         VSIFPrintfL(fp, "\t\t\t</gx:LatLonQuad>\n");
     405             :     }
     406          75 :     VSIFPrintfL(fp, "\t\t</GroundOverlay>\n");
     407             : 
     408         131 :     for (const auto &kv : childTiles)
     409             :     {
     410          56 :         int cx = kv.first.first;
     411          56 :         int cy = kv.first.second;
     412             : 
     413          56 :         double cnorth = south + zoomypixel / 2 * ((cy + 1) * dysize);
     414          56 :         double csouth = south + zoomypixel / 2 * (cy * dysize);
     415          56 :         double ceast = west + zoomxpixel / 2 * ((cx + 1) * dxsize);
     416          56 :         double cwest = west + zoomxpixel / 2 * cx * dxsize;
     417             : 
     418          56 :         if (poTransform)
     419             :         {
     420           8 :             poTransform->Transform(1, &cwest, &csouth);
     421           8 :             poTransform->Transform(1, &ceast, &cnorth);
     422             :         }
     423             : 
     424          56 :         if (fixAntiMeridian && ceast < cwest)
     425             :         {
     426           2 :             ceast += 360;
     427             :         }
     428             : 
     429          56 :         VSIFPrintfL(fp, "\t\t<NetworkLink>\n");
     430          56 :         VSIFPrintfL(fp, "\t\t\t<name>%d/%d/%d%s</name>\n", zoom + 1, cx, cy,
     431             :                     fileExt.c_str());
     432          56 :         VSIFPrintfL(fp, "\t\t\t<Region>\n");
     433          56 :         VSIFPrintfL(fp, "\t\t\t\t<Lod>\n");
     434          56 :         VSIFPrintfL(fp, "\t\t\t\t\t<minLodPixels>128</minLodPixels>\n");
     435          56 :         VSIFPrintfL(fp, "\t\t\t\t\t<maxLodPixels>-1</maxLodPixels>\n");
     436          56 :         VSIFPrintfL(fp, "\t\t\t\t</Lod>\n");
     437          56 :         VSIFPrintfL(fp, "\t\t\t\t<LatLonAltBox>\n");
     438          56 :         VSIFPrintfL(fp, "\t\t\t\t\t<north>%f</north>\n", cnorth);
     439          56 :         VSIFPrintfL(fp, "\t\t\t\t\t<south>%f</south>\n", csouth);
     440          56 :         VSIFPrintfL(fp, "\t\t\t\t\t<east>%f</east>\n", ceast);
     441          56 :         VSIFPrintfL(fp, "\t\t\t\t\t<west>%f</west>\n", cwest);
     442          56 :         VSIFPrintfL(fp, "\t\t\t\t</LatLonAltBox>\n");
     443          56 :         VSIFPrintfL(fp, "\t\t\t</Region>\n");
     444          56 :         VSIFPrintfL(fp, "\t\t\t<Link>\n");
     445          56 :         VSIFPrintfL(fp, "\t\t\t\t<href>../../%d/%d/%d.kml</href>\n", zoom + 1,
     446             :                     cx, cy);
     447          56 :         VSIFPrintfL(fp,
     448             :                     "\t\t\t\t<viewRefreshMode>onRegion</viewRefreshMode>\n");
     449          56 :         VSIFPrintfL(fp, "\t\t\t\t<viewFormat/>\n");
     450          56 :         VSIFPrintfL(fp, "\t\t\t</Link>\n");
     451          56 :         VSIFPrintfL(fp, "\t\t</NetworkLink>\n");
     452             :     }
     453             : 
     454          75 :     VSIFPrintfL(fp, "\t</Document>\n");
     455          75 :     VSIFPrintfL(fp, "</kml>\n");
     456          75 :     VSIFCloseL(fp);
     457             : 
     458          75 :     return TRUE;
     459             : }
     460             : 
     461             : /************************************************************************/
     462             : /*                         DetectTransparency()                         */
     463             : /************************************************************************/
     464          85 : int KmlSuperOverlayReadDataset::DetectTransparency(int rxsize, int rysize,
     465             :                                                    int rx, int ry, int dxsize,
     466             :                                                    int dysize,
     467             :                                                    GDALDataset *poSrcDs)
     468             : {
     469          85 :     int bands = poSrcDs->GetRasterCount();
     470          85 :     int rowOffset = rysize / dysize;
     471          85 :     int loopCount = rysize / rowOffset;
     472          85 :     int hasNoData = 0;
     473          85 :     std::vector<GByte> abyScanline(dxsize);
     474             : 
     475          85 :     int flags = 0;
     476         425 :     for (int band = 1; band <= bands; band++)
     477             :     {
     478         340 :         GDALRasterBand *poBand = poSrcDs->GetRasterBand(band);
     479         340 :         int noDataValue = static_cast<int>(poBand->GetNoDataValue(&hasNoData));
     480             : 
     481         340 :         if (band < 4 && hasNoData)
     482             :         {
     483           0 :             for (int row = 0; row < loopCount; row++)
     484             :             {
     485           0 :                 int yOffset = ry + row * rowOffset;
     486           0 :                 CPL_IGNORE_RET_VAL(poBand->RasterIO(
     487           0 :                     GF_Read, rx, yOffset, rxsize, rowOffset, abyScanline.data(),
     488             :                     dxsize, 1, GDT_Byte, 0, 0, nullptr));
     489           0 :                 for (int i = 0; i < dxsize; i++)
     490             :                 {
     491           0 :                     if (abyScanline[i] == noDataValue)
     492             :                     {
     493           0 :                         flags |= KMLSO_ContainsTransparentPixels;
     494             :                     }
     495             :                     else
     496             :                     {
     497           0 :                         flags |= KMLSO_ContainsOpaquePixels;
     498             :                     }
     499             :                 }
     500             :                 // shortcut - if there are both types of pixels, flags is as
     501             :                 // full as it is going to get.
     502             :                 // so no point continuing, skip to the next band
     503           0 :                 if ((flags & KMLSO_ContainsTransparentPixels) &&
     504           0 :                     (flags & KMLSO_ContainsOpaquePixels))
     505             :                 {
     506           0 :                     break;
     507             :                 }
     508           0 :             }
     509             :         }
     510         340 :         else if (band == 4)
     511             :         {
     512        5525 :             for (int row = 0; row < loopCount; row++)
     513             :             {
     514        5440 :                 int yOffset = ry + row * rowOffset;
     515        5440 :                 CPL_IGNORE_RET_VAL(poBand->RasterIO(
     516        5440 :                     GF_Read, rx, yOffset, rxsize, rowOffset, abyScanline.data(),
     517             :                     dxsize, 1, GDT_Byte, 0, 0, nullptr));
     518     1398080 :                 for (int i = 0; i < dxsize; i++)
     519             :                 {
     520     1392640 :                     if (abyScanline[i] == 255)
     521             :                     {
     522      696320 :                         flags |= KMLSO_ContainsOpaquePixels;
     523             :                     }
     524      696320 :                     else if (abyScanline[i] == 0)
     525             :                     {
     526      696320 :                         flags |= KMLSO_ContainsTransparentPixels;
     527             :                     }
     528             :                     else
     529             :                     {
     530           0 :                         flags |= KMLSO_ContainsPartiallyTransparentPixels;
     531             :                     }
     532             :                 }
     533             :             }
     534             :         }
     535             :     }
     536         170 :     return flags;
     537             : }
     538             : 
     539             : /************************************************************************/
     540             : /*                           CreateCopy()                               */
     541             : /************************************************************************/
     542             : 
     543             : class KmlSuperOverlayDummyDataset final : public GDALDataset
     544             : {
     545             :   public:
     546           1 :     KmlSuperOverlayDummyDataset() = default;
     547             : };
     548             : 
     549             : static GDALDataset *
     550          24 : KmlSuperOverlayCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
     551             :                           CPL_UNUSED int bStrict, char **papszOptions,
     552             :                           GDALProgressFunc pfnProgress, void *pProgressData)
     553             : {
     554          24 :     bool isKmz = false;
     555             : 
     556          24 :     if (pfnProgress == nullptr)
     557           0 :         pfnProgress = GDALDummyProgress;
     558             : 
     559          24 :     int bands = poSrcDS->GetRasterCount();
     560          24 :     if (bands != 1 && bands != 3 && bands != 4)
     561           3 :         return nullptr;
     562             : 
     563             :     // correct the file and get the directory
     564          21 :     char *output_dir = nullptr;
     565          42 :     std::string osFilename;
     566          21 :     if (pszFilename == nullptr)
     567             :     {
     568           0 :         output_dir = CPLGetCurrentDir();
     569           0 :         osFilename = CPLFormFilenameSafe(output_dir, "doc", "kml");
     570             :     }
     571             :     else
     572             :     {
     573          21 :         osFilename = pszFilename;
     574          21 :         const std::string osExtension = CPLGetExtensionSafe(pszFilename);
     575          21 :         const char *extension = osExtension.c_str();
     576          21 :         if (!EQUAL(extension, "kml") && !EQUAL(extension, "kmz"))
     577             :         {
     578           0 :             CPLError(CE_Failure, CPLE_None,
     579             :                      "File extension should be kml or kmz.");
     580           0 :             return nullptr;
     581             :         }
     582          21 :         if (EQUAL(extension, "kmz"))
     583             :         {
     584          17 :             isKmz = true;
     585             :         }
     586             : 
     587          21 :         output_dir = CPLStrdup(CPLGetPathSafe(pszFilename).c_str());
     588          21 :         if (strcmp(output_dir, "") == 0)
     589             :         {
     590           0 :             CPLFree(output_dir);
     591           0 :             output_dir = CPLGetCurrentDir();
     592             :         }
     593             :     }
     594          21 :     pszFilename = osFilename.c_str();
     595             : 
     596          42 :     CPLString outDir = output_dir ? output_dir : "";
     597          21 :     CPLFree(output_dir);
     598          21 :     output_dir = nullptr;
     599             : 
     600          21 :     VSILFILE *zipHandle = nullptr;
     601          21 :     if (isKmz)
     602             :     {
     603          17 :         outDir = "/vsizip/";
     604          17 :         outDir += pszFilename;
     605          17 :         zipHandle = VSIFOpenL(outDir, "wb");
     606          17 :         if (zipHandle == nullptr)
     607             :         {
     608           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s",
     609             :                      pszFilename);
     610           2 :             return nullptr;
     611             :         }
     612             :     }
     613             : 
     614          19 :     GDALDriver *poOutputTileDriver = nullptr;
     615          19 :     GDALDriver *poJpegOutputTileDriver = nullptr;
     616          19 :     GDALDriver *poPngOutputTileDriver = nullptr;
     617          19 :     bool isAutoDriver = false;
     618          19 :     bool isJpegDriver = false;
     619             : 
     620             :     const char *pszFormat =
     621          19 :         CSLFetchNameValueDef(papszOptions, "FORMAT", "JPEG");
     622          19 :     if (EQUAL(pszFormat, "AUTO"))
     623             :     {
     624           1 :         isAutoDriver = true;
     625             :         poJpegOutputTileDriver =
     626           1 :             GetGDALDriverManager()->GetDriverByName("JPEG");
     627           1 :         poPngOutputTileDriver = GetGDALDriverManager()->GetDriverByName("PNG");
     628             :     }
     629             :     else
     630             :     {
     631          18 :         poOutputTileDriver = GetGDALDriverManager()->GetDriverByName(pszFormat);
     632          18 :         if (EQUAL(pszFormat, "JPEG"))
     633             :         {
     634          15 :             isJpegDriver = true;
     635             :         }
     636             :     }
     637             : 
     638          19 :     GDALDriver *poMemDriver = GetGDALDriverManager()->GetDriverByName("MEM");
     639             : 
     640          19 :     if (poMemDriver == nullptr ||
     641          19 :         (!isAutoDriver && poOutputTileDriver == nullptr) ||
     642           1 :         (isAutoDriver && (poJpegOutputTileDriver == nullptr ||
     643             :                           poPngOutputTileDriver == nullptr)))
     644             :     {
     645           0 :         CPLError(CE_Failure, CPLE_None, "Image export driver was not found..");
     646           0 :         if (zipHandle != nullptr)
     647             :         {
     648           0 :             VSIFCloseL(zipHandle);
     649           0 :             VSIUnlink(pszFilename);
     650             :         }
     651           0 :         return nullptr;
     652             :     }
     653             : 
     654          19 :     int xsize = poSrcDS->GetRasterXSize();
     655          19 :     int ysize = poSrcDS->GetRasterYSize();
     656             : 
     657          19 :     double north = 0.0;
     658          19 :     double south = 0.0;
     659          19 :     double east = 0.0;
     660          19 :     double west = 0.0;
     661             : 
     662             :     double adfGeoTransform[6];
     663             : 
     664          19 :     if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
     665             :     {
     666          19 :         north = adfGeoTransform[3];
     667          19 :         south = adfGeoTransform[3] + adfGeoTransform[5] * ysize;
     668          19 :         east = adfGeoTransform[0] + adfGeoTransform[1] * xsize;
     669          19 :         west = adfGeoTransform[0];
     670             :     }
     671             : 
     672          19 :     std::unique_ptr<OGRCoordinateTransformation> poTransform;
     673          19 :     const auto poSrcSRS = poSrcDS->GetSpatialRef();
     674          19 :     if (poSrcSRS && poSrcSRS->IsProjected())
     675             :     {
     676           4 :         OGRSpatialReference poLatLong;
     677           2 :         poLatLong.SetWellKnownGeogCS("WGS84");
     678           2 :         poLatLong.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     679             : 
     680           2 :         poTransform.reset(
     681             :             OGRCreateCoordinateTransformation(poSrcSRS, &poLatLong));
     682           2 :         if (poTransform != nullptr)
     683             :         {
     684           2 :             poTransform->Transform(1, &west, &south);
     685           2 :             poTransform->Transform(1, &east, &north);
     686             :         }
     687             :     }
     688             : 
     689             :     const bool fixAntiMeridian =
     690          19 :         CPLFetchBool(papszOptions, "FIX_ANTIMERIDIAN", false);
     691          19 :     if (fixAntiMeridian && east < west)
     692             :     {
     693           1 :         east += 360;
     694             :     }
     695             : 
     696             :     // Zoom levels of the pyramid.
     697          19 :     int maxzoom = 0;
     698             :     int tilexsize;
     699             :     int tileysize;
     700             :     // Let the longer side determine the max zoom level and x/y tilesizes.
     701          19 :     if (xsize >= ysize)
     702             :     {
     703          19 :         double dtilexsize = xsize;
     704          25 :         while (dtilexsize > 400)  // calculate x tile size
     705             :         {
     706           6 :             dtilexsize = dtilexsize / 2;
     707           6 :             maxzoom++;
     708             :         }
     709          19 :         tilexsize = static_cast<int>(dtilexsize);
     710          19 :         tileysize = static_cast<int>(dtilexsize * ysize / xsize);
     711             :     }
     712             :     else
     713             :     {
     714           0 :         double dtileysize = ysize;
     715           0 :         while (dtileysize > 400)  // calculate y tile size
     716             :         {
     717           0 :             dtileysize = dtileysize / 2;
     718           0 :             maxzoom++;
     719             :         }
     720             : 
     721           0 :         tileysize = static_cast<int>(dtileysize);
     722           0 :         tilexsize = static_cast<int>(dtileysize * xsize / ysize);
     723             :     }
     724             : 
     725          38 :     std::vector<double> zoomxpixels;
     726          38 :     std::vector<double> zoomypixels;
     727          44 :     for (int zoom = 0; zoom < maxzoom + 1; zoom++)
     728             :     {
     729          25 :         zoomxpixels.push_back(adfGeoTransform[1] * pow(2.0, (maxzoom - zoom)));
     730             :         // zoomypixels.push_back(abs(adfGeoTransform[5]) * pow(2.0, (maxzoom -
     731             :         // zoom)));
     732          25 :         zoomypixels.push_back(fabs(adfGeoTransform[5]) *
     733          25 :                               pow(2.0, (maxzoom - zoom)));
     734             :     }
     735             : 
     736          38 :     std::string tmpFileName;
     737          38 :     std::vector<std::string> fileVector;
     738             :     int nRet;
     739             : 
     740          19 :     const char *pszOverlayName = CSLFetchNameValue(papszOptions, "NAME");
     741             :     const char *pszOverlayDescription =
     742          19 :         CSLFetchNameValue(papszOptions, "DESCRIPTION");
     743             : 
     744          19 :     if (isKmz)
     745             :     {
     746          15 :         tmpFileName = CPLFormFilenameSafe(outDir, "doc.kml", nullptr);
     747          15 :         nRet = GenerateRootKml(tmpFileName.c_str(), pszFilename, north, south,
     748             :                                east, west, static_cast<int>(tilexsize),
     749             :                                pszOverlayName, pszOverlayDescription);
     750          15 :         fileVector.push_back(tmpFileName);
     751             :     }
     752             :     else
     753             :     {
     754           4 :         nRet = GenerateRootKml(pszFilename, pszFilename, north, south, east,
     755             :                                west, static_cast<int>(tilexsize),
     756             :                                pszOverlayName, pszOverlayDescription);
     757             :     }
     758             : 
     759          19 :     if (nRet == FALSE)
     760             :     {
     761           0 :         if (zipHandle != nullptr)
     762             :         {
     763           0 :             VSIFCloseL(zipHandle);
     764           0 :             VSIUnlink(pszFilename);
     765             :         }
     766           0 :         return nullptr;
     767             :     }
     768             : 
     769          19 :     const char *pszAltitude = CSLFetchNameValue(papszOptions, "ALTITUDE");
     770             :     const char *pszAltitudeMode =
     771          19 :         CSLFetchNameValue(papszOptions, "ALTITUDEMODE");
     772          19 :     if (pszAltitudeMode != nullptr)
     773             :     {
     774           1 :         if (strcmp(pszAltitudeMode, "clampToGround") == 0)
     775             :         {
     776           0 :             pszAltitudeMode = nullptr;
     777           0 :             pszAltitude = nullptr;
     778             :         }
     779           1 :         else if (strcmp(pszAltitudeMode, "absolute") == 0)
     780             :         {
     781           1 :             if (pszAltitude == nullptr)
     782             :             {
     783           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     784             :                          "Using ALTITUDE=0 as default value");
     785           0 :                 pszAltitude = "0";
     786             :             }
     787             :         }
     788           0 :         else if (strcmp(pszAltitudeMode, "relativeToSeaFloor") == 0)
     789             :         {
     790             :             /* nothing to do */
     791             :         }
     792           0 :         else if (strcmp(pszAltitudeMode, "clampToSeaFloor") == 0)
     793             :         {
     794           0 :             pszAltitude = nullptr;
     795             :         }
     796             :         else
     797             :         {
     798           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     799             :                      "Ignoring unhandled value of ALTITUDEMODE");
     800           0 :             pszAltitudeMode = nullptr;
     801           0 :             pszAltitude = nullptr;
     802             :         }
     803             :     }
     804             : 
     805             :     int zoom;
     806          19 :     int nTotalTiles = 0;
     807          19 :     int nTileCount = 0;
     808             : 
     809          44 :     for (zoom = maxzoom; zoom >= 0; --zoom)
     810             :     {
     811          25 :         const int rmaxxsize = tilexsize * (1 << (maxzoom - zoom));
     812          25 :         const int rmaxysize = tileysize * (1 << (maxzoom - zoom));
     813             : 
     814          25 :         const int xloop = std::max(1, static_cast<int>(xsize / rmaxxsize));
     815          25 :         const int yloop = std::max(1, static_cast<int>(ysize / rmaxysize));
     816          25 :         nTotalTiles += xloop * yloop;
     817             :     }
     818             : 
     819             :     // {(x, y): [((childx, childy), hasChildKML), ...], ...}
     820             :     std::map<std::pair<int, int>,
     821             :              std::vector<std::pair<std::pair<int, int>, bool>>>
     822          38 :         childTiles;
     823             :     std::map<std::pair<int, int>,
     824             :              std::vector<std::pair<std::pair<int, int>, bool>>>
     825          38 :         currentTiles;
     826          19 :     std::pair<int, int> childXYKey;
     827          19 :     std::pair<int, int> parentXYKey;
     828          44 :     for (zoom = maxzoom; zoom >= 0; --zoom)
     829             :     {
     830          25 :         const int rmaxxsize = tilexsize * (1 << (maxzoom - zoom));
     831          25 :         const int rmaxysize = tileysize * (1 << (maxzoom - zoom));
     832             : 
     833          25 :         const int xloop = std::max(1, static_cast<int>(xsize / rmaxxsize));
     834          25 :         const int yloop = std::max(1, static_cast<int>(ysize / rmaxysize));
     835             : 
     836          50 :         std::stringstream zoomStr;
     837          25 :         zoomStr << zoom;
     838             : 
     839          50 :         std::string zoomDir = outDir;
     840          25 :         zoomDir += "/" + zoomStr.str();
     841          25 :         VSIMkdir(zoomDir.c_str(), 0775);
     842             : 
     843          64 :         for (int ix = 0; ix < xloop; ix++)
     844             :         {
     845          39 :             int rxsize = static_cast<int>(rmaxxsize);
     846          39 :             int rx = static_cast<int>(ix * rmaxxsize);
     847          39 :             int dxsize = static_cast<int>(rxsize / rmaxxsize * tilexsize);
     848             : 
     849          78 :             std::stringstream ixStr;
     850          39 :             ixStr << ix;
     851             : 
     852          39 :             zoomDir = outDir;
     853          39 :             zoomDir += "/" + zoomStr.str();
     854          39 :             zoomDir += "/" + ixStr.str();
     855          39 :             VSIMkdir(zoomDir.c_str(), 0775);
     856             : 
     857         154 :             for (int iy = 0; iy < yloop; iy++)
     858             :             {
     859         115 :                 int rysize = static_cast<int>(rmaxysize);
     860         115 :                 int ry = static_cast<int>(ysize - (iy * rmaxysize)) - rysize;
     861         115 :                 int dysize = static_cast<int>(rysize / rmaxysize * tileysize);
     862             : 
     863         115 :                 std::stringstream iyStr;
     864         115 :                 iyStr << iy;
     865             : 
     866         115 :                 if (isAutoDriver)
     867             :                 {
     868          85 :                     int flags = KmlSuperOverlayReadDataset::DetectTransparency(
     869             :                         rxsize, rysize, rx, ry, dxsize, dysize, poSrcDS);
     870          85 :                     if (flags & (KmlSuperOverlayReadDataset::
     871             :                                      KMLSO_ContainsPartiallyTransparentPixels |
     872             :                                  KmlSuperOverlayReadDataset::
     873             :                                      KMLSO_ContainsTransparentPixels))
     874             :                     {
     875          45 :                         if (!(flags &
     876             :                               (KmlSuperOverlayReadDataset::
     877             :                                    KMLSO_ContainsPartiallyTransparentPixels |
     878             :                                KmlSuperOverlayReadDataset::
     879             :                                    KMLSO_ContainsOpaquePixels)))
     880             :                         {
     881             :                             // don't bother creating empty tiles
     882          40 :                             continue;
     883             :                         }
     884           5 :                         poOutputTileDriver = poPngOutputTileDriver;
     885           5 :                         isJpegDriver = false;
     886             :                     }
     887             :                     else
     888             :                     {
     889          40 :                         poOutputTileDriver = poJpegOutputTileDriver;
     890          40 :                         isJpegDriver = true;
     891             :                     }
     892             :                 }
     893             : 
     894         150 :                 std::string fileExt = ".jpg";
     895          75 :                 if (isJpegDriver == false)
     896             :                 {
     897          12 :                     fileExt = ".png";
     898             :                 }
     899         225 :                 std::string filename = zoomDir + "/" + iyStr.str() + fileExt;
     900          75 :                 if (isKmz)
     901             :                 {
     902          19 :                     fileVector.push_back(filename);
     903             :                 }
     904             : 
     905          75 :                 GenerateTiles(filename, zoom, rxsize, rysize, ix, iy, rx, ry,
     906             :                               dxsize, dysize, bands, poSrcDS,
     907             :                               poOutputTileDriver, poMemDriver, isJpegDriver);
     908         225 :                 std::string childKmlfile = zoomDir + "/" + iyStr.str() + ".kml";
     909          75 :                 if (isKmz)
     910             :                 {
     911          19 :                     fileVector.push_back(childKmlfile);
     912             :                 }
     913             : 
     914          75 :                 double tmpSouth =
     915          75 :                     adfGeoTransform[3] + adfGeoTransform[5] * ysize;
     916          75 :                 double zoomxpix = zoomxpixels[zoom];
     917          75 :                 double zoomypix = zoomypixels[zoom];
     918          75 :                 if (zoomxpix == 0)
     919             :                 {
     920           0 :                     zoomxpix = 1;
     921             :                 }
     922             : 
     923          75 :                 if (zoomypix == 0)
     924             :                 {
     925           0 :                     zoomypix = 1;
     926             :                 }
     927             : 
     928          75 :                 childXYKey = std::make_pair(ix, iy);
     929          75 :                 parentXYKey = std::make_pair(ix / 2, iy / 2);
     930             : 
     931             :                 // only create child KML if there are child tiles
     932          75 :                 bool hasChildKML = !childTiles[childXYKey].empty();
     933          75 :                 if (!currentTiles.count(parentXYKey))
     934             :                 {
     935          35 :                     currentTiles[parentXYKey] =
     936          70 :                         std::vector<std::pair<std::pair<int, int>, bool>>();
     937             :                 }
     938         150 :                 currentTiles[parentXYKey].push_back(
     939          75 :                     std::make_pair(std::make_pair(ix, iy), hasChildKML));
     940          75 :                 GenerateChildKml(childKmlfile, zoom, ix, iy, zoomxpix, zoomypix,
     941             :                                  dxsize, dysize, tmpSouth, adfGeoTransform[0],
     942             :                                  xsize, ysize, maxzoom, poTransform.get(),
     943             :                                  fileExt, fixAntiMeridian, pszAltitude,
     944          75 :                                  pszAltitudeMode, childTiles[childXYKey]);
     945             : 
     946          75 :                 nTileCount++;
     947          75 :                 pfnProgress(1.0 * nTileCount / nTotalTiles, "", pProgressData);
     948             :             }
     949             :         }
     950          25 :         childTiles = currentTiles;
     951          25 :         currentTiles.clear();
     952             :     }
     953             : 
     954          19 :     if (zipHandle != nullptr)
     955             :     {
     956          15 :         VSIFCloseL(zipHandle);
     957             :     }
     958             : 
     959          38 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
     960             :     auto poDS = std::unique_ptr<GDALDataset>(
     961          38 :         KmlSuperOverlayReadDataset::Open(&oOpenInfo));
     962          19 :     if (!poDS)
     963           1 :         poDS = std::make_unique<KmlSuperOverlayDummyDataset>();
     964          19 :     return poDS.release();
     965             : }
     966             : 
     967             : /************************************************************************/
     968             : /*                            KMLRemoveSlash()                          */
     969             : /************************************************************************/
     970             : 
     971             : /* replace "a/b/../c" pattern by "a/c" */
     972         122 : static std::string KMLRemoveSlash(const char *pszPathIn)
     973             : {
     974         244 :     std::string osRet(pszPathIn);
     975             : 
     976             :     while (true)
     977             :     {
     978         130 :         size_t nSlashDotDot = osRet.find("/../");
     979         130 :         if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
     980             :             break;
     981           8 :         size_t nPos = nSlashDotDot - 1;
     982          16 :         while (nPos > 0 && osRet[nPos] != '/')
     983           8 :             --nPos;
     984           8 :         if (nPos == 0)
     985           0 :             break;
     986          16 :         osRet = osRet.substr(0, nPos + 1) +
     987          24 :                 osRet.substr(nSlashDotDot + strlen("/../"));
     988           8 :     }
     989         122 :     return osRet;
     990             : }
     991             : 
     992             : /************************************************************************/
     993             : /*                      KmlSuperOverlayReadDataset()                    */
     994             : /************************************************************************/
     995             : 
     996          45 : KmlSuperOverlayReadDataset::KmlSuperOverlayReadDataset()
     997             : {
     998          45 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     999          45 :     m_oSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
    1000          45 :     adfGeoTransform[0] = 0.0;
    1001          45 :     adfGeoTransform[1] = 1.0;
    1002          45 :     adfGeoTransform[2] = 0.0;
    1003          45 :     adfGeoTransform[3] = 0.0;
    1004          45 :     adfGeoTransform[4] = 0.0;
    1005          45 :     adfGeoTransform[5] = 1.0;
    1006          45 : }
    1007             : 
    1008             : /************************************************************************/
    1009             : /*                     ~KmlSuperOverlayReadDataset()                    */
    1010             : /************************************************************************/
    1011             : 
    1012          90 : KmlSuperOverlayReadDataset::~KmlSuperOverlayReadDataset()
    1013             : 
    1014             : {
    1015          45 :     if (psRoot != nullptr)
    1016          36 :         CPLDestroyXMLNode(psRoot);
    1017          45 :     KmlSuperOverlayReadDataset::CloseDependentDatasets();
    1018          90 : }
    1019             : 
    1020             : /************************************************************************/
    1021             : /*                         CloseDependentDatasets()                     */
    1022             : /************************************************************************/
    1023             : 
    1024          45 : int KmlSuperOverlayReadDataset::CloseDependentDatasets()
    1025             : {
    1026          45 :     int bRet = FALSE;
    1027          45 :     if (poDSIcon)
    1028             :     {
    1029          36 :         CPLString l_osFilename(poDSIcon->GetDescription());
    1030          36 :         poDSIcon.reset();
    1031          36 :         VSIUnlink(l_osFilename);
    1032          36 :         bRet = TRUE;
    1033             :     }
    1034             : 
    1035          45 :     LinkedDataset *psCur = psFirstLink;
    1036          45 :     psFirstLink = nullptr;
    1037          45 :     psLastLink = nullptr;
    1038             : 
    1039          57 :     while (psCur != nullptr)
    1040             :     {
    1041          12 :         LinkedDataset *psNext = psCur->psNext;
    1042          12 :         if (psCur->poDS != nullptr)
    1043             :         {
    1044          12 :             if (psCur->poDS->nRefCount == 1)
    1045          12 :                 bRet = TRUE;
    1046          12 :             GDALClose(psCur->poDS);
    1047             :         }
    1048          12 :         delete psCur;
    1049          12 :         psCur = psNext;
    1050             :     }
    1051             : 
    1052          45 :     if (!m_apoOverviewDS.empty())
    1053             :     {
    1054           7 :         bRet = TRUE;
    1055           7 :         m_apoOverviewDS.clear();
    1056             :     }
    1057             : 
    1058          45 :     return bRet;
    1059             : }
    1060             : 
    1061             : /************************************************************************/
    1062             : /*                          GetSpatialRef()                             */
    1063             : /************************************************************************/
    1064             : 
    1065           1 : const OGRSpatialReference *KmlSuperOverlayReadDataset::GetSpatialRef() const
    1066             : 
    1067             : {
    1068           1 :     return &m_oSRS;
    1069             : }
    1070             : 
    1071             : /************************************************************************/
    1072             : /*                          GetGeoTransform()                           */
    1073             : /************************************************************************/
    1074             : 
    1075           1 : CPLErr KmlSuperOverlayReadDataset::GetGeoTransform(double *padfGeoTransform)
    1076             : {
    1077           1 :     memcpy(padfGeoTransform, adfGeoTransform.data(), 6 * sizeof(double));
    1078           1 :     return CE_None;
    1079             : }
    1080             : 
    1081             : /************************************************************************/
    1082             : /*                        KmlSuperOverlayRasterBand()                   */
    1083             : /************************************************************************/
    1084             : 
    1085         180 : KmlSuperOverlayRasterBand::KmlSuperOverlayRasterBand(
    1086         180 :     KmlSuperOverlayReadDataset *poDSIn, int /* nBand*/)
    1087             : {
    1088         180 :     nRasterXSize = poDSIn->nRasterXSize;
    1089         180 :     nRasterYSize = poDSIn->nRasterYSize;
    1090         180 :     eDataType = GDT_Byte;
    1091         180 :     nBlockXSize = 256;
    1092         180 :     nBlockYSize = 256;
    1093         180 : }
    1094             : 
    1095             : /************************************************************************/
    1096             : /*                               IReadBlock()                           */
    1097             : /************************************************************************/
    1098             : 
    1099           8 : CPLErr KmlSuperOverlayRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    1100             :                                              void *pData)
    1101             : {
    1102           8 :     int nXOff = nBlockXOff * nBlockXSize;
    1103           8 :     int nYOff = nBlockYOff * nBlockYSize;
    1104           8 :     int nXSize = nBlockXSize;
    1105           8 :     int nYSize = nBlockYSize;
    1106           8 :     if (nXOff + nXSize > nRasterXSize)
    1107           4 :         nXSize = nRasterXSize - nXOff;
    1108           8 :     if (nYOff + nYSize > nRasterYSize)
    1109           8 :         nYSize = nRasterYSize - nYOff;
    1110             : 
    1111             :     GDALRasterIOExtraArg sExtraArg;
    1112           8 :     INIT_RASTERIO_EXTRA_ARG(sExtraArg);
    1113             : 
    1114          16 :     return IRasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize, pData, nXSize,
    1115          16 :                      nYSize, eDataType, 1, nBlockXSize, &sExtraArg);
    1116             : }
    1117             : 
    1118             : /************************************************************************/
    1119             : /*                          GetColorInterpretation()                    */
    1120             : /************************************************************************/
    1121             : 
    1122          15 : GDALColorInterp KmlSuperOverlayRasterBand::GetColorInterpretation()
    1123             : {
    1124          15 :     return static_cast<GDALColorInterp>(GCI_RedBand + nBand - 1);
    1125             : }
    1126             : 
    1127             : /************************************************************************/
    1128             : /*                            IRasterIO()                               */
    1129             : /************************************************************************/
    1130             : 
    1131          47 : CPLErr KmlSuperOverlayRasterBand::IRasterIO(
    1132             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1133             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1134             :     GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
    1135             : {
    1136             :     KmlSuperOverlayReadDataset *poGDS =
    1137          47 :         cpl::down_cast<KmlSuperOverlayReadDataset *>(poDS);
    1138             : 
    1139          47 :     return poGDS->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
    1140             :                             nBufXSize, nBufYSize, eBufType, 1, &nBand,
    1141          47 :                             nPixelSpace, nLineSpace, 0, psExtraArg);
    1142             : }
    1143             : 
    1144             : /************************************************************************/
    1145             : /*                          GetOverviewCount()                          */
    1146             : /************************************************************************/
    1147             : 
    1148          31 : int KmlSuperOverlayRasterBand::GetOverviewCount()
    1149             : {
    1150             :     KmlSuperOverlayReadDataset *poGDS =
    1151          31 :         cpl::down_cast<KmlSuperOverlayReadDataset *>(poDS);
    1152             : 
    1153          31 :     return static_cast<int>(poGDS->m_apoOverviewDS.size());
    1154             : }
    1155             : 
    1156             : /************************************************************************/
    1157             : /*                           GetOverview()                              */
    1158             : /************************************************************************/
    1159             : 
    1160          37 : GDALRasterBand *KmlSuperOverlayRasterBand::GetOverview(int iOvr)
    1161             : {
    1162             :     KmlSuperOverlayReadDataset *poGDS =
    1163          37 :         cpl::down_cast<KmlSuperOverlayReadDataset *>(poDS);
    1164             : 
    1165          37 :     if (iOvr < 0 || iOvr >= static_cast<int>(poGDS->m_apoOverviewDS.size()))
    1166           0 :         return nullptr;
    1167             : 
    1168          37 :     return poGDS->m_apoOverviewDS[iOvr]->GetRasterBand(nBand);
    1169             : }
    1170             : 
    1171             : /************************************************************************/
    1172             : /*                     KmlSuperOverlayGetBoundingBox()                  */
    1173             : /************************************************************************/
    1174             : 
    1175         114 : static bool KmlSuperOverlayGetBoundingBox(const CPLXMLNode *psNode,
    1176             :                                           std::array<double, 4> &adfExtents)
    1177             : {
    1178         114 :     const CPLXMLNode *psBox = CPLGetXMLNode(psNode, "LatLonBox");
    1179         114 :     if (!psBox)
    1180          75 :         psBox = CPLGetXMLNode(psNode, "LatLonAltBox");
    1181         114 :     if (psBox)
    1182             :     {
    1183         112 :         const char *pszNorth = CPLGetXMLValue(psBox, "north", nullptr);
    1184         112 :         const char *pszSouth = CPLGetXMLValue(psBox, "south", nullptr);
    1185         112 :         const char *pszEast = CPLGetXMLValue(psBox, "east", nullptr);
    1186         112 :         const char *pszWest = CPLGetXMLValue(psBox, "west", nullptr);
    1187         112 :         if (pszNorth && pszSouth && pszEast && pszWest)
    1188             :         {
    1189         112 :             adfExtents[0] = CPLAtof(pszWest);
    1190         112 :             adfExtents[1] = CPLAtof(pszSouth);
    1191         112 :             adfExtents[2] = CPLAtof(pszEast);
    1192         112 :             adfExtents[3] = CPLAtof(pszNorth);
    1193             : 
    1194         112 :             return true;
    1195             :         }
    1196             :     }
    1197             :     else
    1198             :     {
    1199           2 :         const CPLXMLNode *psLatLonQuad = CPLGetXMLNode(psNode, "gx:LatLonQuad");
    1200           2 :         if (psLatLonQuad)
    1201             :         {
    1202             :             const CPLStringList aosTuples(CSLTokenizeString2(
    1203           2 :                 CPLGetXMLValue(psLatLonQuad, "coordinates", ""), " \t\n\r", 0));
    1204           2 :             if (aosTuples.size() == 4)
    1205             :             {
    1206             :                 const CPLStringList aosLL(
    1207           2 :                     CSLTokenizeString2(aosTuples[0], ",", 0));
    1208             :                 const CPLStringList aosLR(
    1209           2 :                     CSLTokenizeString2(aosTuples[1], ",", 0));
    1210             :                 const CPLStringList aosUR(
    1211           2 :                     CSLTokenizeString2(aosTuples[2], ",", 0));
    1212             :                 const CPLStringList aosUL(
    1213           2 :                     CSLTokenizeString2(aosTuples[3], ",", 0));
    1214           4 :                 if (aosLL.size() >= 2 && aosLR.size() >= 2 &&
    1215           2 :                     aosUR.size() >= 2 && aosUL.size() >= 2 &&
    1216           2 :                     strcmp(aosLL[0], aosUL[0]) == 0 &&
    1217           1 :                     strcmp(aosLL[1], aosLR[1]) == 0 &&
    1218           5 :                     strcmp(aosLR[0], aosUR[0]) == 0 &&
    1219           1 :                     strcmp(aosUR[1], aosUL[1]) == 0)
    1220             :                 {
    1221           1 :                     adfExtents[0] = CPLAtof(aosLL[0]);
    1222           1 :                     adfExtents[1] = CPLAtof(aosLL[1]);
    1223           1 :                     adfExtents[2] = CPLAtof(aosUR[0]);
    1224           1 :                     adfExtents[3] = CPLAtof(aosUR[1]);
    1225           1 :                     return true;
    1226             :                 }
    1227             :             }
    1228             :         }
    1229             :     }
    1230             : 
    1231           1 :     return false;
    1232             : }
    1233             : 
    1234             : /************************************************************************/
    1235             : /*                            IRasterIO()                               */
    1236             : /************************************************************************/
    1237             : 
    1238             : class SubImageDesc
    1239             : {
    1240             :   public:
    1241             :     GDALDataset *poDS = nullptr;
    1242             :     std::array<double, 4> adfExtents = {0, 0, 0, 0};
    1243             : };
    1244             : 
    1245          88 : CPLErr KmlSuperOverlayReadDataset::IRasterIO(
    1246             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1247             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1248             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1249             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    1250             : {
    1251          88 :     if (eRWFlag == GF_Write)
    1252           0 :         return CE_Failure;
    1253             : 
    1254          88 :     if (bIsOvr)
    1255             :     {
    1256             :         GDALRasterIOExtraArg sExtraArgs;
    1257           1 :         GDALCopyRasterIOExtraArg(&sExtraArgs, psExtraArg);
    1258           1 :         const int nOvrFactor = poParent->nFactor / nFactor;
    1259           1 :         if (sExtraArgs.bFloatingPointWindowValidity)
    1260             :         {
    1261           0 :             sExtraArgs.dfXOff *= nOvrFactor;
    1262           0 :             sExtraArgs.dfYOff *= nOvrFactor;
    1263           0 :             sExtraArgs.dfXSize *= nOvrFactor;
    1264           0 :             sExtraArgs.dfYSize *= nOvrFactor;
    1265             :         }
    1266           1 :         return poParent->IRasterIO(
    1267             :             eRWFlag, nXOff * nOvrFactor, nYOff * nOvrFactor,
    1268             :             nXSize * nOvrFactor, nYSize * nOvrFactor, pData, nBufXSize,
    1269             :             nBufYSize, eBufType, nBandCount, panBandMap, nPixelSpace,
    1270           1 :             nLineSpace, nBandSpace, &sExtraArgs);
    1271             :     }
    1272             : 
    1273          87 :     double dfXOff = 1.0 * nXOff / nFactor;
    1274          87 :     double dfYOff = 1.0 * nYOff / nFactor;
    1275          87 :     double dfXSize = 1.0 * nXSize / nFactor;
    1276          87 :     double dfYSize = 1.0 * nYSize / nFactor;
    1277             : 
    1278          87 :     int nIconCount = poDSIcon->GetRasterCount();
    1279             : 
    1280          87 :     if (nBufXSize > dfXSize || nBufYSize > dfYSize)
    1281             :     {
    1282             :         const double dfRequestXMin =
    1283          26 :             adfGeoTransform[0] + nXOff * adfGeoTransform[1];
    1284             :         const double dfRequestXMax =
    1285          26 :             adfGeoTransform[0] + (nXOff + nXSize) * adfGeoTransform[1];
    1286             :         const double dfRequestYMin =
    1287          26 :             adfGeoTransform[3] + (nYOff + nYSize) * adfGeoTransform[5];
    1288             :         const double dfRequestYMax =
    1289          26 :             adfGeoTransform[3] + nYOff * adfGeoTransform[5];
    1290             : 
    1291          26 :         const CPLXMLNode *psIter = psDocument->psChild;
    1292          26 :         std::vector<SubImageDesc> aoImages;
    1293          26 :         const double dfXRes = adfGeoTransform[1] * nFactor;
    1294          26 :         const double dfYRes = -adfGeoTransform[5] * nFactor;
    1295          26 :         double dfNewXRes = dfXRes;
    1296          26 :         double dfNewYRes = dfYRes;
    1297             : 
    1298         228 :         while (psIter != nullptr)
    1299             :         {
    1300         202 :             const CPLXMLNode *psRegion = nullptr;
    1301         202 :             const CPLXMLNode *psLink = nullptr;
    1302         202 :             std::array<double, 4> adfExtents = {0, 0, 0, 0};
    1303         202 :             const char *pszHref = nullptr;
    1304         606 :             if (psIter->eType == CXT_Element &&
    1305         202 :                 strcmp(psIter->pszValue, "NetworkLink") == 0 &&
    1306          72 :                 (psRegion = CPLGetXMLNode(psIter, "Region")) != nullptr &&
    1307          72 :                 (psLink = CPLGetXMLNode(psIter, "Link")) != nullptr &&
    1308         476 :                 KmlSuperOverlayGetBoundingBox(psRegion, adfExtents) &&
    1309          72 :                 (pszHref = CPLGetXMLValue(psLink, "href", nullptr)) != nullptr)
    1310             :             {
    1311          72 :                 if (dfRequestXMin < adfExtents[2] &&
    1312          72 :                     dfRequestXMax > adfExtents[0] &&
    1313         200 :                     dfRequestYMin < adfExtents[3] &&
    1314          56 :                     dfRequestYMax > adfExtents[1])
    1315             :                 {
    1316          76 :                     CPLString osSubFilename;
    1317          38 :                     if (STARTS_WITH(pszHref, "http"))
    1318             :                         osSubFilename =
    1319           0 :                             CPLSPrintf("/vsicurl_streaming/%s", pszHref);
    1320             :                     else
    1321             :                     {
    1322          38 :                         const char *pszBaseFilename = osFilename.c_str();
    1323          38 :                         if (EQUAL(CPLGetExtensionSafe(pszBaseFilename).c_str(),
    1324          38 :                                   "kmz") &&
    1325           0 :                             !STARTS_WITH(pszBaseFilename, "/vsizip/"))
    1326             :                         {
    1327           0 :                             osSubFilename = "/vsizip/";
    1328           0 :                             osSubFilename += CPLGetPathSafe(pszBaseFilename);
    1329           0 :                             osSubFilename += "/";
    1330           0 :                             osSubFilename += pszHref;
    1331             :                         }
    1332             :                         else
    1333             :                         {
    1334          38 :                             osSubFilename = CPLFormFilenameSafe(
    1335          76 :                                 CPLGetPathSafe(pszBaseFilename).c_str(),
    1336          38 :                                 pszHref, nullptr);
    1337             :                         }
    1338          38 :                         osSubFilename = KMLRemoveSlash(osSubFilename);
    1339             :                     }
    1340             : 
    1341          38 :                     KmlSuperOverlayReadDataset *poSubImageDS = nullptr;
    1342          38 :                     if (EQUAL(CPLGetExtensionSafe(osSubFilename).c_str(),
    1343             :                               "kml"))
    1344             :                     {
    1345          38 :                         KmlSuperOverlayReadDataset *poRoot =
    1346          38 :                             poParent ? poParent : this;
    1347             :                         LinkedDataset *psLinkDS =
    1348          38 :                             poRoot->oMapChildren[osSubFilename];
    1349          38 :                         if (psLinkDS == nullptr)
    1350             :                         {
    1351          12 :                             if (poRoot->oMapChildren.size() == 64)
    1352             :                             {
    1353           0 :                                 psLinkDS = poRoot->psLastLink;
    1354           0 :                                 CPLAssert(psLinkDS);
    1355           0 :                                 poRoot->oMapChildren.erase(
    1356           0 :                                     psLinkDS->osSubFilename);
    1357           0 :                                 GDALClose(psLinkDS->poDS);
    1358           0 :                                 if (psLinkDS->psPrev != nullptr)
    1359             :                                 {
    1360           0 :                                     poRoot->psLastLink = psLinkDS->psPrev;
    1361           0 :                                     psLinkDS->psPrev->psNext = nullptr;
    1362             :                                 }
    1363             :                                 else
    1364             :                                 {
    1365           0 :                                     CPLAssert(psLinkDS == poRoot->psFirstLink);
    1366           0 :                                     poRoot->psFirstLink = nullptr;
    1367           0 :                                     poRoot->psLastLink = nullptr;
    1368             :                                 }
    1369             :                             }
    1370             :                             else
    1371          12 :                                 psLinkDS = new LinkedDataset();
    1372             : 
    1373          12 :                             poRoot->oMapChildren[osSubFilename] = psLinkDS;
    1374             :                             poSubImageDS =
    1375          12 :                                 cpl::down_cast<KmlSuperOverlayReadDataset *>(
    1376             :                                     KmlSuperOverlayReadDataset::Open(
    1377             :                                         osSubFilename, poRoot));
    1378          12 :                             if (poSubImageDS)
    1379          12 :                                 poSubImageDS->MarkAsShared();
    1380             :                             else
    1381           0 :                                 CPLDebug("KMLSuperOverlay", "Cannot open %s",
    1382             :                                          osSubFilename.c_str());
    1383          12 :                             psLinkDS->osSubFilename = osSubFilename;
    1384          12 :                             psLinkDS->poDS = poSubImageDS;
    1385          12 :                             psLinkDS->psPrev = nullptr;
    1386          12 :                             psLinkDS->psNext = poRoot->psFirstLink;
    1387          12 :                             if (poRoot->psFirstLink != nullptr)
    1388             :                             {
    1389           8 :                                 CPLAssert(poRoot->psFirstLink->psPrev ==
    1390             :                                           nullptr);
    1391           8 :                                 poRoot->psFirstLink->psPrev = psLinkDS;
    1392             :                             }
    1393             :                             else
    1394           4 :                                 poRoot->psLastLink = psLinkDS;
    1395          12 :                             poRoot->psFirstLink = psLinkDS;
    1396             :                         }
    1397             :                         else
    1398             :                         {
    1399          26 :                             poSubImageDS = psLinkDS->poDS;
    1400          26 :                             if (psLinkDS != poRoot->psFirstLink)
    1401             :                             {
    1402          26 :                                 if (psLinkDS == poRoot->psLastLink)
    1403             :                                 {
    1404          25 :                                     poRoot->psLastLink = psLinkDS->psPrev;
    1405          25 :                                     CPLAssert(poRoot->psLastLink != nullptr);
    1406          25 :                                     poRoot->psLastLink->psNext = nullptr;
    1407             :                                 }
    1408             :                                 else
    1409           1 :                                     psLinkDS->psNext->psPrev = psLinkDS->psPrev;
    1410          26 :                                 CPLAssert(psLinkDS->psPrev != nullptr);
    1411          26 :                                 psLinkDS->psPrev->psNext = psLinkDS->psNext;
    1412          26 :                                 psLinkDS->psPrev = nullptr;
    1413          26 :                                 poRoot->psFirstLink->psPrev = psLinkDS;
    1414          26 :                                 psLinkDS->psNext = poRoot->psFirstLink;
    1415          26 :                                 poRoot->psFirstLink = psLinkDS;
    1416             :                             }
    1417             :                         }
    1418             :                     }
    1419          38 :                     if (poSubImageDS)
    1420             :                     {
    1421          38 :                         int nSubImageXSize = poSubImageDS->GetRasterXSize();
    1422          38 :                         int nSubImageYSize = poSubImageDS->GetRasterYSize();
    1423          38 :                         adfExtents[0] = poSubImageDS->adfGeoTransform[0];
    1424          76 :                         adfExtents[1] =
    1425          38 :                             poSubImageDS->adfGeoTransform[3] +
    1426          38 :                             nSubImageYSize * poSubImageDS->adfGeoTransform[5];
    1427          76 :                         adfExtents[2] =
    1428          38 :                             poSubImageDS->adfGeoTransform[0] +
    1429          38 :                             nSubImageXSize * poSubImageDS->adfGeoTransform[1];
    1430          38 :                         adfExtents[3] = poSubImageDS->adfGeoTransform[3];
    1431             : 
    1432             :                         double dfSubXRes =
    1433          38 :                             (adfExtents[2] - adfExtents[0]) / nSubImageXSize;
    1434             :                         double dfSubYRes =
    1435          38 :                             (adfExtents[3] - adfExtents[1]) / nSubImageYSize;
    1436             : 
    1437          38 :                         if (dfSubXRes < dfNewXRes)
    1438          18 :                             dfNewXRes = dfSubXRes;
    1439          38 :                         if (dfSubYRes < dfNewYRes)
    1440          18 :                             dfNewYRes = dfSubYRes;
    1441             : 
    1442          38 :                         SubImageDesc oImageDesc;
    1443          38 :                         oImageDesc.poDS = poSubImageDS;
    1444          38 :                         poSubImageDS->Reference();
    1445          38 :                         oImageDesc.adfExtents = adfExtents;
    1446          38 :                         aoImages.push_back(oImageDesc);
    1447             :                     }
    1448          38 :                     CPL_IGNORE_RET_VAL(osSubFilename);
    1449             :                 }
    1450             :             }
    1451         202 :             psIter = psIter->psNext;
    1452             :         }
    1453             : 
    1454          26 :         if (dfNewXRes < dfXRes || dfNewYRes < dfYRes)
    1455             :         {
    1456          18 :             const double dfXFactor = dfXRes / dfNewXRes;
    1457          18 :             const double dfYFactor = dfYRes / dfNewYRes;
    1458             :             auto poVRTDS = std::make_unique<VRTDataset>(
    1459           0 :                 static_cast<int>(nRasterXSize * dfXFactor + 0.5),
    1460          18 :                 static_cast<int>(nRasterYSize * dfYFactor + 0.5));
    1461             : 
    1462          90 :             for (int iBandIdx = 0; iBandIdx < 4; iBandIdx++)
    1463             :             {
    1464          72 :                 poVRTDS->AddBand(GDT_Byte, nullptr);
    1465             : 
    1466             :                 auto poVRTBand = static_cast<VRTSourcedRasterBand *>(
    1467          72 :                     poVRTDS->GetRasterBand(iBandIdx + 1));
    1468          72 :                 const int nBand = iBandIdx + 1;
    1469          72 :                 if (nBand <= nIconCount || (nIconCount == 1 && nBand != 4))
    1470             :                 {
    1471          72 :                     poVRTBand->AddSimpleSource(
    1472             :                         poDSIcon->GetRasterBand(nBand <= nIconCount ? nBand
    1473             :                                                                     : 1),
    1474          72 :                         0, 0, nRasterXSize, nRasterYSize, 0, 0,
    1475          72 :                         poVRTDS->GetRasterXSize(), poVRTDS->GetRasterYSize(),
    1476             :                         nullptr, VRT_NODATA_UNSET);
    1477             :                 }
    1478             :                 else
    1479             :                 {
    1480           0 :                     poVRTBand->AddComplexSource(
    1481           0 :                         poDSIcon->GetRasterBand(1), 0, 0, nRasterXSize,
    1482           0 :                         nRasterYSize, 0, 0, poVRTDS->GetRasterXSize(),
    1483           0 :                         poVRTDS->GetRasterYSize(), VRT_NODATA_UNSET, 0, 255);
    1484             :                 }
    1485             :             }
    1486             : 
    1487          56 :             for (const auto &oImage : aoImages)
    1488             :             {
    1489             :                 const int nDstXOff = static_cast<int>(
    1490          38 :                     (oImage.adfExtents[0] - adfGeoTransform[0]) / dfNewXRes +
    1491          38 :                     0.5);
    1492             :                 const int nDstYOff = static_cast<int>(
    1493          38 :                     (adfGeoTransform[3] - oImage.adfExtents[3]) / dfNewYRes +
    1494          38 :                     0.5);
    1495             :                 const int nDstXSize = static_cast<int>(
    1496          38 :                     (oImage.adfExtents[2] - oImage.adfExtents[0]) / dfNewXRes +
    1497          38 :                     0.5);
    1498             :                 const int nDstYSize = static_cast<int>(
    1499          38 :                     (oImage.adfExtents[3] - oImage.adfExtents[1]) / dfNewYRes +
    1500          38 :                     0.5);
    1501             : 
    1502          38 :                 const int nSrcBandCount = oImage.poDS->GetRasterCount();
    1503         190 :                 for (int iBandIdx = 0; iBandIdx < 4; iBandIdx++)
    1504             :                 {
    1505         152 :                     const int nBand = iBandIdx + 1;
    1506             :                     auto poVRTBand = static_cast<VRTSourcedRasterBand *>(
    1507         152 :                         poVRTDS->GetRasterBand(iBandIdx + 1));
    1508             : 
    1509         152 :                     if (nBand <= nSrcBandCount ||
    1510           0 :                         (nSrcBandCount == 1 && nBand != 4))
    1511             :                     {
    1512         152 :                         poVRTBand->AddSimpleSource(
    1513         152 :                             oImage.poDS->GetRasterBand(
    1514             :                                 nBand <= nSrcBandCount ? nBand : 1),
    1515         152 :                             0, 0, oImage.poDS->GetRasterXSize(),
    1516         152 :                             oImage.poDS->GetRasterYSize(), nDstXOff, nDstYOff,
    1517             :                             nDstXSize, nDstYSize, nullptr, VRT_NODATA_UNSET);
    1518             :                     }
    1519             :                     else
    1520             :                     {
    1521           0 :                         poVRTBand->AddComplexSource(
    1522           0 :                             oImage.poDS->GetRasterBand(1), 0, 0,
    1523           0 :                             oImage.poDS->GetRasterXSize(),
    1524           0 :                             oImage.poDS->GetRasterYSize(), nDstXOff, nDstYOff,
    1525             :                             nDstXSize, nDstYSize, VRT_NODATA_UNSET, 0, 255);
    1526             :                     }
    1527             :                 }
    1528             :             }
    1529             : 
    1530          18 :             int nReqXOff = static_cast<int>(dfXOff * dfXFactor + 0.5);
    1531          18 :             int nReqYOff = static_cast<int>(dfYOff * dfYFactor + 0.5);
    1532          18 :             int nReqXSize = static_cast<int>(dfXSize * dfXFactor + 0.5);
    1533          18 :             int nReqYSize = static_cast<int>(dfYSize * dfYFactor + 0.5);
    1534          18 :             if (nReqXOff + nReqXSize > poVRTDS->GetRasterXSize())
    1535           0 :                 nReqXSize = poVRTDS->GetRasterXSize() - nReqXOff;
    1536          18 :             if (nReqYOff + nReqYSize > poVRTDS->GetRasterYSize())
    1537           0 :                 nReqYSize = poVRTDS->GetRasterYSize() - nReqYOff;
    1538             : 
    1539             :             GDALRasterIOExtraArg sExtraArgs;
    1540          18 :             INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
    1541             :             // cppcheck-suppress redundantAssignment
    1542          18 :             sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
    1543          18 :             CPLErr eErr = poVRTDS->RasterIO(
    1544             :                 eRWFlag, nReqXOff, nReqYOff, nReqXSize, nReqYSize, pData,
    1545             :                 nBufXSize, nBufYSize, eBufType, nBandCount, panBandMap,
    1546             :                 nPixelSpace, nLineSpace, nBandSpace, &sExtraArgs);
    1547             : 
    1548          56 :             for (auto &oImage : aoImages)
    1549             :             {
    1550          38 :                 oImage.poDS->Dereference();
    1551             :             }
    1552             : 
    1553          18 :             return eErr;
    1554             :         }
    1555             :     }
    1556             : 
    1557          69 :     GDALProgressFunc pfnProgressGlobal = psExtraArg->pfnProgress;
    1558          69 :     void *pProgressDataGlobal = psExtraArg->pProgressData;
    1559          69 :     CPLErr eErr = CE_None;
    1560             : 
    1561         144 :     for (int iBandIdx = 0; iBandIdx < nBandCount && eErr == CE_None; iBandIdx++)
    1562             :     {
    1563          75 :         int nBand = panBandMap[iBandIdx];
    1564             : 
    1565          75 :         if ((nIconCount > 1 || nBand == 4) && nBand > nIconCount)
    1566             :         {
    1567           0 :             GByte nVal = (nBand == 4) ? 255 : 0;
    1568           0 :             for (int j = 0; j < nBufYSize; j++)
    1569             :             {
    1570           0 :                 GDALCopyWords(&nVal, GDT_Byte, 0,
    1571           0 :                               static_cast<GByte *>(pData) + j * nLineSpace +
    1572           0 :                                   iBandIdx * nBandSpace,
    1573             :                               eBufType, static_cast<int>(nPixelSpace),
    1574             :                               nBufXSize);
    1575             :             }
    1576           0 :             continue;
    1577             :         }
    1578             : 
    1579          75 :         int nIconBand = (nIconCount == 1) ? 1 : nBand;
    1580             : 
    1581          75 :         int nReqXOff = static_cast<int>(dfXOff + 0.5);
    1582          75 :         int nReqYOff = static_cast<int>(dfYOff + 0.5);
    1583          75 :         int nReqXSize = static_cast<int>(dfXSize + 0.5);
    1584          75 :         int nReqYSize = static_cast<int>(dfYSize + 0.5);
    1585          75 :         if (nReqXOff + nReqXSize > poDSIcon->GetRasterXSize())
    1586           0 :             nReqXSize = poDSIcon->GetRasterXSize() - nReqXOff;
    1587          75 :         if (nReqYOff + nReqYSize > poDSIcon->GetRasterYSize())
    1588           0 :             nReqYSize = poDSIcon->GetRasterYSize() - nReqYOff;
    1589             : 
    1590             :         GDALRasterIOExtraArg sExtraArgs;
    1591          75 :         INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
    1592             :         // cppcheck-suppress redundantAssignment
    1593          75 :         sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
    1594          75 :         sExtraArgs.pfnProgress = GDALScaledProgress;
    1595         150 :         sExtraArgs.pProgressData = GDALCreateScaledProgress(
    1596          75 :             1.0 * iBandIdx / nBandCount, 1.0 * (iBandIdx + 1) / nBandCount,
    1597             :             pfnProgressGlobal, pProgressDataGlobal);
    1598             : 
    1599          75 :         eErr = poDSIcon->GetRasterBand(nIconBand)->RasterIO(
    1600             :             eRWFlag, nReqXOff, nReqYOff, nReqXSize, nReqYSize,
    1601          75 :             static_cast<GByte *>(pData) + nBandSpace * iBandIdx, nBufXSize,
    1602             :             nBufYSize, eBufType, nPixelSpace, nLineSpace, &sExtraArgs);
    1603             : 
    1604          75 :         GDALDestroyScaledProgress(sExtraArgs.pProgressData);
    1605             :     }
    1606             : 
    1607          69 :     psExtraArg->pfnProgress = pfnProgressGlobal;
    1608          69 :     psExtraArg->pProgressData = pProgressDataGlobal;
    1609             : 
    1610          69 :     return eErr;
    1611             : }
    1612             : 
    1613             : /************************************************************************/
    1614             : /*                    KmlSuperOverlayFindRegionStart()                  */
    1615             : /************************************************************************/
    1616             : 
    1617        1444 : static int KmlSuperOverlayFindRegionStartInternal(CPLXMLNode *psNode,
    1618             :                                                   CPLXMLNode **ppsRegion,
    1619             :                                                   CPLXMLNode **ppsDocument,
    1620             :                                                   CPLXMLNode **ppsGroundOverlay,
    1621             :                                                   CPLXMLNode **ppsLink)
    1622             : {
    1623        1444 :     CPLXMLNode *psRegion = nullptr;
    1624        1444 :     CPLXMLNode *psLink = nullptr;
    1625        1444 :     CPLXMLNode *psGroundOverlay = nullptr;
    1626        2910 :     if (strcmp(psNode->pszValue, "NetworkLink") == 0 &&
    1627        1466 :         (psRegion = CPLGetXMLNode(psNode, "Region")) != nullptr &&
    1628          22 :         (psLink = CPLGetXMLNode(psNode, "Link")) != nullptr)
    1629             :     {
    1630          22 :         *ppsRegion = psRegion;
    1631          22 :         *ppsLink = psLink;
    1632          22 :         return TRUE;
    1633             :     }
    1634        4164 :     if ((strcmp(psNode->pszValue, "Document") == 0 ||
    1635        1320 :          strcmp(psNode->pszValue, "Folder") == 0) &&
    1636        2788 :         (psRegion = CPLGetXMLNode(psNode, "Region")) != nullptr &&
    1637          46 :         (psGroundOverlay = CPLGetXMLNode(psNode, "GroundOverlay")) != nullptr)
    1638             :     {
    1639          46 :         *ppsDocument = psNode;
    1640          46 :         *ppsRegion = psRegion;
    1641          46 :         *ppsGroundOverlay = psGroundOverlay;
    1642          46 :         return TRUE;
    1643             :     }
    1644             : 
    1645        1376 :     CPLXMLNode *psIter = psNode->psChild;
    1646        4061 :     while (psIter != nullptr)
    1647             :     {
    1648        2775 :         if (psIter->eType == CXT_Element)
    1649             :         {
    1650        1236 :             if (KmlSuperOverlayFindRegionStartInternal(
    1651        1236 :                     psIter, ppsRegion, ppsDocument, ppsGroundOverlay, ppsLink))
    1652          90 :                 return TRUE;
    1653             :         }
    1654             : 
    1655        2685 :         psIter = psIter->psNext;
    1656             :     }
    1657             : 
    1658        1286 :     return FALSE;
    1659             : }
    1660             : 
    1661         104 : static int KmlSuperOverlayFindRegionStart(CPLXMLNode *psNode,
    1662             :                                           CPLXMLNode **ppsRegion,
    1663             :                                           CPLXMLNode **ppsDocument,
    1664             :                                           CPLXMLNode **ppsGroundOverlay,
    1665             :                                           CPLXMLNode **ppsLink)
    1666             : {
    1667         104 :     CPLXMLNode *psIter = psNode;
    1668         244 :     while (psIter != nullptr)
    1669             :     {
    1670         208 :         if (psIter->eType == CXT_Element)
    1671             :         {
    1672         208 :             if (KmlSuperOverlayFindRegionStartInternal(
    1673         208 :                     psIter, ppsRegion, ppsDocument, ppsGroundOverlay, ppsLink))
    1674          68 :                 return TRUE;
    1675             :         }
    1676             : 
    1677         140 :         psIter = psIter->psNext;
    1678             :     }
    1679             : 
    1680          36 :     return FALSE;
    1681             : }
    1682             : 
    1683             : /************************************************************************/
    1684             : /*                             Identify()                               */
    1685             : /************************************************************************/
    1686             : 
    1687       53476 : int KmlSuperOverlayReadDataset::Identify(GDALOpenInfo *poOpenInfo)
    1688             : 
    1689             : {
    1690       53476 :     const char *pszExt = poOpenInfo->osExtension.c_str();
    1691       53474 :     if (EQUAL(pszExt, "kmz"))
    1692         143 :         return -1;
    1693       53331 :     if (poOpenInfo->nHeaderBytes == 0)
    1694       48636 :         return FALSE;
    1695        4695 :     if (
    1696             : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    1697        4695 :         !EQUAL(pszExt, "kml") ||
    1698             : #endif
    1699          89 :         strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1700             :                "<kml") == nullptr)
    1701        4606 :         return FALSE;
    1702             : 
    1703         223 :     for (int i = 0; i < 2; i++)
    1704             :     {
    1705             :         // Leave below variable here as the TryToIngest() at end might
    1706             :         // invalidate it
    1707         156 :         const char *pszText =
    1708             :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    1709         156 :         if (strstr(pszText, "<NetworkLink>") != nullptr &&
    1710          14 :             strstr(pszText, "<Region>") != nullptr &&
    1711          14 :             strstr(pszText, "<Link>") != nullptr)
    1712           8 :             return TRUE;
    1713             : 
    1714         148 :         if (strstr(pszText, "<Document>") != nullptr &&
    1715          10 :             strstr(pszText, "<Region>") != nullptr &&
    1716           6 :             strstr(pszText, "<GroundOverlay>") != nullptr)
    1717           6 :             return TRUE;
    1718             : 
    1719         142 :         if (strstr(pszText, "<GroundOverlay>") != nullptr &&
    1720           8 :             strstr(pszText, "<Icon>") != nullptr &&
    1721           8 :             strstr(pszText, "<href>") != nullptr &&
    1722           8 :             (strstr(pszText, "<LatLonBox>") != nullptr ||
    1723           2 :              strstr(pszText, "<gx:LatLonQuad>") != nullptr))
    1724           8 :             return TRUE;
    1725             : 
    1726         134 :         if (i == 0 && !poOpenInfo->TryToIngest(1024 * 10))
    1727           0 :             return FALSE;
    1728             :     }
    1729             : 
    1730          67 :     return -1;
    1731             : }
    1732             : 
    1733             : /************************************************************************/
    1734             : /*                                Open()                                */
    1735             : /************************************************************************/
    1736             : 
    1737         110 : GDALDataset *KmlSuperOverlayReadDataset::Open(GDALOpenInfo *poOpenInfo)
    1738             : 
    1739             : {
    1740         110 :     if (Identify(poOpenInfo) == FALSE)
    1741           0 :         return nullptr;
    1742             : 
    1743         110 :     return Open(poOpenInfo->pszFilename);
    1744             : }
    1745             : 
    1746             : /************************************************************************/
    1747             : /*                         KmlSuperOverlayLoadIcon()                    */
    1748             : /************************************************************************/
    1749             : 
    1750             : #define BUFFER_SIZE 20000000
    1751             : 
    1752             : static std::unique_ptr<GDALDataset>
    1753          36 : KmlSuperOverlayLoadIcon(const char *pszBaseFilename, const char *pszIcon)
    1754             : {
    1755          72 :     const std::string osExt = CPLGetExtensionSafe(pszIcon);
    1756          36 :     const char *pszExt = osExt.c_str();
    1757          36 :     if (!EQUAL(pszExt, "png") && !EQUAL(pszExt, "jpg") &&
    1758           0 :         !EQUAL(pszExt, "jpeg"))
    1759             :     {
    1760           0 :         return nullptr;
    1761             :     }
    1762             : 
    1763          72 :     CPLString osSubFilename;
    1764          36 :     if (STARTS_WITH(pszIcon, "http"))
    1765           0 :         osSubFilename = CPLSPrintf("/vsicurl_streaming/%s", pszIcon);
    1766             :     else
    1767             :     {
    1768          36 :         osSubFilename = CPLFormFilenameSafe(
    1769          72 :             CPLGetPathSafe(pszBaseFilename).c_str(), pszIcon, nullptr);
    1770          36 :         osSubFilename = KMLRemoveSlash(osSubFilename);
    1771             :     }
    1772             : 
    1773          36 :     VSILFILE *fp = VSIFOpenL(osSubFilename, "rb");
    1774          36 :     if (fp == nullptr)
    1775             :     {
    1776           0 :         return nullptr;
    1777             :     }
    1778          36 :     GByte *pabyBuffer = static_cast<GByte *>(VSIMalloc(BUFFER_SIZE));
    1779          36 :     if (pabyBuffer == nullptr)
    1780             :     {
    1781           0 :         VSIFCloseL(fp);
    1782           0 :         return nullptr;
    1783             :     }
    1784          36 :     const size_t nRead = VSIFReadL(pabyBuffer, 1, BUFFER_SIZE, fp);
    1785          36 :     VSIFCloseL(fp);
    1786          36 :     if (nRead == BUFFER_SIZE)
    1787             :     {
    1788           0 :         CPLFree(pabyBuffer);
    1789           0 :         return nullptr;
    1790             :     }
    1791             : 
    1792          36 :     osSubFilename = VSIMemGenerateHiddenFilename("kmlsuperoverlay");
    1793          36 :     VSIFCloseL(VSIFileFromMemBuffer(osSubFilename, pabyBuffer, nRead, TRUE));
    1794             : 
    1795             :     auto poDSIcon = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    1796          72 :         osSubFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    1797          36 :     if (!poDSIcon)
    1798             :     {
    1799           0 :         VSIUnlink(osSubFilename);
    1800           0 :         return nullptr;
    1801             :     }
    1802             : 
    1803          36 :     return poDSIcon;
    1804             : }
    1805             : 
    1806             : /************************************************************************/
    1807             : /*                    KmlSuperOverlayComputeDepth()                     */
    1808             : /************************************************************************/
    1809             : 
    1810          33 : static bool KmlSuperOverlayComputeDepth(const std::string &osFilename,
    1811             :                                         CPLXMLNode *psDocument, int &nLevel)
    1812             : {
    1813          33 :     CPLXMLNode *psIter = psDocument->psChild;
    1814         198 :     while (psIter != nullptr)
    1815             :     {
    1816         174 :         const char *pszHref = nullptr;
    1817         522 :         if (psIter->eType == CXT_Element &&
    1818         174 :             strcmp(psIter->pszValue, "NetworkLink") == 0 &&
    1819         357 :             CPLGetXMLNode(psIter, "Region") != nullptr &&
    1820           9 :             (pszHref = CPLGetXMLValue(psIter, "Link.href", nullptr)) != nullptr)
    1821             :         {
    1822           9 :             if (EQUAL(CPLGetExtensionSafe(pszHref).c_str(), "kml"))
    1823             :             {
    1824           9 :                 CPLString osSubFilename;
    1825           9 :                 if (STARTS_WITH(pszHref, "http"))
    1826             :                     osSubFilename =
    1827           0 :                         CPLSPrintf("/vsicurl_streaming/%s", pszHref);
    1828             :                 else
    1829             :                 {
    1830           9 :                     osSubFilename = CPLFormFilenameSafe(
    1831          18 :                         CPLGetPathSafe(osFilename.c_str()).c_str(), pszHref,
    1832           9 :                         nullptr);
    1833           9 :                     osSubFilename = KMLRemoveSlash(osSubFilename);
    1834             :                 }
    1835             : 
    1836           9 :                 VSILFILE *fp = VSIFOpenL(osSubFilename, "rb");
    1837           9 :                 if (fp != nullptr)
    1838             :                 {
    1839             :                     char *pszBuffer = static_cast<char *>(
    1840           9 :                         VSI_MALLOC_VERBOSE(BUFFER_SIZE + 1));
    1841           9 :                     if (pszBuffer == nullptr)
    1842             :                     {
    1843           0 :                         VSIFCloseL(fp);
    1844           0 :                         return false;
    1845             :                     }
    1846             :                     const size_t nRead =
    1847           9 :                         VSIFReadL(pszBuffer, 1, BUFFER_SIZE, fp);
    1848           9 :                     pszBuffer[nRead] = '\0';
    1849           9 :                     VSIFCloseL(fp);
    1850           9 :                     if (nRead == BUFFER_SIZE)
    1851             :                     {
    1852           0 :                         CPLFree(pszBuffer);
    1853             :                     }
    1854             :                     else
    1855             :                     {
    1856           9 :                         CPLXMLNode *psNode = CPLParseXMLString(pszBuffer);
    1857           9 :                         CPLFree(pszBuffer);
    1858           9 :                         if (psNode != nullptr)
    1859             :                         {
    1860           9 :                             CPLXMLNode *psRegion = nullptr;
    1861           9 :                             CPLXMLNode *psNewDocument = nullptr;
    1862           9 :                             CPLXMLNode *psGroundOverlay = nullptr;
    1863           9 :                             CPLXMLNode *psLink = nullptr;
    1864           9 :                             if (KmlSuperOverlayFindRegionStart(
    1865             :                                     psNode, &psRegion, &psNewDocument,
    1866           9 :                                     &psGroundOverlay, &psLink) &&
    1867           9 :                                 psNewDocument != nullptr && nLevel < 20)
    1868             :                             {
    1869           9 :                                 nLevel++;
    1870           9 :                                 if (!KmlSuperOverlayComputeDepth(
    1871             :                                         osSubFilename, psNewDocument, nLevel))
    1872             :                                 {
    1873           0 :                                     CPLDestroyXMLNode(psNode);
    1874           0 :                                     return false;
    1875             :                                 }
    1876             :                             }
    1877           9 :                             CPLDestroyXMLNode(psNode);
    1878           9 :                             break;
    1879             :                         }
    1880             :                     }
    1881             :                 }
    1882             :             }
    1883             :         }
    1884         165 :         psIter = psIter->psNext;
    1885             :     }
    1886          33 :     return true;
    1887             : }
    1888             : 
    1889             : /************************************************************************/
    1890             : /*                    KmlSingleDocRasterDataset                         */
    1891             : /************************************************************************/
    1892             : 
    1893             : class KmlSingleDocRasterRasterBand;
    1894             : 
    1895             : struct KmlSingleDocRasterTilesDesc
    1896             : {
    1897             :     int nMaxJ_i;    /* i index at which a tile with max j is realized */
    1898             :     int nMaxJ_j;    /* j index at which a tile with max j is realized */
    1899             :     int nMaxI_i;    /* i index at which a tile with max i is realized */
    1900             :     int nMaxI_j;    /* j index at which a tile with max i is realized */
    1901             :     char szExtJ[4]; /* extension of tile at which max j is realized */
    1902             :     char szExtI[4]; /* extension of tile at which max i is realized */
    1903             : };
    1904             : 
    1905             : class KmlSingleDocRasterDataset final : public GDALDataset
    1906             : {
    1907             :     friend class KmlSingleDocRasterRasterBand;
    1908             :     OGRSpatialReference m_oSRS{};
    1909             :     CPLString osDirname{};
    1910             :     CPLString osNominalExt{};
    1911             :     std::unique_ptr<GDALDataset> poCurTileDS{};
    1912             :     std::array<double, 4> adfGlobalExtents = {0, 0, 0, 0};
    1913             :     std::array<double, 6> adfGeoTransform = {0, 0, 0, 0, 0, 0};
    1914             :     std::vector<std::unique_ptr<KmlSingleDocRasterDataset>> m_apoOverviews{};
    1915             :     std::vector<KmlSingleDocRasterTilesDesc> aosDescs{};
    1916             :     int nLevel = 0;
    1917             :     int nTileSize = 0;
    1918             :     bool bHasBuiltOverviews = false;
    1919             :     bool bLockOtherBands = false;
    1920             : 
    1921             :   protected:
    1922             :     virtual int CloseDependentDatasets() override;
    1923             : 
    1924             :   public:
    1925             :     KmlSingleDocRasterDataset();
    1926             :     virtual ~KmlSingleDocRasterDataset();
    1927             : 
    1928           1 :     virtual CPLErr GetGeoTransform(double *padfGeoTransform) override
    1929             :     {
    1930           1 :         memcpy(padfGeoTransform, adfGeoTransform.data(), 6 * sizeof(double));
    1931           1 :         return CE_None;
    1932             :     }
    1933             : 
    1934           1 :     const OGRSpatialReference *GetSpatialRef() const override
    1935             :     {
    1936           1 :         return &m_oSRS;
    1937             :     }
    1938             : 
    1939             :     void BuildOverviews();
    1940             : 
    1941             :     static GDALDataset *Open(const char *pszFilename,
    1942             :                              const CPLString &osFilename, CPLXMLNode *psNode);
    1943             : };
    1944             : 
    1945             : /************************************************************************/
    1946             : /*                    KmlSingleDocRasterRasterBand                      */
    1947             : /************************************************************************/
    1948             : 
    1949             : class KmlSingleDocRasterRasterBand final : public GDALRasterBand
    1950             : {
    1951             :   public:
    1952             :     KmlSingleDocRasterRasterBand(KmlSingleDocRasterDataset *poDS, int nBand);
    1953             : 
    1954             :     virtual CPLErr IReadBlock(int, int, void *) override;
    1955             :     virtual GDALColorInterp GetColorInterpretation() override;
    1956             : 
    1957             :     virtual int GetOverviewCount() override;
    1958             :     virtual GDALRasterBand *GetOverview(int) override;
    1959             : };
    1960             : 
    1961             : /************************************************************************/
    1962             : /*                        KmlSingleDocRasterDataset()                   */
    1963             : /************************************************************************/
    1964             : 
    1965           2 : KmlSingleDocRasterDataset::KmlSingleDocRasterDataset()
    1966             : {
    1967           2 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1968           2 :     m_oSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
    1969           2 : }
    1970             : 
    1971             : /************************************************************************/
    1972             : /*                       ~KmlSingleDocRasterDataset()                   */
    1973             : /************************************************************************/
    1974             : 
    1975           4 : KmlSingleDocRasterDataset::~KmlSingleDocRasterDataset()
    1976             : {
    1977           2 :     KmlSingleDocRasterDataset::CloseDependentDatasets();
    1978           4 : }
    1979             : 
    1980             : /************************************************************************/
    1981             : /*                         CloseDependentDatasets()                     */
    1982             : /************************************************************************/
    1983             : 
    1984           2 : int KmlSingleDocRasterDataset::CloseDependentDatasets()
    1985             : {
    1986           2 :     int bRet = FALSE;
    1987             : 
    1988           2 :     if (poCurTileDS)
    1989             :     {
    1990           2 :         bRet = TRUE;
    1991           2 :         poCurTileDS.reset();
    1992             :     }
    1993           2 :     if (!m_apoOverviews.empty())
    1994             :     {
    1995           1 :         bRet = TRUE;
    1996           1 :         m_apoOverviews.clear();
    1997             :     }
    1998             : 
    1999           2 :     return bRet;
    2000             : }
    2001             : 
    2002             : /************************************************************************/
    2003             : /*                     KmlSingleDocGetDimensions()                      */
    2004             : /************************************************************************/
    2005             : 
    2006           2 : static bool KmlSingleDocGetDimensions(const CPLString &osDirname,
    2007             :                                       const KmlSingleDocRasterTilesDesc &oDesc,
    2008             :                                       int nLevel, int nTileSize, int &nXSize,
    2009             :                                       int &nYSize, int &nBands, int &bHasCT)
    2010             : {
    2011             :     std::string osImageFilename = CPLFormFilenameSafe(
    2012             :         osDirname,
    2013           2 :         CPLSPrintf("kml_image_L%d_%d_%d", nLevel, oDesc.nMaxJ_j, oDesc.nMaxJ_i),
    2014           4 :         oDesc.szExtJ);
    2015             :     auto poImageDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    2016           4 :         osImageFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    2017           2 :     if (!poImageDS)
    2018             :     {
    2019           0 :         return false;
    2020             :     }
    2021             :     int nRightXSize;
    2022           2 :     int nBottomYSize = poImageDS->GetRasterYSize();
    2023           2 :     nBands = poImageDS->GetRasterCount();
    2024           3 :     bHasCT = (nBands == 1 &&
    2025           1 :               poImageDS->GetRasterBand(1)->GetColorTable() != nullptr);
    2026           2 :     if (oDesc.nMaxJ_j == oDesc.nMaxI_j && oDesc.nMaxJ_i == oDesc.nMaxI_i)
    2027             :     {
    2028           2 :         nRightXSize = poImageDS->GetRasterXSize();
    2029             :     }
    2030             :     else
    2031             :     {
    2032             :         osImageFilename =
    2033           0 :             CPLFormFilenameSafe(osDirname,
    2034             :                                 CPLSPrintf("kml_image_L%d_%d_%d", nLevel,
    2035           0 :                                            oDesc.nMaxI_j, oDesc.nMaxI_i),
    2036           0 :                                 oDesc.szExtI);
    2037           0 :         poImageDS.reset(GDALDataset::Open(
    2038             :             osImageFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    2039           0 :         if (!poImageDS)
    2040             :         {
    2041           0 :             return false;
    2042             :         }
    2043           0 :         nRightXSize = poImageDS->GetRasterXSize();
    2044             :     }
    2045             : 
    2046           2 :     nXSize = nRightXSize + oDesc.nMaxI_i * nTileSize;
    2047           2 :     nYSize = nBottomYSize + oDesc.nMaxJ_j * nTileSize;
    2048           2 :     return (nXSize > 0 && nYSize > 0);
    2049             : }
    2050             : 
    2051             : /************************************************************************/
    2052             : /*                           BuildOverviews()                           */
    2053             : /************************************************************************/
    2054             : 
    2055           2 : void KmlSingleDocRasterDataset::BuildOverviews()
    2056             : {
    2057           2 :     if (bHasBuiltOverviews)
    2058           1 :         return;
    2059           1 :     bHasBuiltOverviews = TRUE;
    2060             : 
    2061           2 :     for (int k = 2; k <= static_cast<int>(aosDescs.size()); k++)
    2062             :     {
    2063             :         const KmlSingleDocRasterTilesDesc &oDesc =
    2064           1 :             aosDescs[aosDescs.size() - k];
    2065           1 :         int nXSize = 0;
    2066           1 :         int nYSize = 0;
    2067           1 :         int nTileBands = 0;
    2068           1 :         int bHasCT = FALSE;
    2069           1 :         if (!KmlSingleDocGetDimensions(
    2070           1 :                 osDirname, oDesc, static_cast<int>(aosDescs.size()) - k + 1,
    2071             :                 nTileSize, nXSize, nYSize, nTileBands, bHasCT))
    2072             :         {
    2073           0 :             break;
    2074             :         }
    2075             : 
    2076           2 :         auto poOvrDS = std::make_unique<KmlSingleDocRasterDataset>();
    2077           1 :         poOvrDS->nRasterXSize = nXSize;
    2078           1 :         poOvrDS->nRasterYSize = nYSize;
    2079           1 :         poOvrDS->nLevel = static_cast<int>(aosDescs.size()) - k + 1;
    2080           1 :         poOvrDS->nTileSize = nTileSize;
    2081           1 :         poOvrDS->osDirname = osDirname;
    2082           1 :         poOvrDS->osNominalExt = oDesc.szExtI;
    2083           1 :         poOvrDS->adfGeoTransform[0] = adfGlobalExtents[0];
    2084           1 :         poOvrDS->adfGeoTransform[1] =
    2085           1 :             (adfGlobalExtents[2] - adfGlobalExtents[0]) / poOvrDS->nRasterXSize;
    2086           1 :         poOvrDS->adfGeoTransform[2] = 0.0;
    2087           1 :         poOvrDS->adfGeoTransform[3] = adfGlobalExtents[3];
    2088           1 :         poOvrDS->adfGeoTransform[4] = 0.0;
    2089           1 :         poOvrDS->adfGeoTransform[5] =
    2090           1 :             -(adfGlobalExtents[3] - adfGlobalExtents[1]) /
    2091           1 :             poOvrDS->nRasterXSize;
    2092           5 :         for (int iBand = 1; iBand <= nBands; iBand++)
    2093           8 :             poOvrDS->SetBand(iBand,
    2094           4 :                              std::make_unique<KmlSingleDocRasterRasterBand>(
    2095           8 :                                  poOvrDS.get(), iBand));
    2096           1 :         poOvrDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2097             : 
    2098           1 :         m_apoOverviews.push_back(std::move(poOvrDS));
    2099             :     }
    2100             : }
    2101             : 
    2102             : /************************************************************************/
    2103             : /*                      KmlSingleDocRasterRasterBand()                  */
    2104             : /************************************************************************/
    2105             : 
    2106           8 : KmlSingleDocRasterRasterBand::KmlSingleDocRasterRasterBand(
    2107           8 :     KmlSingleDocRasterDataset *poDSIn, int nBandIn)
    2108             : {
    2109           8 :     poDS = poDSIn;
    2110           8 :     nBand = nBandIn;
    2111           8 :     nBlockXSize = poDSIn->nTileSize;
    2112           8 :     nBlockYSize = poDSIn->nTileSize;
    2113           8 :     eDataType = GDT_Byte;
    2114           8 : }
    2115             : 
    2116             : /************************************************************************/
    2117             : /*                               IReadBlock()                           */
    2118             : /************************************************************************/
    2119             : 
    2120          12 : CPLErr KmlSingleDocRasterRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
    2121             :                                                 void *pImage)
    2122             : {
    2123             :     KmlSingleDocRasterDataset *poGDS =
    2124          12 :         cpl::down_cast<KmlSingleDocRasterDataset *>(poDS);
    2125             :     const std::string osImageFilename =
    2126             :         CPLFormFilenameSafe(poGDS->osDirname,
    2127             :                             CPLSPrintf("kml_image_L%d_%d_%d", poGDS->nLevel,
    2128             :                                        nBlockYOff, nBlockXOff),
    2129          24 :                             poGDS->osNominalExt);
    2130          22 :     if (poGDS->poCurTileDS == nullptr ||
    2131          10 :         strcmp(CPLGetFilename(poGDS->poCurTileDS->GetDescription()),
    2132             :                CPLGetFilename(osImageFilename.c_str())) != 0)
    2133             :     {
    2134           6 :         CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler);
    2135           3 :         poGDS->poCurTileDS.reset(
    2136             :             GDALDataset::Open(osImageFilename.c_str(), GDAL_OF_RASTER));
    2137             :     }
    2138          12 :     GDALDataset *poImageDS = poGDS->poCurTileDS.get();
    2139          12 :     if (poImageDS == nullptr)
    2140             :     {
    2141           0 :         memset(pImage, 0, static_cast<size_t>(nBlockXSize) * nBlockYSize);
    2142           0 :         return CE_None;
    2143             :     }
    2144          12 :     int nXSize = poImageDS->GetRasterXSize();
    2145          12 :     int nYSize = poImageDS->GetRasterYSize();
    2146             : 
    2147          12 :     int nReqXSize = nBlockXSize;
    2148          12 :     if (nBlockXOff * nBlockXSize + nReqXSize > nRasterXSize)
    2149           8 :         nReqXSize = nRasterXSize - nBlockXOff * nBlockXSize;
    2150          12 :     int nReqYSize = nBlockYSize;
    2151          12 :     if (nBlockYOff * nBlockYSize + nReqYSize > nRasterYSize)
    2152          12 :         nReqYSize = nRasterYSize - nBlockYOff * nBlockYSize;
    2153             : 
    2154          12 :     if (nXSize != nReqXSize || nYSize != nReqYSize)
    2155             :     {
    2156           0 :         CPLDebug("KMLSUPEROVERLAY", "Tile %s, dimensions %dx%d, expected %dx%d",
    2157             :                  osImageFilename.c_str(), nXSize, nYSize, nReqXSize, nReqYSize);
    2158           0 :         return CE_Failure;
    2159             :     }
    2160             : 
    2161          12 :     CPLErr eErr = CE_Failure;
    2162          12 :     if (poImageDS->GetRasterCount() == 1)
    2163             :     {
    2164             :         GDALColorTable *poColorTable =
    2165           4 :             poImageDS->GetRasterBand(1)->GetColorTable();
    2166           4 :         if (nBand == 4 && poColorTable == nullptr)
    2167             :         {
    2168             :             /* Add fake alpha band */
    2169           0 :             memset(pImage, 255, static_cast<size_t>(nBlockXSize) * nBlockYSize);
    2170           0 :             eErr = CE_None;
    2171             :         }
    2172             :         else
    2173             :         {
    2174           4 :             eErr = poImageDS->GetRasterBand(1)->RasterIO(
    2175             :                 GF_Read, 0, 0, nXSize, nYSize, pImage, nXSize, nYSize, GDT_Byte,
    2176           4 :                 1, nBlockXSize, nullptr);
    2177             : 
    2178             :             /* Expand color table */
    2179           4 :             if (eErr == CE_None && poColorTable != nullptr)
    2180             :             {
    2181           4 :                 GByte *pabyImage = static_cast<GByte *>(pImage);
    2182             :                 int j, i;
    2183        2276 :                 for (j = 0; j < nReqYSize; j++)
    2184             :                 {
    2185      147680 :                     for (i = 0; i < nReqXSize; i++)
    2186             :                     {
    2187      145408 :                         GByte nVal = pabyImage[j * nBlockXSize + i];
    2188             :                         const GDALColorEntry *poEntry =
    2189      145408 :                             poColorTable->GetColorEntry(nVal);
    2190      145408 :                         if (poEntry != nullptr)
    2191             :                         {
    2192      145408 :                             if (nBand == 1)
    2193       36352 :                                 pabyImage[j * nBlockXSize + i] =
    2194       36352 :                                     static_cast<GByte>(poEntry->c1);
    2195      109056 :                             else if (nBand == 2)
    2196       36352 :                                 pabyImage[j * nBlockXSize + i] =
    2197       36352 :                                     static_cast<GByte>(poEntry->c2);
    2198       72704 :                             else if (nBand == 3)
    2199       36352 :                                 pabyImage[j * nBlockXSize + i] =
    2200       36352 :                                     static_cast<GByte>(poEntry->c3);
    2201             :                             else
    2202       36352 :                                 pabyImage[j * nBlockXSize + i] =
    2203       36352 :                                     static_cast<GByte>(poEntry->c4);
    2204             :                         }
    2205             :                     }
    2206             :                 }
    2207             :             }
    2208             :         }
    2209             :     }
    2210           8 :     else if (nBand <= poImageDS->GetRasterCount())
    2211             :     {
    2212          16 :         eErr = poImageDS->GetRasterBand(nBand)->RasterIO(
    2213             :             GF_Read, 0, 0, nXSize, nYSize, pImage, nXSize, nYSize, GDT_Byte, 1,
    2214           8 :             nBlockXSize, nullptr);
    2215             :     }
    2216           0 :     else if (nBand == 4 && poImageDS->GetRasterCount() == 3)
    2217             :     {
    2218             :         /* Add fake alpha band */
    2219           0 :         memset(pImage, 255, static_cast<size_t>(nBlockXSize) * nBlockYSize);
    2220           0 :         eErr = CE_None;
    2221             :     }
    2222             : 
    2223             :     /* Cache other bands */
    2224          12 :     if (!poGDS->bLockOtherBands)
    2225             :     {
    2226           3 :         poGDS->bLockOtherBands = TRUE;
    2227          15 :         for (int iBand = 1; iBand <= poGDS->nBands; iBand++)
    2228             :         {
    2229          12 :             if (iBand != nBand)
    2230             :             {
    2231             :                 KmlSingleDocRasterRasterBand *poOtherBand =
    2232             :                     static_cast<KmlSingleDocRasterRasterBand *>(
    2233           9 :                         poGDS->GetRasterBand(iBand));
    2234             :                 GDALRasterBlock *poBlock =
    2235           9 :                     poOtherBand->GetLockedBlockRef(nBlockXOff, nBlockYOff);
    2236           9 :                 if (poBlock == nullptr)
    2237           0 :                     continue;
    2238           9 :                 poBlock->DropLock();
    2239             :             }
    2240             :         }
    2241           3 :         poGDS->bLockOtherBands = FALSE;
    2242             :     }
    2243             : 
    2244          12 :     return eErr;
    2245             : }
    2246             : 
    2247             : /************************************************************************/
    2248             : /*                          GetColorInterpretation()                    */
    2249             : /************************************************************************/
    2250             : 
    2251           4 : GDALColorInterp KmlSingleDocRasterRasterBand::GetColorInterpretation()
    2252             : {
    2253           4 :     return static_cast<GDALColorInterp>(GCI_RedBand + nBand - 1);
    2254             : }
    2255             : 
    2256             : /************************************************************************/
    2257             : /*                          GetOverviewCount()                          */
    2258             : /************************************************************************/
    2259             : 
    2260           1 : int KmlSingleDocRasterRasterBand::GetOverviewCount()
    2261             : {
    2262             :     KmlSingleDocRasterDataset *poGDS =
    2263           1 :         cpl::down_cast<KmlSingleDocRasterDataset *>(poDS);
    2264           1 :     poGDS->BuildOverviews();
    2265             : 
    2266           1 :     return static_cast<int>(poGDS->m_apoOverviews.size());
    2267             : }
    2268             : 
    2269             : /************************************************************************/
    2270             : /*                           GetOverview()                              */
    2271             : /************************************************************************/
    2272             : 
    2273           1 : GDALRasterBand *KmlSingleDocRasterRasterBand::GetOverview(int iOvr)
    2274             : {
    2275             :     KmlSingleDocRasterDataset *poGDS =
    2276           1 :         cpl::down_cast<KmlSingleDocRasterDataset *>(poDS);
    2277           1 :     poGDS->BuildOverviews();
    2278             : 
    2279           1 :     if (iOvr < 0 || iOvr >= static_cast<int>(poGDS->m_apoOverviews.size()))
    2280           0 :         return nullptr;
    2281             : 
    2282           1 :     return poGDS->m_apoOverviews[iOvr]->GetRasterBand(nBand);
    2283             : }
    2284             : 
    2285             : /************************************************************************/
    2286             : /*                       KmlSingleDocCollectTiles()                     */
    2287             : /************************************************************************/
    2288             : 
    2289             : static void
    2290          60 : KmlSingleDocCollectTiles(CPLXMLNode *psNode,
    2291             :                          std::vector<KmlSingleDocRasterTilesDesc> &aosDescs,
    2292             :                          CPLString &osURLBase)
    2293             : {
    2294          60 :     if (strcmp(psNode->pszValue, "href") == 0)
    2295             :     {
    2296             :         int level, j, i;
    2297             :         char szExt[4];
    2298           3 :         const char *pszHref = CPLGetXMLValue(psNode, "", "");
    2299           3 :         if (STARTS_WITH(pszHref, "http"))
    2300             :         {
    2301           0 :             osURLBase = CPLGetPathSafe(pszHref);
    2302             :         }
    2303           3 :         if (sscanf(CPLGetFilename(pszHref), "kml_image_L%d_%d_%d.%3s", &level,
    2304           3 :                    &j, &i, szExt) == 4)
    2305             :         {
    2306           3 :             if (level > static_cast<int>(aosDescs.size()))
    2307             :             {
    2308             :                 KmlSingleDocRasterTilesDesc sDesc;
    2309           2 :                 while (level > static_cast<int>(aosDescs.size()) + 1)
    2310             :                 {
    2311           0 :                     sDesc.nMaxJ_i = -1;
    2312           0 :                     sDesc.nMaxJ_j = -1;
    2313           0 :                     sDesc.nMaxI_i = -1;
    2314           0 :                     sDesc.nMaxI_j = -1;
    2315           0 :                     strcpy(sDesc.szExtI, "");
    2316           0 :                     strcpy(sDesc.szExtJ, "");
    2317           0 :                     aosDescs.push_back(sDesc);
    2318             :                 }
    2319             : 
    2320           2 :                 sDesc.nMaxJ_j = j;
    2321           2 :                 sDesc.nMaxJ_i = i;
    2322           2 :                 strcpy(sDesc.szExtJ, szExt);
    2323           2 :                 sDesc.nMaxI_j = j;
    2324           2 :                 sDesc.nMaxI_i = i;
    2325           2 :                 strcpy(sDesc.szExtI, szExt);
    2326           2 :                 aosDescs.push_back(sDesc);
    2327             :             }
    2328             :             else
    2329             :             {
    2330             :                 /* 2010_USACE_JALBTCX_Louisiana_Mississippi_Lidar.kmz has not a
    2331             :                  * lower-right tile */
    2332             :                 /* so the right most tile and the bottom most tile might be
    2333             :                  * different */
    2334           2 :                 if ((j > aosDescs[level - 1].nMaxJ_j) ||
    2335           1 :                     (j == aosDescs[level - 1].nMaxJ_j &&
    2336           1 :                      i > aosDescs[level - 1].nMaxJ_i))
    2337             :                 {
    2338           1 :                     aosDescs[level - 1].nMaxJ_j = j;
    2339           1 :                     aosDescs[level - 1].nMaxJ_i = i;
    2340           1 :                     strcpy(aosDescs[level - 1].szExtJ, szExt);
    2341             :                 }
    2342           1 :                 if (i > aosDescs[level - 1].nMaxI_i ||
    2343           0 :                     (i == aosDescs[level - 1].nMaxI_i &&
    2344           0 :                      j > aosDescs[level - 1].nMaxI_j))
    2345             :                 {
    2346           1 :                     aosDescs[level - 1].nMaxI_j = j;
    2347           1 :                     aosDescs[level - 1].nMaxI_i = i;
    2348           1 :                     strcpy(aosDescs[level - 1].szExtI, szExt);
    2349             :                 }
    2350             :             }
    2351             :         }
    2352             :     }
    2353             :     else
    2354             :     {
    2355          57 :         CPLXMLNode *psIter = psNode->psChild;
    2356         152 :         while (psIter != nullptr)
    2357             :         {
    2358          95 :             if (psIter->eType == CXT_Element)
    2359          59 :                 KmlSingleDocCollectTiles(psIter, aosDescs, osURLBase);
    2360          95 :             psIter = psIter->psNext;
    2361             :         }
    2362             :     }
    2363          60 : }
    2364             : 
    2365             : /************************************************************************/
    2366             : /*                                Open()                                */
    2367             : /************************************************************************/
    2368             : 
    2369             : /* Read raster with a structure like
    2370             :  * http://opentopo.sdsc.edu/files/Haiti/NGA_Haiti_LiDAR2.kmz */
    2371             : /* i.e. made of a doc.kml that list all tiles at all overview levels */
    2372             : /* The tile name pattern is "kml_image_L{level}_{j}_{i}.{png|jpg}" */
    2373          96 : GDALDataset *KmlSingleDocRasterDataset::Open(const char *pszFilename,
    2374             :                                              const CPLString &osFilename,
    2375             :                                              CPLXMLNode *psRoot)
    2376             : {
    2377          96 :     CPLXMLNode *psRootFolder = CPLGetXMLNode(psRoot, "=kml.Document.Folder");
    2378          96 :     if (psRootFolder == nullptr)
    2379          62 :         return nullptr;
    2380          34 :     const char *pszRootFolderName = CPLGetXMLValue(psRootFolder, "name", "");
    2381          34 :     if (strcmp(pszRootFolderName, "kml_image_L1_0_0") != 0)
    2382          33 :         return nullptr;
    2383             : 
    2384           1 :     std::array<double, 4> adfGlobalExtents = {0, 0, 0, 0};
    2385           1 :     CPLXMLNode *psRegion = CPLGetXMLNode(psRootFolder, "Region");
    2386           1 :     if (psRegion == nullptr)
    2387           0 :         return nullptr;
    2388           1 :     if (!KmlSuperOverlayGetBoundingBox(psRegion, adfGlobalExtents))
    2389           0 :         return nullptr;
    2390             : 
    2391           2 :     std::vector<KmlSingleDocRasterTilesDesc> aosDescs;
    2392           2 :     CPLString osDirname = CPLGetPathSafe(osFilename);
    2393           1 :     KmlSingleDocCollectTiles(psRootFolder, aosDescs, osDirname);
    2394           1 :     if (aosDescs.empty())
    2395           0 :         return nullptr;
    2396           3 :     for (const auto &oDesc : aosDescs)
    2397             :     {
    2398           2 :         if (oDesc.nMaxJ_i < 0)
    2399           0 :             return nullptr;
    2400             :     }
    2401             : 
    2402             :     const std::string osImageFilename =
    2403             :         CPLFormFilenameSafe(osDirname,
    2404             :                             CPLSPrintf("kml_image_L%d_%d_%d",
    2405           1 :                                        static_cast<int>(aosDescs.size()), 0, 0),
    2406           2 :                             aosDescs.back().szExtI);
    2407             :     auto poImageDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    2408           2 :         osImageFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    2409           1 :     if (poImageDS == nullptr)
    2410             :     {
    2411           0 :         return nullptr;
    2412             :     }
    2413           1 :     int nTileSize = poImageDS->GetRasterXSize();
    2414           1 :     if (nTileSize != poImageDS->GetRasterYSize())
    2415             :     {
    2416           1 :         nTileSize = 1024;
    2417             :     }
    2418             : 
    2419           1 :     const KmlSingleDocRasterTilesDesc &oDesc = aosDescs.back();
    2420           1 :     int nXSize = 0;
    2421           1 :     int nYSize = 0;
    2422           1 :     int nBands = 0;
    2423           1 :     int bHasCT = FALSE;
    2424           1 :     if (!KmlSingleDocGetDimensions(osDirname, oDesc,
    2425           1 :                                    static_cast<int>(aosDescs.size()), nTileSize,
    2426             :                                    nXSize, nYSize, nBands, bHasCT))
    2427             :     {
    2428           0 :         return nullptr;
    2429             :     }
    2430             : 
    2431           2 :     auto poDS = std::make_unique<KmlSingleDocRasterDataset>();
    2432           1 :     poDS->nRasterXSize = nXSize;
    2433           1 :     poDS->nRasterYSize = nYSize;
    2434           1 :     poDS->nLevel = static_cast<int>(aosDescs.size());
    2435           1 :     poDS->nTileSize = nTileSize;
    2436           1 :     poDS->osDirname = std::move(osDirname);
    2437           1 :     poDS->osNominalExt = oDesc.szExtI;
    2438           1 :     poDS->adfGlobalExtents = adfGlobalExtents;
    2439           1 :     poDS->adfGeoTransform[0] = adfGlobalExtents[0];
    2440           1 :     poDS->adfGeoTransform[1] =
    2441           1 :         (adfGlobalExtents[2] - adfGlobalExtents[0]) / poDS->nRasterXSize;
    2442           1 :     poDS->adfGeoTransform[2] = 0.0;
    2443           1 :     poDS->adfGeoTransform[3] = adfGlobalExtents[3];
    2444           1 :     poDS->adfGeoTransform[4] = 0.0;
    2445           1 :     poDS->adfGeoTransform[5] =
    2446           1 :         -(adfGlobalExtents[3] - adfGlobalExtents[1]) / poDS->nRasterYSize;
    2447           1 :     if (nBands == 1 && bHasCT)
    2448           1 :         nBands = 4;
    2449           5 :     for (int iBand = 1; iBand <= nBands; iBand++)
    2450           8 :         poDS->SetBand(iBand, std::make_unique<KmlSingleDocRasterRasterBand>(
    2451           8 :                                  poDS.get(), iBand));
    2452           1 :     poDS->SetDescription(pszFilename);
    2453           1 :     poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2454           1 :     poDS->aosDescs = std::move(aosDescs);
    2455             : 
    2456           1 :     return poDS.release();
    2457             : }
    2458             : 
    2459             : /************************************************************************/
    2460             : /*                   KmlSingleOverlayRasterDataset                      */
    2461             : /************************************************************************/
    2462             : 
    2463             : class KmlSingleOverlayRasterDataset final : public VRTDataset
    2464             : {
    2465             :   public:
    2466           4 :     KmlSingleOverlayRasterDataset(int nXSize, int nYSize)
    2467           4 :         : VRTDataset(nXSize, nYSize)
    2468             :     {
    2469           4 :     }
    2470             : 
    2471             :     static GDALDataset *Open(const char *pszFilename,
    2472             :                              const CPLString &osFilename, CPLXMLNode *psRoot);
    2473             : };
    2474             : 
    2475             : /************************************************************************/
    2476             : /*                                Open()                                */
    2477             : /************************************************************************/
    2478             : 
    2479             : /* Read raster with a structure like https://trac.osgeo.org/gdal/ticket/6712 */
    2480             : /* i.e. made of a doc.kml that has a single GroundOverlay */
    2481          36 : GDALDataset *KmlSingleOverlayRasterDataset::Open(const char *pszFilename,
    2482             :                                                  const CPLString &osFilename,
    2483             :                                                  CPLXMLNode *psRoot)
    2484             : {
    2485          36 :     CPLXMLNode *psGO = CPLGetXMLNode(psRoot, "=kml.GroundOverlay");
    2486          36 :     if (psGO == nullptr)
    2487             :     {
    2488             :         // Otherwise look for kml.Document.Folder.GroundOverlay if there's
    2489             :         // a single occurrence of Folder and GroundOverlay
    2490          34 :         auto psDoc = CPLGetXMLNode(psRoot, "=kml.Document");
    2491          34 :         if (psDoc == nullptr)
    2492             :         {
    2493           0 :             return nullptr;
    2494             :         }
    2495          34 :         CPLXMLNode *psFolder = nullptr;
    2496         116 :         for (auto psIter = psDoc->psChild; psIter; psIter = psIter->psNext)
    2497             :         {
    2498          98 :             if (psIter->eType == CXT_Element &&
    2499          66 :                 strcmp(psIter->pszValue, "Folder") == 0)
    2500             :             {
    2501          49 :                 if (psFolder == nullptr)
    2502          33 :                     psFolder = psIter;
    2503             :                 else
    2504          16 :                     return nullptr;
    2505             :             }
    2506             :         }
    2507             : 
    2508             :         // folder is not mandatory -- some kml have a structure
    2509             :         // kml.Document.GroundOverlay
    2510          18 :         CPLXMLNode *psParent = psFolder != nullptr ? psFolder : psDoc;
    2511          36 :         for (auto psIter = psParent->psChild; psIter; psIter = psIter->psNext)
    2512             :         {
    2513          18 :             if (psIter->eType == CXT_Element &&
    2514          18 :                 strcmp(psIter->pszValue, "GroundOverlay") == 0)
    2515             :             {
    2516           2 :                 if (psGO == nullptr)
    2517           2 :                     psGO = psIter;
    2518             :                 else
    2519           0 :                     return nullptr;
    2520             :             }
    2521             :         }
    2522          18 :         if (psGO == nullptr)
    2523             :         {
    2524          16 :             return nullptr;
    2525             :         }
    2526             :     }
    2527             : 
    2528           4 :     const char *pszHref = CPLGetXMLValue(psGO, "Icon.href", nullptr);
    2529           4 :     if (pszHref == nullptr)
    2530           0 :         return nullptr;
    2531           4 :     std::array<double, 4> adfExtents = {0, 0, 0, 0};
    2532           4 :     if (!KmlSuperOverlayGetBoundingBox(psGO, adfExtents))
    2533           0 :         return nullptr;
    2534             :     const std::string osImageFilename = CPLFormFilenameSafe(
    2535           8 :         CPLGetPathSafe(osFilename).c_str(), pszHref, nullptr);
    2536           4 :     GDALDataset *poImageDS = GDALDataset::FromHandle(
    2537             :         GDALOpenShared(osImageFilename.c_str(), GA_ReadOnly));
    2538           4 :     if (poImageDS == nullptr)
    2539           0 :         return nullptr;
    2540             : 
    2541             :     auto poDS = std::make_unique<KmlSingleOverlayRasterDataset>(
    2542           8 :         poImageDS->GetRasterXSize(), poImageDS->GetRasterYSize());
    2543          12 :     for (int i = 1; i <= poImageDS->GetRasterCount(); ++i)
    2544             :     {
    2545           8 :         poDS->AddBand(GDT_Byte, nullptr);
    2546             : 
    2547           8 :         auto poImageBand = poImageDS->GetRasterBand(i);
    2548             :         auto poVRTBand =
    2549           8 :             static_cast<VRTSourcedRasterBand *>(poDS->GetRasterBand(i));
    2550          32 :         poVRTBand->AddSimpleSource(
    2551           8 :             poImageBand, 0, 0, poImageDS->GetRasterXSize(),
    2552           8 :             poImageDS->GetRasterYSize(), 0, 0, poImageDS->GetRasterXSize(),
    2553           8 :             poImageDS->GetRasterYSize(), nullptr, VRT_NODATA_UNSET);
    2554             : 
    2555           8 :         poVRTBand->SetColorInterpretation(
    2556           8 :             poImageBand->GetColorInterpretation());
    2557             : 
    2558           8 :         const auto poCT = poImageBand->GetColorTable();
    2559           8 :         if (poCT)
    2560           2 :             poVRTBand->SetColorTable(poCT);
    2561             :     }
    2562           4 :     poImageDS->Dereference();
    2563           4 :     double adfGeoTransform[6] = {
    2564           4 :         adfExtents[0],
    2565           4 :         (adfExtents[2] - adfExtents[0]) / poImageDS->GetRasterXSize(),
    2566             :         0,
    2567           4 :         adfExtents[3],
    2568             :         0,
    2569           8 :         -(adfExtents[3] - adfExtents[1]) / poImageDS->GetRasterYSize()};
    2570           4 :     poDS->SetGeoTransform(adfGeoTransform);
    2571           4 :     poDS->SetProjection(SRS_WKT_WGS84_LAT_LONG);
    2572           4 :     poDS->SetWritable(false);
    2573           4 :     poDS->SetDescription(pszFilename);
    2574             : 
    2575           4 :     return poDS.release();
    2576             : }
    2577             : 
    2578             : /************************************************************************/
    2579             : /*                                Open()                                */
    2580             : /************************************************************************/
    2581             : 
    2582             : GDALDataset *
    2583         144 : KmlSuperOverlayReadDataset::Open(const char *pszFilename,
    2584             :                                  KmlSuperOverlayReadDataset *poParent, int nRec)
    2585             : 
    2586             : {
    2587         144 :     if (nRec == 2)
    2588           0 :         return nullptr;
    2589         288 :     CPLString osFilename(pszFilename);
    2590         144 :     if (EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "kmz"))
    2591             :     {
    2592          65 :         if (!STARTS_WITH(pszFilename, "/vsizip/"))
    2593          65 :             osFilename = CPLSPrintf("/vsizip/%s", pszFilename);
    2594          65 :         char **papszFiles = VSIReadDir(osFilename);
    2595          65 :         if (papszFiles == nullptr)
    2596          48 :             return nullptr;
    2597          17 :         char **papszIter = papszFiles;
    2598          17 :         for (; *papszIter != nullptr; papszIter++)
    2599             :         {
    2600          17 :             if (EQUAL(CPLGetExtensionSafe(*papszIter).c_str(), "kml"))
    2601             :             {
    2602             :                 osFilename =
    2603          17 :                     CPLFormFilenameSafe(osFilename, *papszIter, nullptr);
    2604          17 :                 osFilename = KMLRemoveSlash(osFilename);
    2605          17 :                 break;
    2606             :             }
    2607             :         }
    2608          17 :         CSLDestroy(papszFiles);
    2609             :     }
    2610          96 :     VSILFILE *fp = VSIFOpenL(osFilename, "rb");
    2611          96 :     if (fp == nullptr)
    2612           0 :         return nullptr;
    2613          96 :     char *pszBuffer = static_cast<char *>(VSI_MALLOC_VERBOSE(BUFFER_SIZE + 1));
    2614          96 :     if (pszBuffer == nullptr)
    2615             :     {
    2616           0 :         VSIFCloseL(fp);
    2617           0 :         return nullptr;
    2618             :     }
    2619          96 :     const size_t nRead = VSIFReadL(pszBuffer, 1, BUFFER_SIZE, fp);
    2620          96 :     pszBuffer[nRead] = '\0';
    2621          96 :     VSIFCloseL(fp);
    2622          96 :     if (nRead == BUFFER_SIZE)
    2623             :     {
    2624           0 :         CPLFree(pszBuffer);
    2625           0 :         return nullptr;
    2626             :     }
    2627             : 
    2628          96 :     CPLXMLNode *psNode = CPLParseXMLString(pszBuffer);
    2629          96 :     CPLFree(pszBuffer);
    2630          96 :     if (psNode == nullptr)
    2631           0 :         return nullptr;
    2632             : 
    2633             :     GDALDataset *psSingleDocDS =
    2634          96 :         KmlSingleDocRasterDataset::Open(pszFilename, osFilename, psNode);
    2635          96 :     if (psSingleDocDS != nullptr)
    2636             :     {
    2637           1 :         CPLDestroyXMLNode(psNode);
    2638           1 :         return psSingleDocDS;
    2639             :     }
    2640             : 
    2641          95 :     CPLXMLNode *psRegion = nullptr;
    2642          95 :     CPLXMLNode *psDocument = nullptr;
    2643          95 :     CPLXMLNode *psGroundOverlay = nullptr;
    2644          95 :     CPLXMLNode *psLink = nullptr;
    2645          95 :     if (!KmlSuperOverlayFindRegionStart(psNode, &psRegion, &psDocument,
    2646             :                                         &psGroundOverlay, &psLink))
    2647             :     {
    2648             :         // If we didn't find a super overlay, this still could be a valid kml
    2649             :         // containing a single overlay. Test for that now. (Note that we need to
    2650             :         // test first for super overlay in order to avoid false positive matches
    2651             :         // of super overlay datasets to single overlay datasets)
    2652          36 :         GDALDataset *psSingleOverlayDS = KmlSingleOverlayRasterDataset::Open(
    2653             :             pszFilename, osFilename, psNode);
    2654          36 :         CPLDestroyXMLNode(psNode);
    2655          36 :         return psSingleOverlayDS;
    2656             :     }
    2657             : 
    2658          59 :     if (psLink != nullptr)
    2659             :     {
    2660          22 :         const char *pszHref = CPLGetXMLValue(psLink, "href", nullptr);
    2661          44 :         if (pszHref == nullptr ||
    2662          44 :             !EQUAL(CPLGetExtensionSafe(pszHref).c_str(), "kml"))
    2663             :         {
    2664           0 :             CPLDestroyXMLNode(psNode);
    2665           0 :             return nullptr;
    2666             :         }
    2667             : 
    2668          44 :         CPLString osSubFilename;
    2669          22 :         if (STARTS_WITH(pszHref, "http"))
    2670           0 :             osSubFilename = CPLSPrintf("/vsicurl_streaming/%s", pszHref);
    2671             :         else
    2672             :         {
    2673          22 :             osSubFilename = CPLFormFilenameSafe(
    2674          44 :                 CPLGetPathSafe(osFilename).c_str(), pszHref, nullptr);
    2675          22 :             osSubFilename = KMLRemoveSlash(osSubFilename);
    2676             :         }
    2677             : 
    2678          44 :         CPLString osOverlayName, osOverlayDescription;
    2679          22 :         psDocument = CPLGetXMLNode(psNode, "=kml.Document");
    2680          22 :         if (psDocument)
    2681             :         {
    2682             :             const char *pszOverlayName =
    2683          22 :                 CPLGetXMLValue(psDocument, "name", nullptr);
    2684          44 :             if (pszOverlayName != nullptr &&
    2685          22 :                 strcmp(pszOverlayName,
    2686          44 :                        CPLGetBasenameSafe(pszFilename).c_str()) != 0)
    2687             :             {
    2688           2 :                 osOverlayName = pszOverlayName;
    2689             :             }
    2690             :             const char *pszOverlayDescription =
    2691          22 :                 CPLGetXMLValue(psDocument, "description", nullptr);
    2692          22 :             if (pszOverlayDescription != nullptr)
    2693             :             {
    2694           1 :                 osOverlayDescription = pszOverlayDescription;
    2695             :             }
    2696             :         }
    2697             : 
    2698          22 :         CPLDestroyXMLNode(psNode);
    2699             : 
    2700             :         // FIXME
    2701          22 :         GDALDataset *poDS = Open(osSubFilename, poParent, nRec + 1);
    2702          22 :         if (poDS != nullptr)
    2703             :         {
    2704          21 :             poDS->SetDescription(pszFilename);
    2705             : 
    2706          21 :             if (!osOverlayName.empty())
    2707             :             {
    2708           2 :                 poDS->SetMetadataItem("NAME", osOverlayName);
    2709             :             }
    2710          21 :             if (!osOverlayDescription.empty())
    2711             :             {
    2712           1 :                 poDS->SetMetadataItem("DESCRIPTION", osOverlayDescription);
    2713             :             }
    2714             :         }
    2715             : 
    2716          22 :         return poDS;
    2717             :     }
    2718             : 
    2719          37 :     CPLAssert(psDocument != nullptr);
    2720          37 :     CPLAssert(psGroundOverlay != nullptr);
    2721          37 :     CPLAssert(psRegion != nullptr);
    2722             : 
    2723          37 :     std::array<double, 4> adfExtents = {0, 0, 0, 0};
    2724          37 :     if (!KmlSuperOverlayGetBoundingBox(psGroundOverlay, adfExtents))
    2725             :     {
    2726           1 :         CPLDestroyXMLNode(psNode);
    2727           1 :         return nullptr;
    2728             :     }
    2729             : 
    2730          36 :     const char *pszIcon = CPLGetXMLValue(psGroundOverlay, "Icon.href", nullptr);
    2731          36 :     if (pszIcon == nullptr)
    2732             :     {
    2733           0 :         CPLDestroyXMLNode(psNode);
    2734           0 :         return nullptr;
    2735             :     }
    2736          72 :     auto poDSIcon = KmlSuperOverlayLoadIcon(pszFilename, pszIcon);
    2737          36 :     if (poDSIcon == nullptr)
    2738             :     {
    2739           0 :         CPLDestroyXMLNode(psNode);
    2740           0 :         return nullptr;
    2741             :     }
    2742             : 
    2743             :     int nFactor;
    2744          36 :     if (poParent != nullptr)
    2745          12 :         nFactor = poParent->nFactor / 2;
    2746             :     else
    2747             :     {
    2748          24 :         int nDepth = 0;
    2749          24 :         if (!KmlSuperOverlayComputeDepth(pszFilename, psDocument, nDepth))
    2750             :         {
    2751           0 :             CPLDestroyXMLNode(psNode);
    2752           0 :             return nullptr;
    2753             :         }
    2754          24 :         nFactor = 1 << nDepth;
    2755             :     }
    2756             : 
    2757          72 :     auto poDS = std::make_unique<KmlSuperOverlayReadDataset>();
    2758          36 :     poDS->osFilename = pszFilename;
    2759          36 :     poDS->psRoot = psNode;
    2760          36 :     poDS->psDocument = psDocument;
    2761          36 :     poDS->poParent = poParent;
    2762          36 :     poDS->nFactor = nFactor;
    2763          36 :     poDS->nRasterXSize = nFactor * poDSIcon->GetRasterXSize();
    2764          36 :     poDS->nRasterYSize = nFactor * poDSIcon->GetRasterYSize();
    2765          36 :     poDS->adfGeoTransform[0] = adfExtents[0];
    2766          36 :     poDS->adfGeoTransform[1] =
    2767          36 :         (adfExtents[2] - adfExtents[0]) / poDS->nRasterXSize;
    2768          36 :     poDS->adfGeoTransform[3] = adfExtents[3];
    2769          36 :     poDS->adfGeoTransform[5] =
    2770          36 :         -(adfExtents[3] - adfExtents[1]) / poDS->nRasterYSize;
    2771          36 :     poDS->nBands = 4;
    2772         180 :     for (int i = 0; i < 4; i++)
    2773         288 :         poDS->SetBand(i + 1, std::make_unique<KmlSuperOverlayRasterBand>(
    2774         288 :                                  poDS.get(), i + 1));
    2775          36 :     poDS->SetDescription(pszFilename);
    2776          36 :     poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2777             : 
    2778          45 :     while (poDS->poParent == nullptr && nFactor > 1)
    2779             :     {
    2780           9 :         nFactor /= 2;
    2781             : 
    2782          18 :         auto poOvrDS = std::make_unique<KmlSuperOverlayReadDataset>();
    2783             : 
    2784           9 :         poOvrDS->bIsOvr = true;
    2785             :         // The life-time of objects is such that poOvrDS is destroyed when
    2786             :         // poDS is destroyed.
    2787             :         // coverity[escape]
    2788           9 :         poOvrDS->poParent = poDS.get();
    2789           9 :         poOvrDS->nFactor = nFactor;
    2790           9 :         poOvrDS->nRasterXSize = nFactor * poDSIcon->GetRasterXSize();
    2791           9 :         poOvrDS->nRasterYSize = nFactor * poDSIcon->GetRasterYSize();
    2792           9 :         poOvrDS->adfGeoTransform[0] = adfExtents[0];
    2793           9 :         poOvrDS->adfGeoTransform[1] =
    2794           9 :             (adfExtents[2] - adfExtents[0]) / poOvrDS->nRasterXSize;
    2795           9 :         poOvrDS->adfGeoTransform[3] = adfExtents[3];
    2796           9 :         poOvrDS->adfGeoTransform[5] =
    2797           9 :             -(adfExtents[3] - adfExtents[1]) / poOvrDS->nRasterYSize;
    2798           9 :         poOvrDS->nBands = 4;
    2799          45 :         for (int i = 0; i < 4; i++)
    2800          72 :             poOvrDS->SetBand(i + 1, std::make_unique<KmlSuperOverlayRasterBand>(
    2801          72 :                                         poOvrDS.get(), i + 1));
    2802           9 :         poOvrDS->SetDescription(pszFilename);
    2803           9 :         poOvrDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2804             : 
    2805           9 :         poDS->m_apoOverviewDS.push_back(std::move(poOvrDS));
    2806             :     }
    2807          36 :     poDS->poDSIcon = std::move(poDSIcon);
    2808             : 
    2809          36 :     return poDS.release();
    2810             : }
    2811             : 
    2812             : /************************************************************************/
    2813             : /*                    KmlSuperOverlayDatasetDelete()                    */
    2814             : /************************************************************************/
    2815             : 
    2816           2 : static CPLErr KmlSuperOverlayDatasetDelete(CPL_UNUSED const char *fileName)
    2817             : {
    2818             :     /* Null implementation, so that people can Delete("MEM:::") */
    2819           2 :     return CE_None;
    2820             : }
    2821             : 
    2822             : /************************************************************************/
    2823             : /*                    GDALRegister_KMLSUPEROVERLAY()                    */
    2824             : /************************************************************************/
    2825             : 
    2826        1682 : void CPL_DLL GDALRegister_KMLSUPEROVERLAY()
    2827             : 
    2828             : {
    2829        1682 :     if (GDALGetDriverByName("KMLSUPEROVERLAY") != nullptr)
    2830         301 :         return;
    2831             : 
    2832        1381 :     GDALDriver *poDriver = new GDALDriver();
    2833             : 
    2834        1381 :     poDriver->SetDescription("KMLSUPEROVERLAY");
    2835        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    2836        1381 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Kml Super Overlay");
    2837        1381 :     poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
    2838             :                               "Byte Int16 UInt16 Int32 UInt32 Float32 Float64 "
    2839        1381 :                               "CInt16 CInt32 CFloat32 CFloat64");
    2840             : 
    2841        1381 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "kml kmz");
    2842             : 
    2843        1381 :     poDriver->SetMetadataItem(
    2844             :         GDAL_DMD_CREATIONOPTIONLIST,
    2845             :         "<CreationOptionList>"
    2846             :         "   <Option name='NAME' type='string' description='Overlay name'/>"
    2847             :         "   <Option name='DESCRIPTION' type='string' description='Overlay "
    2848             :         "description'/>"
    2849             :         "   <Option name='ALTITUDE' type='float' description='Distance above "
    2850             :         "the earth surface, in meters, interpreted according to the altitude "
    2851             :         "mode'/>"
    2852             :         "   <Option name='ALTITUDEMODE' type='string-select' "
    2853             :         "default='clampToGround' description='Specifies hows the altitude is "
    2854             :         "interpreted'>"
    2855             :         "       <Value>clampToGround</Value>"
    2856             :         "       <Value>absolute</Value>"
    2857             :         "       <Value>relativeToSeaFloor</Value>"
    2858             :         "       <Value>clampToSeaFloor</Value>"
    2859             :         "   </Option>"
    2860             :         "   <Option name='FORMAT' type='string-select' default='JPEG' "
    2861             :         "description='Format of the tiles'>"
    2862             :         "       <Value>PNG</Value>"
    2863             :         "       <Value>JPEG</Value>"
    2864             :         "       <Value>AUTO</Value>"
    2865             :         "   </Option>"
    2866             :         "   <Option name='FIX_ANTIMERIDIAN' type='boolean' description='Fix "
    2867             :         "for images crossing the antimeridian causing errors in Google Earth' "
    2868             :         "/>"
    2869        1381 :         "</CreationOptionList>");
    2870             : 
    2871        1381 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    2872             : 
    2873        1381 :     poDriver->pfnIdentify = KmlSuperOverlayReadDataset::Identify;
    2874        1381 :     poDriver->pfnOpen = KmlSuperOverlayReadDataset::Open;
    2875        1381 :     poDriver->pfnCreateCopy = KmlSuperOverlayCreateCopy;
    2876        1381 :     poDriver->pfnDelete = KmlSuperOverlayDatasetDelete;
    2877             : 
    2878        1381 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    2879             : }

Generated by: LCOV version 1.14