/******************************************************************************
 *
 * Project:  EDIGEO Translator
 * Purpose:  Implements OGREDIGEODataSource class
 * Author:   Even Rouault, even dot rouault at spatialys.com
 *
 ******************************************************************************
 * Copyright (c) 2011, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "ogr_edigeo.h"
#include "cpl_conv.h"
#include "cpl_string.h"

/************************************************************************/
/*                        OGREDIGEODataSource()                         */
/************************************************************************/

OGREDIGEODataSource::OGREDIGEODataSource()
    : fpTHF(nullptr), papoLayers(nullptr), nLayers(0), poSRS(nullptr),
      bExtentValid(FALSE), dfMinX(0), dfMinY(0), dfMaxX(0), dfMaxY(0),
      bRecodeToUTF8(
          CPLTestBool(CPLGetConfigOption("OGR_EDIGEO_RECODE_TO_UTF8", "YES"))),
      bHasUTF8ContentOnly(TRUE), iATR(-1), iDI3(-1), iDI4(-1), iHEI(-1),
      iFON(-1), iATR_VAL(-1), iANGLE(-1), iSIZE(-1), iOBJ_LNK(-1),
      iOBJ_LNK_LAYER(-1),
      // coverity[tainted_data]
      dfSizeFactor(
          CPLAtof(CPLGetConfigOption("OGR_EDIGEO_FONT_SIZE_FACTOR", "2"))),
      bIncludeFontFamily(CPLTestBool(
          CPLGetConfigOption("OGR_EDIGEO_INCLUDE_FONT_FAMILY", "YES")))
{
    if (dfSizeFactor <= 0 || dfSizeFactor >= 100)
        dfSizeFactor = 2;
}

/************************************************************************/
/*                      ~OGREDIGEODataSource()                          */
/************************************************************************/

OGREDIGEODataSource::~OGREDIGEODataSource()

{
    for (int i = 0; i < nLayers; i++)
        delete papoLayers[i];
    CPLFree(papoLayers);

    if (fpTHF)
        VSIFCloseL(fpTHF);

    if (poSRS)
        poSRS->Release();
}

/************************************************************************/
/*                              GetLayer()                              */
/************************************************************************/

const OGRLayer *OGREDIGEODataSource::GetLayer(int iLayer) const

{
    ReadEDIGEO();
    if (iLayer < 0 || iLayer >= nLayers)
        return nullptr;
    else
        return papoLayers[iLayer];
}

/************************************************************************/
/*                         GetLayerCount()                              */
/************************************************************************/

int OGREDIGEODataSource::GetLayerCount() const
{
    ReadEDIGEO();
    return nLayers;
}

/************************************************************************/
/*                              ReadTHF()                               */
/************************************************************************/

int OGREDIGEODataSource::ReadTHF(VSILFILE *fp) const
{
    const char *pszLine = nullptr;
    while ((pszLine = CPLReadLine2L(fp, 81, nullptr)) != nullptr)
    {
        if (strlen(pszLine) < 8 || pszLine[7] != ':')
            continue;

        /* Cf Z 52000 tableau 56 for field list*/

        if (STARTS_WITH(pszLine, "LONSA"))
        {
            if (!osLON.empty())
            {
                CPLDebug("EDIGEO", "We only handle one lot per THF file");
                break;
            }
            osLON = pszLine + 8;
        }
        else if (STARTS_WITH(pszLine, "GNNSA"))
            osGNN = pszLine + 8;
        else if (STARTS_WITH(pszLine, "GONSA"))
            osGON = pszLine + 8;
        else if (STARTS_WITH(pszLine, "QANSA"))
            osQAN = pszLine + 8;
        else if (STARTS_WITH(pszLine, "DINSA"))
            osDIN = pszLine + 8;
        else if (STARTS_WITH(pszLine, "SCNSA"))
            osSCN = pszLine + 8;
        else if (STARTS_WITH(pszLine, "GDNSA"))
            aosGDN.push_back(pszLine + 8);
    }
    if (osLON.empty())
    {
        CPLDebug("EDIGEO", "LON field missing");
        return 0;
    }
    if (osGON.empty())
    {
        CPLDebug("EDIGEO", "GON field missing");
        return 0;
    }
    if (osDIN.empty())
    {
        CPLDebug("EDIGEO", "DIN field missing");
        return 0;
    }
    if (osSCN.empty())
    {
        CPLDebug("EDIGEO", "SCN field missing");
        return FALSE;
    }

    CPLDebug("EDIGEO", "LON = %s", osLON.c_str());
    CPLDebug("EDIGEO", "GNN = %s", osGNN.c_str());
    CPLDebug("EDIGEO", "GON = %s", osGON.c_str());
    CPLDebug("EDIGEO", "QAN = %s", osQAN.c_str());
    CPLDebug("EDIGEO", "DIN = %s", osDIN.c_str());
    CPLDebug("EDIGEO", "SCN = %s", osSCN.c_str());
    for (int i = 0; i < (int)aosGDN.size(); i++)
        CPLDebug("EDIGEO", "GDN[%d] = %s", i, aosGDN[i].c_str());

    return TRUE;
}

/************************************************************************/
/*                             OpenFile()                               */
/************************************************************************/

VSILFILE *OGREDIGEODataSource::OpenFile(const char *pszType,
                                        const CPLString &osExt) const
{
    CPLString osTmp = osLON + pszType;
    CPLString osFilename = CPLFormCIFilenameSafe(
        CPLGetPathSafe(GetDescription()).c_str(), osTmp.c_str(), osExt.c_str());
    VSILFILE *fp = VSIFOpenL(osFilename, "rb");
    if (fp == nullptr)
    {
        const CPLString osExtLower = CPLString(osExt).tolower();
        const CPLString osFilename2 =
            CPLFormCIFilenameSafe(CPLGetPathSafe(GetDescription()).c_str(),
                                  osTmp.c_str(), osExtLower.c_str());
        fp = VSIFOpenL(osFilename2, "rb");
        if (fp == nullptr)
        {
            CPLDebug("EDIGEO", "Cannot open %s", osFilename.c_str());
        }
    }
    return fp;
}

/************************************************************************/
/*                              ReadGEO()                               */
/************************************************************************/

