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