Skip to content

Commit

Permalink
Merge pull request #8860 from rouault/flatgeobuf_title_description_me…
Browse files Browse the repository at this point in the history
…tadata

FlatGeobuf: add support for reading and writing layer title, description and metadata
  • Loading branch information
rouault authored Nov 28, 2023
2 parents c1369ec + 16ae592 commit d921061
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 26 deletions.
40 changes: 40 additions & 0 deletions autotest/ogr/ogr_flatgeobuf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1217,3 +1217,43 @@ def test_ogr_flatgeobuf_issue_7401():

ogr.GetDriverByName("FlatGeobuf").DeleteDataSource("/vsimem/test.fgb")
assert not gdal.VSIStatL("/vsimem/test.fgb")


###############################################################################
# Test reading and writing layer title, description and metadata


def test_ogr_flatgeobuf_title_description_metadata(tmp_vsimem):

filename = str(tmp_vsimem / "test.fgb")
ds = ogr.GetDriverByName("FlatGeobuf").CreateDataSource(filename)
lyr = ds.CreateLayer(
"test",
geom_type=ogr.wkbPoint,
options=["SPATIAL_INDEX=NO", "TITLE=title", "DESCRIPTION=description"],
)
lyr.SetMetadata({"foo": "bar", "bar": "baz"})
f = ogr.Feature(lyr.GetLayerDefn())
f.SetGeometry(ogr.CreateGeometryFromWkt("POINT (0 0)"))
lyr.CreateFeature(f)
ds = None

ds = ogr.Open(filename)
lyr = ds.GetLayer(0)
assert lyr.GetMetadata_Dict() == {
"TITLE": "title",
"DESCRIPTION": "description",
"foo": "bar",
"bar": "baz",
}

# Test that on FlatGeoBuf -> FlatGeoBuf TITLE and DESCRIPTION get properly propagated.
filename2 = str(tmp_vsimem / "test2.fgb")
ds = gdal.VectorTranslate(filename2, filename)
lyr = ds.GetLayer(0)
assert lyr.GetMetadata_Dict() == {
"TITLE": "title",
"DESCRIPTION": "description",
"foo": "bar",
"bar": "baz",
}
15 changes: 15 additions & 0 deletions doc/source/drivers/vector/flatgeobuf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ On creation, passing a filename without a .fgb suffix will instruct the driver
to create a directory of that name, and create layers as .fgb files in that
directory.

Starting with GDAL 3.9, metadata set at the layer level will be written in the
FlatGeobuf header, and retrieved on reading as layer metadata.

Open options
------------

Expand Down Expand Up @@ -65,6 +68,18 @@ Layer Creation Options
the :cpp:func:`CPLGenerateTempFilename` function.
"/vsimem/" can be used for in-memory temporary files.

- .. lco:: TITLE
:choices: <string>
:since: 3.9

Dataset title (should be relatively short)

- .. lco:: DESCRIPTION
:choices: <string>
:since: 3.9

Dataset description (intended for free form long text)

Creation Issues
---------------

Expand Down
16 changes: 12 additions & 4 deletions ogr/ogrsf_frmts/flatgeobuf/ogr_flatgeobuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ class OGRFlatGeobufLayer final : public OGRLayer,
bool m_ignoreAttributeFilter = false;

// creation
GDALDataset *m_poDS = nullptr; // parent dataset to get metadata from it
bool m_create = false;
std::deque<FeatureItem> m_featureItems; // feature item description used to
// create spatial index
bool m_bCreateSpatialIndexAtClose = true;
bool m_bVerifyBuffers = true;
VSILFILE *m_poFpWrite = nullptr;
uint64_t m_writeOffset = 0; // current write offset
CPLStringList m_aosCreationOption{}; // layer creation options
uint64_t m_writeOffset = 0; // current write offset
uint64_t m_offsetAfterHeader =
0; // offset after dummy header writing (when creating a file without
// spatial index)
Expand Down Expand Up @@ -142,11 +144,12 @@ class OGRFlatGeobufLayer final : public OGRLayer,
OGRFlatGeobufLayer(const FlatGeobuf::Header *, GByte *headerBuf,
const char *pszFilename, VSILFILE *poFp,
uint64_t offset);
OGRFlatGeobufLayer(const char *pszLayerName, const char *pszFilename,
OGRFlatGeobufLayer(GDALDataset *poDS, const char *pszLayerName,
const char *pszFilename,
const OGRSpatialReference *poSpatialRef,
OGRwkbGeometryType eGType,
bool bCreateSpatialIndexAtClose, VSILFILE *poFpWrite,
std::string &osTempFile);
std::string &osTempFile, CSLConstList papszOptions);