int OGREDIGEODataSource::ReadGEO() const
{
    VSILFILE *fp = OpenFile(osGON, "GEO");
    if (fp == nullptr)
        return FALSE;

    const char *pszLine = nullptr;
    while ((pszLine = CPLReadLine2L(fp, 81, nullptr)) != nullptr)
    {
        if (strlen(pszLine) < 8 || pszLine[7] != ':')
            continue;

        if (STARTS_WITH(pszLine, "RELSA"))
        {
            osREL = pszLine + 8;
            CPLDebug("EDIGEO", "REL = %s", osREL.c_str());
            break;
        }
    }

    VSIFCloseL(fp);

    if (osREL.empty())
    {
        CPLDebug("EDIGEO", "REL field missing");
        return FALSE;
    }

    /* All the SRS names mentioned in B.8.2.3 and B.8.3.1 are in the IGN file */
    poSRS = new OGRSpatialReference();
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    CPLString osProj4Str = "+init=IGNF:" + osREL;
    if (poSRS->SetFromUserInput(osProj4Str.c_str()) != OGRERR_NONE)
    {
        /* Hard code a few common cases */
        if (osREL == "LAMB1")
            poSRS->importFromProj4(
                "+proj=lcc +lat_1=49.5 +lat_0=49.5 +lon_0=0 +k_0=0.99987734 "
                "+x_0=600000 +y_0=200000 +a=6378249.2 +b=6356514.999978254 "
                "+nadgrids=ntf_r93.gsb,null +pm=paris +units=m +no_defs");
        else if (osREL == "LAMB2")
            poSRS->importFromProj4(
                "+proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=0 +k_0=0.99987742 "
                "+x_0=600000 +y_0=200000 +a=6378249.2 +b=6356514.999978254 "
                "+nadgrids=ntf_r93.gsb,null +pm=paris +units=m +no_defs");
        else if (osREL == "LAMB3")
            poSRS->importFromProj4(
                "+proj=lcc +lat_1=44.1 +lat_0=44.1 +lon_0=0 +k_0=0.9998775 "
                "+x_0=600000 +y_0=200000 +a=6378249.2 +b=6356514.999978254 "
                "+nadgrids=ntf_r93.gsb,null +pm=paris +units=m +no_defs");
        else if (osREL == "LAMB4")
            poSRS->importFromProj4(
                "+proj=lcc +lat_1=42.165 +lat_0=42.165 +lon_0=0 "
                "+k_0=0.99994471 +x_0=234.358 +y_0=185861.369 +a=6378249.2 "
                "+b=6356514.999978254 +nadgrids=ntf_r93.gsb,null +pm=paris "
                "+units=m +no_defs");
        else if (osREL == "LAMB93")
            poSRS->importFromProj4(
                "+proj=lcc +lat_1=44 +lat_2=49 +lat_0=46.5 +lon_0=3 "
                "+x_0=700000 +y_0=6600000 +ellps=GRS81 +towgs84=0,0,0,0,0,0,0 "
                "+units=m +no_defs");
        else
        {
            CPLDebug("EDIGEO",
                     "Cannot resolve %s SRS. Check that the IGNF file is in "
                     "the directory of PROJ.4 resource files",
                     osREL.c_str());
            delete poSRS;
            poSRS = nullptr;
        }
    }

    return TRUE;
}

/************************************************************************/
/*                              ReadGEN()                               */
/************************************************************************/

int OGREDIGEODataSource::ReadGEN() const
{
    VSILFILE *fp = OpenFile(osGNN, "GEN");
    if (fp == nullptr)
        return FALSE;

    const char *pszLine = nullptr;
    CPLString osCM1;
    CPLString osCM2;
    while ((pszLine = CPLReadLine2L(fp, 81, nullptr)) != nullptr)
    {
        if (strlen(pszLine) < 8 || pszLine[7] != ':')
            continue;

        if (STARTS_WITH(pszLine, "CM1CC"))
        {
            osCM1 = pszLine + 8;
        }
        else if (STARTS_WITH(pszLine, "CM2CC"))
        {
            osCM2 = pszLine + 8;
        }
    }

    VSIFCloseL(fp);

    if (osCM1.empty() || osCM2.empty())
        return FALSE;

    char **papszTokens1 = CSLTokenizeString2(osCM1.c_str(), ";", 0);
    char **papszTokens2 = CSLTokenizeString2(osCM2.c_str(), ";", 0);
    if (CSLCount(papszTokens1) == 2 && CSLCount(papszTokens2) == 2)
    {
        bExtentValid = TRUE;
        dfMinX = CPLAtof(papszTokens1[0]);
        dfMinY = CPLAtof(papszTokens1[1]);
        dfMaxX = CPLAtof(papszTokens2[0]);
        dfMaxY = CPLAtof(papszTokens2[1]);
    }
    CSLDestroy(papszTokens1);
    CSLDestroy(papszTokens2);

    return bExtentValid;
}

/************************************************************************/
/*                              ReadDIC()                               */
/************************************************************************/

int OGREDIGEODataSource::ReadDIC() const
{
    VSILFILE *fp = OpenFile(osDIN, "DIC");
    if (fp == nullptr)
        return FALSE;

    const char *pszLine = nullptr;
    CPLString osRTY;
    CPLString osRID;
    CPLString osLAB;
    CPLString osTYP;
    while (true)
    {
        pszLine = CPLReadLine2L(fp, 81, nullptr);
        if (pszLine != nullptr)
        {
            if (strlen(pszLine) < 8 || pszLine[7] != ':')
                continue;
        }

        if (pszLine == nullptr || STARTS_WITH(pszLine, "RTYSA"))
        {
            if (osRTY == "DID")
            {
                // CPLDebug("EDIGEO", "Object %s = %s",
                //          osRID.c_str(), osLAB.c_str());
                mapObjects[osRID] = osLAB;
            }
            else if (osRTY == "DIA")
            {
                // CPLDebug("EDIGEO", "Attribute %s = %s, %s",
                //          osRID.c_str(), osLAB.c_str(), osTYP.c_str());
                OGREDIGEOAttributeDef sAttributeDef;
                sAttributeDef.osLAB = osLAB;
                sAttributeDef.osTYP = osTYP;
                mapAttributes[osRID] = std::move(sAttributeDef);
            }
        }

        if (pszLine == nullptr)
            break;

        if (STARTS_WITH(pszLine, "RTYSA"))
        {
            osRTY = pszLine + 8;
            osRID = "";
            osLAB = "";
            osTYP = "";
        }
        else if (STARTS_WITH(pszLine, "RIDSA"))
            osRID = pszLine + 8;
        else if (STARTS_WITH(pszLine, "LABSA"))
            osLAB = pszLine + 8;
        else if (STARTS_WITH(pszLine, "TYPSA"))
            osTYP = pszLine + 8;
    }

    VSIFCloseL(fp);

    return TRUE;
}

/************************************************************************/
/*                              ReadSCD()                               */
/************************************************************************/

