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