Skip to content

Commit

Permalink
DGN: add ENCODING open option and creation option
Browse files Browse the repository at this point in the history
Refs #10630
  • Loading branch information
rouault committed Aug 26, 2024
1 parent f8d85a4 commit 0735c5f
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 62 deletions.
29 changes: 29 additions & 0 deletions autotest/ogr/ogr_dgn.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,32 @@ def test_ogr_dgn_open_dgnv8_not_supported():
finally:
if dgnv8_drv:
dgnv8_drv.Register()


###############################################################################
# Test ENCODING creation option and open option


def test_ogr_dgn_encoding(tmp_path):

filename = tmp_path / "test.dgn"
with ogr.GetDriverByName("DGN").CreateDataSource(
filename, options=["ENCODING=ISO-8859-1"]
) as ds:
lyr = ds.CreateLayer("elements")
f = ogr.Feature(lyr.GetLayerDefn())
f["Text"] = "\xc3\xa9ven" # UTF-8 encoded
f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(0 0)"))
lyr.CreateFeature(f)

with ogr.Open(filename) as ds:
lyr = ds.GetLayer(0)
assert lyr.TestCapability(ogr.OLCStringsAsUTF8) == 0
f = lyr.GetNextFeature()
assert f["Text"] == "\xe9ven" # ISO-8859-1

with gdal.OpenEx(filename, open_options=["ENCODING=ISO-8859-1"]) as ds:
lyr = ds.GetLayer(0)
assert lyr.TestCapability(ogr.OLCStringsAsUTF8) == 1
f = lyr.GetNextFeature()
assert f["Text"] == "\xc3\xa9ven" # UTF-8
33 changes: 33 additions & 0 deletions doc/source/drivers/vector/dgn.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,25 @@ Creation Issues
- DGN files can only have one layer. Attempts to create more than one
layer in a DGN file will fail.

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

.. versionadded:: 3.10

|about-open-options|
The following open options are supported:

- .. oo:: ENCODING
:since: 3.10
:choices: <encoding>

An encoding name supported by :cpp:func:`CPLRecode` (i.e. an
`iconv <https://en.wikipedia.org/wiki/Iconv>`__ name)
that indicates the encoding used by Text elements in the DGN file, to
recode them to UTF-8. If not specified (or specified to UTF-8), no
recoding will be done.


Dataset creation options
------------------------

Expand Down Expand Up @@ -180,6 +199,20 @@ The following dataset-creation options are supported:
Override the origin of the design plane. By
default the origin from the seed file is used.

- .. dsco:: ENCODING
:since: 3.10
:choices: <encoding>

An encoding name supported by :cpp:func:`CPLRecode` (i.e. an
`iconv <https://en.wikipedia.org/wiki/Iconv>`__ name)
that indicates the encoding used by Text elements in the DGN file, to
recode them from UTF-8. If not specified (or specified to UTF-8), no
recoding will be done. "ISO-8859-1" (ISO Latin1) is supported even on
builds without iconv support.
Note that no fields in the DGN file itself contain the encoding name,
hence it is the responsibility of the reader to open the file with the
same value for the ENCODING open option.

--------------

- `Dgnlib Page <http://dgnlib.maptools.org/>`__
Expand Down
30 changes: 18 additions & 12 deletions ogr/ogrsf_frmts/dgn/ogr_dgn.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@
/* OGRDGNLayer */
/************************************************************************/

class OGRDGNDataSource;

