Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: FlatGeobuf driver
4 : * Purpose: Implements OGRFlatGeobufDataset class
5 : * Author: Björn Harrtell <bjorn at wololo dot org>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2018-2020, Björn Harrtell <bjorn at wololo dot org>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "ogr_flatgeobuf.h"
14 :
15 : #include <memory>
16 :
17 : #include "header_generated.h"
18 :
19 : // For users not using CMake...
20 : #ifndef flatbuffers
21 : #error \
22 : "Make sure to build with -Dflatbuffers=gdal_flatbuffers (for example) to avoid potential conflict of flatbuffers"
23 : #endif
24 :
25 : using namespace flatbuffers;
26 : using namespace FlatGeobuf;
27 :
28 55507 : static int OGRFlatGeobufDriverIdentify(GDALOpenInfo *poOpenInfo)
29 : {
30 55507 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "FGB:"))
31 0 : return TRUE;
32 :
33 55507 : if (poOpenInfo->bIsDirectory)
34 : {
35 1407 : return -1;
36 : }
37 :
38 54100 : const auto nHeaderBytes = poOpenInfo->nHeaderBytes;
39 54100 : const auto pabyHeader = poOpenInfo->pabyHeader;
40 :
41 54100 : if (nHeaderBytes < 4)
42 50654 : return FALSE;
43 :
44 3446 : if (pabyHeader[0] == 0x66 && pabyHeader[1] == 0x67 && pabyHeader[2] == 0x62)
45 : {
46 306 : if (pabyHeader[3] == 0x03)
47 : {
48 306 : CPLDebug("FlatGeobuf", "Verified magicbytes");
49 306 : return TRUE;
50 : }
51 : else
52 : {
53 0 : CPLError(CE_Failure, CPLE_OpenFailed,
54 : "Unsupported FlatGeobuf version %d.\n",
55 0 : poOpenInfo->pabyHeader[3]);
56 : }
57 : }
58 :
59 3140 : return FALSE;
60 : }
61 :
62 : /************************************************************************/
63 : /* Delete() */
64 : /************************************************************************/
65 :
66 148 : static CPLErr OGRFlatGoBufDriverDelete(const char *pszDataSource)
67 :
68 : {
69 : VSIStatBufL sStatBuf;
70 :
71 148 : if (VSIStatL(pszDataSource, &sStatBuf) != 0)
72 : {
73 0 : CPLError(CE_Failure, CPLE_AppDefined,
74 : "%s does not appear to be a file or directory.",
75 : pszDataSource);
76 :
77 0 : return CE_Failure;
78 : }
79 :
80 148 : if (VSI_ISREG(sStatBuf.st_mode))
81 : {
82 147 : VSIUnlink(pszDataSource);
83 147 : return CE_None;
84 : }
85 :
86 1 : if (VSI_ISDIR(sStatBuf.st_mode))
87 : {
88 1 : char **papszDirEntries = VSIReadDir(pszDataSource);
89 :
90 3 : for (int iFile = 0;
91 3 : papszDirEntries != nullptr && papszDirEntries[iFile] != nullptr;
92 : iFile++)
93 : {
94 2 : if (EQUAL(CPLGetExtensionSafe(papszDirEntries[iFile]).c_str(),
95 : "fgb"))
96 : {
97 2 : VSIUnlink(CPLFormFilenameSafe(pszDataSource,
98 2 : papszDirEntries[iFile], nullptr)
99 : .c_str());
100 : }
101 : }
102 :
103 1 : CSLDestroy(papszDirEntries);
104 :
105 1 : VSIRmdir(pszDataSource);
106 : }
107 :
108 1 : return CE_None;
109 : }
110 :
111 : /************************************************************************/
112 : /* RegisterOGRFlatGeobuf() */
113 : /************************************************************************/
114 :
115 2066 : void RegisterOGRFlatGeobuf()
116 : {
117 2066 : if (GDALGetDriverByName("FlatGeobuf") != nullptr)
118 263 : return;
119 :
120 1803 : GDALDriver *poDriver = new GDALDriver();
121 1803 : poDriver->SetDescription("FlatGeobuf");
122 1803 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
123 1803 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
124 1803 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
125 1803 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
126 1803 : poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
127 1803 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
128 1803 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
129 1803 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
130 1803 : poDriver->SetMetadataItem(GDAL_DCAP_REOPEN_AFTER_WRITE_REQUIRED, "YES");
131 1803 : poDriver->SetMetadataItem(GDAL_DCAP_CAN_READ_AFTER_DELETE, "YES");
132 1803 : poDriver->SetMetadataItem(GDAL_DCAP_MULTIPLE_VECTOR_LAYERS_IN_DIRECTORY,
133 1803 : "YES");
134 :
135 1803 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "FlatGeobuf");
136 1803 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "fgb");
137 1803 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
138 1803 : "drivers/vector/flatgeobuf.html");
139 1803 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
140 1803 : poDriver->SetMetadataItem(
141 : GDAL_DMD_CREATIONFIELDDATATYPES,
142 1803 : "Integer Integer64 Real String Date DateTime Binary");
143 1803 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
144 1803 : "Boolean Int16 Float32");
145 1803 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
146 1803 : "WidthPrecision Comment AlternativeName");
147 :
148 1803 : poDriver->SetMetadataItem(
149 : GDAL_DS_LAYER_CREATIONOPTIONLIST,
150 : "<LayerCreationOptionList>"
151 : " <Option name='SPATIAL_INDEX' type='boolean' description='Whether to "
152 : "create a spatial index' default='YES'/>"
153 : " <Option name='TEMPORARY_DIR' type='string' description='Directory "
154 : "where temporary file should be created'/>"
155 : " <Option name='TITLE' type='string' description='Layer title'/>"
156 : " <Option name='DESCRIPTION' type='string' "
157 : "description='Layer description'/>"
158 1803 : "</LayerCreationOptionList>");
159 1803 : poDriver->SetMetadataItem(
160 : GDAL_DMD_OPENOPTIONLIST,
161 : "<OpenOptionList>"
162 : " <Option name='VERIFY_BUFFERS' type='boolean' description='Verify "
163 : "flatbuffers integrity' default='YES'/>"
164 1803 : "</OpenOptionList>");
165 :
166 1803 : poDriver->SetMetadataItem(GDAL_DCAP_COORDINATE_EPOCH, "YES");
167 1803 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
168 1803 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
169 1803 : "Name WidthPrecision AlternativeName Comment");
170 :
171 1803 : poDriver->pfnOpen = OGRFlatGeobufDataset::Open;
172 1803 : poDriver->pfnCreate = OGRFlatGeobufDataset::Create;
173 1803 : poDriver->pfnIdentify = OGRFlatGeobufDriverIdentify;
174 1803 : poDriver->pfnDelete = OGRFlatGoBufDriverDelete;
175 :
176 1803 : GetGDALDriverManager()->RegisterDriver(poDriver);
177 : }
178 :
179 : /************************************************************************/
180 : /* OGRFlatGeobufDataset() */
181 : /************************************************************************/
182 :
183 883 : OGRFlatGeobufDataset::OGRFlatGeobufDataset(const char *pszName, bool bIsDir,
184 883 : bool bCreate, bool bUpdate)
185 883 : : m_bCreate(bCreate), m_bUpdate(bUpdate), m_bIsDir(bIsDir)
186 : {
187 883 : SetDescription(pszName);
188 883 : }
189 :
190 : /************************************************************************/
191 : /* ~OGRFlatGeobufDataset() */
192 : /************************************************************************/
193 :
194 1766 : OGRFlatGeobufDataset::~OGRFlatGeobufDataset()
195 : {
196 883 : OGRFlatGeobufDataset::Close();
197 1766 : }
198 :
199 : /************************************************************************/
200 : /* Close() */
201 : /************************************************************************/
202 :
203 1201 : CPLErr OGRFlatGeobufDataset::Close(GDALProgressFunc, void *)
204 : {
205 1201 : CPLErr eErr = CE_None;
206 1201 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
207 : {
208 883 : if (OGRFlatGeobufDataset::FlushCache(true) != CE_None)
209 0 : eErr = CE_Failure;
210 :
211 1209 : for (auto &poLayer : m_apoLayers)
212 : {
213 326 : if (poLayer->Close() != CE_None)
214 0 : eErr = CE_Failure;
215 : }
216 :
217 883 : if (GDALDataset::Close() != CE_None)
218 0 : eErr = CE_Failure;
219 : }
220 1201 : return eErr;
221 : }
222 :
223 : /************************************************************************/
224 : /* Open() */
225 : /************************************************************************/
226 :
227 779 : GDALDataset *OGRFlatGeobufDataset::Open(GDALOpenInfo *poOpenInfo)
228 : {
229 779 : if (OGRFlatGeobufDriverIdentify(poOpenInfo) == FALSE)
230 0 : return nullptr;
231 :
232 : const auto bVerifyBuffers =
233 779 : CPLFetchBool(poOpenInfo->papszOpenOptions, "VERIFY_BUFFERS", true);
234 :
235 779 : auto isDir = CPL_TO_BOOL(poOpenInfo->bIsDirectory);
236 779 : auto bUpdate = poOpenInfo->eAccess == GA_Update;
237 :
238 779 : if (isDir && bUpdate)
239 : {
240 71 : return nullptr;
241 : }
242 :
243 708 : auto poDS = std::make_unique<OGRFlatGeobufDataset>(poOpenInfo->pszFilename,
244 1416 : isDir, false, bUpdate);
245 :
246 708 : if (poOpenInfo->bIsDirectory)
247 : {
248 558 : CPLStringList aosFiles(VSIReadDir(poOpenInfo->pszFilename));
249 558 : int nCountFGB = 0;
250 558 : int nCountNonFGB = 0;
251 23029 : for (int i = 0; i < aosFiles.size(); i++)
252 : {
253 22471 : if (strcmp(aosFiles[i], ".") == 0 || strcmp(aosFiles[i], "..") == 0)
254 536 : continue;
255 21935 : if (EQUAL(CPLGetExtensionSafe(aosFiles[i]).c_str(), "fgb"))
256 2 : nCountFGB++;
257 : else
258 21933 : nCountNonFGB++;
259 : }
260 : // Consider that a directory is a FlatGeobuf dataset if there is a
261 : // majority of .fgb files in it
262 558 : if (nCountFGB == 0 || nCountFGB < nCountNonFGB)
263 : {
264 557 : return nullptr;
265 : }
266 3 : for (int i = 0; i < aosFiles.size(); i++)
267 : {
268 2 : if (EQUAL(CPLGetExtensionSafe(aosFiles[i]).c_str(), "fgb"))
269 : {
270 0 : const CPLString osFilename(CPLFormFilenameSafe(
271 4 : poOpenInfo->pszFilename, aosFiles[i], nullptr));
272 2 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
273 2 : if (fp)
274 : {
275 2 : if (!poDS->OpenFile(osFilename, fp, bVerifyBuffers))
276 0 : VSIFCloseL(fp);
277 : }
278 : }
279 : }
280 : }
281 : else
282 : {
283 150 : if (poOpenInfo->fpL != nullptr)
284 : {
285 150 : if (poDS->OpenFile(poOpenInfo->pszFilename, poOpenInfo->fpL,
286 : bVerifyBuffers))
287 150 : poOpenInfo->fpL = nullptr;
288 : }
289 : else
290 : {
291 0 : return nullptr;
292 : }
293 : }
294 151 : return poDS.release();
295 : }
296 :
297 : /************************************************************************/
298 : /* OpenFile() */
299 : /************************************************************************/
300 :
301 152 : bool OGRFlatGeobufDataset::OpenFile(const char *pszFilename, VSILFILE *fp,
302 : bool bVerifyBuffers)
303 : {
304 152 : CPLDebugOnly("FlatGeobuf", "Opening OGRFlatGeobufLayer");
305 : auto poLayer = std::unique_ptr<OGRFlatGeobufLayer>(
306 304 : OGRFlatGeobufLayer::Open(pszFilename, fp, bVerifyBuffers));
307 152 : if (!poLayer)
308 0 : return false;
309 :
310 152 : if (m_bUpdate)
311 : {
312 1 : CPLDebugOnly("FlatGeobuf", "Creating OGRFlatGeobufEditableLayer");
313 : auto poEditableLayer = std::unique_ptr<OGRFlatGeobufEditableLayer>(
314 1 : new OGRFlatGeobufEditableLayer(poLayer.release(),
315 1 : papszOpenOptions));
316 1 : m_apoLayers.push_back(std::move(poEditableLayer));
317 : }
318 : else
319 : {
320 151 : m_apoLayers.push_back(std::move(poLayer));
321 : }
322 :
323 152 : return true;
324 : }
325 :
326 175 : GDALDataset *OGRFlatGeobufDataset::Create(const char *pszName, int /* nBands */,
327 : CPL_UNUSED int nXSize,
328 : CPL_UNUSED int nYSize,
329 : CPL_UNUSED GDALDataType eDT,
330 : CSLConstList /* papszOptions */)
331 : {
332 : // First, ensure there isn't any such file yet.
333 : VSIStatBufL sStatBuf;
334 :
335 175 : if (VSIStatL(pszName, &sStatBuf) == 0)
336 : {
337 0 : CPLError(CE_Failure, CPLE_AppDefined,
338 : "It seems a file system object called '%s' already exists.",
339 : pszName);
340 :
341 0 : return nullptr;
342 : }
343 :
344 175 : bool bIsDir = false;
345 175 : if (!EQUAL(CPLGetExtensionSafe(pszName).c_str(), "fgb"))
346 : {
347 1 : if (VSIMkdir(pszName, 0755) != 0)
348 : {
349 0 : CPLError(CE_Failure, CPLE_AppDefined,
350 : "Failed to create directory %s:\n%s", pszName,
351 0 : VSIStrerror(errno));
352 0 : return nullptr;
353 : }
354 1 : bIsDir = true;
355 : }
356 :
357 175 : return new OGRFlatGeobufDataset(pszName, bIsDir, true, false);
358 : }
359 :
360 586 : const OGRLayer *OGRFlatGeobufDataset::GetLayer(int iLayer) const
361 : {
362 586 : if (iLayer < 0 || iLayer >= GetLayerCount())
363 2 : return nullptr;
364 584 : return m_apoLayers[iLayer]->GetLayer();
365 : }
366 :
367 93 : int OGRFlatGeobufDataset::TestCapability(const char *pszCap) const
368 : {
369 93 : if (EQUAL(pszCap, ODsCCreateLayer))
370 38 : return m_bCreate && (m_bIsDir || m_apoLayers.empty());
371 55 : else if (EQUAL(pszCap, ODsCCurveGeometries))
372 22 : return true;
373 33 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
374 2 : return true;
375 31 : else if (EQUAL(pszCap, ODsCZGeometries))
376 2 : return true;
377 29 : else if (EQUAL(pszCap, ODsCRandomLayerWrite))
378 0 : return m_bUpdate;
379 : else
380 29 : return false;
381 : }
382 :
383 : /************************************************************************/
384 : /* LaunderLayerName() */
385 : /************************************************************************/
386 :
387 2 : static CPLString LaunderLayerName(const char *pszLayerName)
388 : {
389 4 : std::string osRet(CPLLaunderForFilenameSafe(pszLayerName, nullptr));
390 2 : if (osRet != pszLayerName)
391 : {
392 1 : CPLError(CE_Warning, CPLE_AppDefined,
393 : "Invalid layer name for a file name: %s. Laundered to %s.",
394 : pszLayerName, osRet.c_str());
395 : }
396 4 : return osRet;
397 : }
398 :
399 : OGRLayer *
400 192 : OGRFlatGeobufDataset::ICreateLayer(const char *pszLayerName,
401 : const OGRGeomFieldDefn *poGeomFieldDefn,
402 : CSLConstList papszOptions)
403 : {
404 : // Verify we are in update mode.
405 192 : if (!m_bCreate)
406 : {
407 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
408 : "Data source %s opened read-only.\n"
409 : "New layer %s cannot be created.",
410 0 : GetDescription(), pszLayerName);
411 :
412 0 : return nullptr;
413 : }
414 192 : if (!m_bIsDir && !m_apoLayers.empty())
415 : {
416 16 : CPLError(CE_Failure, CPLE_NoWriteAccess,
417 : "Can create only one single layer in a .fgb file. "
418 : "Use a directory output for multiple layers");
419 :
420 16 : return nullptr;
421 : }
422 :
423 176 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
424 : const auto poSpatialRef =
425 176 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
426 :
427 : // Verify that the datasource is a directory.
428 : VSIStatBufL sStatBuf;
429 :
430 : // What filename would we use?
431 352 : CPLString osFilename;
432 :
433 176 : if (m_bIsDir)
434 4 : osFilename = CPLFormFilenameSafe(
435 6 : GetDescription(), LaunderLayerName(pszLayerName).c_str(), "fgb");
436 : else
437 174 : osFilename = GetDescription();
438 :
439 : // Does this directory/file already exist?
440 176 : if (VSIStatL(osFilename, &sStatBuf) == 0)
441 : {
442 0 : CPLError(CE_Failure, CPLE_FileIO,
443 : "Attempt to create layer %s, but %s already exists.",
444 : pszLayerName, osFilename.c_str());
445 0 : return nullptr;
446 : }
447 :
448 : bool bCreateSpatialIndexAtClose =
449 176 : CPLFetchBool(papszOptions, "SPATIAL_INDEX", true);
450 :
451 : auto poLayer =
452 : std::unique_ptr<OGRFlatGeobufLayer>(OGRFlatGeobufLayer::Create(
453 : this, pszLayerName, osFilename, poSpatialRef, eGType,
454 352 : bCreateSpatialIndexAtClose, papszOptions));
455 176 : if (poLayer == nullptr)
456 2 : return nullptr;
457 :
458 174 : m_apoLayers.push_back(std::move(poLayer));
459 :
460 174 : return m_apoLayers.back()->GetLayer();
461 : }
462 :
463 : /************************************************************************/
464 : // GetFileList() */
465 : /************************************************************************/
466 :
467 3 : char **OGRFlatGeobufDataset::GetFileList()
468 : {
469 6 : CPLStringList oFileList;
470 7 : for (const auto &poLayer : m_apoLayers)
471 : {
472 4 : oFileList.AddString(poLayer->GetFilename().c_str());
473 : }
474 6 : return oFileList.StealList();
475 : }
|