/*****************************************************************************
 * Copyright (c) 1999, 2001, Frank Warmerdam warmerdam@pobox.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/

/**
 * \file
 * Implements S57Reader class.
 */

#include <assert.h>
#include "gdal/cpl_conv.h"
#include "gdal/cpl_string.h"
#include "gdal/ogr_api.h"
#include "ogr_s57.h"
#include "s57.h"

/************************************************************************/
/*                             S57Reader()                              */
/************************************************************************/

S57Reader::S57Reader(const char *pszFilename)

{
  pszModuleName = CPLStrdup(pszFilename);
  pszDSNM = NULL;

  poModule = NULL;

  nFDefnCount = 0;
  papoFDefnList = NULL;

  nCOMF = 1000000;
  nSOMF = 10;

  poRegistrar = NULL;
  bFileIngested = FALSE;

  nNextFEIndex = 0;
  nNextVIIndex = 0;
  nNextVCIndex = 0;
  nNextVEIndex = 0;
  nNextVFIndex = 0;

  iPointOffset = 0;
  poMultiPoint = NULL;

  papszOptions = NULL;

  nOptionFlags = S57M_UPDATES;

  bMissingWarningIssued = FALSE;
  bAttrWarningIssued = FALSE;

  Nall = 0;
  Aall = 0;
}

/************************************************************************/
/*                             ~S57Reader()                             */
/************************************************************************/

S57Reader::~S57Reader()

{
  Close();

  CPLFree(pszModuleName);
  CSLDestroy(papszOptions);

  CPLFree(papoFDefnList);
}

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

int S57Reader::Open(int bTestOpen)

{
  if (poModule != NULL) {
    Rewind();
    return TRUE;
  }

  poModule = new DDFModule();
  if (!poModule->Open(pszModuleName)) {
    // notdef: test bTestOpen.
    delete poModule;
    poModule = NULL;
    return FALSE;
  }

  // note that the following won't work for catalogs.
  if (poModule->FindFieldDefn("DSID") == NULL) {
    if (!bTestOpen) {
      CPLError(CE_Failure, CPLE_AppDefined,
               "%s is an ISO8211 file, but not an S-57 data file.\n",
               pszModuleName);
    }
    delete poModule;
    poModule = NULL;
    return FALSE;
  }

  // Make sure the FSPT field is marked as repeating.
  DDFFieldDefn *poFSPT = poModule->FindFieldDefn("FSPT");
  if (poFSPT != NULL && !poFSPT->IsRepeating()) {
    CPLDebug("S57", "Forcing FSPT field to be repeating.");
    poFSPT->SetRepeatingFlag(TRUE);
  }

  nNextFEIndex = 0;
  nNextVIIndex = 0;
  nNextVCIndex = 0;
  nNextVEIndex = 0;
  nNextVFIndex = 0;

  return TRUE;
}

/************************************************************************/
/*                               Close()                                */
/************************************************************************/

void S57Reader::Close()

{
  if (poModule != NULL) {
    oVI_Index.Clear();
    oVC_Index.Clear();
    oVE_Index.Clear();
    oVF_Index.Clear();
    oFE_Index.Clear();

    ClearPendingMultiPoint();

    delete poModule;
    poModule = NULL;

    bFileIngested = FALSE;

    CPLFreeConfig();

    CPLFree(pszDSNM);
    pszDSNM = NULL;
  }
}

/************************************************************************/
/*                       ClearPendingMultiPoint()                       */
/************************************************************************/

void S57Reader::ClearPendingMultiPoint()

{
  if (poMultiPoint != NULL) {
    delete poMultiPoint;
    poMultiPoint = NULL;
  }
}

/************************************************************************/
/*                       NextPendingMultiPoint()                        */
/************************************************************************/

OGRFeature *S57Reader::NextPendingMultiPoint()

{
  CPLAssert(poMultiPoint != NULL);
  CPLAssert(wkbFlatten(poMultiPoint->GetGeometryRef()->getGeometryType()) ==
            wkbMultiPoint);

  OGRFeatureDefn *poDefn = poMultiPoint->GetDefnRef();
  OGRFeature *poPoint = new OGRFeature(poDefn);
  OGRMultiPoint *poMPGeom = (OGRMultiPoint *)poMultiPoint->GetGeometryRef();
  OGRPoint *poSrcPoint;

  poPoint->SetFID(poMultiPoint->GetFID());

  for (int i = 0; i < poDefn->GetFieldCount(); i++) {
    poPoint->SetField(i, poMultiPoint->GetRawFieldRef(i));
  }

  poSrcPoint = (OGRPoint *)poMPGeom->getGeometryRef(iPointOffset++);
  poPoint->SetGeometry(poSrcPoint);

  poPoint->SetField("DEPTH", poSrcPoint->getZ());

  if (iPointOffset >= poMPGeom->getNumGeometries()) ClearPendingMultiPoint();

  return poPoint;
}

/************************************************************************/
/*                             SetOptions()                             */
/************************************************************************/

void S57Reader::SetOptions(char **papszOptionsIn)

{
  const char *pszOptionValue;

  CSLDestroy(papszOptions);
  papszOptions = CSLDuplicate(papszOptionsIn);

  pszOptionValue = CSLFetchNameValue(papszOptions, S57O_SPLIT_MULTIPOINT);
  if (pszOptionValue != NULL && !EQUAL(pszOptionValue, "OFF"))
    nOptionFlags |= S57M_SPLIT_MULTIPOINT;
  else
    nOptionFlags &= ~S57M_SPLIT_MULTIPOINT;

  pszOptionValue = CSLFetchNameValue(papszOptions, S57O_ADD_SOUNDG_DEPTH);
  if (pszOptionValue != NULL && !EQUAL(pszOptionValue, "OFF"))
    nOptionFlags |= S57M_ADD_SOUNDG_DEPTH;
  else
    nOptionFlags &= ~S57M_ADD_SOUNDG_DEPTH;

  CPLAssert(!(nOptionFlags & S57M_ADD_SOUNDG_DEPTH) ||
            (nOptionFlags & S57M_SPLIT_MULTIPOINT));

  pszOptionValue = CSLFetchNameValue(papszOptions, S57O_LNAM_REFS);
  if (pszOptionValue != NULL && !EQUAL(pszOptionValue, "OFF"))
    nOptionFlags |= S57M_LNAM_REFS;
  else
    nOptionFlags &= ~S57M_LNAM_REFS;

  pszOptionValue = CSLFetchNameValue(papszOptions, S57O_UPDATES);
  if (pszOptionValue != NULL && !EQUAL(pszOptionValue, "OFF"))
    nOptionFlags |= S57M_UPDATES;
  else
    nOptionFlags &= ~S57M_UPDATES;

  pszOptionValue = CSLFetchNameValue(papszOptions, S57O_PRESERVE_EMPTY_NUMBERS);
  if (pszOptionValue != NULL && !EQUAL(pszOptionValue, "OFF"))
    nOptionFlags |= S57M_PRESERVE_EMPTY_NUMBERS;
  else
    nOptionFlags &= ~S57M_PRESERVE_EMPTY_NUMBERS;

  pszOptionValue = CSLFetchNameValue(papszOptions, S57O_RETURN_PRIMITIVES);
  if (pszOptionValue != NULL && !EQUAL(pszOptionValue, "OFF"))
    nOptionFlags |= S57M_RETURN_PRIMITIVES;
  else
    nOptionFlags &= ~S57M_RETURN_PRIMITIVES;

  pszOptionValue = CSLFetchNameValue(papszOptions, S57O_RETURN_LINKAGES);
  if (pszOptionValue != NULL && !EQUAL(pszOptionValue, "OFF"))
    nOptionFlags |= S57M_RETURN_LINKAGES;
  else
    nOptionFlags &= ~S57M_RETURN_LINKAGES;
}

/************************************************************************/
/*                           SetClassBased()                            */
/************************************************************************/

void S57Reader::SetClassBased(S57ClassRegistrar *poReg)

{
  poRegistrar = poReg;
}

/************************************************************************/
/*                               Rewind()                               */
/************************************************************************/

void S57Reader::Rewind()

{
  ClearPendingMultiPoint();
  nNextFEIndex = 0;
  nNextVIIndex = 0;
  nNextVCIndex = 0;
  nNextVEIndex = 0;
  nNextVFIndex = 0;
}

/************************************************************************/
/*                               Ingest()                               */
/*                                                                      */
/*      Read all the records into memory, adding to the appropriate     */
/*      indexes.                                                        */
/************************************************************************/

int S57Reader::Ingest(CallBackFunction pcallback) {
  DDFRecord *poRecord;

 // CPLSetConfigOption("CPL_DEBUG", "S57");

  if (poModule == NULL || bFileIngested) return 0;

  /* -------------------------------------------------------------------- */
  /*      Read all the records in the module, and place them in           */
  /*      appropriate indexes.                                            */
  /* -------------------------------------------------------------------- */
  while ((poRecord = poModule->ReadRecord()) != NULL) {
    if (pcallback) {
      if (!(*pcallback)()) return 0;
    }

    DDFField *poKeyField = poRecord->GetField(1);
    const char *pszname = poKeyField->GetFieldDefn()->GetName();

    if (EQUAL(pszname, "VRID")) {
#if 0
            int         nRCNM = poRecord->GetIntSubfield( "VRID",0, "RCNM",0);
            int         nRCID = poRecord->GetIntSubfield( "VRID",0, "RCID",0);
#else
      int nRCNM = 0, nRCID = 0;
      DDFField *poField = poRecord->FindField("VRID", 0);
      if (poField) {
        int nBytesRemaining;
        DDFSubfieldDefn *poSFDefn;
        poSFDefn = poField->GetFieldDefn()->FindSubfieldDefn("RCNM");
        if (poSFDefn) {
          const char *pachData =
              poField->GetSubfieldData(poSFDefn, &nBytesRemaining, 0);
          nRCNM = poSFDefn->ExtractIntData(pachData, nBytesRemaining, NULL);
        }

        poSFDefn = poField->GetFieldDefn()->FindSubfieldDefn("RCID");
        if (poSFDefn) {
          const char *pachData =
              poField->GetSubfieldData(poSFDefn, &nBytesRemaining, 0);
          nRCID = poSFDefn->ExtractIntData(pachData, nBytesRemaining, NULL);
        }
      }
#endif

      switch (nRCNM) {
        case RCNM_VI:
          oVI_Index.AddRecord(nRCID, poRecord->Copy());
          break;

        case RCNM_VC:
          oVC_Index.AddRecord(nRCID, poRecord->Copy());
          break;

        case RCNM_VE:
          oVE_Index.AddRecord(nRCID, poRecord->Copy());
          break;

        case RCNM_VF:
          oVF_Index.AddRecord(nRCID, poRecord->Copy());
          break;

        default:
          CPLAssert(FALSE);
          break;
      }

    }

    //  Feature records
    else if (EQUAL(pszname, "FRID")) {
      //              poRecord->Dump(stderr);               //  for debugging,
      //              try ./opencpn &>test.dbg

      int nRCID = poRecord->GetIntSubfield("FRID", 0, "RCID", 0);
      oFE_Index.AddRecord(nRCID, poRecord->Copy());

    }

    //  Convenience values
    else if (EQUAL(pszname, "DSPM")) {
      nCOMF = MAX(1, poRecord->GetIntSubfield("DSPM", 0, "COMF", 0));
      nSOMF = MAX(1, poRecord->GetIntSubfield("DSPM", 0, "SOMF", 0));
      nCSCL = MAX(1, poRecord->GetIntSubfield("DSPM", 0, "CSCL", 0));

    }

    else if (EQUAL(pszname, "DSID")) {
      CPLFree(pszDSNM);
      pszDSNM = CPLStrdup(poRecord->GetStringSubfield("DSID", 0, "DSNM", 0));
      Nall = poRecord->GetIntSubfield("DSSI", 0, "NALL", 0);
      Aall = poRecord->GetIntSubfield("DSSI", 0, "AALL", 0);
    }

    else {
      CPLDebug("S57", "Skipping %s record in S57Reader::Ingest().\n",
               poKeyField->GetFieldDefn()->GetName());
    }
  }

  bFileIngested = TRUE;

  /* -------------------------------------------------------------------- */
  /*      If update support is enabled, read and apply them.              */
  /* -------------------------------------------------------------------- */
  int update_return = 0;
  if (nOptionFlags & S57M_UPDATES) update_return = FindAndApplyUpdates();

  return update_return;
}