class OGRDGNLayer final : public OGRLayer
{
GDALDataset *m_poDS = nullptr;
OGRDGNDataSource *m_poDS = nullptr;
OGRFeatureDefn *poFeatureDefn;

int iNextShapeId;
Expand All @@ -64,7 +66,7 @@ class OGRDGNLayer final : public OGRLayer
OGRErr CreateFeatureWithGeom(OGRFeature *, const OGRGeometry *);

public:
OGRDGNLayer(GDALDataset *poDS, const char *pszName, DGNHandle hDGN,
OGRDGNLayer(OGRDGNDataSource *poDS, const char *pszName, DGNHandle hDGN,
int bUpdate);
virtual ~OGRDGNLayer();

Expand Down Expand Up @@ -97,10 +99,7 @@ class OGRDGNLayer final : public OGRLayer

OGRErr ICreateFeature(OGRFeature *poFeature) override;

GDALDataset *GetDataset() override
{
return m_poDS;
}
GDALDataset *GetDataset() override;
};

/************************************************************************/
Expand All @@ -109,19 +108,21 @@ class OGRDGNLayer final : public OGRLayer

class OGRDGNDataSource final : public OGRDataSource
{
OGRDGNLayer **papoLayers;
int nLayers;
OGRDGNLayer **papoLayers = nullptr;
int nLayers = 0;

char *pszName;
DGNHandle hDGN;
char *pszName = nullptr;
DGNHandle hDGN = nullptr;

char **papszOptions;
char **papszOptions = nullptr;

std::string m_osEncoding{};

public:
OGRDGNDataSource();
~OGRDGNDataSource();

int Open(const char *, int bTestOpen, int bUpdate);
bool Open(GDALOpenInfo *poOpenInfo);
bool PreCreate(const char *, char **);

OGRLayer *ICreateLayer(const char *pszName,
Expand All @@ -141,6 +142,11 @@ class OGRDGNDataSource final : public OGRDataSource
OGRLayer *GetLayer(int) override;

int TestCapability(const char *) override;

const std::string &GetEncoding() const
{
return m_osEncoding;
}
};

#endif /* ndef OGR_DGN_H_INCLUDED */
53 changes: 14 additions & 39 deletions ogr/ogrsf_frmts/dgn/ogrdgndatasource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@
/* OGRDGNDataSource() */
/************************************************************************/

OGRDGNDataSource::OGRDGNDataSource()
: papoLayers(nullptr), nLayers(0), pszName(nullptr), hDGN(nullptr),
papszOptions(nullptr)
{
}
OGRDGNDataSource::OGRDGNDataSource() = default;

/************************************************************************/
/* ~OGRDGNDataSource() */
Expand All @@ -62,54 +58,32 @@ OGRDGNDataSource::~OGRDGNDataSource()
/* Open() */
/************************************************************************/

int OGRDGNDataSource::Open(const char *pszNewName, int bTestOpen, int bUpdate)
bool OGRDGNDataSource::Open(GDALOpenInfo *poOpenInfo)

{
CPLAssert(nLayers == 0);

/* -------------------------------------------------------------------- */
/* For now we require files to have the .dgn or .DGN */
/* extension. Eventually we will implement a more */
/* sophisticated test to see if it is a dgn file. */
/* -------------------------------------------------------------------- */
if (bTestOpen)
{

VSILFILE *fp = VSIFOpenL(pszNewName, "rb");
if (fp == nullptr)
return FALSE;
m_osEncoding =
CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ENCODING", "");

GByte abyHeader[512];
const int nHeaderBytes =
static_cast<int>(VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp));

VSIFCloseL(fp);

if (nHeaderBytes < 512)
return FALSE;

if (!DGNTestOpen(abyHeader, nHeaderBytes))
return FALSE;
}
CPLAssert(nLayers == 0);

/* -------------------------------------------------------------------- */
/* Try to open the file as a DGN file. */
/* -------------------------------------------------------------------- */
hDGN = DGNOpen(pszNewName, bUpdate);
const bool bUpdate = (poOpenInfo->eAccess == GA_Update);
hDGN = DGNOpen(poOpenInfo->pszFilename, bUpdate);
if (hDGN == nullptr)
{
if (!bTestOpen)
CPLError(CE_Failure, CPLE_AppDefined,
"Unable to open %s as a Microstation .dgn file.",
pszNewName);
return FALSE;
CPLError(CE_Failure, CPLE_AppDefined,
"Unable to open %s as a Microstation .dgn file.",
poOpenInfo->pszFilename);
return false;
}

/* -------------------------------------------------------------------- */
/* Create the layer object. */
/* -------------------------------------------------------------------- */
OGRDGNLayer *poLayer = new OGRDGNLayer(this, "elements", hDGN, bUpdate);
pszName = CPLStrdup(pszNewName);
pszName = CPLStrdup(poOpenInfo->pszFilename);

/* -------------------------------------------------------------------- */
/* Add layer to data source layer list. */
Expand All @@ -118,7 +92,7 @@ int OGRDGNDataSource::Open(const char *pszNewName, int bTestOpen, int bUpdate)
CPLRealloc(papoLayers, sizeof(OGRDGNLayer *) * (nLayers + 1)));
papoLayers[nLayers++] = poLayer;

return TRUE;
return true;
}

/************************************************************************/
Expand Down Expand Up @@ -163,6 +137,7 @@ bool OGRDGNDataSource::PreCreate(const char *pszFilename, char **papszOptionsIn)
papszOptions = CSLDuplicate(papszOptionsIn);
pszName = CPLStrdup(pszFilename);

m_osEncoding = CSLFetchNameValueDef(papszOptionsIn, "ENCODING", "");
return true;
}

Expand Down
15 changes: 11 additions & 4 deletions ogr/ogrsf_frmts/dgn/ogrdgndriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#include "cpl_conv.h"

/************************************************************************/
/* Open() */
/* OGRDGNDriverIdentify() */
/************************************************************************/

static int OGRDGNDriverIdentify(GDALOpenInfo *poOpenInfo)
Expand Down Expand Up @@ -76,9 +76,7 @@ static GDALDataset *OGRDGNDriverOpen(GDALOpenInfo *poOpenInfo)

OGRDGNDataSource *poDS = new OGRDGNDataSource();

