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