int OGREDIGEODataSource::ReadSCD() const
{
    VSILFILE *fp = OpenFile(osSCN, "SCD");
    if (fp == nullptr)
        return FALSE;

    const char *pszLine = nullptr;
    CPLString osRTY, osRID, osNameRID, osKND;
    strListType aosAttrRID;
    int nWidth = 0;
    while (true)
    {
        pszLine = CPLReadLine2L(fp, 81, nullptr);
        if (pszLine != nullptr)
        {
            if (strlen(pszLine) < 8 || pszLine[7] != ':')
                continue;
        }

        if (pszLine == nullptr || STARTS_WITH(pszLine, "RTYSA"))
        {
            if (osRTY == "OBJ")
            {
                if (mapObjects.find(osNameRID) == mapObjects.end())
                {
                    CPLDebug("EDIGEO", "Cannot find object %s",
                             osNameRID.c_str());
                }
                else
                {
                    OGREDIGEOObjectDescriptor objDesc;
                    objDesc.osRID = osRID;
                    objDesc.osNameRID = osNameRID;
                    objDesc.osKND = osKND;
                    objDesc.aosAttrRID = aosAttrRID;
                    /*CPLDebug("EDIGEO", "Object %s = %s, %s, %d attributes",
                            osRID.c_str(), osNameRID.c_str(), osKND.c_str(),
                            (int)aosAttrRID.size());*/

                    aoObjList.push_back(std::move(objDesc));
                }
            }
            else if (osRTY == "ATT")
            {
                if (mapAttributes.find(osNameRID) == mapAttributes.end())
                {
                    CPLDebug("EDIGEO", "Cannot find attribute %s",
                             osNameRID.c_str());
                }
                else
                {
                    OGREDIGEOAttributeDescriptor attDesc;
                    attDesc.osRID = osRID;
                    attDesc.osNameRID = osNameRID;
                    attDesc.nWidth = nWidth;
                    /*CPLDebug("EDIGEO", "Attribute %s = %s, %d",
                            osRID.c_str(), osNameRID.c_str(), nWidth);*/

                    mapAttributesSCD[osRID] = std::move(attDesc);
                }
            }
        }

        if (pszLine == nullptr)
            break;
        if (STARTS_WITH(pszLine, "RTYSA"))
        {
            osRTY = pszLine + 8;
            osRID = "";
            osNameRID = "";
            osKND = "";
            aosAttrRID.resize(0);
            nWidth = 0;
        }
        else if (STARTS_WITH(pszLine, "RIDSA"))
            osRID = pszLine + 8;
        else if (STARTS_WITH(pszLine, "DIPCP"))
        {
            const char *pszDIP = pszLine + 8;
            char **papszTokens = CSLTokenizeString2(pszDIP, ";", 0);
            if (CSLCount(papszTokens) == 4)
            {
                osNameRID = papszTokens[3];
            }
            CSLDestroy(papszTokens);
        }
        else if (STARTS_WITH(pszLine, "KNDSA"))
            osKND = pszLine + 8;
        else if (STARTS_WITH(pszLine, "AAPCP"))
        {
            const char *pszAAP = pszLine + 8;
            char **papszTokens = CSLTokenizeString2(pszAAP, ";", 0);
            if (CSLCount(papszTokens) == 4)
            {
                const char *pszAttRID = papszTokens[3];
                aosAttrRID.push_back(pszAttRID);
            }
            CSLDestroy(papszTokens);
        }
        else if (STARTS_WITH(pszLine, "CANSN"))
            nWidth = atoi(pszLine + 8);
    }

    VSIFCloseL(fp);

    return TRUE;
}

/************************************************************************/
/*                              ReadQAL()                               */
/************************************************************************/

int OGREDIGEODataSource::ReadQAL() const
{
    VSILFILE *fp = OpenFile(osQAN, "QAL");
    if (fp == nullptr)
        return FALSE;

    const char *pszLine = nullptr;
    CPLString osRTY;
    CPLString osRID;
    int nODA = 0;
    int nUDA = 0;
    while (true)
    {
        pszLine = CPLReadLine2L(fp, 81, nullptr);
        if (pszLine != nullptr)
        {
            if (strlen(pszLine) < 8 || pszLine[7] != ':')
                continue;
        }

        if (pszLine == nullptr || STARTS_WITH(pszLine, "RTYSA"))
        {
            if (osRTY == "QUP")
            {
                mapQAL[osRID] = intintType(nODA, nUDA);
            }
            if (pszLine == nullptr)
                break;
            osRTY = pszLine + 8;
            osRID = "";
            nODA = 0;
            nUDA = 0;
        }
        else if (STARTS_WITH(pszLine, "RIDSA"))
            osRID = pszLine + 8;
        else if (STARTS_WITH(pszLine, "ODASD"))
            nODA = atoi(pszLine + 8);
        else if (STARTS_WITH(pszLine, "UDASD"))
            nUDA = atoi(pszLine + 8);
    }

    VSIFCloseL(fp);

    return TRUE;
}

/************************************************************************/
/*                       CreateLayerFromObjectDesc()                    */
/************************************************************************/

int OGREDIGEODataSource::CreateLayerFromObjectDesc(
    const OGREDIGEOObjectDescriptor &objDesc) const
{
    OGRwkbGeometryType eType = wkbUnknown;
    if (objDesc.osKND == "ARE")
        eType = wkbPolygon;
    else if (objDesc.osKND == "LIN")
        eType = wkbLineString;
    else if (objDesc.osKND == "PCT")
        eType = wkbPoint;
    else
    {
        CPLDebug("EDIGEO", "Unknown KND : %s", objDesc.osKND.c_str());
        return FALSE;
    }

    const char *pszLayerName = objDesc.osRID.c_str();
    // mapObjects.find(objDesc.osNameRID)->second.c_str();
    OGREDIGEOLayer *poLayer = new OGREDIGEOLayer(
        const_cast<OGREDIGEODataSource *>(this), pszLayerName, eType, poSRS);

    poLayer->AddFieldDefn("OBJECT_RID", OFTString, "");

    for (int j = 0; j < (int)objDesc.aosAttrRID.size(); j++)
    {
        std::map<CPLString, OGREDIGEOAttributeDescriptor>::iterator it =
            mapAttributesSCD.find(objDesc.aosAttrRID[j]);
        if (it != mapAttributesSCD.end())
        {
            const OGREDIGEOAttributeDescriptor &attrDesc = it->second;
            const OGREDIGEOAttributeDef &attrDef =
                mapAttributes[attrDesc.osNameRID];
            OGRFieldType eFieldType = OFTString;
            if (attrDef.osTYP == "R" || attrDef.osTYP == "E")
                eFieldType = OFTReal;
            else if (attrDef.osTYP == "I" || attrDef.osTYP == "N")
                eFieldType = OFTInteger;

            poLayer->AddFieldDefn(attrDef.osLAB, eFieldType,
                                  objDesc.aosAttrRID[j]);
        }
    }

    if (strcmp(poLayer->GetName(), "ID_S_OBJ_Z_1_2_2") == 0)
    {
        OGRFeatureDefn *poFDefn = poLayer->GetLayerDefn();

        iATR = poFDefn->GetFieldIndex("ATR");
        iDI3 = poFDefn->GetFieldIndex("DI3");
        iDI4 = poFDefn->GetFieldIndex("DI4");
        iHEI = poFDefn->GetFieldIndex("HEI");
        iFON = poFDefn->GetFieldIndex("FON");

        poLayer->AddFieldDefn("OGR_OBJ_LNK", OFTString, "");
        iOBJ_LNK = poFDefn->GetFieldIndex("OGR_OBJ_LNK");

        poLayer->AddFieldDefn("OGR_OBJ_LNK_LAYER", OFTString, "");
        iOBJ_LNK_LAYER = poFDefn->GetFieldIndex("OGR_OBJ_LNK_LAYER");

        poLayer->AddFieldDefn("OGR_ATR_VAL", OFTString, "");
        iATR_VAL = poFDefn->GetFieldIndex("OGR_ATR_VAL");

        poLayer->AddFieldDefn("OGR_ANGLE", OFTReal, "");
        iANGLE = poFDefn->GetFieldIndex("OGR_ANGLE");

        poLayer->AddFieldDefn("OGR_FONT_SIZE", OFTReal, "");
        iSIZE = poFDefn->GetFieldIndex("OGR_FONT_SIZE");
    }
    else if (!mapQAL.empty())
    {
        poLayer->AddFieldDefn("CREAT_DATE", OFTInteger, "");
        poLayer->AddFieldDefn("UPDATE_DATE", OFTInteger, "");
    }

    mapLayer[objDesc.osRID] = poLayer;

    papoLayers =
        (OGRLayer **)CPLRealloc(papoLayers, (nLayers + 1) * sizeof(OGRLayer *));
    papoLayers[nLayers] = poLayer;
    nLayers++;

    return TRUE;
}

