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 53412 : static int OGRFlatGeobufDriverIdentify(GDALOpenInfo *poOpenInfo)
29 : {
30 53412 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "FGB:"))
31 0 : return TRUE;
32 :
33 53412 : if (poOpenInfo->bIsDirectory)
34 : {
35 1303 : return -1;
36 : }
37 :
38 52109 : const auto nHeaderBytes = poOpenInfo->nHeaderBytes;
39 52109 : const auto pabyHeader = poOpenInfo->pabyHeader;
40 :
41 52109 : if (nHeaderBytes < 4)
42 48898 : return FALSE;
43 :
44 3211 : 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 2905 : 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 2024 : void RegisterOGRFlatGeobuf()
116 : {
117 2024 : if (GDALGetDriverByName("FlatGeobuf") != nullptr)
118 283 : return;
119 :
120 1741 : GDALDriver *poDriver = new GDALDriver();
121 1741 : poDriver->SetDescription("FlatGeobuf");
122 1741 : poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
123 1741 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES");
124 1741 : poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES");
125 1741 : poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES");
126 1741 : poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES");
127 1741 : poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES");
128 1741 : poDriver->SetMetadataItem(GDAL_DCAP_MEASURED_GEOMETRIES, "YES");
129 1741 : poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
130 1741 : poDriver->SetMetadataItem(GDAL_DCAP_REOPEN_AFTER_WRITE_REQUIRED, "YES");
131 1741 : poDriver->SetMetadataItem(GDAL_DCAP_CAN_READ_AFTER_DELETE, "YES");
132 1741 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "FlatGeobuf");
133 1741 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "fgb");
134 1741 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
135 1741 : "drivers/vector/flatgeobuf.html");
136 1741 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
137 1741 : poDriver->SetMetadataItem(
138 : GDAL_DMD_CREATIONFIELDDATATYPES,
139 1741 : "Integer Integer64 Real String Date DateTime Binary");
140 1741 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONFIELDDATASUBTYPES,
141 1741 : "Boolean Int16 Float32");
142 1741 : poDriver->SetMetadataItem(GDAL_DMD_CREATION_FIELD_DEFN_FLAGS,
143 1741 : "WidthPrecision Comment AlternativeName");
144 :
145 1741 : poDriver->SetMetadataItem(
146 : GDAL_DS_LAYER_CREATIONOPTIONLIST,
147 : "<LayerCreationOptionList>"
148 : " <Option name='SPATIAL_INDEX' type='boolean' description='Whether to "
149 : "create a spatial index' default='YES'/>"
150 : " <Option name='TEMPORARY_DIR' type='string' description='Directory "
151 : "where temporary file should be created'/>"
152 : " <Option name='TITLE' type='string' description='Layer title'/>"
153 : " <Option name='DESCRIPTION' type='string' "
154 : "description='Layer description'/>"
155 1741 : "</LayerCreationOptionList>");
156 1741 : poDriver->SetMetadataItem(
157 : GDAL_DMD_OPENOPTIONLIST,
158 : "<OpenOptionList>"
159 : " <Option name='VERIFY_BUFFERS' type='boolean' description='Verify "
160 : "flatbuffers integrity' default='YES'/>"
161 1741 : "</OpenOptionList>");
162 :
163 1741 : poDriver->SetMetadataItem(GDAL_DCAP_COORDINATE_EPOCH, "YES");
164 1741 : poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");
165 1741 : poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS,
166 1741 : "Name WidthPrecision AlternativeName Comment");
167 :
168 1741 : poDriver->pfnOpen = OGRFlatGeobufDataset::Open;
169 1741 : poDriver->pfnCreate = OGRFlatGeobufDataset::Create;
170 1741 : poDriver->pfnIdentify = OGRFlatGeobufDriverIdentify;
171 1741 : poDriver->pfnDelete = OGRFlatGoBufDriverDelete;
172 :
173 1741 : GetGDALDriverManager()->RegisterDriver(poDriver);
174 : }
175 :
176 : /************************************************************************/
177 : /* OGRFlatGeobufDataset() */
178 : /************************************************************************/
179 :
180 845 : OGRFlatGeobufDataset::OGRFlatGeobufDataset(const char *pszName, bool bIsDir,
181 845 : bool bCreate, bool bUpdate)
182 845 : : m_bCreate(bCreate), m_bUpdate(bUpdate), m_bIsDir(bIsDir)
183 : {
184 845 : SetDescription(pszName);
185 845 : }
186 :
187 : /************************************************************************/
188 : /* ~OGRFlatGeobufDataset() */
189 : /************************************************************************/
190 :
191 1690 : OGRFlatGeobufDataset::~OGRFlatGeobufDataset()
192 : {
193 845 : OGRFlatGeobufDataset::Close();
194 1690 : }
195 :
196 : /************************************************************************/
197 : /* Close() */
198 : /************************************************************************/
199 :
200 1163 : CPLErr OGRFlatGeobufDataset::Close()
201 : {
202 1163 : CPLErr eErr = CE_None;
203 1163 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
204 : {
205 845 : if (OGRFlatGeobufDataset::FlushCache(true) != CE_None)
206 0 : eErr = CE_Failure;
207 :
208 1171 : for (auto &poLayer : m_apoLayers)
209 : {
210 326 : if (poLayer->Close() != CE_None)
211 0 : eErr = CE_Failure;
212 : }
213 :
214 845 : if (GDALDataset::Close() != CE_None)
215 0 : eErr = CE_Failure;
216 : }
217 1163 : return eErr;
218 : }
219 :
220 : /************************************************************************/
221 : /* Open() */
222 : /************************************************************************/
223 :
224 741 : GDALDataset *OGRFlatGeobufDataset::Open(GDALOpenInfo *poOpenInfo)
225 : {
226 741 : if (OGRFlatGeobufDriverIdentify(poOpenInfo) == FALSE)
227 0 : return nullptr;
228 :
229 : const auto bVerifyBuffers =
230 741 : CPLFetchBool(poOpenInfo->papszOpenOptions, "VERIFY_BUFFERS", true);
231 :
232 741 : auto isDir = CPL_TO_BOOL(poOpenInfo->bIsDirectory);
233 741 : auto bUpdate = poOpenInfo->eAccess == GA_Update;
234 :
235 741 : if (isDir && bUpdate)
236 : {
237 71 : return nullptr;
238 : }
239 :
240 : auto poDS = std::unique_ptr<OGRFlatGeobufDataset>(new OGRFlatGeobufDataset(
241 1340 : poOpenInfo->pszFilename, isDir, false, bUpdate));
242 :
243 670 : if (poOpenInfo->bIsDirectory)
244 : {
245 520 : CPLStringList aosFiles(VSIReadDir(poOpenInfo->pszFilename));
246 520 : int nCountFGB = 0;
247 520 : int nCountNonFGB = 0;
248 22914 : for (int i = 0; i < aosFiles.size(); i++)
249 : {
250 22394 : if (strcmp(aosFiles[i], ".") == 0 || strcmp(aosFiles[i], "..") == 0)
251 508 : continue;
252 21886 : if (EQUAL(CPLGetExtensionSafe(aosFiles[i]).c_str(), "fgb"))
253 2 : nCountFGB++;
254 : else
255 21884 : nCountNonFGB++;
256 : }
257 : // Consider that a directory is a FlatGeobuf dataset if there is a
258 : // majority of .fgb files in it
259 520 : if (nCountFGB == 0 || nCountFGB < nCountNonFGB)
260 : {
261 519 : return nullptr;
262 : }
263 3 : for (int i = 0; i < aosFiles.size(); i++)
264 : {
265 2 : if (EQUAL(CPLGetExtensionSafe(aosFiles[i]).c_str(), "fgb"))
266 : {
267 0 : const CPLString osFilename(CPLFormFilenameSafe(
268 4 : poOpenInfo->pszFilename, aosFiles[i], nullptr));
269 2 : VSILFILE *fp = VSIFOpenL(osFilename, "rb");
270 2 : if (fp)
271 : {
272 2 : if (!poDS->OpenFile(osFilename, fp, bVerifyBuffers))
273 0 : VSIFCloseL(fp);
274 : }
275 : }
276 : }
277 : }
278 : else
279 : {
280 150 : if (poOpenInfo->fpL != nullptr)
281 : {
282 150 : if (poDS->OpenFile(poOpenInfo->pszFilename, poOpenInfo->fpL,
283 : bVerifyBuffers))
284 150 : poOpenInfo->fpL = nullptr;
285 : }
286 : else
287 : {
288 0 : return nullptr;
289 : }
290 : }
291 151 : return poDS.release();
292 : }
293 :
294 : /************************************************************************/
295 : /* OpenFile() */
296 : /************************************************************************/
297 :
298 152 : bool OGRFlatGeobufDataset::OpenFile(const char *pszFilename, VSILFILE *fp,
299 : bool bVerifyBuffers)
300 : {
301 152 : CPLDebugOnly("FlatGeobuf", "Opening OGRFlatGeobufLayer");
302 : auto poLayer = std::unique_ptr<OGRFlatGeobufLayer>(
303 304 : OGRFlatGeobufLayer::Open(pszFilename, fp, bVerifyBuffers));
304 152 : if (!poLayer)
305 0 : return false;
306 :
307 152 : if (m_bUpdate)
308 : {
309 1 : CPLDebugOnly("FlatGeobuf", "Creating OGRFlatGeobufEditableLayer");
310 : auto poEditableLayer = std::unique_ptr<OGRFlatGeobufEditableLayer>(
311 1 : new OGRFlatGeobufEditableLayer(poLayer.release(),
312 1 : papszOpenOptions));
313 1 : m_apoLayers.push_back(std::move(poEditableLayer));
314 : }
315 : else
316 : {
317 151 : m_apoLayers.push_back(std::move(poLayer));
318 : }
319 :
320 152 : return true;
321 : }
322 :
323 175 : GDALDataset *OGRFlatGeobufDataset::Create(const char *pszName, int /* nBands */,
324 : CPL_UNUSED int nXSize,
325 : CPL_UNUSED int nYSize,
326 : CPL_UNUSED GDALDataType eDT,
327 : char ** /* papszOptions */)
328 : {
329 : // First, ensure there isn't any such file yet.
330 : VSIStatBufL sStatBuf;
331 :
332 175 : if (VSIStatL(pszName, &sStatBuf) == 0)
333 : {
334 0 : CPLError(CE_Failure, CPLE_AppDefined,
335 : "It seems a file system object called '%s' already exists.",
336 : pszName);
337 :
338 0 : return nullptr;
339 : }
340 :
341 175 : bool bIsDir = false;
342 175 : if (!EQUAL(CPLGetExtensionSafe(pszName).c_str(), "fgb"))
343 : {
344 1 : if (VSIMkdir(pszName, 0755) != 0)
345 : {
346 0 : CPLError(CE_Failure, CPLE_AppDefined,
347 : "Failed to create directory %s:\n%s", pszName,
348 0 : VSIStrerror(errno));
349 0 : return nullptr;
350 : }
351 1 : bIsDir = true;
352 : }
353 :
354 175 : return new OGRFlatGeobufDataset(pszName, bIsDir, true, false);
355 : }
356 :
357 586 : const OGRLayer *OGRFlatGeobufDataset::GetLayer(int iLayer) const
358 : {
359 586 : if (iLayer < 0 || iLayer >= GetLayerCount())
360 2 : return nullptr;
361 584 : return m_apoLayers[iLayer]->GetLayer();
362 : }
363 :
364 93 : int OGRFlatGeobufDataset::TestCapability(const char *pszCap) const
365 : {
366 93 : if (EQUAL(pszCap, ODsCCreateLayer))
367 38 : return m_bCreate && (m_bIsDir || m_apoLayers.empty());
368 55 : else if (EQUAL(pszCap, ODsCCurveGeometries))
369 22 : return true;
370 33 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
371 2 : return true;
372 31 : else if (EQUAL(pszCap, ODsCZGeometries))
373 2 : return true;
374 29 : else if (EQUAL(pszCap, ODsCRandomLayerWrite))
375 0 : return m_bUpdate;
376 : else
377 29 : return false;
378 : }
379 :
380 : /************************************************************************/
381 : /* LaunderLayerName() */
382 : /************************************************************************/
383 :
384 2 : static CPLString LaunderLayerName(const char *pszLayerName)
385 : {
386 4 : std::string osRet(CPLLaunderForFilenameSafe(pszLayerName, nullptr));
387 2 : if (osRet != pszLayerName)
388 : {
389 1 : CPLError(CE_Warning, CPLE_AppDefined,
390 : "Invalid layer name for a file name: %s. Laundered to %s.",
391 : pszLayerName, osRet.c_str());
392 : }
393 4 : return osRet;
394 : }
395 :
396 : OGRLayer *
397 192 : OGRFlatGeobufDataset::ICreateLayer(const char *pszLayerName,
398 : const OGRGeomFieldDefn *poGeomFieldDefn,
399 : CSLConstList papszOptions)
400 : {
401 : // Verify we are in update mode.
402 192 : if (!m_bCreate)
403 : {
404 0 : CPLError(CE_Failure, CPLE_NoWriteAccess,
405 : "Data source %s opened read-only.\n"
406 : "New layer %s cannot be created.",
407 0 : GetDescription(), pszLayerName);
408 :
409 0 : return nullptr;
410 : }
411 192 : if (!m_bIsDir && !m_apoLayers.empty())
412 : {
413 16 : CPLError(CE_Failure, CPLE_NoWriteAccess,
414 : "Can create only one single layer in a .fgb file. "
415 : "Use a directory output for multiple layers");
416 :
417 16 : return nullptr;
418 : }
419 :
420 176 : const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
421 : const auto poSpatialRef =
422 176 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
423 :
424 : // Verify that the datasource is a directory.
425 : VSIStatBufL sStatBuf;
426 :
427 : // What filename would we use?
428 352 : CPLString osFilename;
429 :
430 176 : if (m_bIsDir)
431 4 : osFilename = CPLFormFilenameSafe(
432 6 : GetDescription(), LaunderLayerName(pszLayerName).c_str(), "fgb");
433 : else
434 174 : osFilename = GetDescription();
435 :
436 : // Does this directory/file already exist?
437 176 : if (VSIStatL(osFilename, &sStatBuf) == 0)
438 : {
439 0 : CPLError(CE_Failure, CPLE_FileIO,
440 : "Attempt to create layer %s, but %s already exists.",
441 : pszLayerName, osFilename.c_str());
442 0 : return nullptr;
443 : }
444 :
445 : bool bCreateSpatialIndexAtClose =
446 176 : CPLFetchBool(papszOptions, "SPATIAL_INDEX", true);
447 :
448 : auto poLayer =
449 : std::unique_ptr<OGRFlatGeobufLayer>(OGRFlatGeobufLayer::Create(
450 : this, pszLayerName, osFilename, poSpatialRef, eGType,
451 352 : bCreateSpatialIndexAtClose, papszOptions));
452 176 : if (poLayer == nullptr)
453 2 : return nullptr;
454 :
455 174 : m_apoLayers.push_back(std::move(poLayer));
456 :
457 174 : return m_apoLayers.back()->GetLayer();
458 : }
459 :
460 : /************************************************************************/
461 : // GetFileList() */
462 : /************************************************************************/
463 :
464 3 : char **OGRFlatGeobufDataset::GetFileList()
465 : {
466 6 : CPLStringList oFileList;
467 7 : for (const auto &poLayer : m_apoLayers)
468 : {
469 4 : oFileList.AddString(poLayer->GetFilename().c_str());
470 : }
471 6 : return oFileList.StealList();
472 : }
|