/************************************************************************/
/*                           SetNextFEIndex()                           */
/************************************************************************/

void S57Reader::SetNextFEIndex(int nNewIndex, int nRCNM)

{
  if (nRCNM == RCNM_VI)
    nNextVIIndex = nNewIndex;
  else if (nRCNM == RCNM_VC)
    nNextVCIndex = nNewIndex;
  else if (nRCNM == RCNM_VE)
    nNextVEIndex = nNewIndex;
  else if (nRCNM == RCNM_VF)
    nNextVFIndex = nNewIndex;
  else {
    if (nNextFEIndex != nNewIndex) ClearPendingMultiPoint();

    nNextFEIndex = nNewIndex;
  }
}

/************************************************************************/
/*                           GetNextFEIndex()                           */
/************************************************************************/

int S57Reader::GetNextFEIndex(int nRCNM)

{
  if (nRCNM == RCNM_VI)
    return nNextVIIndex;
  else if (nRCNM == RCNM_VC)
    return nNextVCIndex;
  else if (nRCNM == RCNM_VE)
    return nNextVEIndex;
  else if (nRCNM == RCNM_VF)
    return nNextVFIndex;
  else
    return nNextFEIndex;
}

/************************************************************************/
/*                          ReadNextFeature()                           */
/************************************************************************/

OGRFeature *S57Reader::ReadNextFeature(OGRFeatureDefn *poTarget)