protected:
virtual int GetNextArrowArray(struct ArrowArrayStream *,
Expand All @@ -163,7 +166,7 @@ class OGRFlatGeobufLayer final : public OGRLayer,
static OGRFlatGeobufLayer *Open(const char *pszFilename, VSILFILE *fp,
bool bVerifyBuffers);
static OGRFlatGeobufLayer *
Create(const char *pszLayerName, const char *pszFilename,
Create(GDALDataset *poDS, const char *pszLayerName, const char *pszFilename,
const OGRSpatialReference *poSpatialRef, OGRwkbGeometryType eGType,
bool bCreateSpatialIndexAtClose, char **papszOptions);

Expand Down Expand Up @@ -192,6 +195,11 @@ class OGRFlatGeobufLayer final : public OGRLayer,
m_bVerifyBuffers = CPL_TO_BOOL(bFlag);
}

GDALDataset *GetDataset() override
{
return m_poDS;
}

const std::string &GetFilename() const override
{
return m_osFilename;
Expand Down
5 changes: 4 additions & 1 deletion ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobufdataset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ void RegisterOGRFlatGeobuf()
"create a spatial index' default='YES'/>"
" <Option name='TEMPORARY_DIR' type='string' description='Directory "
"where temporary file should be created'/>"
" <Option name='TITLE' type='string' description='Layer title'/>"
" <Option name='DESCRIPTION' type='string' "
"description='Layer description'/>"
"</LayerCreationOptionList>");
poDriver->SetMetadataItem(
GDAL_DMD_OPENOPTIONLIST,
Expand Down Expand Up @@ -445,7 +448,7 @@ OGRLayer *OGRFlatGeobufDataset::ICreateLayer(

auto poLayer =
std::unique_ptr<OGRFlatGeobufLayer>(OGRFlatGeobufLayer::Create(
pszLayerName, osFilename, poSpatialRef, eGType,
this, pszLayerName, osFilename, poSpatialRef, eGType,
bCreateSpatialIndexAtClose, papszOptions));
if (poLayer == nullptr)
return nullptr;
Expand Down
5 changes: 3 additions & 2 deletions ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobufeditablelayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ OGRErr OGRFlatGeobufEditableLayerSynchronizer::EditableSyncToDisk(
auto createIndex = m_poFlatGeobufLayer->GetIndexNodeSize() > 0;

OGRFlatGeobufLayer *poFlatGeobufTmpLayer = OGRFlatGeobufLayer::Create(
osLayerName.c_str(), osTmpFilename.c_str(), spatialRef, gType,
createIndex, m_papszOpenOptions);
m_poFlatGeobufLayer->GetDataset(), osLayerName.c_str(),
osTmpFilename.c_str(), spatialRef, gType, createIndex,
m_papszOpenOptions);
if (poFlatGeobufTmpLayer == nullptr)
return OGRERR_FAILURE;

Expand Down
111 changes: 92 additions & 19 deletions ogr/ogrsf_frmts/flatgeobuf/ogrflatgeobuflayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,31 @@ OGRFlatGeobufLayer::OGRFlatGeobufLayer(const Header *poHeader, GByte *headerBuf,

m_eGType = getOGRwkbGeometryType();

if (const auto title = poHeader->title())
SetMetadataItem("TITLE", title->c_str());

if (const auto description = poHeader->description())
SetMetadataItem("DESCRIPTION", description->c_str());

if (const auto metadata = poHeader->metadata())
{
CPLJSONDocument oDoc;
CPLErrorHandlerPusher oQuietError(CPLQuietErrorHandler);
CPLErrorStateBackuper oErrorStateBackuper;
if (oDoc.LoadMemory(metadata->c_str()) &&
oDoc.GetRoot().GetType() == CPLJSONObject::Type::Object)
{
for (auto oItem : oDoc.GetRoot().GetChildren())
{
if (oItem.GetType() == CPLJSONObject::Type::String)
{
SetMetadataItem(oItem.GetName().c_str(),
oItem.ToString().c_str());
}
}
}
}

const char *pszName =
m_poHeader->name() ? m_poHeader->name()->c_str() : "unknown";
m_poFeatureDefn = new OGRFeatureDefn(pszName);
Expand All @@ -168,19 +193,16 @@ OGRFlatGeobufLayer::OGRFlatGeobufLayer(const Header *poHeader, GByte *headerBuf,
m_poFeatureDefn->Reference();
}

OGRFlatGeobufLayer::OGRFlatGeobufLayer(const char *pszLayerName,
const char *pszFilename,
const OGRSpatialReference *poSpatialRef,
OGRwkbGeometryType eGType,
bool bCreateSpatialIndexAtClose,
VSILFILE *poFpWrite,
std::string &osTempFile)
: m_eGType(eGType),
OGRFlatGeobufLayer::OGRFlatGeobufLayer(
GDALDataset *poDS, const char *pszLayerName, const char *pszFilename,
const OGRSpatialReference *poSpatialRef, OGRwkbGeometryType eGType,
bool bCreateSpatialIndexAtClose, VSILFILE *poFpWrite,
std::string &osTempFile, CSLConstList papszOptions)
: m_eGType(eGType), m_poDS(poDS), m_create(true),
m_bCreateSpatialIndexAtClose(bCreateSpatialIndexAtClose),
m_poFpWrite(poFpWrite), m_osTempFile(osTempFile)
m_poFpWrite(poFpWrite), m_aosCreationOption(papszOptions),
m_osTempFile(osTempFile)
{
m_create = true;

if (pszLayerName)
m_osLayerName = pszLayerName;
if (pszFilename)
Expand Down Expand Up @@ -457,9 +479,61 @@ void OGRFlatGeobufLayer::writeHeader(VSILFILE *poFp, uint64_t featuresCount,
CPLFree(pszWKT);
}

std::string osTitle(m_aosCreationOption.FetchNameValueDef("TITLE", ""));
std::string osDescription(
m_aosCreationOption.FetchNameValueDef("DESCRIPTION", ""));
std::string osMetadata;
CPLJSONObject oMetadataJSONObj;
bool bEmptyMetadata = true;
for (GDALMajorObject *poContainer :
{static_cast<GDALMajorObject *>(this),
static_cast<GDALMajorObject *>(
m_poDS && m_poDS->GetLayerCount() == 1 ? m_poDS : nullptr)})
{
if (poContainer)
{
if (char **papszMD = poContainer->GetMetadata())
{
for (CSLConstList papszIter = papszMD; *papszIter; ++papszIter)
{
char *pszKey = nullptr;
const char *pszValue =
CPLParseNameValue(*papszIter, &pszKey);
if (pszKey && pszValue && !EQUAL(pszKey, OLMD_FID64))
{
if (EQUAL(pszKey, "TITLE"))
{
if (osTitle.empty())
osTitle = pszValue;
}
else if (EQUAL(pszKey, "DESCRIPTION"))
{
if (osDescription.empty())
osDescription = pszValue;
}
else
{
bEmptyMetadata = false;
oMetadataJSONObj.Add(pszKey, pszValue);
}
}
CPLFree(pszKey);
}
}
}
}
if (!bEmptyMetadata)
{
osMetadata =
oMetadataJSONObj.Format(CPLJSONObject::PrettyFormat::Plain);
}

const auto header = CreateHeaderDirect(
fbb, m_osLayerName.c_str(), extentVector, m_geometryType, m_hasZ,
m_hasM, m_hasT, m_hasTM, &columns, featuresCount, m_indexNodeSize, crs);
m_hasM, m_hasT, m_hasTM, &columns, featuresCount, m_indexNodeSize, crs,
osTitle.empty() ? nullptr : osTitle.c_str(),
osDescription.empty() ? nullptr : osDescription.c_str(),
osMetadata.empty() ? nullptr : osMetadata.c_str());
fbb.FinishSizePrefixed(header);
c = VSIFWriteL(fbb.GetBufferPointer(), 1, fbb.GetSize(), poFp);
CPLDebugOnly("FlatGeobuf", "Wrote header (%lu bytes)",
Expand Down Expand Up @@ -2409,20 +2483,19 @@ VSILFILE *OGRFlatGeobufLayer::CreateOutputFile(const CPLString &osFilename,
return poFpWrite;
}

OGRFlatGeobufLayer *
OGRFlatGeobufLayer::Create(const char *pszLayerName, const char *pszFilename,
const OGRSpatialReference *poSpatialRef,
OGRwkbGeometryType eGType,
bool bCreateSpatialIndexAtClose, char **papszOptions)
OGRFlatGeobufLayer *OGRFlatGeobufLayer::Create(
GDALDataset *poDS, const char *pszLayerName, const char *pszFilename,
const OGRSpatialReference *poSpatialRef, OGRwkbGeometryType eGType,
bool bCreateSpatialIndexAtClose, char **papszOptions)
{
std::string osTempFile = GetTempFilePath(pszFilename, papszOptions);
VSILFILE *poFpWrite =
CreateOutputFile(pszFilename, papszOptions, bCreateSpatialIndexAtClose);
if (poFpWrite == nullptr)
return nullptr;
OGRFlatGeobufLayer *layer = new OGRFlatGeobufLayer(
pszLayerName, pszFilename, poSpatialRef, eGType,
bCreateSpatialIndexAtClose, poFpWrite, osTempFile);
poDS, pszLayerName, pszFilename, poSpatialRef, eGType,
bCreateSpatialIndexAtClose, poFpWrite, osTempFile, papszOptions);
return layer;
}

Expand Down

0 comments on commit d921061

Please sign in to comment.