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