/************************************************************************/
/*                              ReadVEC()                               */
/************************************************************************/

int OGREDIGEODataSource::ReadVEC(const char *pszVECName) const
{
    VSILFILE *fp = OpenFile(pszVECName, "VEC");
    if (fp == nullptr)
        return FALSE;

    const char *pszLine = nullptr;
    CPLString osRTY, osRID;
    xyPairListType aXY;
    CPLString osLnkStartType, osLnkStartName, osLnkEndType, osLnkEndName;
    strListType osLnkEndNameList;
    CPLString osAttId;
    std::vector<strstrType> aosAttIdVal;
    CPLString osSCP;
    CPLString osQUP_RID;
    int bIso8859_1 = FALSE;

    while (true)
    {
        pszLine = CPLReadLine2L(fp, 81, nullptr);
    skip_read_next_line:
        if (pszLine != nullptr)
        {
            if (strlen(pszLine) < 8 || pszLine[7] != ':')
                continue;
        }

        if (pszLine == nullptr || STARTS_WITH(pszLine, "RTYSA"))
        {
            if (osRTY == "PAR")
            {
                if (aXY.size() < 2)
                    CPLDebug("EDIGEO", "Error: ARC %s has not enough points",
                             osRID.c_str());
                else
                    mapPAR[osRID] = aXY;
            }
            else if (osRTY == "LNK")
            {
                if (osLnkStartType == "PAR" && osLnkEndType == "PFE")
                {
                    /*CPLDebug("EDIGEO", "PFE[%s] -> PAR[%s]",
                             osLnkEndName.c_str(), osLnkStartName.c_str());*/
                    if (mapPFE_PAR.find(osLnkEndName) == mapPFE_PAR.end())
                        mapPFE_PAR[osLnkEndName].push_back(osLnkStartName);
                    else
                    {
                        int bAlreadyExists = FALSE;
                        strListType &osPARList = mapPFE_PAR[osLnkEndName];
                        for (int j = 0; j < (int)osPARList.size(); j++)
                        {
                            if (osPARList[j] == osLnkStartName)
                                bAlreadyExists = TRUE;
                        }
                        if (!bAlreadyExists)
                            osPARList.push_back(osLnkStartName);
                    }
                }
                else if (osLnkStartType == "FEA" && osLnkEndType == "PFE")
                {
                    /*CPLDebug("EDIGEO", "FEA[%s] -> PFE[%s]",
                             osLnkStartName.c_str(), osLnkEndName.c_str());*/
                    listFEA_PFE.push_back(
                        std::pair(osLnkStartName, osLnkEndNameList));
                }
                else if (osLnkStartType == "FEA" && osLnkEndType == "PAR")
                {
                    /*CPLDebug("EDIGEO", "FEA[%s] -> PAR[%s]",
                             osLnkStartName.c_str(), osLnkEndName.c_str());*/
                    listFEA_PAR.push_back(
                        std::pair(osLnkStartName, osLnkEndNameList));
                }
                else if (osLnkStartType == "FEA" && osLnkEndType == "PNO")
                {
                    /*CPLDebug("EDIGEO", "FEA[%s] -> PNO[%s]",
                             osLnkStartName.c_str(), osLnkEndName.c_str());*/
                    listFEA_PNO.push_back(
                        strstrType(osLnkStartName, osLnkEndName));
                }
                else if (osLnkStartType == "FEA" && osLnkEndType == "FEA")
                {
                    /*CPLDebug("EDIGEO", "FEA[%s] -> FEA[%s]",
                             osLnkStartName.c_str(), osLnkEndName.c_str());*/
                    if (osSCP == "IS_S_REL_IWW")
                        mapFEA_FEA[osLnkStartName] = osLnkEndName;
                }
                else if (osLnkStartType == "PAR" && osLnkEndType == "PNO")
                {
                }
                else
                {
                    CPLDebug("EDIGEO", "Unhandled LNK(%s) %s=%s --> %s=%s",
                             osRID.c_str(), osLnkStartType.c_str(),
                             osLnkStartName.c_str(), osLnkEndType.c_str(),
                             osLnkEndName.c_str());
                }
            }
            else if (osRTY == "FEA")
            {
                OGREDIGEOFEADesc feaDesc;
                feaDesc.aosAttIdVal = aosAttIdVal;
                feaDesc.osSCP = osSCP;
                feaDesc.osQUP_RID = osQUP_RID;
                mapFEA[osRID] = std::move(feaDesc);
            }
            else if (osRTY == "PNO")
            {
                if (aXY.size() == 1)
                {
                    /*CPLDebug("EDIGEO", "PNO[%s] = %f, %f",
                             osRID.c_str(), aXY[0].first, aXY[0].second);*/
                    mapPNO[osRID] = aXY[0];
                }
            }
            if (pszLine == nullptr)
                break;
            osRTY = pszLine + 8;
            osRID = "";
            aXY.resize(0);
            osLnkStartType = "";
            osLnkStartName = "";
            osLnkEndType = "";
            osLnkEndName = "";
            osAttId = "";
            aosAttIdVal.resize(0);
            osLnkEndNameList.resize(0);
            osSCP = "";
            osQUP_RID = "";
            bIso8859_1 = FALSE;
        }
        else if (STARTS_WITH(pszLine, "RIDSA"))
            osRID = pszLine + 8;
        else if (STARTS_WITH(pszLine, "CORCC"))
        {
            const char *pszY = strchr(pszLine + 8, ';');
            if (pszY)
            {
                double dfX = CPLAtof(pszLine + 8);
                double dfY = CPLAtof(pszY + 1);
                aXY.push_back(xyPairType(dfX, dfY));
            }
        }
        else if (STARTS_WITH(pszLine, "FTPCP"))
        {
            char **papszTokens = CSLTokenizeString2(pszLine + 8, ";", 0);
            if (CSLCount(papszTokens) == 4)
            {
                if (osLnkStartType.empty())
                {
                    osLnkStartType = papszTokens[2];
                    osLnkStartName = papszTokens[3];
                }
                else
                {
                    osLnkEndType = papszTokens[2];
                    osLnkEndName = papszTokens[3];
                    osLnkEndNameList.push_back(osLnkEndName);
                }
            }
            CSLDestroy(papszTokens);
        }
        else if (STARTS_WITH(pszLine, "SCPCP"))
        {
            char **papszTokens = CSLTokenizeString2(pszLine + 8, ";", 0);
            if (CSLCount(papszTokens) == 4)
            {
                if (osRTY == "LNK")
                {
                    if (strcmp(papszTokens[2], "ASS") == 0)
                        osSCP = papszTokens[3];
                }
                else if (strcmp(papszTokens[2], "OBJ") == 0)
                    osSCP = papszTokens[3];
            }
            CSLDestroy(papszTokens);
        }
        else if (STARTS_WITH(pszLine, "ATPCP"))
        {
            char **papszTokens = CSLTokenizeString2(pszLine + 8, ";", 0);
            if (CSLCount(papszTokens) == 4)
            {
                if (strcmp(papszTokens[2], "ATT") == 0)
                    osAttId = papszTokens[3];
            }
            CSLDestroy(papszTokens);
        }
        else if (strcmp(pszLine, "TEXT 06:8859-1") == 0)
        {
            bIso8859_1 = TRUE;
        }
        else if (STARTS_WITH(pszLine, "ATVS"))
        {
            CPLString osAttVal = pszLine + 8;
            while (true)
            {
                pszLine = CPLReadLine2L(fp, 81, nullptr);
                if (pszLine != nullptr && strlen(pszLine) >= 8 &&
                    pszLine[7] == ':' && STARTS_WITH(pszLine, "NEXT "))
                {
                    osAttVal += pszLine + 8;
                }
                else
                {
                    break;
                }
            }
            if (bIso8859_1 && bRecodeToUTF8)
            {
                char *pszNewVal = CPLRecode(osAttVal.c_str(), CPL_ENC_ISO8859_1,
                                            CPL_ENC_UTF8);
                osAttVal = pszNewVal;
                CPLFree(pszNewVal);
            }
            else if (bHasUTF8ContentOnly)
            {
                bHasUTF8ContentOnly = CPLIsUTF8(osAttVal.c_str(), -1);
            }
            if (!osAttId.empty())
                aosAttIdVal.push_back(strstrType(osAttId, osAttVal));
            osAttId = "";
            bIso8859_1 = FALSE;
            goto skip_read_next_line;
        }
        else if (STARTS_WITH(pszLine, "ATVCP"))
        {
            char **papszTokens = CSLTokenizeString2(pszLine + 8, ";", 0);
            if (CSLCount(papszTokens) == 4)
            {
                if (strcmp(papszTokens[2], "ATT") == 0)
                {
                    CPLString osAttVal = papszTokens[3];
                    if (!osAttId.empty())
                        aosAttIdVal.push_back(strstrType(osAttId, osAttVal));
                    osAttId = "";
                }
            }
            CSLDestroy(papszTokens);
        }
        else if (STARTS_WITH(pszLine, "QAPCP"))
        {
            char **papszTokens = CSLTokenizeString2(pszLine + 8, ";", 0);
            if (CSLCount(papszTokens) == 4)
            {
                if (strcmp(papszTokens[2], "QUP") == 0)
                {
                    osQUP_RID = papszTokens[3];
                }
            }
            CSLDestroy(papszTokens);
        }
    }

    VSIFCloseL(fp);

    return TRUE;
}