{
  if (!bFileIngested) Ingest();

  /* -------------------------------------------------------------------- */
  /*      Special case for "in progress" multipoints being split up.      */
  /* -------------------------------------------------------------------- */
  if (poMultiPoint != NULL) {
    if (poTarget == NULL || poTarget == poMultiPoint->GetDefnRef()) {
      return NextPendingMultiPoint();
    } else {
      ClearPendingMultiPoint();
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Next vector feature?                                            */
  /* -------------------------------------------------------------------- */
  if (nOptionFlags & S57M_RETURN_PRIMITIVES) {
    int nRCNM = 0;
    int *pnCounter = NULL;

    if (poTarget == NULL) {
      if (nNextVIIndex < oVI_Index.GetCount()) {
        nRCNM = RCNM_VI;
        pnCounter = &nNextVIIndex;
      } else if (nNextVCIndex < oVC_Index.GetCount()) {
        nRCNM = RCNM_VC;
        pnCounter = &nNextVCIndex;
      } else if (nNextVEIndex < oVE_Index.GetCount()) {
        nRCNM = RCNM_VE;
        pnCounter = &nNextVEIndex;
      } else if (nNextVFIndex < oVF_Index.GetCount()) {
        nRCNM = RCNM_VF;
        pnCounter = &nNextVFIndex;
      }
    } else {
      if (EQUAL(poTarget->GetName(), OGRN_VI)) {
        nRCNM = RCNM_VI;
        pnCounter = &nNextVIIndex;
      } else if (EQUAL(poTarget->GetName(), OGRN_VC)) {
        nRCNM = RCNM_VC;
        pnCounter = &nNextVCIndex;
      } else if (EQUAL(poTarget->GetName(), OGRN_VE)) {
        nRCNM = RCNM_VE;
        pnCounter = &nNextVEIndex;
      } else if (EQUAL(poTarget->GetName(), OGRN_VF)) {
        nRCNM = RCNM_VF;
        pnCounter = &nNextVFIndex;
      }
    }

    if (nRCNM != 0) {
      OGRFeature *poFeature = ReadVector(*pnCounter, nRCNM);
      if (poFeature != NULL) {
        *pnCounter += 1;
        return poFeature;
      }
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Next feature.                                                   */
  /* -------------------------------------------------------------------- */
  while (nNextFEIndex < oFE_Index.GetCount()) {
    OGRFeature *poFeature;

    poFeature = ReadFeature(nNextFEIndex++, poTarget);
    if (poFeature != NULL) {
      if ((nOptionFlags & S57M_SPLIT_MULTIPOINT) &&
          poFeature->GetGeometryRef() != NULL &&
          wkbFlatten(poFeature->GetGeometryRef()->getGeometryType()) ==
              wkbMultiPoint) {
        poMultiPoint = poFeature;
        iPointOffset = 0;
        return NextPendingMultiPoint();
      }

      return poFeature;
    }
  }

  return NULL;
}

/************************************************************************/
/*                            ReadFeature()                             */
/*                                                                      */
/*      Read the features who's id is provided.                         */
/************************************************************************/
/*
OGRFeature *S57Reader::ReadFeature( int nFeatureId, OGRFeatureDefn *poTarget )

{
    OGRFeature  *poFeature;

    if( nFeatureId < 0 || nFeatureId >= oFE_Index.GetCount() )
        return NULL;


    poFeature = AssembleFeature( oFE_Index.GetByIndex(nFeatureId),
                                 poTarget );
    if( poFeature != NULL )
        poFeature->SetFID( nFeatureId );

    return poFeature;
}

*/
/************************************************************************/
/*                            ReadFeature()                             */
/*                                                                      */
/*      Read the features who's id is provided.                         */
/************************************************************************/

OGRFeature *S57Reader::ReadFeature(int nFeatureId, OGRFeatureDefn *poTarget)

{
  OGRFeature *poFeature;

  if (nFeatureId < 0 || nFeatureId >= oFE_Index.GetCount()) return NULL;

  DDFRecord *poRecord = oFE_Index.GetByIndex(nFeatureId);

  if (poTarget) {
    int nRecord_OBJL = poRecord->GetIntSubfield("FRID", 0, "OBJL", 0);
    int nOBJL = poTarget->GetOBJL();

    if (nRecord_OBJL == nOBJL)  // tentative match
    {
      poFeature = AssembleFeature(oFE_Index.GetByIndex(nFeatureId), poTarget);

      if (poFeature != NULL) poFeature->SetFID(nFeatureId);

      return poFeature;
    } else
      return NULL;
  } else {
    poFeature = AssembleFeature(oFE_Index.GetByIndex(nFeatureId), poTarget);
    if (poFeature != NULL) poFeature->SetFID(nFeatureId);

    return poFeature;
  }
}

/************************************************************************/
/*                          AssembleFeature()                           */
/*                                                                      */
/*      Assemble an OGR feature based on a feature record.              */
/************************************************************************/

OGRFeature *S57Reader::AssembleFeature(DDFRecord *poRecord,
                                       OGRFeatureDefn *poTarget)

{
  int nPRIM, nOBJL;
  OGRFeatureDefn *poFDefn;

  /* -------------------------------------------------------------------- */
  /*      Find the feature definition to use.  Currently this is based    */
  /*      on the primitive, but eventually this should be based on the    */
  /*      object class (FRID.OBJL) in some cases, and the primitive in    */
  /*      others.                                                         */
  /* -------------------------------------------------------------------- */
  poFDefn = FindFDefn(poRecord);
  if (poFDefn == NULL) {
    //  It is possible that a Feature was added by update, whose Class
    //  was not already present in the module before updates.
    //  So, if necessary, try to create and add an OGRFeatureDefn for this
    //  Feature.
    int nOBJL = poRecord->GetIntSubfield("FRID", 0, "OBJL", 0);
    poFDefn =
        S57GenerateObjectClassDefn(poRegistrar, nOBJL, this->GetOptionFlags());
    if (poFDefn)
      AddFeatureDefn(poFDefn);
    else
      return NULL;
  }

  /* -------------------------------------------------------------------- */
  /*      Does this match our target feature definition?  If not skip     */
  /*      this feature.                                                   */
  /* -------------------------------------------------------------------- */
  if (poTarget != NULL && poFDefn != poTarget) return NULL;

  /* -------------------------------------------------------------------- */
  /*      Create the new feature object.                                  */
  /* -------------------------------------------------------------------- */
  OGRFeature *poFeature;

  poFeature = new OGRFeature(poFDefn);

  /* -------------------------------------------------------------------- */
  /*      Assign a few standard feature attribues.                        */
  /* -------------------------------------------------------------------- */
  nOBJL = poRecord->GetIntSubfield("FRID", 0, "OBJL", 0);
  poFeature->SetField("OBJL", nOBJL);

  poFeature->SetField("RCID", poRecord->GetIntSubfield("FRID", 0, "RCID", 0));
  poFeature->SetField("PRIM", poRecord->GetIntSubfield("FRID", 0, "PRIM", 0));
  poFeature->SetField("GRUP", poRecord->GetIntSubfield("FRID", 0, "GRUP", 0));
  poFeature->SetField("RVER", poRecord->GetIntSubfield("FRID", 0, "RVER", 0));
  poFeature->SetField("AGEN", poRecord->GetIntSubfield("FOID", 0, "AGEN", 0));
  poFeature->SetField("FIDN", poRecord->GetIntSubfield("FOID", 0, "FIDN", 0));
  poFeature->SetField("FIDS", poRecord->GetIntSubfield("FOID", 0, "FIDS", 0));

  /* -------------------------------------------------------------------- */
  /*      Generate long name, if requested.                               */
  /* -------------------------------------------------------------------- */
  if (nOptionFlags & S57M_LNAM_REFS) {
    GenerateLNAMAndRefs(poRecord, poFeature);
  }

  /* -------------------------------------------------------------------- */
  /*      Generate primitive references if requested.                     */
  /* -------------------------------------------------------------------- */
  if (nOptionFlags & S57M_RETURN_LINKAGES)
    GenerateFSPTAttributes(poRecord, poFeature);

  /* -------------------------------------------------------------------- */
  /*      Apply object class specific attributes, if supported.           */
  /* -------------------------------------------------------------------- */
  if (poRegistrar != NULL) ApplyObjectClassAttributes(poRecord, poFeature);

  /* -------------------------------------------------------------------- */
  /*      Find and assign spatial component.                              */
  /* -------------------------------------------------------------------- */
  nPRIM = poRecord->GetIntSubfield("FRID", 0, "PRIM", 0);

  if (nPRIM == PRIM_P) {
    if (nOBJL == 129) /* SOUNDG */
      AssembleSoundingGeometry(poRecord, poFeature);
    else
      AssemblePointGeometry(poRecord, poFeature);
  } else if (nPRIM == PRIM_L) {
    AssembleLineGeometry(poRecord, poFeature);
  } else if (nPRIM == PRIM_A) {
    AssembleAreaGeometry(poRecord, poFeature);
  }

  return poFeature;
}

/************************************************************************/
/*                     ApplyObjectClassAttributes()                     */
/************************************************************************/

void S57Reader::ApplyObjectClassAttributes(DDFRecord *poRecord,
                                           OGRFeature *poFeature)

{
  /* -------------------------------------------------------------------- */
  /*      ATTF Attributes                                                 */
  /* -------------------------------------------------------------------- */
  DDFField *poATTF = poRecord->FindField("ATTF");
  int nAttrCount, iAttr;

  if (poATTF == NULL) return;

  DDFFieldDefn *poDefn = poATTF->GetFieldDefn();

  nAttrCount = poATTF->GetRepeatCount();
  for (iAttr = 0; iAttr < nAttrCount; iAttr++) {
    int nAttrId = poRecord->GetIntSubfield("ATTF", 0, "ATTL", iAttr);
    const char *pszAcronym;

    if (nAttrId < 1 || nAttrId > poRegistrar->GetMaxAttrIndex() ||
        (pszAcronym = poRegistrar->GetAttrAcronym(nAttrId)) == NULL) {
      if (!bAttrWarningIssued) {
        bAttrWarningIssued = TRUE;
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Illegal feature attribute id (ATTF:ATTL[%d]) of %d\n"
                 "on feature FIDN=%d, FIDS=%d.\n"
                 "Skipping attribute, no more warnings will be issued.",
                 iAttr, nAttrId, poFeature->GetFieldAsInteger("FIDN"),
                 poFeature->GetFieldAsInteger("FIDS"));
      }

      continue;
    }

    /* Fetch the attribute value */
    const char *pszValue;
    pszValue = poRecord->GetStringSubfield("ATTF", 0, "ATVL", iAttr);

    /* Apply to feature in an appropriate way */
    int iField;
    OGRFieldDefn *poFldDefn;

    iField = poFeature->GetDefnRef()->GetFieldIndex(pszAcronym);
    if (iField < 0) {
      if (!bMissingWarningIssued) {
        bMissingWarningIssued = TRUE;
        CPLError(CE_Warning, CPLE_AppDefined,
                 "For feature \"%s\", attribute \"%s\" ignored, not in "
                 "expected schema.\n",
                 poFeature->GetDefnRef()->GetName(), pszAcronym);
      }
      continue;
    }

    // Handle deleted attributes
    // If the first char of the attribute is 0x7f, then unset this field.
    // Any later requests for the attribute value will retrun an empty string.
    if (pszValue[0] == 0x7f) {
      poFeature->UnsetField(iField);
      continue;
    }

    poFldDefn = poFeature->GetDefnRef()->GetFieldDefn(iField);
    if (poFldDefn->GetType() == OFTInteger || poFldDefn->GetType() == OFTReal) {
      if (strlen(pszValue) == 0) {
        if (nOptionFlags & S57M_PRESERVE_EMPTY_NUMBERS) {
          poFeature->SetField(iField, EMPTY_NUMBER_MARKER);
        } else {
          /* leave as null if value was empty string */;
        }
      } else {
        poFeature->SetField(iField, pszValue);
      }
    } else {
      poFeature->SetField(iField, pszValue);
    }
  }

  /* -------------------------------------------------------------------- */
  /*      NATF (national) attributes                                      */
  /* -------------------------------------------------------------------- */
  DDFField *poNATF = poRecord->FindField("NATF");

  if (poNATF == NULL) return;

  nAttrCount = poNATF->GetRepeatCount();
  for (iAttr = 0; iAttr < nAttrCount; iAttr++) {
    int nAttrId = poRecord->GetIntSubfield("NATF", 0, "ATTL", iAttr);
    const char *pszAcronym;

    if (nAttrId < 1 || nAttrId >= poRegistrar->GetMaxAttrIndex() ||
        (pszAcronym = poRegistrar->GetAttrAcronym(nAttrId)) == NULL) {
      //            poRecord->Dump(stdout);
      //            int     xnAttrId =
      //            poRecord->GetIntSubfield("NATF",0,"ATTL",iAttr);
      static int bAttrWarningIssued = FALSE;

      if (!bAttrWarningIssued) {
        bAttrWarningIssued = TRUE;
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Illegal feature attribute id (NATF:ATTL[%d]) of %d\n"
                 "on feature FIDN=%d, FIDS=%d.\n"
                 "Skipping attribute, no more warnings will be issued.",
                 iAttr, nAttrId, poFeature->GetFieldAsInteger("FIDN"),
                 poFeature->GetFieldAsInteger("FIDS"));
      }

      continue;
    }

    // Guard against undefined fields
    if (poFeature->GetFieldIndex(pszAcronym) < 0) continue;

    const char *pszValue =
        poRecord->GetStringSubfield("NATF", 0, "ATVL", iAttr);
    if (pszValue != NULL) {
      //      If National Language strings are encoded as UCS-2 (a.k.a UTF-16)
      //      then we capture and duplicate the attribute string directly,
      //      in order to avoid truncation that would happen if it were
      //      considered a simple char *

      if (Nall ==
          2) {  // national string encoded in UCS-2, determined from DSID record

        //      Compute the data size
        DDFField *poField;
        int nLength = 0;
        poField = poRecord->FindField("NATF", 0);
        if (poField) {
          DDFSubfieldDefn *poSFDefn;

          poSFDefn = poField->GetFieldDefn()->FindSubfieldDefn("ATVL");
          if (poSFDefn) {
            int max_length = 0;
            const char *pachData =
                poField->GetSubfieldData(poSFDefn, &max_length, iAttr);
            nLength = poSFDefn->GetDataLength(pachData, max_length, NULL);
          }
        }

        if (nLength) {
          //  Make the new length a multiple of 2, so that
          //  later stages will count chars correctly
          //  Also, be sure that the string ends with 00 00
          int new_len = ((nLength / 2) + 2) * 2;
          char *aa = (char *)calloc(new_len, 1);
          memcpy(aa, pszValue, nLength);

          int index = poFeature->GetFieldIndex(pszAcronym);
          OGRField *field = poFeature->GetRawFieldRef(index);
          field->String = aa;
        }
      } else {  //  encoded as ISO8859_1, pass it along
        poFeature->SetField(pszAcronym, pszValue);
      }
    }
  }
}

/************************************************************************/
/*                        generatelnamandrefs()                         */
/************************************************************************/

void S57Reader::GenerateLNAMAndRefs(DDFRecord *poRecord, OGRFeature *poFeature)

{
  char szLNAM[32];

  /* -------------------------------------------------------------------- */
  /*      Apply the LNAM to the object.                                   */
  /* -------------------------------------------------------------------- */
  sprintf(szLNAM, "%04X%08X%04X", poFeature->GetFieldAsInteger("AGEN"),
          poFeature->GetFieldAsInteger("FIDN"),
          poFeature->GetFieldAsInteger("FIDS"));
  poFeature->SetField("LNAM", szLNAM);

  /* -------------------------------------------------------------------- */
  /*      Do we have references to other features.                        */
  /* -------------------------------------------------------------------- */
  DDFField *poFFPT;

  poFFPT = poRecord->FindField("FFPT");

  if (poFFPT == NULL) return;

  /* -------------------------------------------------------------------- */
  /*      Apply references.                                               */
  /* -------------------------------------------------------------------- */
  int nRefCount = poFFPT->GetRepeatCount();
  DDFSubfieldDefn *poLNAM;
  char **papszRefs = NULL;
  int *panRIND = (int *)CPLMalloc(sizeof(int) * nRefCount);

  poLNAM = poFFPT->GetFieldDefn()->FindSubfieldDefn("LNAM");
  if (poLNAM == NULL) return;

  for (int iRef = 0; iRef < nRefCount; iRef++) {
    unsigned char *pabyData;

    pabyData = (unsigned char *)poFFPT->GetSubfieldData(poLNAM, NULL, iRef);

    sprintf(szLNAM, "%02X%02X%02X%02X%02X%02X%02X%02X", pabyData[1],
            pabyData[0],                                        /* AGEN */
            pabyData[5], pabyData[4], pabyData[3], pabyData[2], /* FIDN */
            pabyData[7], pabyData[6]);

    papszRefs = CSLAddString(papszRefs, szLNAM);

    panRIND[iRef] = pabyData[8];
  }

  poFeature->SetField("LNAM_REFS", papszRefs);
  CSLDestroy(papszRefs);

  poFeature->SetField("FFPT_RIND", nRefCount, panRIND);
  CPLFree(panRIND);
}

/************************************************************************/
/*                       GenerateFSPTAttributes()                       */
/************************************************************************/

void S57Reader::GenerateFSPTAttributes(DDFRecord *poRecord,
                                       OGRFeature *poFeature)

{
  /* -------------------------------------------------------------------- */
  /*      Feature the spatial record containing the point.                */
  /* -------------------------------------------------------------------- */
  DDFField *poFSPT;
  int nCount, i;

  poFSPT = poRecord->FindField("FSPT");
  if (poFSPT == NULL) return;

  nCount = poFSPT->GetRepeatCount();

  /* -------------------------------------------------------------------- */
  /*      Allocate working lists of the attributes.                       */
  /* -------------------------------------------------------------------- */
  int *panORNT, *panUSAG, *panMASK, *panRCNM, *panRCID;

  panORNT = (int *)CPLMalloc(sizeof(int) * nCount);
  panUSAG = (int *)CPLMalloc(sizeof(int) * nCount);
  panMASK = (int *)CPLMalloc(sizeof(int) * nCount);
  panRCNM = (int *)CPLMalloc(sizeof(int) * nCount);
  panRCID = (int *)CPLMalloc(sizeof(int) * nCount);

  /* -------------------------------------------------------------------- */
  /*      loop over all entries, decoding them.                           */
  /* -------------------------------------------------------------------- */
  for (i = 0; i < nCount; i++) {
    panRCID[i] = ParseName(poFSPT, i, panRCNM + i);
    panORNT[i] = poRecord->GetIntSubfield("FSPT", 0, "ORNT", i);
    panUSAG[i] = poRecord->GetIntSubfield("FSPT", 0, "USAG", i);
    panMASK[i] = poRecord->GetIntSubfield("FSPT", 0, "MASK", i);
  }

  /* -------------------------------------------------------------------- */
  /*      Assign to feature.                                              */
  /* -------------------------------------------------------------------- */
  poFeature->SetField("NAME_RCNM", nCount, panRCNM);
  poFeature->SetField("NAME_RCID", nCount, panRCID);
  poFeature->SetField("ORNT", nCount, panORNT);
  poFeature->SetField("USAG", nCount, panUSAG);
  poFeature->SetField("MASK", nCount, panMASK);

  /* -------------------------------------------------------------------- */
  /*      Cleanup.                                                        */
  /* -------------------------------------------------------------------- */
  CPLFree(panRCNM);
  CPLFree(panRCID);
  CPLFree(panORNT);
  CPLFree(panUSAG);
  CPLFree(panMASK);
}

/************************************************************************/
/*                             ReadVector()                             */
/*                                                                      */
/*      Read a vector primitive objects based on the type (RCNM_)       */
/*      and index within the related index.                             */
/************************************************************************/

OGRFeature *S57Reader::ReadVector(int nFeatureId, int nRCNM)

{
  DDFRecordIndex *poIndex;
  const char *pszFDName = NULL;

  /* -------------------------------------------------------------------- */
  /*      What type of vector are we fetching.                            */
  /* -------------------------------------------------------------------- */
  switch (nRCNM) {
    case RCNM_VI:
      poIndex = &oVI_Index;
      pszFDName = OGRN_VI;
      break;

    case RCNM_VC:
      poIndex = &oVC_Index;
      pszFDName = OGRN_VC;
      break;

    case RCNM_VE:
      poIndex = &oVE_Index;
      pszFDName = OGRN_VE;
      break;

    case RCNM_VF:
      poIndex = &oVF_Index;
      pszFDName = OGRN_VF;
      break;

    default:
      CPLAssert(FALSE);
      return NULL;
  }

  if (nFeatureId < 0 || nFeatureId >= poIndex->GetCount()) return NULL;

  DDFRecord *poRecord = poIndex->GetByIndex(nFeatureId);

  /* -------------------------------------------------------------------- */
  /*      Find the feature definition to use.                             */
  /* -------------------------------------------------------------------- */
  OGRFeatureDefn *poFDefn = NULL;

  for (int i = 0; i < nFDefnCount; i++) {
    if (EQUAL(papoFDefnList[i]->GetName(), pszFDName)) {
      poFDefn = papoFDefnList[i];
      break;
    }
  }

  if (poFDefn == NULL) {
    CPLAssert(FALSE);
    return NULL;
  }

  /* -------------------------------------------------------------------- */
  /*      Create feature, and assign standard fields.                     */
  /* -------------------------------------------------------------------- */
  OGRFeature *poFeature = new OGRFeature(poFDefn);

  poFeature->SetFID(nFeatureId);

  poFeature->SetField("RCNM", poRecord->GetIntSubfield("VRID", 0, "RCNM", 0));
  poFeature->SetField("RCID", poRecord->GetIntSubfield("VRID", 0, "RCID", 0));
  poFeature->SetField("RVER", poRecord->GetIntSubfield("VRID", 0, "RVER", 0));
  poFeature->SetField("RUIN", poRecord->GetIntSubfield("VRID", 0, "RUIN", 0));

  /* -------------------------------------------------------------------- */
  /*      Collect point geometries.                                       */
  /* -------------------------------------------------------------------- */
  if (nRCNM == RCNM_VI || nRCNM == RCNM_VC) {
    double dfX = 0.0, dfY = 0.0, dfZ = 0.0;

    if (poRecord->FindField("SG2D") != NULL) {
      dfX = poRecord->GetIntSubfield("SG2D", 0, "XCOO", 0) / (double)nCOMF;
      dfY = poRecord->GetIntSubfield("SG2D", 0, "YCOO", 0) / (double)nCOMF;
      poFeature->SetGeometryDirectly(new OGRPoint(dfX, dfY));
    }

    else if (poRecord->FindField("SG3D") != NULL) /* presume sounding*/
    {
      int i, nVCount = poRecord->FindField("SG3D")->GetRepeatCount();
      if (nVCount == 1) {
        dfX = poRecord->GetIntSubfield("SG3D", 0, "XCOO", 0) / (double)nCOMF;
        dfY = poRecord->GetIntSubfield("SG3D", 0, "YCOO", 0) / (double)nCOMF;
        dfZ = poRecord->GetIntSubfield("SG3D", 0, "VE3D", 0) / (double)nSOMF;
        poFeature->SetGeometryDirectly(new OGRPoint(dfX, dfY, dfZ));
      } else {
        OGRMultiPoint *poMP = new OGRMultiPoint();

        for (i = 0; i < nVCount; i++) {
          dfX = poRecord->GetIntSubfield("SG3D", 0, "XCOO", i) / (double)nCOMF;
          dfY = poRecord->GetIntSubfield("SG3D", 0, "YCOO", i) / (double)nCOMF;
          dfZ = poRecord->GetIntSubfield("SG3D", 0, "VE3D", i) / (double)nSOMF;

          poMP->addGeometryDirectly(new OGRPoint(dfX, dfY, dfZ));
        }

        poFeature->SetGeometryDirectly(poMP);
      }
    }

  }

  /* -------------------------------------------------------------------- */
  /*      Collect an edge geometry.                                       */
  /* -------------------------------------------------------------------- */
  else if (nRCNM == RCNM_VE && poRecord->FindField("SG2D") != NULL) {
    int i, nVCount = poRecord->FindField("SG2D")->GetRepeatCount();

    if (nVCount == 1) {
      //              poRecord->Dump(stdout);
      OGRLineString *poLine = new OGRLineString();

      int jpt = 0;
      while (poRecord->FindField("SG2D", jpt) != NULL) {
        poLine->setPoint(
            jpt,
            poRecord->GetIntSubfield("SG2D", jpt, "XCOO", 0) / (double)nCOMF,
            poRecord->GetIntSubfield("SG2D", jpt, "YCOO", 0) / (double)nCOMF);
        jpt++;
      }

      poLine->setNumPoints(jpt);

      poFeature->SetGeometryDirectly(poLine);

    } else {
      OGRLineString *poLine = new OGRLineString();

      //        if(nVCount != 1)
      //        {
      //              poRecord->Dump(stdout);
      //              nVCount = poRecord->FindField("SG2D")->GetRepeatCount();
      //        }

      poLine->setNumPoints(nVCount);

      for (i = 0; i < nVCount; i++) {
        poLine->setPoint(
            i, poRecord->GetIntSubfield("SG2D", 0, "XCOO", i) / (double)nCOMF,
            poRecord->GetIntSubfield("SG2D", 0, "YCOO", i) / (double)nCOMF);
      }
      poFeature->SetGeometryDirectly(poLine);
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Special edge fields.                                            */
  /* -------------------------------------------------------------------- */
  DDFField *poVRPT;

  if (nRCNM == RCNM_VE && (poVRPT = poRecord->FindField("VRPT")) != NULL) {
    poFeature->SetField("NAME_RCNM_0", RCNM_VC);
    poFeature->SetField("NAME_RCID_0", ParseName(poVRPT, 0));
    poFeature->SetField("ORNT_0",
                        poRecord->GetIntSubfield("VRPT", 0, "ORNT", 0));
    poFeature->SetField("USAG_0",
                        poRecord->GetIntSubfield("VRPT", 0, "USAG", 0));
    poFeature->SetField("TOPI_0",
                        poRecord->GetIntSubfield("VRPT", 0, "TOPI", 0));
    poFeature->SetField("MASK_0",
                        poRecord->GetIntSubfield("VRPT", 0, "MASK", 0));

    if (poVRPT->GetRepeatCount() > 1) {
      poFeature->SetField("NAME_RCNM_1", RCNM_VC);
      poFeature->SetField("NAME_RCID_1", ParseName(poVRPT, 1));
      poFeature->SetField("ORNT_1",
                          poRecord->GetIntSubfield("VRPT", 0, "ORNT", 1));
      poFeature->SetField("USAG_1",
                          poRecord->GetIntSubfield("VRPT", 0, "USAG", 1));
      poFeature->SetField("TOPI_1",
                          poRecord->GetIntSubfield("VRPT", 0, "TOPI", 1));
      poFeature->SetField("MASK_1",
                          poRecord->GetIntSubfield("VRPT", 0, "MASK", 1));
    } else {
      DDFField *poVRPTEnd = poRecord->FindField("VRPT", 1);

      if (poVRPTEnd) {
        poFeature->SetField("NAME_RCNM_1", RCNM_VC);
        poFeature->SetField("NAME_RCID_1", ParseName(poVRPTEnd, 0));
        poFeature->SetField("ORNT_1",
                            poRecord->GetIntSubfield("VRPT", 1, "ORNT", 0));
        poFeature->SetField("USAG_1",
                            poRecord->GetIntSubfield("VRPT", 1, "USAG", 0));
        poFeature->SetField("TOPI_1",
                            poRecord->GetIntSubfield("VRPT", 1, "TOPI", 0));
        poFeature->SetField("MASK_1",
                            poRecord->GetIntSubfield("VRPT", 1, "MASK", 0));
      } else
        CPLDebug("S57", "Vector End Point not found, edge omitted.");
    }
  }

  return poFeature;
}

/************************************************************************/
/*                             FetchPoint()                             */
/*                                                                      */
/*      Fetch the location and quality of a spatial point object.       */
/************************************************************************/

int S57Reader::FetchPoint(int nRCNM, int nRCID, double *pdfX, double *pdfY,
                          double *pdfZ, int *pnquality)

{
  DDFRecord *poSRecord;

  if (nRCNM == RCNM_VI)
    poSRecord = oVI_Index.FindRecord(nRCID);
  else
    poSRecord = oVC_Index.FindRecord(nRCID);

  if (poSRecord == NULL) return FALSE;

  //      Fetch the quality information
  if (NULL != pnquality) {
    DDFField *f;
    if ((f = poSRecord->FindField("ATTV")) != NULL) {
      DDFSubfieldDefn *sfd = (f->GetFieldDefn())->FindSubfieldDefn("ATVL");
      if (NULL != sfd) {
        int nSuccess;
        char *s = (char *)poSRecord->GetStringSubfield("ATTV", 0, "ATVL", 0,
                                                       &nSuccess);
        if (nSuccess) {
          *pnquality = atoi(s);
        }
      }
    }
  }

  double dfX = 0.0, dfY = 0.0, dfZ = 0.0;

  if (poSRecord->FindField("SG2D") != NULL) {
    dfX = poSRecord->GetIntSubfield("SG2D", 0, "XCOO", 0) / (double)nCOMF;
    dfY = poSRecord->GetIntSubfield("SG2D", 0, "YCOO", 0) / (double)nCOMF;
  } else if (poSRecord->FindField("SG3D") != NULL) {
    dfX = poSRecord->GetIntSubfield("SG3D", 0, "XCOO", 0) / (double)nCOMF;
    dfY = poSRecord->GetIntSubfield("SG3D", 0, "YCOO", 0) / (double)nCOMF;
    dfZ = poSRecord->GetIntSubfield("SG3D", 0, "VE3D", 0) / (double)nSOMF;
  } else
    return FALSE;

  if (pdfX != NULL) *pdfX = dfX;
  if (pdfY != NULL) *pdfY = dfY;
  if (pdfZ != NULL) *pdfZ = dfZ;

  return TRUE;
}

/************************************************************************/
/*                       AssemblePointGeometry()                        */
/************************************************************************/

void S57Reader::AssemblePointGeometry(DDFRecord *poFRecord,
                                      OGRFeature *poFeature)

{
  DDFField *poFSPT;
  int nRCNM, nRCID;

  /* -------------------------------------------------------------------- */
  /*      Feature the spatial record containing the point.                */
  /* -------------------------------------------------------------------- */
  poFSPT = poFRecord->FindField("FSPT");
  if (poFSPT == NULL) return;

  if (poFSPT->GetRepeatCount() != 1) {
#ifdef DEBUG
    fprintf(stderr, "Point features with other than one spatial linkage.\n");
    poFRecord->Dump(stderr);
#endif
    CPLDebug("S57",
             "Point feature encountered with other than one spatial linkage.");
  }

  nRCID = ParseName(poFSPT, 0, &nRCNM);

  double dfX = 0.0, dfY = 0.0, dfZ = 0.0;

  int nquality = 10;  // default is "precisely known"
  if (!FetchPoint(nRCNM, nRCID, &dfX, &dfY, &dfZ, &nquality)) {
    CPLAssert(FALSE);
    return;
  }

  poFeature->SetGeometryDirectly(new OGRPoint(dfX, dfY, dfZ));
  OGRPoint *pp = (OGRPoint *)poFeature->GetGeometryRef();
  pp->setnQual(nquality);
}

/************************************************************************/
/*                      AssembleSoundingGeometry()                      */
/************************************************************************/

void S57Reader::AssembleSoundingGeometry(DDFRecord *poFRecord,
                                         OGRFeature *poFeature)

{
  DDFField *poFSPT;
  int nRCNM, nRCID;
  DDFRecord *poSRecord;

  /* -------------------------------------------------------------------- */
  /*      Feature the spatial record containing the point.                */
  /* -------------------------------------------------------------------- */
  poFSPT = poFRecord->FindField("FSPT");
  if (poFSPT == NULL) return;

  CPLAssert(poFSPT->GetRepeatCount() == 1);

  nRCID = ParseName(poFSPT, 0, &nRCNM);

  if (nRCNM == RCNM_VI)
    poSRecord = oVI_Index.FindRecord(nRCID);
  else
    poSRecord = oVC_Index.FindRecord(nRCID);

  if (poSRecord == NULL) return;

  /* -------------------------------------------------------------------- */
  /*      Extract vertices.                                               */
  /* -------------------------------------------------------------------- */
  OGRMultiPoint *poMP = new OGRMultiPoint();
  DDFField *poField;
  int nPointCount, i, nBytesLeft;
  DDFSubfieldDefn *poXCOO, *poYCOO, *poVE3D;
  const char *pachData;

  poField = poSRecord->FindField("SG2D");
  if (poField == NULL) poField = poSRecord->FindField("SG3D");
  if (poField == NULL) return;

  poXCOO = poField->GetFieldDefn()->FindSubfieldDefn("XCOO");
  poYCOO = poField->GetFieldDefn()->FindSubfieldDefn("YCOO");
  poVE3D = poField->GetFieldDefn()->FindSubfieldDefn("VE3D");

  nPointCount = poField->GetRepeatCount();

  pachData = poField->GetData();
  nBytesLeft = poField->GetDataSize();

  for (i = 0; i < nPointCount; i++) {
    double dfX, dfY, dfZ = 0.0;
    int nBytesConsumed;

    dfY = poYCOO->ExtractIntData(pachData, nBytesLeft, &nBytesConsumed) /
          (double)nCOMF;
    nBytesLeft -= nBytesConsumed;
    pachData += nBytesConsumed;

    dfX = poXCOO->ExtractIntData(pachData, nBytesLeft, &nBytesConsumed) /
          (double)nCOMF;
    nBytesLeft -= nBytesConsumed;
    pachData += nBytesConsumed;

    if (poVE3D != NULL) {
      dfZ = poYCOO->ExtractIntData(pachData, nBytesLeft, &nBytesConsumed) /
            (double)nSOMF;
      nBytesLeft -= nBytesConsumed;
      pachData += nBytesConsumed;
    }

    poMP->addGeometryDirectly(new OGRPoint(dfX, dfY, dfZ));
  }

  poFeature->SetGeometryDirectly(poMP);
}

/************************************************************************/
/*                        AssembleLineGeometry()                        */
/************************************************************************/

void S57Reader::AssembleLineGeometry(DDFRecord *poFRecord,
                                     OGRFeature *poFeature)

{
  DDFField *poFSPT;
  int nEdgeCount;
  OGRLineString *poLine = new OGRLineString();

  /* -------------------------------------------------------------------- */
  /*      Find the FSPT field.                                            */
  /* -------------------------------------------------------------------- */
  poFSPT = poFRecord->FindField("FSPT");
  if (poFSPT == NULL) return;

  nEdgeCount = poFSPT->GetRepeatCount();

  /* ==================================================================== */
  /*      Loop collecting edges.                                          */
  /* ==================================================================== */
  for (int iEdge = 0; iEdge < nEdgeCount; iEdge++) {
    DDFRecord *poSRecord;
    int nRCID;

    /* -------------------------------------------------------------------- */
    /*      Find the spatial record for this edge.                          */
    /* -------------------------------------------------------------------- */
    nRCID = ParseName(poFSPT, iEdge);

    poSRecord = oVE_Index.FindRecord(nRCID);
    if (poSRecord == NULL) {
      CPLError(CE_Warning, CPLE_AppDefined,
               "Couldn't find spatial record %d.\n"
               "Feature OBJL=%s, RCID=%d may have corrupt or"
               "missing geometry.",
               nRCID, poFeature->GetDefnRef()->GetName(),
               poFRecord->GetIntSubfield("FRID", 0, "RCID", 0));
      continue;
    }

    /* -------------------------------------------------------------------- */
    /*      Establish the number of vertices, and whether we need to        */
    /*      reverse or not.                                                 */
    /* -------------------------------------------------------------------- */
    int nVCount;
    int nStart, nEnd, nInc;
    DDFField *poSG2D = poSRecord->FindField("SG2D");
    DDFSubfieldDefn *poXCOO = NULL, *poYCOO = NULL;

    if (poSG2D != NULL) {
      poXCOO = poSG2D->GetFieldDefn()->FindSubfieldDefn("XCOO");
      poYCOO = poSG2D->GetFieldDefn()->FindSubfieldDefn("YCOO");

      nVCount = poSG2D->GetRepeatCount();
    } else
      nVCount = 0;

    DDFField *poField = poSRecord->FindField("VRPT");
    int nVC_RCID0 = 0;
    int nVC_RCID1 = 0;
    int nVC_RCIDStart, nVC_RCIDEnd;

    if (poField == NULL) {
      CPLError(CE_Warning, CPLE_AppDefined,
               "Couldn't find field VRPT in spatial record %d.\n"
               "Feature OBJL=%s, RCID=%d may have corrupt or"
               "missing geometry.",
               nRCID, poFeature->GetDefnRef()->GetName(),
               poFRecord->GetIntSubfield("FRID", 0, "RCID", 0));
      continue;
    }

    if (poField->GetRepeatCount() > 1) {
      nVC_RCID0 = ParseName(poField, 0);
      nVC_RCID1 = ParseName(poField, 1);
    } else {
      nVC_RCID0 = ParseName(poField, 0);
      DDFField *poFieldEnd = poSRecord->FindField("VRPT", 1);
      if (poFieldEnd) nVC_RCID1 = ParseName(poFieldEnd, 0);
    }

    if (poFRecord->GetIntSubfield("FSPT", 0, "ORNT", iEdge) == 2) {
      nStart = nVCount - 1;
      nEnd = 0;
      nInc = -1;
      nVC_RCIDStart = nVC_RCID1;  // reversed
      nVC_RCIDEnd = nVC_RCID0;
    } else {
      nStart = 0;
      nEnd = nVCount - 1;
      nInc = 1;
      nVC_RCIDStart = nVC_RCID0;
      nVC_RCIDEnd = nVC_RCID1;
    }

    /* -------------------------------------------------------------------- */
    /*      Add the start node, if this is the first edge.                  */
    /* -------------------------------------------------------------------- */
    if (iEdge == 0) {
      int nVC_RCID = 0;
      double dfX, dfY;
      /*
                  if(poField)
                  {
                        if( nInc == 1 )
                              nVC_RCID = ParseName( poField, 0 );
                        else
                              nVC_RCID = ParseName( poField, 1 );
                  }
      */

      if (FetchPoint(RCNM_VC, nVC_RCIDStart, &dfX, &dfY))
        poLine->addPoint(dfX, dfY);
      else
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Unable to fetch start node RCID%d.\n"
                 "Feature OBJL=%s, RCID=%d may have corrupt or"
                 " missing geometry.",
                 nVC_RCID, poFeature->GetDefnRef()->GetName(),
                 poFRecord->GetIntSubfield("FRID", 0, "RCID", 0));
    }

    /* -------------------------------------------------------------------- */
    /*      Collect the vertices.                                           */
    /* -------------------------------------------------------------------- */
    int nVBase = poLine->getNumPoints();

    if (nVCount == 1) {
      int jpt = 0;
      while (poSRecord->FindField("SG2D", jpt)) {
        poLine->addPoint(
            poSRecord->GetIntSubfield("SG2D", jpt, "XCOO", 0) / (double)nCOMF,
            poSRecord->GetIntSubfield("SG2D", jpt, "YCOO", 0) / (double)nCOMF);
        jpt++;
      }
    } else {
      poLine->setNumPoints(nVCount + nVBase);

      for (int i = nStart; i != nEnd + nInc; i += nInc) {
        double dfX, dfY;
        const char *pachData;
        int nBytesRemaining;

        pachData = poSG2D->GetSubfieldData(poXCOO, &nBytesRemaining, i);

        dfX = poXCOO->ExtractIntData(pachData, nBytesRemaining, NULL) /
              (double)nCOMF;

        pachData = poSG2D->GetSubfieldData(poYCOO, &nBytesRemaining, i);

        dfY = poXCOO->ExtractIntData(pachData, nBytesRemaining, NULL) /
              (double)nCOMF;

        poLine->setPoint(nVBase++, dfX, dfY);
      }
    }

    /* -------------------------------------------------------------------- */
    /*      Add the end node.                                               */
    /* -------------------------------------------------------------------- */
    {
      int nVC_RCID = 0;
      double dfX, dfY;
      /*
                  if(poField)
                  {
                        if( nInc == 1 )
                              nVC_RCID = ParseName( poField, 1 );
                        else
                              nVC_RCID = ParseName( poField, 0 );
                  }
      */
      if (FetchPoint(RCNM_VC, nVC_RCIDEnd, &dfX, &dfY))
        poLine->addPoint(dfX, dfY);
      else
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Unable to fetch end node RCID=%d.\n"
                 "Feature OBJL=%s, RCID=%d may have corrupt or"
                 " missing geometry.",
                 nVC_RCID, poFeature->GetDefnRef()->GetName(),
                 poFRecord->GetIntSubfield("FRID", 0, "RCID", 0));
    }
  }

  if (poLine->getNumPoints() >= 2)
    poFeature->SetGeometryDirectly(poLine);
  else
    delete poLine;
}

/************************************************************************/
/*                        AssembleAreaGeometry()                        */
/************************************************************************/

void S57Reader::AssembleAreaGeometry(DDFRecord *poFRecord,
                                     OGRFeature *poFeature)

{
  DDFField *poFSPT;
  OGRGeometryCollection *poLines = new OGRGeometryCollection();
  //    poFRecord->Dump(stdout);
  /* -------------------------------------------------------------------- */
  /*      Find the FSPT fields.                                           */
  /* -------------------------------------------------------------------- */
  for (int iFSPT = 0; (poFSPT = poFRecord->FindField("FSPT", iFSPT)) != NULL;
       iFSPT++) {
    int nEdgeCount;

    nEdgeCount = poFSPT->GetRepeatCount();

    /* ==================================================================== */
    /*      Loop collecting edges.                                          */
    /* ==================================================================== */
    for (int iEdge = 0; iEdge < nEdgeCount; iEdge++) {
      DDFRecord *poSRecord;
      int nRCID;

      /* -------------------------------------------------------------------- */
      /*      Find the spatial record for this edge.                          */
      /* -------------------------------------------------------------------- */
      nRCID = ParseName(poFSPT, iEdge);

      poSRecord = oVE_Index.FindRecord(nRCID);
      if (poSRecord == NULL) {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Couldn't find spatial record %d.", nRCID);
        continue;
      }

      /* -------------------------------------------------------------------- */
      /*      Establish the number of vertices, and whether we need to        */
      /*      reverse or not.                                                 */
      /* -------------------------------------------------------------------- */
      //            poSRecord->Dump(stdout);

      OGRLineString *poLine = new OGRLineString();

      int nVCount;
      int nStart, nEnd, nInc;
      DDFField *poSG2D = poSRecord->FindField("SG2D");
      DDFSubfieldDefn *poXCOO = NULL, *poYCOO = NULL;

      if (poSG2D != NULL) {
        poXCOO = poSG2D->GetFieldDefn()->FindSubfieldDefn("XCOO");
        poYCOO = poSG2D->GetFieldDefn()->FindSubfieldDefn("YCOO");

        nVCount = poSG2D->GetRepeatCount();
      } else
        nVCount = 0;

      DDFField *poField = poSRecord->FindField("VRPT");
      int nVC_RCID0 = 0;
      int nVC_RCID1 = 0;
      int nVC_RCIDStart, nVC_RCIDEnd;

      if (poField && poField->GetRepeatCount() > 1) {
        nVC_RCID0 = ParseName(poField, 0);
        nVC_RCID1 = ParseName(poField, 1);
      } else {
        if (poField) nVC_RCID0 = ParseName(poField, 0);
        DDFField *poFieldEnd = poSRecord->FindField("VRPT", 1);
        if (poFieldEnd) nVC_RCID1 = ParseName(poFieldEnd, 0);
      }

      if (poFRecord->GetIntSubfield("FSPT", 0, "ORNT", iEdge) == 2) {
        nStart = nVCount - 1;
        nEnd = 0;
        nInc = -1;
        nVC_RCIDStart = nVC_RCID1;  // reversed
        nVC_RCIDEnd = nVC_RCID0;
      } else {
        nStart = 0;
        nEnd = nVCount - 1;
        nInc = 1;
        nVC_RCIDStart = nVC_RCID0;
        nVC_RCIDEnd = nVC_RCID1;
      }

      /* -------------------------------------------------------------------- */
      /*      Add the start node.                                             */
      /* -------------------------------------------------------------------- */
      {
        double dfX, dfY;

        if (FetchPoint(RCNM_VC, nVC_RCIDStart, &dfX, &dfY))
          poLine->addPoint(dfX, dfY);
      }

      /* -------------------------------------------------------------------- */
      /*      Collect the vertices.                                           */
      /* -------------------------------------------------------------------- */
      int nVBase = poLine->getNumPoints();

      if (nVCount == 1) {
        int jpt = 0;
        while (poSRecord->FindField("SG2D", jpt)) {
          poLine->addPoint(
              poSRecord->GetIntSubfield("SG2D", jpt, "XCOO", 0) / (double)nCOMF,
              poSRecord->GetIntSubfield("SG2D", jpt, "YCOO", 0) /
                  (double)nCOMF);
          jpt++;
        }
      } else {
        poLine->setNumPoints(nVCount + nVBase);

        for (int i = nStart; i != nEnd + nInc; i += nInc) {
          double dfX, dfY;
          const char *pachData;
          int nBytesRemaining;

          pachData = poSG2D->GetSubfieldData(poXCOO, &nBytesRemaining, i);

          dfX = poXCOO->ExtractIntData(pachData, nBytesRemaining, NULL) /
                (double)nCOMF;

          pachData = poSG2D->GetSubfieldData(poYCOO, &nBytesRemaining, i);

          dfY = poXCOO->ExtractIntData(pachData, nBytesRemaining, NULL) /
                (double)nCOMF;

          poLine->setPoint(nVBase++, dfX, dfY);
        }
      }
      /* -------------------------------------------------------------------- */
      /*      Add the end node.                                               */
      /* -------------------------------------------------------------------- */
      {
        double dfX, dfY;

        if (FetchPoint(RCNM_VC, nVC_RCIDEnd, &dfX, &dfY))
          poLine->addPoint(dfX, dfY);
      }

      poLines->addGeometryDirectly(poLine);
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Build lines into a polygon.                                     */
  /* -------------------------------------------------------------------- */
  OGRPolygon *poPolygon;
  OGRErr eErr;

  poPolygon = (OGRPolygon *)OGRBuildPolygonFromEdges((OGRGeometryH)poLines,
                                                     TRUE, FALSE, 0.0, &eErr);
  if (eErr != OGRERR_NONE) {
    CPLError(CE_Warning, CPLE_AppDefined,
             "Polygon assembly has failed for feature FIDN=%d,FIDS=%d.\n"
             "Geometry may be missing or incomplete.",
             poFeature->GetFieldAsInteger("FIDN"),
             poFeature->GetFieldAsInteger("FIDS"));
  }

  delete poLines;

  if (poPolygon != NULL) poFeature->SetGeometryDirectly(poPolygon);
}

/************************************************************************/
/*                             FindFDefn()                              */
/*                                                                      */
/*      Find the OGRFeatureDefn corresponding to the passed feature     */
/*      record.  It will search based on geometry class, or object      */
/*      class depending on the bClassBased setting.                     */
/************************************************************************/

OGRFeatureDefn *S57Reader::FindFDefn(DDFRecord *poRecord)

{
  if (poRegistrar != NULL) {
    int nOBJL = poRecord->GetIntSubfield("FRID", 0, "OBJL", 0);

    if (!poRegistrar->SelectClass(nOBJL)) {
      for (int i = 0; i < nFDefnCount; i++) {
        if (EQUAL(papoFDefnList[i]->GetName(), "Generic"))
          return papoFDefnList[i];
      }
      return NULL;
    }

    for (int i = 0; i < nFDefnCount; i++) {
      if (EQUAL(papoFDefnList[i]->GetName(), poRegistrar->GetAcronym()))
        return papoFDefnList[i];
    }

    return NULL;
  } else {
    int nPRIM = poRecord->GetIntSubfield("FRID", 0, "PRIM", 0);
    OGRwkbGeometryType eGType;

    if (nPRIM == PRIM_P)
      eGType = wkbPoint;
    else if (nPRIM == PRIM_L)
      eGType = wkbLineString;
    else if (nPRIM == PRIM_A)
      eGType = wkbPolygon;
    else
      eGType = wkbNone;

    for (int i = 0; i < nFDefnCount; i++) {
      if (papoFDefnList[i]->GetGeomType() == eGType) return papoFDefnList[i];
    }
  }

  return NULL;
}

/************************************************************************/
/*                             ParseName()                              */
/*                                                                      */
/*      Pull the RCNM and RCID values from a NAME field.  The RCID      */
/*      is returned and the RCNM can be gotten via the pnRCNM argument. */
/************************************************************************/

int S57Reader::ParseName(DDFField *poField, int nIndex, int *pnRCNM)

{
  unsigned char *pabyData;

  pabyData = (unsigned char *)poField->GetSubfieldData(
      poField->GetFieldDefn()->FindSubfieldDefn("NAME"), NULL, nIndex);

  if (pnRCNM != NULL) *pnRCNM = pabyData[0];

  return pabyData[1] + pabyData[2] * 256 + pabyData[3] * 256 * 256 +
         pabyData[4] * 256 * 256 * 256;
}

/************************************************************************/
/*                           AddFeatureDefn()                           */
/************************************************************************/

void S57Reader::AddFeatureDefn(OGRFeatureDefn *poFDefn)

{
  nFDefnCount++;
  papoFDefnList = (OGRFeatureDefn **)CPLRealloc(
      papoFDefnList, sizeof(OGRFeatureDefn *) * nFDefnCount);

  papoFDefnList[nFDefnCount - 1] = poFDefn;
}

/************************************************************************/
/*                          CollectClassList()                          */
/*                                                                      */
/*      Establish the list of classes (unique OBJL values) that         */
/*      occur in this dataset.                                          */
/************************************************************************/

int S57Reader::CollectClassList(int *panClassCount, int nMaxClass)

{
  int bSuccess = TRUE;

  if (!bFileIngested) Ingest();

  for (int iFEIndex = 0; iFEIndex < oFE_Index.GetCount(); iFEIndex++) {
    DDFRecord *poRecord = oFE_Index.GetByIndex(iFEIndex);
    int nOBJL = poRecord->GetIntSubfield("FRID", 0, "OBJL", 0);

    if (nOBJL >= nMaxClass)
      bSuccess = FALSE;
    else
      panClassCount[nOBJL]++;
  }

  return bSuccess;
}

/************************************************************************/
/*                         ApplyRecordUpdate()                          */
/*                                                                      */
/*      Update one target record based on an S-57 update record         */
/*      (RUIN=3).                                                       */
/************************************************************************/

int S57Reader::ApplyRecordUpdate(DDFRecord *poTarget, DDFRecord *poUpdate)

{
  const char *pszKey = poUpdate->GetField(1)->GetFieldDefn()->GetName();

  /* -------------------------------------------------------------------- */
  /*      Validate versioning.                                            */
  /* -------------------------------------------------------------------- */
  if (poTarget->GetIntSubfield(pszKey, 0, "RVER", 0) + 1 !=
      poUpdate->GetIntSubfield(pszKey, 0, "RVER", 0)) {
    CPLError(CE_Warning, CPLE_AppDefined,
             "On RecordUpdate, mismatched RVER value for "
             "RCNM=%d,RCID=%d...update RVER is %d, target RVER is %d.",
             poTarget->GetIntSubfield(pszKey, 0, "RCNM", 0),
             poTarget->GetIntSubfield(pszKey, 0, "RCID", 0),
             poUpdate->GetIntSubfield(pszKey, 0, "RVER", 0),
             poTarget->GetIntSubfield(pszKey, 0, "RVER", 0));

    CPLAssert(FALSE);
    return FALSE;
  }

  //    More checks for validity
  if (poUpdate->FindField("FRID") != NULL) {
    /*
              int up_FIDN = poUpdate->GetIntSubfield( "FOID", 0, "FIDN", 0 );
              int up_FIDS = poUpdate->GetIntSubfield( "FOID", 0, "FIDS", 0 );
              int tar_FIDN = poTarget->GetIntSubfield( "FOID", 0, "FIDN", 0 );
              int tar_FIDS = poTarget->GetIntSubfield( "FOID", 0, "FIDS", 0 );
              if((up_FIDN != tar_FIDN) || (up_FIDS != tar_FIDS))
              {
              CPLError( CE_Warning, CPLE_AppDefined,
              "On RecordUpdate, mismatched FIDN/FIDS.... target FIDN=%d, target
       FIDS=%d   update FIDN=%d, update FIDS=%d.", tar_FIDN, tar_FIDS, up_FIDN,
       up_FIDS);

              return FALSE;
        }
    */
    int up_PRIM = poUpdate->GetIntSubfield("FRID", 0, "PRIM", 0);
    int tar_PRIM = poTarget->GetIntSubfield("FRID", 0, "PRIM", 0);
    if (up_PRIM != tar_PRIM) {
      CPLError(
          CE_Warning, CPLE_AppDefined,
          "On RecordUpdate, mismatched PRIM.... target PRIM=%d, update PRIM=%d",
          tar_PRIM, up_PRIM);
      return FALSE;
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Update the target version.                                      */
  /* -------------------------------------------------------------------- */
  unsigned int *pnRVER;
  DDFField *poKey = poTarget->FindField(pszKey);
  DDFSubfieldDefn *poRVER_SFD;

  if (poKey == NULL) {
    CPLAssert(FALSE);
    return FALSE;
  }

  poRVER_SFD = poKey->GetFieldDefn()->FindSubfieldDefn("RVER");
  if (poRVER_SFD == NULL) return FALSE;

  pnRVER = (unsigned int *)poKey->GetSubfieldData(poRVER_SFD, NULL, 0);

  *pnRVER += 1;

  /* -------------------------------------------------------------------- */
  /*      Check for, and apply record record to spatial record pointer    */
  /*      updates.                                                        */
  /* -------------------------------------------------------------------- */
  if (poUpdate->FindField("FSPC") != NULL) {
    int nFSUI = poUpdate->GetIntSubfield("FSPC", 0, "FSUI", 0);
    int nFSIX = poUpdate->GetIntSubfield("FSPC", 0, "FSIX", 0);
    int nNSPT = poUpdate->GetIntSubfield("FSPC", 0, "NSPT", 0);
    DDFField *poSrcFSPT = poUpdate->FindField("FSPT");
    DDFField *poDstFSPT = poTarget->FindField("FSPT");
    int nPtrSize;

    if ((poSrcFSPT == NULL && nFSUI != 2) || poDstFSPT == NULL) {
      CPLAssert(FALSE);
      return FALSE;
    }

    nPtrSize = poDstFSPT->GetFieldDefn()->GetFixedWidth();

    if (nFSUI == 1) /* INSERT */
    {
      char *pachInsertion;
      int nInsertionBytes = nPtrSize * nNSPT;

      pachInsertion = (char *)CPLMalloc(nInsertionBytes + nPtrSize);
      memcpy(pachInsertion, poSrcFSPT->GetData(), nInsertionBytes);

      /*
      ** If we are inserting before an instance that already
      ** exists, we must add it to the end of the data being
      ** inserted.
      */
      if (nFSIX <= poDstFSPT->GetRepeatCount()) {
        memcpy(pachInsertion + nInsertionBytes,
               poDstFSPT->GetData() + nPtrSize * (nFSIX - 1), nPtrSize);
        nInsertionBytes += nPtrSize;
      }

      poTarget->SetFieldRaw(poDstFSPT, nFSIX - 1, pachInsertion,
                            nInsertionBytes);
      CPLFree(pachInsertion);
    } else if (nFSUI == 2) /* DELETE */
    {
      /* Wipe each deleted coordinate */
      for (int i = nNSPT - 1; i >= 0; i--) {
        poTarget->SetFieldRaw(poDstFSPT, i + nFSIX - 1, NULL, 0);
      }
    } else if (nFSUI == 3) /* MODIFY */
    {
      /* copy over each ptr */
      for (int i = 0; i < nNSPT; i++) {
        const char *pachRawData;

        pachRawData = poSrcFSPT->GetData() + nPtrSize * i;

        poTarget->SetFieldRaw(poDstFSPT, i + nFSIX - 1, pachRawData, nPtrSize);
      }
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Check for, and apply vector record to vector record pointer     */
  /*      updates.                                                        */
  /* -------------------------------------------------------------------- */
  if (poUpdate->FindField("VRPC") != NULL) {
    int nVPUI = poUpdate->GetIntSubfield("VRPC", 0, "VPUI", 0);
    int nVPIX = poUpdate->GetIntSubfield("VRPC", 0, "VPIX", 0);
    int nNVPT = poUpdate->GetIntSubfield("VRPC", 0, "NVPT", 0);
    DDFField *poSrcVRPT = poUpdate->FindField("VRPT");
    DDFField *poDstVRPT = poTarget->FindField("VRPT");
    int nPtrSize;

    if ((poSrcVRPT == NULL && nVPUI != 2) || poDstVRPT == NULL) {
      CPLAssert(FALSE);
      return FALSE;
    }

    nPtrSize = poDstVRPT->GetFieldDefn()->GetFixedWidth();

    if (nVPUI == 1) /* INSERT */
    {
      char *pachInsertion;
      int nInsertionBytes = nPtrSize * nNVPT;

      pachInsertion = (char *)CPLMalloc(nInsertionBytes + nPtrSize);
      memcpy(pachInsertion, poSrcVRPT->GetData(), nInsertionBytes);

      /*
      ** If we are inserting before an instance that already
      ** exists, we must add it to the end of the data being
      ** inserted.
      */
      if (nVPIX <= poDstVRPT->GetRepeatCount()) {
        memcpy(pachInsertion + nInsertionBytes,
               poDstVRPT->GetData() + nPtrSize * (nVPIX - 1), nPtrSize);
        nInsertionBytes += nPtrSize;
      }

      poTarget->SetFieldRaw(poDstVRPT, nVPIX - 1, pachInsertion,
                            nInsertionBytes);
      CPLFree(pachInsertion);
    } else if (nVPUI == 2) /* DELETE */
    {
      /* Wipe each deleted coordinate */
      for (int i = nNVPT - 1; i >= 0; i--) {
        poTarget->SetFieldRaw(poDstVRPT, i + nVPIX - 1, NULL, 0);
      }
    } else if (nVPUI == 3) /* MODIFY */
    {
      /* copy over each ptr */
      for (int i = 0; i < nNVPT; i++) {
        const char *pachRawData;

        pachRawData = poSrcVRPT->GetData() + nPtrSize * i;

        poTarget->SetFieldRaw(poDstVRPT, i + nVPIX - 1, pachRawData, nPtrSize);
      }
    }
  }

  /* -------------------------------------------------------------------- */
  /*      Check for, and apply record update to coordinates.              */
  /* -------------------------------------------------------------------- */
  if (poUpdate->FindField("SGCC") != NULL) {
    int nCCUI = poUpdate->GetIntSubfield("SGCC", 0, "CCUI", 0);
    int nCCIX = poUpdate->GetIntSubfield("SGCC", 0, "CCIX", 0);
    int nCCNC = poUpdate->GetIntSubfield("SGCC", 0, "CCNC", 0);
    DDFField *poSrcSG2D = poUpdate->FindField("SG2D");
    DDFField *poDstSG2D = poTarget->FindField("SG2D");
    int nCoordSize;

    /* If we don't have SG2D, check for SG3D */
    if (poDstSG2D == NULL) {
      poDstSG2D = poTarget->FindField("SG3D");
      if (poDstSG2D != NULL) {
        poSrcSG2D = poUpdate->FindField("SG3D");
      }
    }

    if (poDstSG2D == NULL && nCCUI == 2) {
      // Trying to delete a coordinate that does not exist...
      // Theoretically, this is an error.
      //  But we have seen this from some HOs, (China/HongKong) and seems to be
      //  OK to ignore
      return TRUE;
    }

    if ((poSrcSG2D == NULL && nCCUI != 2) ||
        (poDstSG2D == NULL && nCCUI != 1)) {
      // CPLAssert( FALSE );
      return FALSE;
    }

    if (poDstSG2D == NULL) {
      poTarget->AddField(poTarget->GetModule()->FindFieldDefn("SG2D"));
      poDstSG2D = poTarget->FindField("SG2D");
      if (poDstSG2D == NULL) {
        // CPLAssert( FALSE );
        return FALSE;
      }

      // Delete null default data that was created
      poTarget->SetFieldRaw(poDstSG2D, 0, NULL, 0);
    }

    nCoordSize = poDstSG2D->GetFieldDefn()->GetFixedWidth();

    if (nCCUI == 1) /* INSERT */
    {
      char *pachInsertion;
      int nInsertionBytes = nCoordSize * nCCNC;

      pachInsertion = (char *)CPLMalloc(nInsertionBytes + nCoordSize);
      memcpy(pachInsertion, poSrcSG2D->GetData(), nInsertionBytes);

      /*
      ** If we are inserting before an instance that already
      ** exists, we must add it to the end of the data being
      ** inserted.
      */
      if (nCCIX <= poDstSG2D->GetRepeatCount()) {
        memcpy(pachInsertion + nInsertionBytes,
               poDstSG2D->GetData() + nCoordSize * (nCCIX - 1), nCoordSize);
        nInsertionBytes += nCoordSize;
      }

      poTarget->SetFieldRaw(poDstSG2D, nCCIX - 1, pachInsertion,
                            nInsertionBytes);
      CPLFree(pachInsertion);

    } else if (nCCUI == 2) /* DELETE */
    {
      /* Wipe each deleted coordinate */
      for (int i = nCCNC - 1; i >= 0; i--) {
        poTarget->SetFieldRaw(poDstSG2D, i + nCCIX - 1, NULL, 0);
      }
    } else if (nCCUI == 3) /* MODIFY */
    {
      /* copy over each ptr */
      for (int i = 0; i < nCCNC; i++) {
        const char *pachRawData;

        pachRawData = poSrcSG2D->GetData() + nCoordSize * i;

        poTarget->SetFieldRaw(poDstSG2D, i + nCCIX - 1, pachRawData,
                              nCoordSize);
      }
    }
  }

  /* -------------------------------------------------------------------- */
  /*      We don't currently handle FFPC (feature to feature linkage)     */
  /*      issues, but we will at least report them when debugging.        */
  /* -------------------------------------------------------------------- */
  /*
      if( poUpdate->FindField( "FFPC" ) != NULL )
      {
          CPLDebug( "S57", "Found FFPC, but not applying it." );
      }
  */
  /* -------------------------------------------------------------------- */
  /*      Check for and apply changes to attribute lists.                 */
  /* -------------------------------------------------------------------- */
  if (poUpdate->FindField("ATTF") != NULL) {
    bool b_newField = false;
    DDFSubfieldDefn *poSrcATVLDefn;
    DDFField *poSrcATTF = poUpdate->FindField("ATTF");
    DDFField *poDstATTF = poTarget->FindField("ATTF");

    if (NULL == poDstATTF) {
      //  This probably means that the update applies to an attribute that
      //  doesn't (yet) exist To fix, we need to add an attribute, then update
      //  it.

      DDFFieldDefn *poATTF = poTarget->GetModule()->FindFieldDefn("ATTF");
      poTarget->AddField(poATTF);
      poDstATTF = poTarget->FindField("ATTF");
      b_newField = true;
    }

    int nRepeatCount = poSrcATTF->GetRepeatCount();

    poSrcATVLDefn = poSrcATTF->GetFieldDefn()->FindSubfieldDefn("ATVL");

    for (int iAtt = 0; iAtt < nRepeatCount; iAtt++) {
      int nATTL = poUpdate->GetIntSubfield("ATTF", 0, "ATTL", iAtt);
      int iTAtt, nDataBytes;
      const char *pszRawData;

      for (iTAtt = poDstATTF->GetRepeatCount() - 1; iTAtt >= 0; iTAtt--) {
        if (poTarget->GetIntSubfield("ATTF", 0, "ATTL", iTAtt) == nATTL) break;
      }
      if (iTAtt == -1) iTAtt = poDstATTF->GetRepeatCount();

      //  If we just added a new field above, then the first attribute will be
      //  0.
      //   We should replace this one
      if (b_newField) {
        if (poTarget->GetIntSubfield("ATTF", 0, "ATTL", 0) == 0) {
          iTAtt = 0;
          b_newField = false;
        }
      }

      pszRawData = poSrcATTF->GetInstanceData(iAtt, &nDataBytes);
      poTarget->SetFieldRaw(poDstATTF, iTAtt, pszRawData, nDataBytes);
    }
  }

  if (poUpdate->FindField("NATF") != NULL) {
    bool b_newField = false;
    DDFSubfieldDefn *poSrcATVLDefn;
    DDFField *poSrcATTF = poUpdate->FindField("NATF");
    DDFField *poDstATTF = poTarget->FindField("NATF");

    //        int up_FIDN = poUpdate->GetIntSubfield( "FOID", 0, "FIDN", 0 );
    //        if(up_FIDN == 1103712044 /*1225530334*/){
    //            poTarget->Dump(stdout);
    //        }

    if (NULL == poDstATTF) {
      //  This probably means that the update applies to an attribute that
      //  doesn't (yet) exist To fix, we need to add an attribute, then update
      //  it.

      DDFFieldDefn *poNATF = poTarget->GetModule()->FindFieldDefn("NATF");
      poTarget->AddField(poNATF);
      poDstATTF = poTarget->FindField("NATF");
      b_newField = true;

      //            poTarget->Dump(stdout);

      //            CPLDebug( "S57","Could not find target ATTF field for
      //            attribute update");
      //           return FALSE;
    }

    int nRepeatCount = poSrcATTF->GetRepeatCount();

    poSrcATVLDefn = poSrcATTF->GetFieldDefn()->FindSubfieldDefn("ATVL");

    for (int iAtt = 0; iAtt < nRepeatCount; iAtt++) {
      int nATTL = poUpdate->GetIntSubfield("NATF", 0, "ATTL", iAtt);
      int iTAtt, nDataBytes;
      const char *pszRawData;

      for (iTAtt = poDstATTF->GetRepeatCount() - 1; iTAtt >= 0; iTAtt--) {
        if (poTarget->GetIntSubfield("NATF", 0, "ATTL", iTAtt) == nATTL) break;
      }
      if (iTAtt == -1) iTAtt = poDstATTF->GetRepeatCount();

      //  If we just added a new field above, then the first attribute will be
      //  0.
      //   We should replace this one
      if (b_newField) {
        if (poTarget->GetIntSubfield("NATF", 0, "ATTL", 0) == 0) {
          iTAtt = 0;
          b_newField = false;
        }
      }

      pszRawData = poSrcATTF->GetInstanceData(iAtt, &nDataBytes);

      //            poTarget->Dump(stdout);
      poTarget->SetFieldRaw(poDstATTF, iTAtt, pszRawData, nDataBytes);  /// dsr
      //            poTarget->Dump(stdout);
    }
  }

  return TRUE;
}

/************************************************************************/
/*                            ApplyUpdates()                            */
/*                                                                      */
/*      Read records from an update file, and apply them to the         */
/*      currently loaded index of features.                             */
/************************************************************************/

int S57Reader::ApplyUpdates(DDFModule *poUpdateModule, int iUpdate)

{
  DDFRecord *poRecord;

  int ret_code = 0;

  /* -------------------------------------------------------------------- */
  /*      Ensure base file is loaded.                                     */
  /* -------------------------------------------------------------------- */
  Ingest();

  /* -------------------------------------------------------------------- */
  /*      Read records, and apply as updates.                             */
  /* -------------------------------------------------------------------- */
  while ((poRecord = poUpdateModule->ReadRecord()) != NULL) {
    DDFField *poKeyField = poRecord->GetField(1);
    const char *pszKey = poKeyField->GetFieldDefn()->GetName();

    if (EQUAL(pszKey, "VRID") || EQUAL(pszKey, "FRID")) {
      int nRCNM = poRecord->GetIntSubfield(pszKey, 0, "RCNM", 0);
      int nRCID = poRecord->GetIntSubfield(pszKey, 0, "RCID", 0);
      int nRVER = poRecord->GetIntSubfield(pszKey, 0, "RVER", 0);
      int nRUIN = poRecord->GetIntSubfield(pszKey, 0, "RUIN", 0);
      DDFRecordIndex *poIndex = NULL;

      if (EQUAL(poKeyField->GetFieldDefn()->GetName(), "VRID")) {
        switch (nRCNM) {
          case RCNM_VI:
            poIndex = &oVI_Index;
            break;

          case RCNM_VC:
            poIndex = &oVC_Index;
            break;

          case RCNM_VE:
            poIndex = &oVE_Index;
            break;

          case RCNM_VF:
            poIndex = &oVF_Index;
            break;

          default:
            CPLAssert(FALSE);
            break;
        }
      } else {
        poIndex = &oFE_Index;
      }

      if (poIndex != NULL) {
        if (nRUIN == 1) /* insert */
        {
          //                      CPLDebug( "S57","Insert Record, RCID=%d",
          //                      nRCID);
          poIndex->AddRecord(nRCID, poRecord->CloneOn(poModule));
        } else if (nRUIN == 2) /* delete */
        {
          //                      CPLDebug( "S57","Remove Record, RCID=%d",
          //                      nRCID);
          DDFRecord *poTarget;

          poTarget = poIndex->FindRecord(nRCID);
          if (poTarget == NULL) {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "While applying update %d, Can't find RCNM=%d,RCID=%d for "
                     "delete.",
                     iUpdate, nRCNM, nRCID);
            ret_code = BAD_UPDATE;
          } else if (poTarget->GetIntSubfield(pszKey, 0, "RVER", 0) !=
                     nRVER - 1) {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "While applying update %d, On RecordRemove, mismatched "
                     "RVER value for RCNM=%d,RCID=%d...update RVER is %d, "
                     "target RVER is %d.",
                     iUpdate, nRCNM, nRCID, nRVER,
                     poTarget->GetIntSubfield(pszKey, 0, "RVER", 0));
            CPLError(
                CE_Warning, CPLE_AppDefined,
                "While applying update %d, Removal of RCNM=%d,RCID=%d failed.",
                iUpdate, nRCNM, nRCID);
            ret_code = BAD_UPDATE;

          } else {
            poIndex->RemoveRecord(nRCID);
          }
        }

        else if (nRUIN == 3) /* modify in place */
        {
          //                    CPLDebug( "S57","Update Record, RCID=%d",
          //                    nRCID);
          DDFRecord *poTarget;

          poTarget = poIndex->FindRecord(nRCID);
          if (poTarget == NULL) {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "While applying update %d, Can't find RCNM=%d,RCID=%d for "
                     "update.",
                     iUpdate, nRCNM, nRCID);
            ret_code = BAD_UPDATE;

          } else {
            if (!ApplyRecordUpdate(poTarget, poRecord)) {
              CPLError(CE_Warning, CPLE_AppDefined,
                       "While applying update %d, an update to RCNM=%d,RCID=%d "
                       "failed.",
                       iUpdate, nRCNM, nRCID);
              ret_code = BAD_UPDATE;
            }
          }
        }
      }
    }

    else if (EQUAL(pszKey, "DSID")) {
      /* Check for canceled cell */;
      char *u = (char *)(poRecord->GetStringSubfield("DSID", 0, "EDTN", 0));
      if (!strncmp(u, "0", 1)) {
        return BAD_UPDATE;
      }
    }

    else {
      CPLDebug("S57",
               "While applying update %d, Skipping %s record in "
               "S57Reader::ApplyUpdates().",
               iUpdate, pszKey);
      ret_code = BAD_UPDATE;
    }
  }

  return ret_code;
}

/************************************************************************/
/*                        FindAndApplyUpdates()                         */
/*                                                                      */
/*      Find all update files that would appear to apply to this        */
/*      base file.                                                      */
/************************************************************************/

int S57Reader::FindAndApplyUpdates(const char *pszPath)

{
  int iUpdate;
  int bSuccess = TRUE;
  int ret_code = 0;

  if (pszPath == NULL) pszPath = pszModuleName;

  if (!EQUAL(CPLGetExtension(pszPath), "000")) {
    CPLError(CE_Failure, CPLE_AppDefined,
             "Can't apply updates to a base file with a different\n"
             "extension than .000.");
    return BAD_UPDATE;
  }

  for (iUpdate = 1; bSuccess; iUpdate++) {
    char szExtension[16];
    char *pszUpdateFilename;
    DDFModule oUpdateModule;

    assert(iUpdate <= 999);
    sprintf(szExtension, "%03d", iUpdate);

    pszUpdateFilename = CPLStrdup(CPLResetExtension(pszPath, szExtension));

    bSuccess = oUpdateModule.Open(pszUpdateFilename, TRUE);

    if (bSuccess)
      CPLDebug("S57", "Applying feature updates from %s.", pszUpdateFilename);
    CPLFree(pszUpdateFilename);

    if (bSuccess) {
      int update_ret = ApplyUpdates(&oUpdateModule, iUpdate);
      if (update_ret) {
        ret_code = update_ret;
        bSuccess = false;     // Stop the loop
      }
    }
  }

  return ret_code;
}

/************************************************************************/
/*                             GetExtent()                              */
/*                                                                      */
/*      Scan all the cached records collecting spatial bounds as        */
/*      efficiently as possible for this transfer.                      */
/************************************************************************/

// Android uses clang compiler.
// Problem also appears on GCC, on __ARM_ARCH.
//  At optimization -O3, this function has trouble with alignment of values,
//  Specifically, conversion of an int32 from a buffer into double.
//  Workaround: We disable optimization for this little used function.
#ifdef __ARM_ARCH
#if defined(__clang__)
[[clang::optnone]]
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC push_options
#pragma GCC optimize("O0")

#endif
#endif

OGRErr S57Reader::GetExtent(OGREnvelope *psExtent, int bForce)

{
#define INDEX_COUNT 4

  DDFRecordIndex *apoIndex[INDEX_COUNT];

  /* -------------------------------------------------------------------- */
  /*      If we aren't forced to get the extent say no if we haven't      */
  /*      already indexed the iso8211 records.                            */
  /* -------------------------------------------------------------------- */
  if (!bForce && !bFileIngested) return OGRERR_FAILURE;

  Ingest();

  /* -------------------------------------------------------------------- */
  /*      We will scan all the low level vector elements for extents      */
  /*      coordinates.                                                    */
  /* -------------------------------------------------------------------- */
  int bGotExtents = FALSE;
  double nXMin = 0, nXMax = 0, nYMin = 0, nYMax = 0;

  apoIndex[0] = &oVI_Index;
  apoIndex[1] = &oVC_Index;
  apoIndex[2] = &oVE_Index;
  apoIndex[3] = &oVF_Index;

  for (int iIndex = 0; iIndex < INDEX_COUNT; iIndex++) {
    DDFRecordIndex *poIndex = apoIndex[iIndex];

    for (int iVIndex = 0; iVIndex < poIndex->GetCount(); iVIndex++) {
      DDFRecord *poRecord = poIndex->GetByIndex(iVIndex);
      DDFField *poSG3D = poRecord->FindField("SG3D");
      DDFField *poSG2D = poRecord->FindField("SG2D");

      if (poSG3D != NULL) {
        int i, nVCount = poSG3D->GetRepeatCount();
        GInt32 *panData, nX, nY;

        panData = (GInt32 *)poSG3D->GetData();
        for (i = 0; i < nVCount; i++) {
          nX = CPL_LSBWORD32(panData[i * 3 + 1]);
          nY = CPL_LSBWORD32(panData[i * 3 + 0]);

          double dnX = nX / (double)nCOMF;
          double dnY = nY / (double)nCOMF;

          if (bGotExtents) {
            nXMin = MIN(nXMin, dnX);
            nXMax = MAX(nXMax, dnX);
            nYMin = MIN(nYMin, dnY);
            nYMax = MAX(nYMax, dnY);
          } else {
            nXMin = nXMax = dnX;
            nYMin = nYMax = dnY;
            bGotExtents = TRUE;
          }
        }
      } else if (poSG2D != NULL) {
        int i, nVCount = poSG2D->GetRepeatCount();
        GInt32 *panData, nX, nY;

        panData = (GInt32 *)poSG2D->GetData();
        for (i = 0; i < nVCount; i++) {
          nX = CPL_LSBWORD32(panData[i * 2 + 1]);
          nY = CPL_LSBWORD32(panData[i * 2 + 0]);

          double dnX = nX / (double)nCOMF;
          double dnY = nY / (double)nCOMF;

          if (bGotExtents) {
            nXMin = MIN(nXMin, dnX);
            nXMax = MAX(nXMax, dnX);
            nYMin = MIN(nYMin, dnY);
            nYMax = MAX(nYMax, dnY);
          } else {
            nXMin = nXMax = dnX;
            nYMin = nYMax = dnY;
            bGotExtents = TRUE;
          }
        }
      }
    }
  }

  if (!bGotExtents)
    return OGRERR_FAILURE;
  else {
    psExtent->MinX = nXMin;
    psExtent->MaxX = nXMax;
    psExtent->MinY = nYMin;
    psExtent->MaxY = nYMax;

    return OGRERR_NONE;
  }
}

#ifdef __ARM_ARCH
#if defined(__GNUC__) || defined(__GNUG__)
#pragma GCC pop_options
#endif
#endif
