Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implementation of PMTiles
5 : * Author: Even Rouault <even.rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2023, Planet Labs
9 : *
10 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_json.h"
30 :
31 : #include "ogrsf_frmts.h"
32 : #include "ogrpmtilesfrommbtiles.h"
33 :
34 : #include "include_pmtiles.h"
35 :
36 : #include "cpl_compressor.h"
37 : #include "cpl_md5.h"
38 : #include "cpl_string.h"
39 : #include "cpl_vsi_virtual.h"
40 :
41 : #include <algorithm>
42 : #include <array>
43 : #include <cassert>
44 : #include <unordered_map>
45 : #include <utility>
46 :
47 : /************************************************************************/
48 : /* ProcessMetadata() */
49 : /************************************************************************/
50 :
51 35 : static bool ProcessMetadata(GDALDataset *poSQLiteDS, pmtiles::headerv3 &sHeader,
52 : std::string &osMetadata)
53 : {
54 :
55 35 : auto poMetadata = poSQLiteDS->GetLayerByName("metadata");
56 35 : if (!poMetadata)
57 : {
58 0 : CPLError(CE_Failure, CPLE_AppDefined, "metadata table not found");
59 0 : return false;
60 : }
61 :
62 35 : const int iName = poMetadata->GetLayerDefn()->GetFieldIndex("name");
63 35 : const int iValue = poMetadata->GetLayerDefn()->GetFieldIndex("value");
64 35 : if (iName < 0 || iValue < 0)
65 : {
66 0 : CPLError(CE_Failure, CPLE_AppDefined,
67 : "Bad structure for metadata table");
68 0 : return false;
69 : }
70 :
71 70 : CPLJSONObject oObj;
72 70 : CPLJSONDocument oJsonDoc;
73 420 : for (auto &&poFeature : poMetadata)
74 : {
75 385 : const char *pszName = poFeature->GetFieldAsString(iName);
76 385 : const char *pszValue = poFeature->GetFieldAsString(iValue);
77 385 : if (EQUAL(pszName, "json"))
78 : {
79 35 : if (!oJsonDoc.LoadMemory(pszValue))
80 : {
81 0 : CPLError(CE_Failure, CPLE_AppDefined,
82 : "Cannot parse 'json' metadata item");
83 0 : return false;
84 : }
85 105 : for (const auto &oChild : oJsonDoc.GetRoot().GetChildren())
86 : {
87 70 : oObj.Add(oChild.GetName(), oChild);
88 : }
89 : }
90 : else
91 : {
92 350 : oObj.Add(pszName, pszValue);
93 : }
94 : }
95 :
96 : // MBTiles advertises scheme=tms. Override this
97 35 : oObj.Set("scheme", "xyz");
98 :
99 105 : const auto osFormat = oObj.GetString("format", "{missing}");
100 35 : if (osFormat != "pbf")
101 : {
102 0 : CPLError(CE_Failure, CPLE_AppDefined, "format=%s unhandled",
103 : osFormat.c_str());
104 0 : return false;
105 : }
106 :
107 35 : int nMinZoom = atoi(oObj.GetString("minzoom", "-1").c_str());
108 35 : if (nMinZoom < 0 || nMinZoom > 255)
109 : {
110 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing or invalid minzoom");
111 0 : return false;
112 : }
113 :
114 35 : int nMaxZoom = atoi(oObj.GetString("maxzoom", "-1").c_str());
115 35 : if (nMaxZoom < 0 || nMaxZoom > 255)
116 : {
117 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing or invalid maxzoom");
118 0 : return false;
119 : }
120 :
121 : const CPLStringList aosCenter(
122 105 : CSLTokenizeString2(oObj.GetString("center").c_str(), ",", 0));
123 35 : if (aosCenter.size() != 3)
124 : {
125 0 : CPLError(CE_Failure, CPLE_AppDefined, "Expected 3 values for center");
126 0 : return false;
127 : }
128 35 : const double dfCenterLong = CPLAtof(aosCenter[0]);
129 35 : const double dfCenterLat = CPLAtof(aosCenter[1]);
130 35 : if (std::fabs(dfCenterLong) > 180 || std::fabs(dfCenterLat) > 90)
131 : {
132 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid center");
133 0 : return false;
134 : }
135 35 : const int nCenterZoom = atoi(aosCenter[2]);
136 35 : if (nCenterZoom < 0 || nCenterZoom > 255)
137 : {
138 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing or invalid center zoom");
139 0 : return false;
140 : }
141 :
142 : const CPLStringList aosBounds(
143 105 : CSLTokenizeString2(oObj.GetString("bounds").c_str(), ",", 0));
144 35 : if (aosBounds.size() != 4)
145 : {
146 0 : CPLError(CE_Failure, CPLE_AppDefined, "Expected 4 values for bounds");
147 0 : return false;
148 : }
149 35 : const double dfMinX = CPLAtof(aosBounds[0]);
150 35 : const double dfMinY = CPLAtof(aosBounds[1]);
151 35 : const double dfMaxX = CPLAtof(aosBounds[2]);
152 35 : const double dfMaxY = CPLAtof(aosBounds[3]);
153 35 : if (std::fabs(dfMinX) > 180 || std::fabs(dfMinY) > 90 ||
154 19 : std::fabs(dfMaxX) > 180 || std::fabs(dfMaxY) > 90)
155 : {
156 16 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid bounds");
157 16 : return false;
158 : }
159 :
160 19 : CPLJSONDocument oMetadataDoc;
161 19 : oMetadataDoc.SetRoot(oObj);
162 19 : osMetadata = oMetadataDoc.SaveAsString();
163 : // CPLDebugOnly("PMTiles", "Metadata = %s", osMetadata.c_str());
164 :
165 19 : sHeader.root_dir_offset = 127;
166 19 : sHeader.root_dir_bytes = 0;
167 19 : sHeader.json_metadata_offset = 0;
168 19 : sHeader.json_metadata_bytes = 0;
169 19 : sHeader.leaf_dirs_offset = 0;
170 19 : sHeader.leaf_dirs_bytes = 0;
171 19 : sHeader.tile_data_offset = 0;
172 19 : sHeader.tile_data_bytes = 0;
173 19 : sHeader.addressed_tiles_count = 0;
174 19 : sHeader.tile_entries_count = 0;
175 19 : sHeader.tile_contents_count = 0;
176 19 : sHeader.clustered = true;
177 19 : sHeader.internal_compression = pmtiles::COMPRESSION_GZIP;
178 19 : sHeader.tile_compression = pmtiles::COMPRESSION_GZIP;
179 19 : sHeader.tile_type = pmtiles::TILETYPE_MVT;
180 19 : sHeader.min_zoom = static_cast<uint8_t>(nMinZoom);
181 19 : sHeader.max_zoom = static_cast<uint8_t>(nMaxZoom);
182 19 : sHeader.min_lon_e7 = static_cast<int32_t>(dfMinX * 10e6);
183 19 : sHeader.min_lat_e7 = static_cast<int32_t>(dfMinY * 10e6);
184 19 : sHeader.max_lon_e7 = static_cast<int32_t>(dfMaxX * 10e6);
185 19 : sHeader.max_lat_e7 = static_cast<int32_t>(dfMaxY * 10e6);
186 19 : sHeader.center_zoom = static_cast<uint8_t>(nCenterZoom);
187 19 : sHeader.center_lon_e7 = static_cast<int32_t>(dfCenterLong * 10e6);
188 19 : sHeader.center_lat_e7 = static_cast<int32_t>(dfCenterLat * 10e6);
189 :
190 19 : return true;
191 : }
192 :
193 : /************************************************************************/
194 : /* HashArray() */
195 : /************************************************************************/
196 :
197 : // From https://codereview.stackexchange.com/questions/171999/specializing-stdhash-for-stdarray
198 : // We do not use std::hash<std::array<T, N>> as the name of the struct
199 : // because with gcc 5.4 we get the following error:
200 : // https://stackoverflow.com/questions/25594644/warning-specialization-of-template-in-different-namespace
201 : template <class T, size_t N> struct HashArray
202 : {
203 : CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
204 328 : size_t operator()(const std::array<T, N> &key) const
205 : {
206 : std::hash<T> hasher;
207 328 : size_t result = 0;
208 5576 : for (size_t i = 0; i < N; ++i)
209 : {
210 5248 : result = result * 31 + hasher(key[i]);
211 : }
212 328 : return result;
213 : }
214 : };
215 :
216 : /************************************************************************/
217 : /* OGRPMTilesConvertFromMBTiles() */
218 : /************************************************************************/
219 :
220 35 : bool OGRPMTilesConvertFromMBTiles(const char *pszDestName,
221 : const char *pszSrcName)
222 : {
223 35 : const char *const apszAllowedDrivers[] = {"SQLite", nullptr};
224 : auto poSQLiteDS = std::unique_ptr<GDALDataset>(
225 70 : GDALDataset::Open(pszSrcName, GDAL_OF_VECTOR, apszAllowedDrivers));
226 35 : if (!poSQLiteDS)
227 : {
228 0 : CPLError(CE_Failure, CPLE_AppDefined,
229 : "Cannot open %s with SQLite driver", pszSrcName);
230 0 : return false;
231 : }
232 :
233 : pmtiles::headerv3 sHeader;
234 70 : std::string osMetadata;
235 35 : if (!ProcessMetadata(poSQLiteDS.get(), sHeader, osMetadata))
236 16 : return false;
237 :
238 19 : auto poTilesLayer = poSQLiteDS->GetLayerByName("tiles");
239 19 : if (!poTilesLayer)
240 : {
241 0 : CPLError(CE_Failure, CPLE_AppDefined, "tiles table not found");
242 0 : return false;
243 : }
244 :
245 : const int iZoomLevel =
246 19 : poTilesLayer->GetLayerDefn()->GetFieldIndex("zoom_level");
247 : const int iTileColumn =
248 19 : poTilesLayer->GetLayerDefn()->GetFieldIndex("tile_column");
249 : const int iTileRow =
250 19 : poTilesLayer->GetLayerDefn()->GetFieldIndex("tile_row");
251 : const int iTileData =
252 19 : poTilesLayer->GetLayerDefn()->GetFieldIndex("tile_data");
253 19 : if (iZoomLevel < 0 || iTileColumn < 0 || iTileRow < 0 || iTileData < 0)
254 : {
255 0 : CPLError(CE_Failure, CPLE_AppDefined, "Bad structure for tiles table");
256 0 : return false;
257 : }
258 :
259 : struct TileEntry
260 : {
261 : uint64_t nTileId;
262 : std::array<unsigned char, 16> abyMD5;
263 : };
264 :
265 : // In a first step browse through the tiles table to compute the PMTiles
266 : // tile_id of each tile, and compute a hash of the tile data for
267 : // deduplication
268 38 : std::vector<TileEntry> asTileEntries;
269 276 : for (auto &&poFeature : poTilesLayer)
270 : {
271 257 : const int nZoomLevel = poFeature->GetFieldAsInteger(iZoomLevel);
272 257 : if (nZoomLevel < 0 || nZoomLevel > 30)
273 : {
274 0 : CPLError(CE_Warning, CPLE_AppDefined,
275 : "Skipping tile with missing or invalid zoom_level");
276 0 : continue;
277 : }
278 257 : const int nColumn = poFeature->GetFieldAsInteger(iTileColumn);
279 257 : if (nColumn < 0 || nColumn >= (1 << nZoomLevel))
280 : {
281 0 : CPLError(CE_Warning, CPLE_AppDefined,
282 : "Skipping tile with missing or invalid tile_column");
283 0 : continue;
284 : }
285 257 : const int nRow = poFeature->GetFieldAsInteger(iTileRow);
286 257 : if (nRow < 0 || nRow >= (1 << nZoomLevel))
287 : {
288 0 : CPLError(CE_Warning, CPLE_AppDefined,
289 : "Skipping tile with missing or invalid tile_row");
290 0 : continue;
291 : }
292 : // MBTiles uses a 0=bottom-most row, whereas PMTiles uses
293 : // 0=top-most row
294 257 : const int nY = (1 << nZoomLevel) - 1 - nRow;
295 : uint64_t nTileId;
296 : try
297 : {
298 257 : nTileId = pmtiles::zxy_to_tileid(static_cast<uint8_t>(nZoomLevel),
299 : nColumn, nY);
300 : }
301 0 : catch (const std::exception &e)
302 : {
303 : // shouldn't happen given previous checks
304 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot compute tile id: %s",
305 0 : e.what());
306 0 : return false;
307 : }
308 257 : int nTileDataLength = 0;
309 : const GByte *pabyData =
310 257 : poFeature->GetFieldAsBinary(iTileData, &nTileDataLength);
311 257 : if (!pabyData)
312 : {
313 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing tile_data");
314 0 : return false;
315 : }
316 :
317 : TileEntry sEntry;
318 257 : sEntry.nTileId = nTileId;
319 :
320 : CPLMD5Context md5context;
321 257 : CPLMD5Init(&md5context);
322 257 : CPLMD5Update(&md5context, pabyData, nTileDataLength);
323 257 : CPLMD5Final(&sEntry.abyMD5[0], &md5context);
324 : try
325 : {
326 257 : asTileEntries.push_back(sEntry);
327 : }
328 0 : catch (const std::exception &e)
329 : {
330 0 : CPLError(CE_Failure, CPLE_AppDefined,
331 0 : "Out of memory browsing through tiles: %s", e.what());
332 0 : return false;
333 : }
334 : }
335 :
336 : // Sort the tiles by ascending tile_id. This is a requirement to build
337 : // the PMTiles directories.
338 19 : std::sort(asTileEntries.begin(), asTileEntries.end(),
339 840 : [](const TileEntry &a, const TileEntry &b)
340 840 : { return a.nTileId < b.nTileId; });
341 :
342 : // Let's build a temporary file that contains the tile data in
343 : // a way that corresponds to the "clustered" mode, that is
344 : // "offsets are either contiguous with the previous offset+length, or
345 : // refer to a lesser offset, when writing with deduplication."
346 57 : std::string osTmpFile(std::string(pszDestName) + ".tmp");
347 19 : if (!VSIIsLocal(pszDestName))
348 : {
349 0 : osTmpFile = CPLGenerateTempFilename(CPLGetFilename(pszDestName));
350 : }
351 :
352 : auto poTmpFile =
353 38 : VSIVirtualHandleUniquePtr(VSIFOpenL(osTmpFile.c_str(), "wb+"));
354 19 : VSIUnlink(osTmpFile.c_str());
355 19 : if (!poTmpFile)
356 : {
357 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s for write",
358 : osTmpFile.c_str());
359 0 : return false;
360 : }
361 :
362 : struct ResetAndUnlinkTmpFile
363 : {
364 : VSIVirtualHandleUniquePtr &m_poFile;
365 : std::string m_osFilename;
366 :
367 19 : ResetAndUnlinkTmpFile(VSIVirtualHandleUniquePtr &poFile,
368 : const std::string &osFilename)
369 19 : : m_poFile(poFile), m_osFilename(osFilename)
370 : {
371 19 : }
372 :
373 19 : ~ResetAndUnlinkTmpFile()
374 19 : {
375 19 : m_poFile.reset();
376 19 : VSIUnlink(m_osFilename.c_str());
377 19 : }
378 : };
379 :
380 38 : ResetAndUnlinkTmpFile oReseer(poTmpFile, osTmpFile);
381 :
382 38 : std::vector<pmtiles::entryv3> asPMTilesEntries;
383 19 : uint64_t nLastTileId = 0;
384 19 : uint64_t nFileOffset = 0;
385 : std::array<unsigned char, 16> abyLastMD5;
386 : std::unordered_map<std::array<unsigned char, 16>,
387 : std::pair<uint64_t, uint32_t>,
388 : HashArray<unsigned char, 16>>
389 38 : oMapMD5ToOffsetLen;
390 276 : for (const auto &sEntry : asTileEntries)
391 : {
392 257 : if (sEntry.nTileId == nLastTileId + 1 && sEntry.abyMD5 == abyLastMD5)
393 : {
394 : // If the tile id immediately follows the previous one and
395 : // has the same tile data, increase the run_length
396 3 : asPMTilesEntries.back().run_length++;
397 : }
398 : else
399 : {
400 254 : pmtiles::entryv3 sPMTilesEntry;
401 254 : sPMTilesEntry.tile_id = sEntry.nTileId;
402 254 : sPMTilesEntry.run_length = 1;
403 :
404 254 : auto oIter = oMapMD5ToOffsetLen.find(sEntry.abyMD5);
405 254 : if (oIter != oMapMD5ToOffsetLen.end())
406 : {
407 : // Point to previously written tile data if this content
408 : // has already been written
409 180 : sPMTilesEntry.offset = oIter->second.first;
410 180 : sPMTilesEntry.length = oIter->second.second;
411 : }
412 : else
413 : {
414 : try
415 : {
416 74 : const auto sXYZ = pmtiles::tileid_to_zxy(sEntry.nTileId);
417 74 : poTilesLayer->SetAttributeFilter(CPLSPrintf(
418 : "zoom_level = %d AND tile_column = %u AND tile_row = "
419 : "%u",
420 74 : sXYZ.z, sXYZ.x, (1U << sXYZ.z) - 1U - sXYZ.y));
421 : }
422 0 : catch (const std::exception &e)
423 : {
424 : // shouldn't happen given previous checks
425 0 : CPLError(CE_Failure, CPLE_AppDefined,
426 0 : "Cannot compute xyz: %s", e.what());
427 0 : return false;
428 : }
429 74 : poTilesLayer->ResetReading();
430 : auto poFeature =
431 74 : std::unique_ptr<OGRFeature>(poTilesLayer->GetNextFeature());
432 74 : if (!poFeature)
433 : {
434 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find tile");
435 0 : return false;
436 : }
437 74 : int nTileDataLength = 0;
438 : const GByte *pabyData =
439 74 : poFeature->GetFieldAsBinary(iTileData, &nTileDataLength);
440 74 : if (!pabyData)
441 : {
442 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing tile_data");
443 0 : return false;
444 : }
445 :
446 74 : sPMTilesEntry.offset = nFileOffset;
447 74 : sPMTilesEntry.length = nTileDataLength;
448 :
449 74 : oMapMD5ToOffsetLen[sEntry.abyMD5] =
450 148 : std::pair<uint64_t, uint32_t>(nFileOffset, nTileDataLength);
451 :
452 74 : nFileOffset += nTileDataLength;
453 :
454 74 : if (poTmpFile->Write(pabyData, nTileDataLength, 1) != 1)
455 : {
456 0 : CPLError(CE_Failure, CPLE_FileIO, "Failed writing");
457 0 : return false;
458 : }
459 : }
460 :
461 254 : asPMTilesEntries.push_back(sPMTilesEntry);
462 :
463 254 : nLastTileId = sEntry.nTileId;
464 254 : abyLastMD5 = sEntry.abyMD5;
465 : }
466 : }
467 :
468 19 : const CPLCompressor *psCompressor = CPLGetCompressor("gzip");
469 19 : assert(psCompressor);
470 38 : std::string osCompressed;
471 :
472 : struct compression_exception : std::exception
473 : {
474 0 : const char *what() const noexcept override
475 : {
476 0 : return "Compression failed";
477 : }
478 : };
479 :
480 38 : const auto oCompressFunc = [psCompressor,
481 : &osCompressed](const std::string &osBytes,
482 228 : uint8_t) -> std::string
483 : {
484 38 : osCompressed.resize(32 + osBytes.size() * 2);
485 38 : size_t nOutputSize = osCompressed.size();
486 38 : void *pOutputData = &osCompressed[0];
487 38 : if (!psCompressor->pfnFunc(osBytes.data(), osBytes.size(), &pOutputData,
488 : &nOutputSize, nullptr,
489 38 : psCompressor->user_data))
490 : {
491 0 : throw compression_exception();
492 : }
493 38 : osCompressed.resize(nOutputSize);
494 76 : return osCompressed;
495 19 : };
496 :
497 38 : std::string osCompressedMetadata;
498 :
499 38 : std::string osRootBytes;
500 38 : std::string osLeaveBytes;
501 : int nNumLeaves;
502 : try
503 : {
504 : osCompressedMetadata =
505 19 : oCompressFunc(osMetadata, pmtiles::COMPRESSION_GZIP);
506 :
507 : // Build the root and leave directories (one depth max)
508 19 : std::tie(osRootBytes, osLeaveBytes, nNumLeaves) =
509 38 : pmtiles::make_root_leaves(oCompressFunc, pmtiles::COMPRESSION_GZIP,
510 19 : asPMTilesEntries);
511 : }
512 0 : catch (const std::exception &e)
513 : {
514 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot build directories: %s",
515 0 : e.what());
516 0 : return false;
517 : }
518 :
519 : // Finalize the header fields related to offsets and size of the
520 : // different parts of the file
521 19 : sHeader.root_dir_bytes = osRootBytes.size();
522 19 : sHeader.json_metadata_offset =
523 19 : sHeader.root_dir_offset + sHeader.root_dir_bytes;
524 19 : sHeader.json_metadata_bytes = osCompressedMetadata.size();
525 19 : sHeader.leaf_dirs_offset =
526 19 : sHeader.json_metadata_offset + sHeader.json_metadata_bytes;
527 19 : sHeader.leaf_dirs_bytes = osLeaveBytes.size();
528 19 : sHeader.tile_data_offset =
529 19 : sHeader.leaf_dirs_offset + sHeader.leaf_dirs_bytes;
530 19 : sHeader.tile_data_bytes = nFileOffset;
531 :
532 : // Nomber of tiles that are addressable in the PMTiles archive, that is
533 : // the number of tiles we would have if not deduplicating them
534 19 : sHeader.addressed_tiles_count = asTileEntries.size();
535 :
536 : // Number of tile entries in root and leave directories
537 : // ie entries whose run_length >= 1
538 19 : sHeader.tile_entries_count = asPMTilesEntries.size();
539 :
540 : // Number of distinct tile blobs
541 19 : sHeader.tile_contents_count = oMapMD5ToOffsetLen.size();
542 :
543 : // Now build the final file!
544 38 : auto poFile = VSIVirtualHandleUniquePtr(VSIFOpenL(pszDestName, "wb"));
545 19 : if (!poFile)
546 : {
547 0 : CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s for write",
548 : pszDestName);
549 0 : return false;
550 : }
551 38 : const auto osHeader = sHeader.serialize();
552 :
553 19 : if (poTmpFile->Seek(0, SEEK_SET) != 0 ||
554 19 : poFile->Write(osHeader.data(), osHeader.size(), 1) != 1 ||
555 19 : poFile->Write(osRootBytes.data(), osRootBytes.size(), 1) != 1 ||
556 38 : poFile->Write(osCompressedMetadata.data(), osCompressedMetadata.size(),
557 57 : 1) != 1 ||
558 19 : (!osLeaveBytes.empty() &&
559 0 : poFile->Write(osLeaveBytes.data(), osLeaveBytes.size(), 1) != 1))
560 : {
561 0 : CPLError(CE_Failure, CPLE_FileIO, "Failed writing");
562 0 : return false;
563 : }
564 :
565 : // Copy content of the temporary file at end of the output file.
566 38 : std::string oCopyBuffer;
567 19 : oCopyBuffer.resize(1024 * 1024);
568 19 : const uint64_t nTotalSize = nFileOffset;
569 19 : nFileOffset = 0;
570 33 : while (nFileOffset < nTotalSize)
571 : {
572 : const size_t nToRead = static_cast<size_t>(
573 14 : std::min<uint64_t>(nTotalSize - nFileOffset, oCopyBuffer.size()));
574 28 : if (poTmpFile->Read(&oCopyBuffer[0], nToRead, 1) != 1 ||
575 14 : poFile->Write(&oCopyBuffer[0], nToRead, 1) != 1)
576 : {
577 0 : CPLError(CE_Failure, CPLE_FileIO, "Failed writing");
578 0 : return false;
579 : }
580 14 : nFileOffset += nToRead;
581 : }
582 :
583 19 : if (poFile->Close() != 0)
584 0 : return false;
585 :
586 19 : return true;
587 : }
|