/************************************************************************/
/*                        CreateFeature()                               */
/************************************************************************/

OGRFeature *OGREDIGEODataSource::CreateFeature(const CPLString &osFEA) const
{
    const std::map<CPLString, OGREDIGEOFEADesc>::iterator itFEA =
        mapFEA.find(osFEA);
    if (itFEA == mapFEA.end())
    {
        CPLDebug("EDIGEO", "ERROR: Cannot find FEA %s", osFEA.c_str());
        return nullptr;
    }

    const OGREDIGEOFEADesc &fea = itFEA->second;
    const std::map<CPLString, OGREDIGEOLayer *>::iterator itLyr =
        mapLayer.find(fea.osSCP);
    if (itLyr != mapLayer.end())
    {
        OGREDIGEOLayer *poLayer = itLyr->second;

        OGRFeature *poFeature = new OGRFeature(poLayer->GetLayerDefn());
        poFeature->SetField(0, itFEA->first.c_str());
        for (int i = 0; i < (int)fea.aosAttIdVal.size(); i++)
        {
            const CPLString &id = fea.aosAttIdVal[i].first;
            const CPLString &val = fea.aosAttIdVal[i].second;
            int iIndex = poLayer->GetAttributeIndex(id);
            if (iIndex != -1)
                poFeature->SetField(iIndex, val.c_str());
            else
                CPLDebug("EDIGEO", "ERROR: Cannot find attribute %s",
                         id.c_str());
        }

        if (strcmp(poLayer->GetName(), "ID_S_OBJ_Z_1_2_2") != 0 &&
            !mapQAL.empty() && !fea.osQUP_RID.empty())
        {
            const std::map<CPLString, intintType>::iterator itQAL =
                mapQAL.find(fea.osQUP_RID);
            if (itQAL != mapQAL.end())
            {
                const intintType &creationUpdateDate = itQAL->second;
                if (creationUpdateDate.first != 0)
                    poFeature->SetField("CREAT_DATE", creationUpdateDate.first);
                if (creationUpdateDate.second != 0)
                    poFeature->SetField("UPDATE_DATE",
                                        creationUpdateDate.second);
            }
        }

        poLayer->AddFeature(poFeature);

        return poFeature;
    }
    else
    {
        CPLDebug("EDIGEO", "ERROR: Cannot find layer %s", fea.osSCP.c_str());
        return nullptr;
    }
}

/************************************************************************/
/*                             SetStyle()                               */
/************************************************************************/