if (!poDS->Open(poOpenInfo->pszFilename, TRUE,
(poOpenInfo->eAccess == GA_Update)) ||
poDS->GetLayerCount() == 0)
if (!poDS->Open(poOpenInfo) || poDS->GetLayerCount() == 0)
{
delete poDS;
return nullptr;
Expand Down Expand Up @@ -131,6 +129,13 @@ void RegisterOGRDGN()
poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES");
poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE");

poDriver->SetMetadataItem(
GDAL_DMD_OPENOPTIONLIST,
"<OpenOptionList>"
" <Option name='ENCODING' type='string' description="
"'Encoding name, as supported by iconv'/>"
"</OpenOptionList>");

poDriver->SetMetadataItem(
GDAL_DMD_CREATIONOPTIONLIST,
"<CreationOptionList>"
Expand Down Expand Up @@ -167,6 +172,8 @@ void RegisterOGRDGN()
" <Option name='ORIGIN' type='string' description='Value as x,y,z. "
"Override the origin of the design plane. By default the origin from "
"the seed file is used.'/>"
" <Option name='ENCODING' type='string' description="
"'Encoding name, as supported by iconv'/>"
"</CreationOptionList>");

poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST,
Expand Down
50 changes: 43 additions & 7 deletions ogr/ogrsf_frmts/dgn/ogrdgnlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
/* OGRDGNLayer() */
/************************************************************************/

OGRDGNLayer::OGRDGNLayer(GDALDataset *poDS, const char *pszName,
OGRDGNLayer::OGRDGNLayer(OGRDGNDataSource *poDS, const char *pszName,
DGNHandle hDGNIn, int bUpdateIn)
: m_poDS(poDS), poFeatureDefn(new OGRFeatureDefn(pszName)), iNextShapeId(0),
hDGN(hDGNIn), bUpdate(bUpdateIn)
Expand Down Expand Up @@ -628,11 +628,23 @@ OGRFeature *OGRDGNLayer::ElementToFeature(DGNElemCore *psElement, int nRecLevel)

poFeature->SetGeometryDirectly(poPoint);

const size_t nOgrFSLen = strlen(psText->string) + 150;
const auto &osEncoding = m_poDS->GetEncoding();
std::string osText;
if (!osEncoding.empty() && osEncoding != CPL_ENC_UTF8)
{
osText = CPLString(psText->string)
.Recode(osEncoding.c_str(), CPL_ENC_UTF8);
}
else
{
osText = psText->string;
}

const size_t nOgrFSLen = osText.size() + 150;
char *pszOgrFS = static_cast<char *>(CPLMalloc(nOgrFSLen));

// setup the basic label.
snprintf(pszOgrFS, nOgrFSLen, "LABEL(t:\"%s\"", psText->string);
snprintf(pszOgrFS, nOgrFSLen, "LABEL(t:\"%s\"", osText.c_str());

// set the color if we have it.
if (strlen(szFSColor) > 0)
Expand Down Expand Up @@ -793,7 +805,7 @@ OGRFeature *OGRDGNLayer::ElementToFeature(DGNElemCore *psElement, int nRecLevel)
poFeature->SetStyleString(pszOgrFS);
CPLFree(pszOgrFS);

poFeature->SetField("Text", psText->string);
poFeature->SetField("Text", osText.c_str());
}
break;

Expand Down Expand Up @@ -928,6 +940,9 @@ int OGRDGNLayer::TestCapability(const char *pszCap)
else if (EQUAL(pszCap, OLCZGeometries))
return TRUE;

else if (EQUAL(pszCap, OLCStringsAsUTF8))
return !m_poDS->GetEncoding().empty();

return FALSE;
}

Expand Down Expand Up @@ -1183,11 +1198,23 @@ DGNElemCore **OGRDGNLayer::TranslateLabel(OGRFeature *poFeature)
}
}

std::string osText;
const auto &osEncoding = m_poDS->GetEncoding();
if (!osEncoding.empty() && osEncoding != CPL_ENC_UTF8)
{
osText = CPLString(pszText).Recode(CPL_ENC_UTF8, osEncoding.c_str());
}
else
{
osText = pszText;
}

DGNElemCore **papsGroup =
static_cast<DGNElemCore **>(CPLCalloc(sizeof(void *), 2));
papsGroup[0] = DGNCreateTextElem(
hDGN, pszText, nFontID, DGNJ_LEFT_BOTTOM, dfCharHeight, dfCharHeight,
dfRotation, nullptr, poPoint->getX(), poPoint->getY(), poPoint->getZ());
papsGroup[0] =
DGNCreateTextElem(hDGN, osText.c_str(), nFontID, DGNJ_LEFT_BOTTOM,
dfCharHeight, dfCharHeight, dfRotation, nullptr,
poPoint->getX(), poPoint->getY(), poPoint->getZ());

if (poLabel)
delete poLabel;
Expand Down Expand Up @@ -1387,3 +1414,12 @@ OGRErr OGRDGNLayer::CreateFeatureWithGeom(OGRFeature *poFeature,

return OGRERR_NONE;
}

/************************************************************************/
/* GetDataset() */
/************************************************************************/

GDALDataset *OGRDGNLayer::GetDataset()
{
return m_poDS;
}

0 comments on commit 0735c5f

Please sign in to comment.