int OGREDIGEODataSource::SetStyle(const CPLString &osFEA,
                                  OGRFeature *poFeature) const
{
    /* EDIGEO PCI specific */
    /* See EDIGeO_PCI.pdf, chapter 3 "Principes généraux de */
    /* positionnement de la toponymie. */
    const char *pszATR = nullptr;
    if (strcmp(poFeature->GetDefnRef()->GetName(), "ID_S_OBJ_Z_1_2_2") == 0 &&
        iATR != -1 && (pszATR = poFeature->GetFieldAsString(iATR)) != nullptr)
    {
        const CPLString osATR = pszATR;
        std::map<CPLString, CPLString>::iterator itFEA_FEA =
            mapFEA_FEA.find(osFEA);
        if (itFEA_FEA != mapFEA_FEA.end())
        {
            const CPLString &osOBJ_LNK = itFEA_FEA->second;
            std::map<CPLString, OGREDIGEOFEADesc>::iterator itFEA_LNK =
                mapFEA.find(osOBJ_LNK);
            if (itFEA_LNK != mapFEA.end())
            {
                const OGREDIGEOFEADesc &fea_lnk = itFEA_LNK->second;
                for (int j = 0; j < (int)fea_lnk.aosAttIdVal.size(); j++)
                {
                    if (fea_lnk.aosAttIdVal[j].first == osATR)
                    {
                        double dfAngle = 0;
                        if (iDI3 != -1 && iDI4 != -1)
                        {
                            double dfBaseVectorX =
                                poFeature->GetFieldAsDouble(iDI3);
                            double dfBaseVectorY =
                                poFeature->GetFieldAsDouble(iDI4);
                            dfAngle = atan2(dfBaseVectorY, dfBaseVectorX) /
                                      M_PI * 180;
                            if (dfAngle < 0)
                                dfAngle += 360;
                        }
                        double dfSize = 1;
                        if (iHEI != -1)
                            dfSize = poFeature->GetFieldAsDouble(iHEI);
                        if (dfSize <= 0 || dfSize >= 100)
                            dfSize = 1;
                        const char *pszFontFamily = nullptr;
                        if (iFON != -1)
                            pszFontFamily = poFeature->GetFieldAsString(iFON);

                        CPLString osStyle("LABEL(t:\"");
                        osStyle += fea_lnk.aosAttIdVal[j].second;
                        osStyle += "\"";
                        if (dfAngle != 0)
                        {
                            osStyle += ",a:";
                            osStyle += CPLString().Printf("%.1f", dfAngle);
                        }
                        if (pszFontFamily != nullptr && bIncludeFontFamily)
                        {
                            osStyle += ",f:\"";
                            osStyle += pszFontFamily;
                            osStyle += "\"";
                        }
                        osStyle += ",s:";
                        osStyle += CPLString().Printf("%.1f", dfSize);
                        osStyle += ",c:#000000)";
                        poFeature->SetStyleString(osStyle);

                        poFeature->SetField(iATR_VAL,
                                            fea_lnk.aosAttIdVal[j].second);
                        poFeature->SetField(iANGLE, dfAngle);
                        poFeature->SetField(iSIZE, dfSize * dfSizeFactor);
                        poFeature->SetField(iOBJ_LNK, osOBJ_LNK);
                        poFeature->SetField(iOBJ_LNK_LAYER, fea_lnk.osSCP);

                        setLayersWithLabels.insert(fea_lnk.osSCP);

                        break;
                    }
                }
            }
        }
    }

    return TRUE;
}

/************************************************************************/
/*                           BuildPoints()                              */
/************************************************************************/

int OGREDIGEODataSource::BuildPoints() const
{
    for (int i = 0; i < (int)listFEA_PNO.size(); i++)
    {
        const CPLString &osFEA = listFEA_PNO[i].first;
        const CPLString &osPNO = listFEA_PNO[i].second;
        const std::map<CPLString, xyPairType>::iterator itPNO =
            mapPNO.find(osPNO);
        if (itPNO == mapPNO.end())
        {
            CPLDebug("EDIGEO", "Cannot find PNO %s", osPNO.c_str());
        }
        else
        {
            OGRFeature *poFeature = CreateFeature(osFEA);
            if (poFeature)
            {
                const xyPairType &pno = itPNO->second;
                OGRPoint *poPoint = new OGRPoint(pno.first, pno.second);
                if (poSRS)
                    poPoint->assignSpatialReference(poSRS);
                poFeature->SetGeometryDirectly(poPoint);

                SetStyle(osFEA, poFeature);
            }
        }
    }

    return TRUE;
}

/************************************************************************/
/*                        BuildLineStrings()                            */
/************************************************************************/

int OGREDIGEODataSource::BuildLineStrings() const
{
    int i, iter;

    for (iter = 0; iter < (int)listFEA_PAR.size(); iter++)
    {
        const CPLString &osFEA = listFEA_PAR[iter].first;
        const strListType &aosPAR = listFEA_PAR[iter].second;
        OGRFeature *poFeature = CreateFeature(osFEA);
        if (poFeature)
        {
            OGRGeometry *poGeom = nullptr;
            OGRMultiLineString *poMulti = nullptr;
            for (int k = 0; k < (int)aosPAR.size(); k++)
            {
                const std::map<CPLString, xyPairListType>::iterator itPAR =
                    mapPAR.find(aosPAR[k]);
                if (itPAR != mapPAR.end())
                {
                    const xyPairListType &arc = itPAR->second;

                    OGRLineString *poLS = new OGRLineString();
                    poLS->setNumPoints((int)arc.size());
                    for (i = 0; i < (int)arc.size(); i++)
                    {
                        poLS->setPoint(i, arc[i].first, arc[i].second);
                    }

                    if (poGeom != nullptr)
                    {
                        if (poMulti == nullptr)
                        {
                            poMulti = new OGRMultiLineString();
                            poMulti->addGeometryDirectly(poGeom);
                            poGeom = poMulti;
                        }
                        poMulti->addGeometryDirectly(poLS);
                    }
                    else
                        poGeom = poLS;
                }
                else
                    CPLDebug("EDIGEO", "ERROR: Cannot find ARC %s",
                             aosPAR[k].c_str());
            }
            if (poGeom != nullptr)
            {
                poGeom->assignSpatialReference(poSRS);
                poFeature->SetGeometryDirectly(poGeom);
            }
        }
    }

    return TRUE;
}

/************************************************************************/
/*                           BuildPolygon()                             */
/************************************************************************/

int OGREDIGEODataSource::BuildPolygon(const CPLString &osFEA,
                                      const strListType &aosPFE) const
{
    std::vector<xyPairListType> aoXYList;

    for (int k = 0; k < (int)aosPFE.size(); k++)
    {
        const std::map<CPLString, strListType>::iterator itPFE_PAR =
            mapPFE_PAR.find(aosPFE[k]);
        if (itPFE_PAR == mapPFE_PAR.end())
        {
            CPLDebug("EDIGEO", "ERROR: Cannot find PFE %s", aosPFE[k].c_str());
            return FALSE;
        }

        const strListType &aosPARList = itPFE_PAR->second;

        /* --------------------------------------------------------------------
         */
        /*      Resolve arc ids to arc coordinate lists. */
        /* --------------------------------------------------------------------
         */
        std::vector<const xyPairListType *> aoPARPtrList;
        for (int i = 0; i < (int)aosPARList.size(); i++)
        {
            const std::map<CPLString, xyPairListType>::iterator itPAR =
                mapPAR.find(aosPARList[i]);
            if (itPAR != mapPAR.end())
                aoPARPtrList.push_back(&(itPAR->second));
            else
                CPLDebug("EDIGEO", "ERROR: Cannot find ARC %s",
                         aosPARList[i].c_str());
        }

        if (aoPARPtrList.empty())
            return FALSE;

        /* --------------------------------------------------------------------
         */
        /*      Now try to chain all arcs together. */
        /* --------------------------------------------------------------------
         */

        for (int j = 0; j < (int)aoPARPtrList.size(); j++)
        {
            if (aoPARPtrList[j] == nullptr)
                continue;
            const xyPairListType &sFirstRing = *(aoPARPtrList[j]);
            const xyPairType *psNext = &(sFirstRing.back());

            xyPairListType aoXY;
            for (int i = 0; i < (int)sFirstRing.size(); i++)
                aoXY.push_back(sFirstRing[i]);
            aoPARPtrList[j] = nullptr;

            int nIter = 1;
            while (aoXY.back() != aoXY[0] && nIter < (int)aoPARPtrList.size())
            {
                bool bFound = false;
                bool bReverseSecond = false;
                int i = 0;  // Used after for.
                for (; i < (int)aoPARPtrList.size(); i++)
                {
                    if (aoPARPtrList[i] != nullptr)
                    {
                        const xyPairListType &sSecondRing = *(aoPARPtrList[i]);
                        if (*psNext == sSecondRing[0])
                        {
                            bFound = true;
                            bReverseSecond = false;
                            break;
                        }
                        else if (*psNext == sSecondRing.back())
                        {
                            bFound = true;
                            bReverseSecond = true;
                            break;
                        }
                    }
                }

                if (!bFound)
                {
                    CPLDebug("EDIGEO", "Cannot find ring for FEA %s / PFE %s",
                             osFEA.c_str(), aosPFE[k].c_str());
                    break;
                }
                else
                {
                    const xyPairListType &secondRing = *(aoPARPtrList[i]);
                    aoPARPtrList[i] = nullptr;
                    if (!bReverseSecond)
                    {
                        for (i = 1; i < (int)secondRing.size(); i++)
                            aoXY.push_back(secondRing[i]);
                        psNext = &secondRing.back();
                    }
                    else
                    {
                        for (i = 1; i < (int)secondRing.size(); i++)
                            aoXY.push_back(
                                secondRing[secondRing.size() - 1 - i]);
                        psNext = &secondRing[0];
                    }
                }

                nIter++;
            }

            aoXYList.push_back(std::move(aoXY));
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Create feature.                                                 */
    /* -------------------------------------------------------------------- */
    OGRFeature *poFeature = CreateFeature(osFEA);
    if (poFeature)
    {
        std::vector<OGRGeometry *> aosPolygons;
        for (int j = 0; j < (int)aoXYList.size(); j++)
        {
            const xyPairListType &aoXY = aoXYList[j];
            OGRLinearRing *poLS = new OGRLinearRing();
            poLS->setNumPoints((int)aoXY.size());
            for (int i = 0; i < (int)aoXY.size(); i++)
                poLS->setPoint(i, aoXY[i].first, aoXY[i].second);
            poLS->closeRings();
            OGRPolygon *poPolygon = new OGRPolygon();
            poPolygon->addRingDirectly(poLS);
            aosPolygons.push_back(poPolygon);
        }

        int bIsValidGeometry = FALSE;
        OGRGeometry *poGeom = OGRGeometryFactory::organizePolygons(
            &aosPolygons[0], (int)aosPolygons.size(), &bIsValidGeometry,
            nullptr);
        if (poGeom)
        {
            if (poSRS)
                poGeom->assignSpatialReference(poSRS);
            poFeature->SetGeometryDirectly(poGeom);
        }
    }
    return TRUE;
}

/************************************************************************/
/*                          BuildPolygons()                             */
/************************************************************************/

int OGREDIGEODataSource::BuildPolygons() const
{
    for (int iter = 0; iter < (int)listFEA_PFE.size(); iter++)
    {
        const CPLString &osFEA = listFEA_PFE[iter].first;
        const strListType &aosPFE = listFEA_PFE[iter].second;
        BuildPolygon(osFEA, aosPFE);
    }

    return TRUE;
}

/************************************************************************/
/*                  OGREDIGEOSortForQGIS()                              */
/************************************************************************/

static int OGREDIGEOSortForQGIS(const void *a, const void *b)
{
    OGREDIGEOLayer *poLayerA = *((OGREDIGEOLayer **)a);
    OGREDIGEOLayer *poLayerB = *((OGREDIGEOLayer **)b);
    int nTypeA, nTypeB;
    switch (poLayerA->GetLayerDefn()->GetGeomType())
    {
        case wkbPoint:
            nTypeA = 1;
            break;
        case wkbLineString:
            nTypeA = 2;
            break;
        case wkbPolygon:
            nTypeA = 3;
            break;
        default:
            nTypeA = 4;
            break;
    }
    switch (poLayerB->GetLayerDefn()->GetGeomType())
    {
        case wkbPoint:
            nTypeB = 1;
            break;
        case wkbLineString:
            nTypeB = 2;
            break;
        case wkbPolygon:
            nTypeB = 3;
            break;
        default:
            nTypeB = 4;
            break;
    }
    if (nTypeA == nTypeB)
    {
        int nCmp = strcmp(poLayerA->GetName(), poLayerB->GetName());
        if (nCmp == 0)
            return 0;

        static const char *const apszPolyOrder[] = {
            "COMMUNE_id",  "LIEUDIT_id",  "SECTION_id", "SUBDSECT_id",
            "SUBDFISC_id", "PARCELLE_id", "BATIMENT_id"};
        for (int i = 0; i < (int)(sizeof(apszPolyOrder) / sizeof(char *)); i++)
        {
            if (strcmp(poLayerA->GetName(), apszPolyOrder[i]) == 0)
                return -1;
            if (strcmp(poLayerB->GetName(), apszPolyOrder[i]) == 0)
                return 1;
        }
        return nCmp;
    }
    else
        return nTypeB - nTypeA;
}

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

int OGREDIGEODataSource::Open(const char *pszFilename)

{
    fpTHF = VSIFOpenL(pszFilename, "rb");
    if (fpTHF == nullptr)
        return FALSE;

    const char *pszLine = nullptr;
    int i = 0;
    bool bIsEDIGEO = false;
    while (i < 100 && (pszLine = CPLReadLine2L(fpTHF, 81, nullptr)) != nullptr)
    {
        if (strcmp(pszLine, "RTYSA03:GTS") == 0)
        {
            bIsEDIGEO = true;
            break;
        }
        i++;
    }

    if (!bIsEDIGEO)
    {
        VSIFCloseL(fpTHF);
        fpTHF = nullptr;
        return FALSE;
    }

    return TRUE;
}

/************************************************************************/
/*                           ReadEDIGEO()                               */
/************************************************************************/

void OGREDIGEODataSource::ReadEDIGEO() const
{
    std::lock_guard oLock(m_oMutex);
    if (bHasReadEDIGEO)
        return;

    bHasReadEDIGEO = TRUE;

    /* -------------------------------------------------------------------- */
    /*      Read .THF file                                                  */
    /* -------------------------------------------------------------------- */
    VSIFSeekL(fpTHF, 0, SEEK_SET);
    if (!ReadTHF(fpTHF))
    {
        VSIFCloseL(fpTHF);
        fpTHF = nullptr;
        return;
    }
    VSIFCloseL(fpTHF);
    fpTHF = nullptr;

    /* -------------------------------------------------------------------- */
    /*      Read .GEO file                                                  */
    /* -------------------------------------------------------------------- */
    if (!ReadGEO())
        return;

    /* -------------------------------------------------------------------- */
    /*      Read .GEN file                                                  */
    /* -------------------------------------------------------------------- */
    if (!osGNN.empty())
        ReadGEN();

    /* -------------------------------------------------------------------- */
    /*      Read .DIC file                                                  */
    /* -------------------------------------------------------------------- */
    if (!ReadDIC())
        return;

    /* -------------------------------------------------------------------- */
    /*      Read .SCD file                                                  */
    /* -------------------------------------------------------------------- */
    if (!ReadSCD())
        return;

    /* -------------------------------------------------------------------- */
    /*      Read .QAL file                                                  */
    /* -------------------------------------------------------------------- */
    if (!osQAN.empty())
        ReadQAL();

    /* -------------------------------------------------------------------- */
    /*      Create layers from SCD definitions                              */
    /* -------------------------------------------------------------------- */
    for (int i = 0; i < (int)aoObjList.size(); i++)
    {
        CreateLayerFromObjectDesc(aoObjList[i]);
    }

    /* -------------------------------------------------------------------- */
    /*      Read .VEC files and create features                             */
    /* -------------------------------------------------------------------- */
    for (int i = 0; i < (int)aosGDN.size(); i++)
    {
        ReadVEC(aosGDN[i]);

        BuildPoints();
        BuildLineStrings();
        BuildPolygons();

        mapPNO.clear();
        mapPAR.clear();
        mapFEA.clear();
        mapPFE_PAR.clear();
        listFEA_PFE.clear();
        listFEA_PAR.clear();
        listFEA_PNO.clear();
        mapFEA_FEA.clear();
    }

    mapObjects.clear();
    mapAttributes.clear();
    mapAttributesSCD.clear();
    mapQAL.clear();

    /* -------------------------------------------------------------------- */
    /*      Delete empty layers                                             */
    /* -------------------------------------------------------------------- */
    for (int i = 0; i < nLayers; /*nothing*/)
    {
        if (papoLayers[i]->GetFeatureCount(TRUE) == 0)
        {
            delete papoLayers[i];
            if (i < nLayers - 1)
                memmove(papoLayers + i, papoLayers + i + 1,
                        (nLayers - i - 1) * sizeof(OGREDIGEOLayer *));
            nLayers--;
        }
        else
            i++;
    }

    /* -------------------------------------------------------------------- */
    /*      When added from QGIS, the layers must be ordered from           */
    /*      bottom (Polygon) to top (Point) to get nice visual effect       */
    /* -------------------------------------------------------------------- */
    if (CPLTestBool(CPLGetConfigOption("OGR_EDIGEO_SORT_FOR_QGIS", "YES")))
        qsort(papoLayers, nLayers, sizeof(OGREDIGEOLayer *),
              OGREDIGEOSortForQGIS);

    /* -------------------------------------------------------------------- */
    /*      Create a label layer for each feature layer                     */
    /* -------------------------------------------------------------------- */
    if (CPLTestBool(
            CPLGetConfigOption("OGR_EDIGEO_CREATE_LABEL_LAYERS", "YES")))
        CreateLabelLayers();

    return;
}

/************************************************************************/
/*                         CreateLabelLayers()                          */
/************************************************************************/

void OGREDIGEODataSource::CreateLabelLayers() const
{
    OGRLayer *poLayer = const_cast<OGREDIGEODataSource *>(this)->GetLayerByName(
        "ID_S_OBJ_Z_1_2_2");
    if (poLayer == nullptr)
        return;

    std::map<CPLString, OGREDIGEOLayer *> mapLayerNameToLayer;

    OGRFeature *poFeature = nullptr;
    OGRFeatureDefn *poFeatureDefn = poLayer->GetLayerDefn();
    while ((poFeature = poLayer->GetNextFeature()) != nullptr)
    {
        const char *pszBelongingLayerName =
            poFeature->GetFieldAsString(iOBJ_LNK_LAYER);
        if (pszBelongingLayerName)
        {
            CPLString osBelongingLayerName = pszBelongingLayerName;
            std::map<CPLString, OGREDIGEOLayer *>::iterator it =
                mapLayerNameToLayer.find(osBelongingLayerName);
            OGREDIGEOLayer *poLabelLayer = nullptr;

            if (it == mapLayerNameToLayer.end())
            {
                /* Create label layer if it does not already exist */
                CPLString osLayerLabelName = osBelongingLayerName + "_LABEL";
                poLabelLayer = new OGREDIGEOLayer(
                    const_cast<OGREDIGEODataSource *>(this),
                    osLayerLabelName.c_str(), wkbPoint, poSRS);
                OGRFeatureDefn *poLabelFeatureDefn =
                    poLabelLayer->GetLayerDefn();
                for (int i = 0; i < poFeatureDefn->GetFieldCount(); i++)
                    poLabelFeatureDefn->AddFieldDefn(
                        poFeatureDefn->GetFieldDefn(i));
                mapLayerNameToLayer[osBelongingLayerName] = poLabelLayer;

                papoLayers = (OGRLayer **)CPLRealloc(
                    papoLayers, (nLayers + 1) * sizeof(OGRLayer *));
                papoLayers[nLayers] = poLabelLayer;
                nLayers++;
            }
            else
            {
                poLabelLayer = mapLayerNameToLayer[osBelongingLayerName];
            }

            OGRFeature *poNewFeature =
                new OGRFeature(poLabelLayer->GetLayerDefn());
            poNewFeature->SetFrom(poFeature);
            poLabelLayer->AddFeature(poNewFeature);
        }
        delete poFeature;
    }

    poLayer->ResetReading();
}
