/******************************************************************************
 *
 * Project:  OpenCPN
 * Purpose:  S52 Conditional Symbology Library
 * Author:   David Register, Sylvain Duclos
 *
 ***************************************************************************
 *   Copyright (C) 2010 by David S. Register   *
 *
 *   Copyright (C) 2000-2001  Sylvain Duclos
 *   sylvain_duclos@yahoo.com
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.             *
 ***************************************************************************
 *
 */

#include "wx/wxprec.h"

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif  // precompiled headers

#include "wx/tokenzr.h"

#include "s52plib.h"
#include "s52utils.h"

bool GetDoubleAttr(S57Obj *obj, const char *AttrName, double &val);

#define UNKNOWN 1e6  // HUGE_VAL   // INFINITY/NAN

#ifndef chk_snprintf
#define chk_snprintf(buf, len, fmt, ...)                      \
  {                                                           \
    int r = snprintf(buf, len, fmt, ##__VA_ARGS__);           \
    if (r == -1 || r >= len) wxLogWarning("snprint overrun"); \
  }
#endif

WX_DEFINE_ARRAY_DOUBLE(double, ArrayOfSortedDoubles);

// size of attributes value list buffer
#define LISTSIZE 32  // list size

extern s52plib *ps52plib;

wxString *CSQUAPNT01(S57Obj *obj);
wxString *CSQUALIN01(S57Obj *obj);

wxArrayPtrVoid *GetChartFloatingATONArray(ObjRazRules *rzRules) {
  S57Obj *obj = rzRules->obj;
  if (obj->m_chart_context)
    return obj->m_chart_context->pFloatingATONArray;
  else
    return NULL;
}

wxArrayPtrVoid *GetChartRigidATONArray(ObjRazRules *rzRules) {
  S57Obj *obj = rzRules->obj;
  if (obj->m_chart_context->chart)
    return obj->m_chart_context->pRigidATONArray;
  else
    return NULL;
}

static void *CLRLIN01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

  printf("s52csny : CLRLIN01 ERROR no conditional symbology for: %s\n",
         rzRules->LUP->OBCL);
  return NULL;
}

static void *DATCVR01(void *param) {
  // Remarks: This conditional symbology procedure describes procedures for:
  // - symbolizing the limit of ENC coverage;
  // - symbolizing navigational purpose boundaries ("scale boundarie"); and
  // - indicating overscale display.
  //
  // Note that the mandatory meta object CATQUA is symbolized by the look-up
  // table.
  //
  // Because the methods adopted by an ECDIS to meet the IMO and IHO
  // requirements listed on the next page will depend on the manufacturer's
  // software, and cannot be described in terms of a flow chart in the same way
  // as other conditional procedures, this procedure is in the form of written
  // notes.

  //    ObjRazRules *rzRules = (ObjRazRules *)param;
  //    S57Obj *obj = rzRules->obj;

  wxString rule_str;
  ///////////////////////
  // 1- REQUIREMENT
  // (IMO/IHO specs. explenation)

  ///////////////////////
  // 2- ENC COVERAGE
  //
  // 2.1- Limit of ENC coverage
  // datcvr01 = g_string_new(";OP(3OD11060);LC(HODATA01)");
  rule_str.Append(_T("LC(HODATA01)"));
  //    rule_str.Append("AC(DEPDW)");
  // FIXME: get cell extend

  // 2.2- No data areas
  // This can be done outside of CS (ie when clearing the screen in Mesa)
  // FIXME: ";OP(0---);AC(NODATA)"
  // FIXME: set geo to cover earth (!)

  //////////////////////
  // 3- SCALE BOUNDARIES
  //
  // 3.1- Chart scale boundaties
  // FIXME;
  // g_string_append(datcvr01, ";LS(SOLD,1,CHGRD)");
  // -OR- LC(SCLBDYnn) (?)
  //
  // ;OP(3OS21030)

  // 3.2- Graphical index of navigational purpose
  // FIXME: draw extent of available SENC in DB

  //////////////////////
  // 4- OVERSCALE
  //
  // FIXME: get meta date CSCL of DSPM field
  // FIXME: get object M_CSCL or CSCALE
  //
  // 4.1- Overscale indication
  // FIXME: compute, scale = [denominator of the compilation scale] /
  //                         [denominator of the display scale]
  // FIXME: draw overscale indication (ie TX("X%3.1f",scale))
  //
  // 4.2- Ovescale area at a chart scale boundary
  // FIXME: test if next chart is over scale (ie going from large scale chart
  //        to a small scale chart)
  // FIXME: draw AP(OVERSC01) on overscale part of display
  // g_string(";OP(3OS21030)");

  //
  // 4.3- Larger scale data available
  // FIXME: display indication of better scale available (?)

  wxString datcvr01;
  datcvr01.Append(rule_str);
  datcvr01.Append('\037');

  char *r = (char *)malloc(datcvr01.Len() + 1);
  strcpy(r, datcvr01.mb_str());

  return r;
}

bool GetIntAttr(S57Obj *obj, const char *AttrName, int &val) {
  int idx = obj->GetAttributeIndex(AttrName);

  if (idx >= 0) {
    //      using idx to get the attribute value
    S57attVal *v = obj->attVal->Item(idx);

    assert(v->valType == OGR_INT);
    val = *(int *)(v->value);

    return true;
  } else
    return false;
}
#if 0
bool GetIntAttr(S57Obj *obj, const char *AttrName, int &val)
{
    char *attList = (char *)calloc(obj->attList->Len()+1, 1);
    strncpy(attList, obj->attList->mb_str(), obj->attList->Len());

    char *patl = attList;
    char *patr;
    int idx = 0;
    while(*patl)
    {
        patr = patl;
        while(*patr != '\037')
            patr++;

        if(!strncmp(patl, AttrName, 6))
            break;

        patl = patr + 1;
        idx++;
    }

    if(!*patl)                                     // Requested Attribute not found
        {
            free(attList);
            return false;                            // so don't return a value
        }

        //      using idx to get the attribute value
        wxArrayOfS57attVal      *pattrVal = obj->attVal;

    S57attVal *v = pattrVal->Item(idx);
    val = *(int*)(v->value);

    free(attList);
    return true;
}
#endif
/*
bool GetFloatAttr(S57Obj *obj, char *AttrName, float &val)
{
        char *attList = (char *)(obj->attList->mb_str());        //attList is
wxString

        char *patl = attList;
        char *patr;
        int idx = 0;
        while(*patl)
        {
                patr = patl;
                while(*patr != '\037')
                        patr++;

                if(!strncmp(patl, AttrName, 6))
                        break;

                patl = patr + 1;
                idx++;
        }

        if(!*patl)                                        // Requested Attribute
not found
        {
             free(attList);
             return false;                                // so don't return a
value
        }

//      using idx to get the attribute value
        wxArrayOfS57attVal      *pattrVal = obj->attVal;

        S57attVal *v = pattrVal->Item(idx);
        val = *(float*)(v->value);

        free(attList);
        return true;
}

*/
bool GetDoubleAttr(S57Obj *obj, const char *AttrName, double &val) {
  int idx = obj->GetAttributeIndex(AttrName);

  if (idx >= 0) {
    //      using idx to get the attribute value

    S57attVal *v = obj->attVal->Item(idx);
    assert(v->valType == OGR_REAL);
    val = *(double *)(v->value);

    return true;
  } else
    return false;
}

bool GetStringAttr(S57Obj *obj, const char *AttrName, char *pval, int nc) {
  int idx = obj->GetAttributeIndex(AttrName);

  if (idx >= 0) {
    //      using idx to get the attribute value
    S57attVal *v = obj->attVal->Item(idx);

    assert(v->valType == OGR_STR);
    char *val = (char *)(v->value);

    strncpy(pval, val, nc);

    return true;
  } else
    return false;
}

wxString *GetStringAttrWXS(S57Obj *obj, const char *AttrName) {
  int idx = obj->GetAttributeIndex(AttrName);

  if (idx >= 0) {
    //      using idx to get the attribute value
    S57attVal *v = obj->attVal->Item(idx);

    assert(v->valType == OGR_STR);
    char *val = (char *)(v->value);

    return new wxString(val, wxConvUTF8);
  } else
    return NULL;
}

static int _parseList(const char *str_in, char *buf, int buf_size)
// Put a string of comma delimited number in an array (buf).
// Return: the number of value in buf.
// Assume: - number < 256,
//         - list size less than buf_size .
// Note: buf is \0 terminated for strpbrk().
{
  char *str = (char *)str_in;
  int i = 0;

  if (NULL != str && *str != '\0') {
    do {
      if (i >= LISTSIZE - 1) {
        printf("OVERFLOW --value in list lost!!\n");
        break;
      }

      /*
      if (255 <  (unsigned char) atoi(str)) {
          PRINTF("value overflow (>255)\n");
          exit(0);
      }
      */

      buf[i++] = (unsigned char)atoi(str);

      while (isdigit(*str)) str++;  // next

    } while (*str++ != '\0');  // skip ',' or exit
  }

  buf[i] = '\0';

  return i;
}

static int _atPtPos(S57Obj *objNew, wxArrayPtrVoid *curntList, int bSectorCheck)
// return TRUE if there is a light at this position
// or if its an extended arc radius else FALSE
{
  unsigned int i;

  if (NULL == curntList) return false;

  for (i = 0; i < curntList->GetCount(); i++) {
    S57Obj *objOld = (S57Obj *)curntList->Item(i);

    if ((objOld->x == objNew->x) && (objOld->y == objNew->y)) {
      if (!bSectorCheck) return TRUE;
      /*
                  else {
                      // check for extend arc radius
                      GString *Asectr1str = S57_getAttVal(geoOld, "SECTR1");
                      GString *Asectr2str = S57_getAttVal(geoOld, "SECTR2");
                      GString *Bsectr1str = S57_getAttVal(geoNew, "SECTR1");
                      GString *Bsectr2str = S57_getAttVal(geoNew, "SECTR2");

                      // check  present
                      if (NULL == Asectr1str ||
                          NULL == Asectr1str ||
                          NULL == Asectr1str ||
                          NULL == Asectr1str)
                          return FALSE;

                      {
                          double Asectr1 = atof(Asectr1str->str);
                          double Asectr2 = atof(Asectr2str->str);
                          double Bsectr1 = atof(Bsectr1str->str);
                          double Bsectr2 = atof(Bsectr2str->str);
                          double Asweep = (Asectr1 > Asectr2) ?
                              Asectr2-Asectr1+360 : Asectr2-Asectr1;
                          double Bsweep = (Bsectr1 > Bsectr2) ?
                              Bsectr2-Bsectr1+360 : Bsectr2-Bsectr1;

                          // check sector overlap
                          if (Asectr2<=Bsectr1 || Asectr1>=Bsectr2) {
                              if (Asweep == Bsweep) {
                                  g_string_truncate(Bsectr2str, 0);
                                  g_string_sprintf(Bsectr2str, "%f",Bsectr2-1);
                                  S57_setAtt(geoNew, "SECTR2", Bsectr2str->str);
                              }

                              return FALSE;
                          }

                          // check if other sector larger
                          if (Asweep >= Bsweep)
                              return TRUE;
                      }
                  }
      */
    }
  }

  return FALSE;
}

wxString _selSYcol(char *buf, bool bsectr, double valnmr) {
  wxString sym;

  if (!bsectr) {
    sym = _T(";SY(LITDEF11");  // default

    // max 1 color
    if ('\0' == buf[1]) {
      if (strpbrk(buf, "\003"))
        sym = _T(";SY(LIGHTS11");
      else if (strpbrk(buf, "\004"))
        sym = _T(";SY(LIGHTS12");
      else if (strpbrk(buf, "\001\006\011"))
        sym = _T(";SY(LIGHTS13");
    } else {
      // max 2 color
      if ('\0' == buf[2]) {
        if (strpbrk(buf, "\001") && strpbrk(buf, "\003"))
          sym = _T(";SY(LIGHTS11");
        else if (strpbrk(buf, "\001") && strpbrk(buf, "\004"))
          sym = _T(";SY(LIGHTS12");
      }
    }
  }

  else  // all-round fixed light symbolized as a circle, radius depends on color
        // This treatment is seen on SeeMyDenc by SevenCs
        // This may not be S-52 compliant....
  {
    //      Another non-standard extension....
    //      All round light circle diameter is scaled if the light has a
    //      reasonable VALNMR attribute
    int radius = 3;
    if (valnmr > 0) {
      if (valnmr < 7.0)
        radius = 3;
      else if (valnmr < 15.0)
        radius = 10;
      else if (valnmr < 30.0)
        radius = 15;
      else
        radius = 20;
    }

    // max 1 color
    if ('\0' == buf[1]) {
      if (strpbrk(buf, "\003"))
        sym.Printf(_T(",LITRD, 2,0,360,%d,0"), radius + 1);
      else if (strpbrk(buf, "\004"))
        sym.Printf(_T(",LITGN, 2,0,360,%d,0"), radius);
      else if (strpbrk(buf, "\001\006\011"))
        sym.Printf(_T(",LITYW, 2,0,360,%d,0"), radius + 2);
      else if (strpbrk(buf, "\014"))
        sym.Printf(_T(",CHMGD, 2,0,360,%d,0"), radius + 3);
      else
        sym.Printf(_T(",CHMGD, 2,0,360,%d,0"), radius + 5);  // default

    } else if ('\0' == buf[2])  // or 2 color
    {
      if (strpbrk(buf, "\001") && strpbrk(buf, "\003"))
        sym.Printf(_T(",LITRD, 2,0,360,%d,0"), radius + 1);
      else if (strpbrk(buf, "\001") && strpbrk(buf, "\004"))
        sym.Printf(_T(",LITGN, 2,0,360,%d,0"), radius);
      else
        sym.Printf(_T(",CHMGD, 2,0,360,%d,0"), radius + 5);  // default

    } else
      sym.Printf(_T(",CHMGD, 2,0,360,%d,0"), radius + 5);

    if (sym.Len()) sym.Prepend(_T(";CA(OUTLW, 4"));
  }

  return sym;
}

static double _DEPVAL01(S57Obj *obj, double least_depth)
// Remarks: S-57 Appendix B1 Annex A requires in Section 6 that areas of rocks
// be encoded as area obstruction, and that area OBSTRNs and area WRECKS be
// covered by either group 1 object DEPARE or group 1 object UNSARE. If the
// value of the attribute VALSOU for an area OBSTRN or WRECKS is missing, the
// DRVAL1 of an underlying DEPARE is the preferred default for establishing a
// depth value. This procedure either finds the shallowest DRVAL1 of the one or
// more underlying DEPAREs, or returns an "unknown"" depth value to the main
// procedure for the next default procedure.

// NOTE: UNSARE test is useless since least_depth is already UNKNOWN
{
  least_depth = UNKNOWN;

  /*
      S57_geo *geoTmp = geo;

      // NOTE: the geo list is unchange (_UDWHAZ03 will unlink geo)
      while (NULL != (geoTmp = S57_nextObj(geoTmp))) {
          GString *objlstr = S57_getAttVal(geoTmp, "OBJL");
          int      objl    = (NULL == objlstr) ? 0 : atoi(objlstr->str);

          // get area DEPARE  that intersect this area
          if (DEPARE==objl && LINES_T==S57_getObjtype(geo)) {
              GString *drval1str = S57_getAttVal(geoTmp, "DRVAL1");
              double   drval1    = (NULL == drval1str) ? 9.0 :
     atof(drval1str->str);

              if (NULL != drval1str) {
                  if (UNKNOWN==least_depth || least_depth<drval1)
                      least_depth = drval1;
              }

          }
      }
    */
  return least_depth;
}

static wxString *_UDWHAZ03(S57Obj *obj, double depth_value,
                           ObjRazRules *rzRules, bool *promote_return)
// Remarks: Obstructions or isolated underwater dangers of depths less than the
// safety contour which lie within the safe waters defined by the safety contour
// are to be presented by a specific isolated danger symbol as hazardous objects
// and put in IMO category DISPLAYBASE (see (3), App.2, 1.3). This task
// is performed by this conditional symbology procedure.
{
  wxString udwhaz03str;
  int danger = FALSE;
  int expsou = 0;
  double safety_contour = S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR);
  bool b_promote = false;

  if (depth_value == UNKNOWN) {
    GetIntAttr(obj, "EXPSOU", expsou);
    if (expsou != 1) danger = TRUE;
  }
  if (danger == FALSE && (expsou == 1 || depth_value <= safety_contour)) {
    // that intersect this point/line/area for OBSTRN04
    // that intersect this point/area      for WRECKS02

    // get area DEPARE & DRGARE that intersect this point/line/area

    std::list<S57Obj*> *pobj_list = NULL;
//FIXME plib
//     if (obj->m_chart_context->chart)
//       pobj_list = obj->m_chart_context->chart->GetAssociatedObjects(obj);
//     else
    {
      danger = false;
      //            wxString *ret_str = new wxString(udwhaz03str);
      //            return ret_str;
    }

    if (pobj_list) {
      for (S57Obj* ptest_obj: *pobj_list) {
        if (GEO_LINE == ptest_obj->Primitive_type) {
          double drval2 = 0.0;
          GetDoubleAttr(ptest_obj, "DRVAL2", drval2);

          if (drval2 < safety_contour) {
            danger = TRUE;
            break;
          }
        } else {
          double drval1 = 0.0;
          GetDoubleAttr(ptest_obj, "DRVAL1", drval1);

#if 0
                    double drval2 = 0.0;
                    GetDoubleAttr(ptest_obj, "DRVAL2", drval2);

                    if(expsou == 1 || depth_value < drval2 )
                        b_promote = true;
#endif

          if (drval1 >= safety_contour && expsou != 1) {
            danger = TRUE;
            break;
          }
        }
      }

      delete pobj_list;
    }
  }

  if (TRUE == danger) {
    int watlev = 0;  // Enum 0 invalid
    GetIntAttr(obj, "WATLEV", watlev);

    if ((1 == watlev) || (2 == watlev)) {
      // dry
      //                  udwhaz03str = _T(";OP(--D14050)");
    } else {
      udwhaz03str = _T(";SY(ISODGR51)");  //_T(";OP(8OD14010);SY(ISODGR51)");
      //                  S57_setAtt(geo, "SCAMIN", "INFINITE");
    }

    //  Move this object to DisplayBase category
    rzRules->obj->m_DisplayCat = DISPLAYBASE;

    /*
                GString *watlevstr = S57_getAttVal(geo, "WATLEV");
                if (NULL != watlevstr && ('1' == *watlevstr->str || '2' ==
       *watlevstr->str)) udwhaz03str = g_string_new(";OP(--D14050"); else {
                    udwhaz03str = g_string_new(";OP(8OD14010);SY(ISODGR01)");
                    S57_setAtt(geo, "SCAMIN", "INFINITE");
                }
    */
  }

  if (promote_return) *promote_return = b_promote;

  wxString *ret_str = new wxString(udwhaz03str);
  return ret_str;
}

// Remarks: An object of the class "depth area" is coloured and covered with
// fill patterns according to the mariners selections of shallow contour, safety
// contour and deep contour. This requires a decision making process based  on
// DRVAL1 and DRVAL2. Objects of the class "dredged area" are handled by this
// routine as well to ensure a consistent symbolization of areas that represent
// the surface of the seabed.

static void *DEPARE01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  double drval1, drval2;
  bool drval1_found;

  //      Determine the color based on mariner selections

  drval1 = -1.0;  // default values
  drval1_found = GetDoubleAttr(obj, "DRVAL1", drval1);
  drval2 = drval1 + 0.01;
  GetDoubleAttr(obj, "DRVAL2", drval2);

  //   Create a string of the proper color reference

  wxString rule_str = _T("AC(DEPIT)");

  if (drval1 >= 0.0 && drval2 > 0.0) rule_str = _T("AC(DEPVS)");

  if (TRUE == S52_getMarinerParam(S52_MAR_TWO_SHADES)) {
    if (drval1 >= S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR) &&
        drval2 > S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)) {
      rule_str = _T("AC(DEPDW)");
    }
  } else {
    if (drval1 >= S52_getMarinerParam(S52_MAR_SHALLOW_CONTOUR) &&
        drval2 > S52_getMarinerParam(S52_MAR_SHALLOW_CONTOUR))
      rule_str = _T("AC(DEPMS)");

    if (drval1 >= S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR) &&
        drval2 > S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)) {
      rule_str = _T("AC(DEPMD)");
    }

    if (drval1 >= S52_getMarinerParam(S52_MAR_DEEP_CONTOUR) &&
        drval2 > S52_getMarinerParam(S52_MAR_DEEP_CONTOUR)) {
      rule_str = _T("AC(DEPDW)");
    }
  }

  //  If object is DRGARE....

  if (!strncmp(rzRules->LUP->OBCL, "DRGARE", 6)) {
    if (!drval1_found)  // If DRVAL1 was not defined...
    {
      rule_str = _T("AC(DEPMD)");
    }
    rule_str.Append(_T(";AP(DRGARE01)"));
    rule_str.Append(_T(";LS(DASH,1,CHGRF)"));

    // Todo Restrictions
    /*
            char pval[30];
            if(true == GetStringAttr(obj, "RESTRN", pval, 20))
            {
                GString *rescsp01 = _RESCSP01(geo);
                if (NULL != rescsp01)
                {
                    g_string_append(depare01, rescsp01->str);
                    g_string_free(rescsp01, TRUE);
                }
            }
    */
  }

  rule_str.Append('\037');

  return strdup(rule_str.mb_str());
}
/*
static void *DEPCNT02A(void *param)
{
 //    ObjRazRules *rzRules = (ObjRazRules *)param;
 //    S57Obj *obj = rzRules->obj;

        //      Add another rule onto the ruleList

    Rules *r = NULL;

    r = (Rules*)calloc(1, sizeof(Rules));
    r->ruleType = RUL_SIM_LN;
    r->INSTstr = "SOLD,1,DEPCN";                                // points into
the plib data space

        return NULL;

}
*/

static void *DEPCNT02(void *param)
// Remarks: An object of the class "depth contour" or "line depth area" is
// highlighted and must be shown under all circumstances if it matches the
// safety contour depth value entered by the mariner (see IMO PS 3.6). But,
// while the mariner is free to enter any safety contour depth value that he
// thinks is suitable for the safety of his ship, the SENC only contains a
// limited choice of depth contours. This symbology procedure determines whether
// a contour matches the selected safety contour. If the selected safety contour
// does not exist in the data, the procedure will default to the next deeper
// contour. The contour selected is highlighted as the safety contour and put in
// DISPLAYBASE. The procedure also identifies any line segment of the spatial
// component of the object that has a "QUAPOS" value indicating unreliable
// positioning, and symbolizes it with a double dashed line.
//
// Note: Depth contours are not normally labeled. The ECDIS may provide labels,
// on demand only as with other text, or provide the depth value on cursor
// picking
{
  //      GString *depcnt02  = NULL;
  //      int      safe      = FALSE;     // initialy not a safety contour
  //      GString *objlstr   = NULL;
  //      int      objl      = 0;
  //      GString *quaposstr = NULL;
  //      int      quapos    = 0;
  double drval1, drval2;
  bool safe = FALSE;
  wxString rule_str;
  double safety_contour = S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR);

  //      objlstr = S57_getAttVal(geo, "OBJL");
  //      objl    = (NULL == objlstr) ? 0 : atoi(objlstr->str);

  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;
  // Debug
        //if(obj->Index == 5014)
              //int tty = 5;

  if ((!strncmp(obj->FeatureName, "DEPARE", 6)) &&
      GEO_LINE == obj->Primitive_type) {
    drval1 = 0.0;  // default values
    GetDoubleAttr(obj, "DRVAL1", drval1);
    drval2 = drval1;
    GetDoubleAttr(obj, "DRVAL2", drval2);

    if (drval1 <= safety_contour) {
      if (drval2 >= safety_contour) safe = TRUE;
    }

    else {
      double next_safe_contour = 1e6;
      next_safe_contour = obj->m_chart_context->safety_contour;
      if (fabs(drval1 - next_safe_contour) < 1e-4) safe = true;
    }

  } else {
    // continuation A (DEPCNT)
    double valdco = 0;
    GetDoubleAttr(obj, "VALDCO", valdco);

    if (valdco == safety_contour)
      safe = TRUE;  // this is useless !?!?

    else {
      double next_safe_contour = 1e6;
      next_safe_contour = obj->m_chart_context->safety_contour;
      if (fabs(valdco - next_safe_contour) < 1e-4) safe = true;
    }

      /*
                        if (valdco > safety_contour)
                        {
                              safe = FALSE;
                              if (1 == S52_state)
                                    return NULL;
                              else {
                                    S57_geo *geoTmp = geo;

                          // get area DEPARE & DRGARE that intersect this line
                                    while (NULL != (geoTmp =
         S57_nextObj(geoTmp))){ GString *drval1str = S57_getAttVal(geoTmp,
         "DRVAL1"); double   drval1    = (NULL == drval1str) ? 0.0 :
         atof(drval1str->str);

                                          if (NULL == drval1str) {
                                                safe = TRUE;
                                                break;
                                          }

                                          if (drval1 <
         S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)) { safe = TRUE; break;
                                          }
                                    }
                          // debug trace
                          //if (safe) PRINTF("** DEPCN: SAFE FOUND**\n");
                              }

      */
  }

  // Continuation B
  int quapos = 0;
  GetIntAttr(obj, "QUAPOS",
             quapos);  // QUAPOS is an E (Enumerated) type attribute

  if (0 != quapos) {
    if (2 <= quapos && quapos < 10) {
      if (safe) {
        wxString safeCntr = _T("LS(DASH,2,DEPSC)");
        S57Obj tempObj;
        LUPrec *safelup = ps52plib->S52_LUPLookup(PLAIN_BOUNDARIES, "SAFECD",
                                                  &tempObj, false);
        if (safelup) safeCntr = *safelup->INST;
        rule_str = _T(";") + safeCntr;
      } else
        rule_str = _T(";LS(DASH,1,DEPCN)");
    }
  } else {
    if (safe) {
      wxString safeCntr = _T("LS(SOLD,2,DEPSC)");
      S57Obj tempObj;
      LUPrec *safelup =
          ps52plib->S52_LUPLookup(PLAIN_BOUNDARIES, "SAFECN", &tempObj, false);
      if (safelup) safeCntr = *safelup->INST;
      rule_str = _T(";") + safeCntr;
    } else
      rule_str = _T(";LS(SOLD,1,DEPCN)");
  }

  if (safe) {
    //            S57_setAtt(geo, "SCAMIN", "INFINITE");
    //            rule_str.Prepend(_T(";OP(8OD13010)"));       //depcnt02 =
    //            g_string_prepend(depcnt02, ";OP(8OD13010)");
    //  Move this object to DisplayBase category
    rzRules->obj->m_DisplayCat = DISPLAYBASE;
    rzRules->obj->Scamin = 1e8+1;  // effectively no SCAMIN
    //            rzRules->LUP->DPRI = PRIO_HAZARDS;

  } else {
    //            rule_str.Prepend(_T(";OP(---33020)"));       //depcnt02 =
    //            g_string_prepend(depcnt02, ";OP(---33020)");
  }
  // facultative in S-52
  // if (TRUE == S52_getMarinerParam(S52_MAR_SHOW_TEXT)) {
  //    GString *sndfrm02 = _SNDFRM02(geo, depth_value);
  //    depcnt02 = g_string_append(depcnt02, sndfrm02->str);
  //    g_string_free(sndfrm02, TRUE);
  //}

  // debug
  // PRINTF("depth= %f\n", depth_value);

  //           S57_unlinkObj(geo);

  rule_str.Append('\037');

  return strdup(rule_str.mb_str());

  //            return depcnt02;
}

static void *DEPVAL01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

  printf("s52csny : DEPVAL01 ERROR no conditional symbology for: %s\n",
         rzRules->LUP->OBCL);
  return NULL;
}

static void *LEGLIN02(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

  printf("s52csny : LEGLIN02 ERROR no conditional symbology for: %s\n",
         rzRules->LUP->OBCL);
  return NULL;
}

/*
static void *LIGHTS04A(void *param)
{
        ObjRazRules *rzRules = (ObjRazRules *)param;
        S57Obj *obj = rzRules->obj;

        wxString rule_str;

        char col_str[2];
        GetStringAttr(obj, "COLOUR", col_str, 1);

        double height_val = 0;
        GetDoubleAttr(obj, "HEIGHT", height_val);

//      if(obj->attList->Contains(wxString("HEIGHT")))
//              int uupr = 5;

//      Different symbology depending upon Paper or Simplified Mariner Selection


    if(ps52plib->m_nSymbolStyle == PAPER_CHART)
    {
            if(col_str[0] == '3')
            {                                                     // red
                    if(height_val)
                        rule_str = _T("SY(LIGHTS93)");            // all round
                    else
                        rule_str = _T("SY(LIGHTS01)");            // flare
            }

            else if(col_str[0] == '4')                            // green
            {
                    if(height_val)
                        rule_str = _T("SY(LIGHTS92)");
                    else
                        rule_str = _T("SY(LIGHTS02)");
            }

            else                                                  // Generic,
shows as yellow
            {
                    if(height_val)
                        rule_str = _T("SY(LIGHTS91)");
                    else
                        rule_str = _T("SY(LIGHTS03)");
            }
    }

    else                                                     // must be
Simplified
    {
            if(col_str[0] == '3')
            {                                                     // red
                    if(height_val)
                        rule_str = _T("SY(LIGHTS93)");            // all round
                    else
                        rule_str = _T("SY(LIGHTS01)");            // flare
            }

            else if(col_str[0] == '4')                            // green
            {
                    if(height_val)
                        rule_str = _T("SY(LIGHTS92)");
                    else
                        rule_str = _T("SY(LIGHTS02)");
            }

            else                                                  // Generic,
shows as yellow
            {
                    if(height_val)
                        rule_str = _T("SY(LIGHTS91)");
                    else
                        rule_str = _T("SY(LIGHTS03)");
            }
    }



    rule_str.Append('\037');

    char *r = (char *)malloc(rule_str.Len() + 1);
    strcpy(r, rule_str.mb_str());
    return r;
}

//    A simple placeholder for Conditional Symbology method LIGHTS05
static void *LIGHTS05A(void *param)
{
      ObjRazRules *rzRules = (ObjRazRules *)param;
      S57Obj *obj = rzRules->obj;

      wxString rule_str;

      char col_str[2];
      GetStringAttr(obj, "COLOUR", col_str, 1);

      double height_val = 0;
      GetDoubleAttr(obj, "HEIGHT", height_val);

      if(col_str[0] == '3')
      {                                                     // red
                  rule_str = _T("SY(LIGHTS11)");            // flare
      }

      else if(col_str[0] == '4')                            // green
                  rule_str = _T("SY(LIGHTS12)");

      else                                                  // Generic, shows as
yellow rule_str = _T("SY(LIGHTS13)");



      rule_str.Append('\037');

      char *r = (char *)malloc(rule_str.Len() + 1);
      strcpy(r, rule_str.mb_str());
      return r;
}
*/

static wxString _LITDSN01(S57Obj *obj);
static void *LIGHTS06(void *param);

static void *LIGHTS05(void *param)
// Remarks: A light is one of the most complex S-57 objects. Its presentation
// depends on whether it is a light on a floating or fixed platform, its range,
// it's colour and so on. This conditional symbology procedure derives the
// correct presentation from these parameters and also generates an area that
// shows the coverage of the light.
//
// Notes on light sectors:
// 1.) The radial leg-lines defining the light sectors are normally drawn to
// only 25mm from the light to avoid clutter (see Part C). However, the mariner
// should be able to select "full light-sector lines" and have the leg-lines
// extended to the nominal range of the light (VALMAR).
//
// 2.) Part C of this procedure symbolizes the sectors at the light itself. In
// addition, it should be possible, upon request, for the mariner to be capable
// of identifying the colour and sector limit lines of the sectors affecting the
// ship even if the light itself is off the display. [ed. last sentence in bold]

{
  // As transition, we use the PLIB 4.0 LIGHTS06 Procedure
  return LIGHTS06(param);

#define UNKNOWN_DOUBLE -9;
  wxString lights05;

  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  double valnmr = UNKNOWN_DOUBLE;
  GetDoubleAttr(obj, "VALNMR", valnmr);

  char catlitstr[20] = {'\0'};
  GetStringAttr(obj, "CATLIT", catlitstr, 19);

  char litvisstr[20] = {'\0'};
  ;
  GetStringAttr(obj, "LITVIS", litvisstr, 19);

  char catlit[LISTSIZE] = {'\0'};
  char litvis[LISTSIZE] = {'\0'};
  char col_str[20] = {'\0'};

  bool flare_at_45 = false;
  double sectr1 = UNKNOWN_DOUBLE;
  double sectr2 = UNKNOWN_DOUBLE;
  double sweep = 0.;
  char colist[LISTSIZE] = {'\0'};  // colour list
  bool b_isflare = false;

  wxString orientstr;

  if (strlen(catlitstr)) {
    _parseList(catlitstr, catlit, sizeof(colist));

    // FIXME: OR vs AND/OR
    if (strpbrk(catlit, "\010\013")) {
      lights05.Append(_T(";SY(LIGHTS82)"));
      goto l05_end;
    }

    if (strpbrk(catlit, "\011")) {
      lights05.Append(_T(";SY(LIGHTS81)"));
      goto l05_end;
    }

    /*
            if (strpbrk(catlit, "\001\020")) {
                orientstr = S57_getAttVal(geo, "ORIENT");
                if (NULL != orientstr) {
                    // FIXME: create a geo object (!?) LINE of lenght VALNMR
                    // using ORIENT (from seaward) & POINT_T position
                    g_string_append(lights05, ";LS(DASH,1,CHBLK)");
                }
            }
    */
  }

  // Continuation A

  GetStringAttr(obj, "COLOUR", col_str, 19);

  if (strlen(col_str))
    _parseList(col_str, colist, sizeof(colist));
  else {
    colist[0] = '\014';  // magenta (12)
    colist[1] = '\000';
  }

  GetDoubleAttr(obj, "SECTR1", sectr1);
  GetDoubleAttr(obj, "SECTR2", sectr2);

  if ((-9 == sectr1) || (-9 == sectr2)) {
    // This is not a sector light

    //      What follows is one interpretation of the modern (3_3 +)
    //      Presentation Library CS flow chart, which I(dsr) have never seen
    //      verbatim. We will use flare light symbols for floating aids, and all
    //      round sector lights for fixed aids.

    wxString ssym;

    //FIXME plib
//     if (_atPtPos(obj, GetChartFloatingATONArray(rzRules),
//                  false))  // Is this LIGHTS feature colocated with ...ANY...
//                           // floating aid?
//     {
//       flare_at_45 = false;
//
//       // TODO create LightArray in s57chart.
//       //  Then, if another LIGHT object is colocated here, set flare_at_45
//       /*            if(_atPtPos(obj, rzRules->chart->pLIGHTSArray, false)) // Is
//          this LIGHTS feature colocated with another LIGHTS?
//
//
//                   //    If the light is white, yellow, or orange, make it a
//          flare at 45 degrees if(strpbrk(colist, "\001\005\011")) flare_at_45 =
//          true;
//       */
//       ssym = _selSYcol(colist, 0, valnmr);  // flare
//       b_isflare = true;
//     } else
    {
      ssym = _selSYcol(colist, 1, valnmr);  // all round light
      b_isflare = false;
    }

    //  Is the light a directional or moire?
    if (strpbrk(catlit, "\001\016")) {
      if (orientstr.Len()) {
        lights05.Append(ssym);
        lights05.Append(orientstr);
        lights05.Append(
            _T(";TE('%03.0lf deg','ORIENT',3,3,3,'15110',3,1,CHBLK,23)" ));
      } else
        lights05.Append(_T(";SY(QUESMRK1)"));
    } else {
      lights05.Append(ssym);
      if (b_isflare) {
        if (flare_at_45)
          lights05.Append(_T(",45)"));
        else
          lights05.Append(_T(",135)"));
      }
    }

    goto l05_end;
  }

  // Continuation B --sector light
  if (-9 == sectr1) {
    sectr1 = 0.0;
    sectr2 = 0.0;
  } else
    sweep = (sectr1 > sectr2) ? sectr2 - sectr1 + 360 : sectr2 - sectr1;

  if (sweep < 1.0 || sweep == 360.0) {
    // handle all round light
    wxString ssym = _selSYcol(colist, 1, valnmr);  // all round light
    lights05.Append(ssym);

    /*
            if (TRUE == S52_getMarinerParam(S52_MAR_SHOW_TEXT)) {
                GString *litdsn01 = _LITDSN01(geo);
                if (NULL != litdsn01) {
                    g_string_append(lights05, ";TX('");
                    g_string_append(lights05, litdsn01->str);
                    g_string_append(lights05, "',3,2,3,'15110',2,0,CHBLK,23)" );
                    g_string_free(litdsn01, TRUE);
                }

            }
    */
    goto l05_end;
  }

  /*
      // scan for other lights with sector overlap at this position
      // compute light sector radius according to other sector
      if (1 == S52_state)
      {
          _setPtPos(geo, SECTRLIST);
          g_string_free(lights05, TRUE);
          return NULL;
      }
      else
      {
          extend_arc_radius = _atPtPos(geo, SECTRLIST);

          // passe value via attribs to _renderAC
          if (extend_arc_radius)
              // FIXME: draw radius 25 mm
              S57_setAtt(geo, "extend_arc_radius", "Y");
          else
              // FIXME: draw radius 20 mm
              S57_setAtt(geo, "extend_arc_radius", "N");
      }
  */
  // setup sector
  {
    //        Build the (opencpn private) command string like this:
    //        e.g.  ";CA(OUTLW, 4,LITRD, 2, sectr1, sectr2, radius)"

    double arc_radius = 20.;  // mm
    double sector_radius = 25.;

    //      Another non-standard extension....
    //      Sector light arc radius is scaled if the light has a reasonable
    //      VALNMR attribute
    if (valnmr > 0) {
      if (valnmr < 15.0)
        arc_radius = 10.;
      else if (valnmr < 30.0)
        arc_radius = 15.;
      else
        arc_radius = 20.;
    }

    char sym[80];
    strcpy(sym, ";CA(OUTLW, 4");

    // max 1 color
    if ('\0' == colist[1]) {
      if (strpbrk(colist, "\003"))
        strcat(sym, ",LITRD, 2");
      else if (strpbrk(colist, "\004"))
        strcat(sym, ",LITGN, 2");
      else if (strpbrk(colist, "\001\006\013"))
        strcat(sym, ",LITYW, 2");
      else
        strcat(sym, ",CHMGD, 2");  // default is magenta

    } else if ('\0' == colist[2]) {
      if (strpbrk(colist, "\001") && strpbrk(colist, "\003"))
        strcat(sym, ",LITRD, 2");
      else if (strpbrk(colist, "\001") && strpbrk(colist, "\004"))
        strcat(sym, ",LITGN, 2");
      else
        strcat(sym, ",CHMGD, 2");  // default is magenta
    } else
      strcat(sym, ",CHMGD, 2");  // default is magenta

    if (strlen(litvisstr))  // Obscured/faint sector?
    {
      _parseList(litvisstr, litvis, sizeof(litvis));

      if (strpbrk(litvis, "\003\007\010")) strcpy(sym, ";CA(CHBLK, 4,CHBRN, 1");
    }

    if (sectr2 <= sectr1) sectr2 += 360;

    //    Sectors are defined from seaward
    if (sectr1 > 180)
      sectr1 -= 180;
    else
      sectr1 += 180;

    if (sectr2 > 180)
      sectr2 -= 180;
    else
      sectr2 += 180;

    char arc_data[80];
    sprintf(arc_data, ",%5.1f, %5.1f, %5.1f, %5.1f", sectr1, sectr2, arc_radius,
            sector_radius);

    strcat(sym, arc_data);

    wxString ssym(sym, wxConvUTF8);
    lights05 = ssym;

    goto l05_end;
  }

l05_end:

  //      if( ps52plib->m_bShowLdisText )
  {
    // Only show Light in certain position once. Otherwise there will be
    // clutter.
    static double lastLat, lastLon;
    static wxString lastDescription;
    bool isFirstSector = true;

    if (lastLat == obj->m_lat && lastLon == obj->m_lon) isFirstSector = false;
    lastLat = obj->m_lat;
    lastLon = obj->m_lon;

    wxString litdsn01 = _LITDSN01(obj);

    if (litdsn01.Len() && isFirstSector) {
      lastDescription = litdsn01;
      lights05.Append(_T(";TX('"));
      lights05.Append(litdsn01);

      if (flare_at_45)
        lights05.Append(_T("',3,3,3,'15110',2,-1,CHBLK,23)" ));
      else
        lights05.Append(_T("',3,2,3,'15110',2,0,CHBLK,23)" ));
    }

    if (!isFirstSector && lastDescription != litdsn01) {
      lastDescription = litdsn01;
      lights05.Append(_T(";TX('"));
      lights05.Append(litdsn01);
      lights05.Append(_T("',3,2,3,'15110',2,1,CHBLK,23)" ));
    }
  }

  lights05.Append('\037');

  char *r = (char *)malloc(lights05.Len() + 1);
  strcpy(r, lights05.mb_str());

  return r;
}

static void *LIGHTS06(void *param)
// Remarks: A light is one of the most complex S-57 objects. Its presentation
// depends on whether it is a light on a floating or fixed platform, its range,
// it's colour and so on. This conditional symbology procedure derives the
// correct presentation from these parameters and also generates an area that
// shows the coverage of the light.
//
// Notes on light sectors:
// 1.) The radial leg-lines defining the light sectors are normally drawn to
// only 25mm from the light to avoid clutter (see Part C). However, the mariner
// should be able to select "full light-sector lines" and have the leg-lines
// extended to the nominal range of the light (VALMAR).
//
// 2.) Part C of this procedure symbolizes the sectors at the light itself. In
// addition, it should be possible, upon request, for the mariner to be capable
// of identifying the colour and sector limit lines of the sectors affecting the
// ship even if the light itself is off the display. [ed. last sentence in bold]

{
#define UNKNOWN_DOUBLE -9;
  wxString lights06;

  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  double valnmr = 9.0;
  GetDoubleAttr(obj, "VALNMR", valnmr);

  char catlitstr[20] = {'\0'};
  GetStringAttr(obj, "CATLIT", catlitstr, 19);

  char litvisstr[20] = {'\0'};
  ;
  GetStringAttr(obj, "LITVIS", litvisstr, 19);

  char catlit[LISTSIZE] = {'\0'};
  char litvis[LISTSIZE] = {'\0'};
  char col_str[20] = {'\0'};

  bool flare_at_45 = false;
  double sectr1 = UNKNOWN_DOUBLE;
  double sectr2 = UNKNOWN_DOUBLE;
  double sweep = 0.;
  char colist[LISTSIZE] = {'\0'};  // colour list
  bool b_isflare = false;

  wxString orientstr;

  if (strlen(catlitstr)) {
    _parseList(catlitstr, catlit, sizeof(colist));

    if (strpbrk(catlit, "\010\013")) {
      lights06.Append(_T(";SY(LIGHTS82)"));
      goto l06_end;
    }

    if (strpbrk(catlit, "\011")) {
      lights06.Append(_T(";SY(LIGHTS81)"));
      goto l06_end;
    }

    /*
     *        if (strpbrk(catlit, "\001\020")) {
     *            orientstr = S57_getAttVal(geo, "ORIENT");
     *            if (NULL != orientstr) {
     *                // FIXME: create a geo object (!?) LINE of lenght VALNMR
     *                // using ORIENT (from seaward) & POINT_T position
     *                g_string_append(lights05, ";LS(DASH,1,CHBLK)");
     }
     }
     */
  }

  // Continuation A

  GetStringAttr(obj, "COLOUR", col_str, 19);

  if (strlen(col_str))
    _parseList(col_str, colist, sizeof(colist));
  else {
    colist[0] = '\014';  // magenta (12)
    colist[1] = '\000';
  }

  GetDoubleAttr(obj, "SECTR1", sectr1);
  GetDoubleAttr(obj, "SECTR2", sectr2);

  if ((-9 == sectr1) || (-9 == sectr2)) {
    // This is not a sector light

    wxString ssym;

    if (valnmr < 10.0) {
      // TODO create LightArray in s57chart.
      //  Then, if another LIGHT object is colocated here, set flare_at_45
      /*            if(_atPtPos(obj, rzRules->chart->pLIGHTSArray, false)) // Is
       * this LIGHTS feature colocated with another LIGHTS?
       *
       *
       *            //    If the light is white, yellow, or orange, make it a
       * flare at 45 degrees if(strpbrk(colist, "\001\005\011")) flare_at_45 =
       * true;
       */
      ssym = _selSYcol(colist, 0, valnmr);  // flare
      b_isflare = true;
      flare_at_45 = false;
    } else {
      ssym = _selSYcol(colist, 1, valnmr);  // all round light
      b_isflare = false;
    }

    //  Is the light a directional or moire?
    if (strpbrk(catlit, "\001\016")) {
      if (orientstr.Len()) {
        lights06.Append(ssym);
        lights06.Append(orientstr);
        lights06.Append(
            _T(";TE('%03.0lf deg','ORIENT',3,3,3,'15110',3,1,CHBLK,23)" ));
      } else
        lights06.Append(_T(";SY(QUESMRK1)"));
    } else {
      lights06.Append(ssym);

      if (b_isflare) {
        if (flare_at_45)
          lights06.Append(_T(",45)"));
        else
          lights06.Append(_T(",135)"));
      }
    }

    goto l06_end;
  }

  // Continuation B --sector light
  if (-9 == sectr1) {
    sectr1 = 0.0;
    sectr2 = 0.0;
  } else
    sweep = (sectr1 > sectr2) ? sectr2 - sectr1 + 360 : sectr2 - sectr1;

  if (sweep < 1.0 || sweep == 360.0) {
    // handle all round light
    wxString ssym = _selSYcol(colist, 1, valnmr);  // all round light
    lights06.Append(ssym);

    /*
     *        if (TRUE == S52_getMarinerParam(S52_MAR_SHOW_TEXT)) {
     *            GString *litdsn01 = _LITDSN01(geo);
     *            if (NULL != litdsn01) {
     *                g_string_append(lights05, ";TX('");
     *                g_string_append(lights05, litdsn01->str);
     *                g_string_append(lights05, "',3,2,3,'15110',2,0,CHBLK,23)"
     );
     *                g_string_free(litdsn01, TRUE);
     }

     }
     */
    goto l06_end;
  }

  /*
   *    // scan for other lights with sector overlap at this position
   *    // compute light sector radius according to other sector
   *    if (1 == S52_state)
   *    {
   *        _setPtPos(geo, SECTRLIST);
   *        g_string_free(lights05, TRUE);
   *        return NULL;
   }
   else
   {
       extend_arc_radius = _atPtPos(geo, SECTRLIST);

       // passe value via attribs to _renderAC
       if (extend_arc_radius)
           // FIXME: draw radius 25 mm
           S57_setAtt(geo, "extend_arc_radius", "Y");
       else
           // FIXME: draw radius 20 mm
           S57_setAtt(geo, "extend_arc_radius", "N");
   }
   */
  // setup sector
  {
    //        Build the (opencpn private) command string like this:
    //        e.g.  ";CA(OUTLW, 4,LITRD, 2, sectr1, sectr2, radius)"

    double arc_radius = 20.;  // mm
    double sector_radius = 25.;

    //      Another non-standard extension....
    //      Sector light arc radius is scaled if the light has a reasonable
    //      VALNMR attribute
    if (valnmr > 0) {
      if (valnmr < 15.0)
        arc_radius = 10.;
      else if (valnmr < 30.0)
        arc_radius = 15.;
      else
        arc_radius = 20.;
    }

    char sym[80];
    strcpy(sym, ";CA(OUTLW, 4");

    // max 1 color
    if ('\0' == colist[1]) {
      if (strpbrk(colist, "\003"))
        strcat(sym, ",LITRD, 2");
      else if (strpbrk(colist, "\004"))
        strcat(sym, ",LITGN, 2");
      else if (strpbrk(colist, "\001\006\013"))
        strcat(sym, ",LITYW, 2");
      else
        strcat(sym, ",CHMGD, 2");  // default is magenta

    } else if ('\0' == colist[2]) {
      if (strpbrk(colist, "\001") && strpbrk(colist, "\003"))
        strcat(sym, ",LITRD, 2");
      else if (strpbrk(colist, "\001") && strpbrk(colist, "\004"))
        strcat(sym, ",LITGN, 2");
      else
        strcat(sym, ",CHMGD, 2");  // default is magenta
    } else
      strcat(sym, ",CHMGD, 2");  // default is magenta

    if (strlen(litvisstr))  // Obscured/faint sector?
    {
      _parseList(litvisstr, litvis, sizeof(litvis));

      if (strpbrk(litvis, "\003\007\010")) strcpy(sym, ";CA(CHBLK, 4,CHBRN, 1");
    }

    if (sectr2 <= sectr1) sectr2 += 360;

    //    Sectors are defined from seaward
    if (sectr1 > 180)
      sectr1 -= 180;
    else
      sectr1 += 180;

    if (sectr2 > 180)
      sectr2 -= 180;
    else
      sectr2 += 180;

    char arc_data[80];
    sprintf(arc_data, ",%5.1f, %5.1f, %5.1f, %5.1f", sectr1, sectr2, arc_radius,
            sector_radius);

    strcat(sym, arc_data);

    wxString ssym(sym, wxConvUTF8);
    lights06 = ssym;

    goto l06_end;
  }

l06_end:

  //      if( ps52plib->m_bShowLdisText )
  {
    // Only show Light in certain position once. Otherwise there will be
    // clutter.
    static double lastLat, lastLon;
    static wxString lastDescription;
    bool isFirstSector = true;

    if (lastLat == obj->m_lat && lastLon == obj->m_lon) isFirstSector = false;
    lastLat = obj->m_lat;
    lastLon = obj->m_lon;

    wxString litdsn01 = _LITDSN01(obj);

    if (litdsn01.Len() && isFirstSector) {
      lastDescription = litdsn01;
      lights06.Append(_T(";TX('"));
      lights06.Append(litdsn01);

      if (flare_at_45)
        lights06.Append(_T("',3,3,3,'15110',2,-1,CHBLK,23)" ));
      else
        lights06.Append(_T("',3,2,3,'15110',2,0,CHBLK,23)" ));
    }

    if (!isFirstSector && lastDescription != litdsn01) {
      lastDescription = litdsn01;
      lights06.Append(_T(";TX('"));
      lights06.Append(litdsn01);
      lights06.Append(_T("',3,2,3,'15110',2,1,CHBLK,23)" ));
    }
  }

  lights06.Append('\037');

  char *r = (char *)malloc(lights06.Len() + 1);
  strcpy(r, lights06.mb_str());

  return r;
}

static void *LITDSN01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

  printf("s52csny : LITDSN01 ERROR no conditional symbology for: %s\n",
         rzRules->LUP->OBCL);
  return NULL;
}

/*
static void *OBSTRN04a(void *param)
{
        ObjRazRules *rzRules = (ObjRazRules *)param;
//      S57Obj *obj = rzRules->obj;

        static int f03;
        if(!f03)
            printf("s52csny : OBSTRN04 ERROR no conditional symbology for:
%s\n",rzRules->LUP->OBCL); f03++; return NULL;
}
*/

wxString SNDFRM02(S57Obj *obj, double depth_value);

static void *OBSTRN04(void *param)
// Remarks: Obstructions or isolated underwater dangers of depths less than the
// safety contour which lie within the safe waters defined by the safety contour
// are to be presented by a specific isolated danger symbol and put in IMO
// category DISPLAYBASE (see (3), App.2, 1.3). This task is performed
// by the sub-procedure "UDWHAZ03" which is called by this symbology
// procedure. Objects of the class "under water rock" are handled by this
// routine as well to ensure a consistent symbolization of isolated dangers on
// the seabed.
{
  wxString obstrn04str;
  //      GString *sndfrm02str = NULL;
  wxString *udwhaz03str = NULL;
  //      GString *valsoustr   = S57_getAttVal(geo, "VALSOU");
  bool b_promote = false;

  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  // TODO    Debug Hook
  //       if(obj->Index == 534)
  //             int yyp = 5;

  double valsou = UNKNOWN;
  double depth_value = UNKNOWN;
  double least_depth = UNKNOWN;

  wxString sndfrm02str;
  wxString *quapnt01str = NULL;

  GetDoubleAttr(obj, "VALSOU", valsou);
  wxString *objName = GetStringAttrWXS(obj, "OBJNAM");

  if (valsou != UNKNOWN) {
    depth_value = valsou;
    sndfrm02str = SNDFRM02(obj, valsou);
  } else {
    if (GEO_AREA == obj->Primitive_type)
      least_depth = _DEPVAL01(obj, least_depth);

    if (UNKNOWN == least_depth) {
      int catobs = 0;
      GetIntAttr(obj, "CATOBS", catobs);
      int watlev = 0;
      GetIntAttr(obj, "WATLEV", watlev);
      int expsou = 0;
      GetIntAttr(obj, "EXPSOU", expsou);
      if (expsou != 1) {
        if (6 == catobs)
          depth_value = 0.01;
        else if (0 == watlev)  // default
          depth_value = -15.0;
        else {
          switch (watlev) {
            case 5:
              depth_value = 0.0;
              break;
            case 3:
              depth_value = 0.01;
              break;
            case 4:
            case 1:
            case 2:
            default:
              depth_value = -15.0;
              break;
          }
        }
      }
    } else
      depth_value = least_depth;
  }

  udwhaz03str = _UDWHAZ03(obj, depth_value, rzRules, &b_promote);

  if (GEO_POINT == obj->Primitive_type) {
    // Continuation A
    int sounding = FALSE;
    quapnt01str = CSQUAPNT01(obj);

    if (0 != udwhaz03str->Len()) {
      obstrn04str.Append(*udwhaz03str);
      obstrn04str.Append(*quapnt01str);

      goto end;
    }

    if (UNKNOWN != valsou) {
      if (valsou <= 20.0) {
        int watlev = -9;
        GetIntAttr(obj, "WATLEV", watlev);

        if (!strncmp(obj->FeatureName, "UWTROC", 6)) {
          if (-9 == watlev) {  // default
            obstrn04str.Append(_T(";SY(DANGER51)"));
            sounding = TRUE;
          } else {
            switch (watlev) {
              case 3:
                obstrn04str.Append(_T(";SY(DANGER51)"));
                sounding = TRUE;
                break;
              case 4:
              case 5:
                obstrn04str.Append(_T(";SY(UWTROC04)"));
                sounding = FALSE;
                break;
              default:
                obstrn04str.Append(_T(";SY(DANGER51)"));
                sounding = TRUE;
                break;
            }
          }
          if (b_promote) {
            //  Move this UWTROC object to DisplayBase category
            rzRules->obj->m_DisplayCat = DISPLAYBASE;
          }
        } else {               // OBSTRN
          if (-9 == watlev) {  // default
            obstrn04str.Append(_T(";SY(DANGER01)"));
            sounding = TRUE;
          } else {
            switch (watlev) {
              case 1:
              case 2:
                obstrn04str.Append(_T(";SY(LNDARE01)"));
                sounding = FALSE;
                break;
              case 3:
                obstrn04str.Append(_T(";SY(DANGER52)"));
                sounding = TRUE;
                break;
              case 4:
              case 5:
                obstrn04str.Append(_T(";SY(DANGER53)"));
                sounding = TRUE;
                break;
              default:
                obstrn04str.Append(_T(";SY(DANGER51)"));
                sounding = TRUE;
                break;
            }
          }
        }
      } else {  // valsou > 20.0
        obstrn04str.Append(_T(";SY(DANGER52)"));
        sounding = TRUE;
      }
    } else {  // NO valsou
      //                  GString *objlstr   = S57_getAttVal(geo, "OBJL");
      //                  int     objl       = (NULL == objlstr)? 0 :
      //                  atoi(objlstr->str);
      int watlev = -9;
      GetIntAttr(obj, "WATLEV", watlev);
      //                  GString *watlevstr = S57_getAttVal(geo, "WATLEV");

      if (!strncmp(obj->FeatureName, "UWTROC", 6)) {
        if (watlev == -9)  // default
          obstrn04str.Append(_T(";SY(UWTROC04)"));
        else {
          switch (watlev) {
            case 2:
              obstrn04str.Append(_T(";SY(LNDARE01)"));
              break;
            case 3:
              obstrn04str.Append(_T(";SY(UWTROC03)"));
              break;
            default:
              obstrn04str.Append(_T(";SY(UWTROC04)"));
              break;
          }
        }

        if (b_promote) {
          //  Move this UWTROC object to DisplayBase category
          rzRules->obj->m_DisplayCat = DISPLAYBASE;
        }
      } else {             // OBSTRN
        if (-9 == watlev)  // default
          obstrn04str = _T(";SY(OBSTRN01)");
        else {
          switch (watlev) {
            case 1:
              obstrn04str.Append(_T(";SY(OBSTRN11)"));
              break;
            case 2:
              obstrn04str.Append(_T(";SY(OBSTRN11)"));
              break;
            case 3:
              obstrn04str.Append(_T(";SY(OBSTRN01)"));
              break;
            case 4:
              obstrn04str.Append(_T(";SY(OBSTRN03)"));
              break;
            case 5:
              obstrn04str.Append(_T(";SY(OBSTRN03)"));
              break;
            default:
              obstrn04str.Append(_T(";SY(OBSTRN01)"));
              break;
          }
        }
      }
    }

    if (sounding) obstrn04str.Append(sndfrm02str);

    obstrn04str.Append(*quapnt01str);

    goto end;

  }  // if geopoint
  else {
    if (GEO_LINE == obj->Primitive_type) {
      // Continuation B

      quapnt01str = CSQUAPNT01(obj);

      if (quapnt01str->Len() > 1) {
        long quapos;
        quapnt01str->ToLong(&quapos);
        if (2 <= quapos && quapos < 10) {
          if (udwhaz03str->Len())
            obstrn04str.Append(_T(";LC(LOWACC41)"));
          else
            obstrn04str.Append(_T(";LC(LOWACC31)"));
        }
        goto end;
      }

      if (udwhaz03str->Len()) {
        obstrn04str.Append(_T("LS(DOTT,2,CHBLK)"));
        goto end;
      }

      if (UNKNOWN != valsou) {
        if (valsou <= 20.0)
          obstrn04str.Append(_T(";LS(DOTT,2,CHBLK)"));
        else
          obstrn04str.Append(_T(";LS(DASH,2,CHBLK)"));
      } else
        obstrn04str.Append(_T(";LS(DOTT,2,CHBLK)"));

      if (udwhaz03str->Len()) {
        //  Show the isolated danger symbol at the midpoint of the line
      } else {
        if (UNKNOWN != valsou)
          if (valsou <= 20.0) obstrn04str.Append(sndfrm02str);
      }
    }

    else  // Area feature
    {
      quapnt01str = CSQUAPNT01(obj);

      if (0 != udwhaz03str->Len()) {
        obstrn04str.Append(_T(";AC(DEPVS);AP(FOULAR01)"));
        obstrn04str.Append(_T(";LS(DOTT,2,CHBLK)"));
        obstrn04str.Append(*udwhaz03str);
        obstrn04str.Append(*quapnt01str);

        goto end;
      }

      if (UNKNOWN != valsou) {
        // BUG in CA49995B.000 if we get here because there is no color
        // beside NODATA (ie there is a hole in group 1 area!)
        // g_string_append(obstrn04, ";AC(UINFR)");

        if (valsou <= 20.0)
          obstrn04str.Append(_T(";LS(DOTT,2,CHBLK)"));
        else
          obstrn04str.Append(_T(";LS(DASH,2,CHBLK)"));

        obstrn04str.Append(sndfrm02str);

      } else {
        int watlev = -9;
        GetIntAttr(obj, "WATLEV", watlev);
        //                        GString *watlevstr = S57_getAttVal(geo,
        //                        "WATLEV");

        if (watlev == -9)  // default
          obstrn04str.Append(_T(";AC(DEPVS);LS(DOTT,2,CHBLK)"));
        else {
          switch (watlev) {
            case 1:
            case 2:
              obstrn04str.Append(_T(";AC(CHBRN);LS(SOLD,2,CSTLN)"));
              break;
            case 4:
              obstrn04str.Append(_T(";AC(DEPIT);LS(DASH,2,CSTLN)"));
              break;
            case 5:
            case 3: {
              int catobs = -9;
              GetIntAttr(obj, "CATOBS", catobs);
              if (6 == catobs)
                obstrn04str.Append(
                    _T(";AC(DEPVS);AP(FOULAR01);LS(DOTT,2,CHBLK)"));
              else
                obstrn04str.Append(_T(";AC(DEPVS);LS(DOTT,2,CHBLK)"));
            } break;
            default:
              obstrn04str.Append(_T(";AC(DEPVS);LS(DOTT,2,CHBLK)"));
              break;
          }
        }
      }
      obstrn04str.Append(*quapnt01str);
      goto end;

      /*
                  // Continuation C (AREAS_T)
                        GString *quapnt01str = CSQUAPNT01(geo);
                        if (NULL != udwhaz03str) {
                              g_string_append(obstrn04str,
         ";AC(DEPVS);AP(FOULAR01)"); g_string_append(obstrn04str,
         ";LS(DOTT,2,CHBLK)"); g_string_append(obstrn04str, udwhaz03str->str);
                              if (NULL != quapnt01str)
                                    g_string_append(obstrn04str,
         quapnt01str->str);

                              return obstrn04str;
                        }

                        if (UNKNOWN != valsou) {
                      // BUG in CA49995B.000 if we get here because there is no
         color
                      // beside NODATA (ie there is a hole in group 1 area!)
                      //g_string_append(obstrn04, ";AC(UINFR)");

                              if (valsou <= 20.0)
                                    g_string_append(obstrn04str,
         ";LS(DOTT,2,CHBLK)"); else g_string_append(obstrn04str,
         ";LS(DASH,2,CHBLK)");

                              g_string_append(obstrn04str, sndfrm02str->str);

                        } else {
                              GString *watlevstr = S57_getAttVal(geo, "WATLEV");

                              if (NULL == watlevstr)   // default
                                    g_string_append(obstrn04str,
         ";AC(DEPVS);LS(DOTT,2,CHBLK)"); else { if ('3' == *watlevstr->str) {
                                          GString *catobsstr =
         S57_getAttVal(geo, "CATOBS"); if (NULL != catobsstr && '6' ==
         *catobsstr->str) g_string_append(obstrn04str,
         ";AC(DEPVS);AP(FOULAR01);LS(DOTT,2,CHBLK)"); } else { switch
         (*watlevstr->str) { case '1': case '2': g_string_append(obstrn04str,
         ";AC(CHBRN);LS(SOLD,2,CSTLN)"); break; case '4':
         g_string_append(obstrn04str, ";AC(DEPIT);LS(DASH,2,CSTLN)"); break;
                                                case '5':
                                                case '3':
                                                      default :
         g_string_append(obstrn04str, ";AC(DEPVS);LS(DOTT,2,CHBLK)");  break;
                                          }
                                    }
                              }
                        }

                        g_string_append(obstrn04str, quapnt01str->str);

                        return obstrn04str;
      */
    }  // area
  }

end:

  // This is a specialization, to print OBJNAM for obstructions, if available
  // Seen in NZ ENCs, e.g. "Horn Rock"
  if (objName) {
    obstrn04str.Append(_T(";TX(OBJNAM,1,2,3,'15118',-1,-1,CHBLK,26)"));
    delete objName;
  }

  obstrn04str.Append('\037');

  char *r = (char *)malloc(obstrn04str.Len() + 1);
  strcpy(r, obstrn04str.mb_str());

  delete udwhaz03str;
  delete quapnt01str;

  return r;
}

static void *OWNSHP02(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

  printf("s52csny : OWNSHP02 ERROR no conditional symbology for: %s\n",
         rzRules->LUP->OBCL);
  return NULL;
}

static void *PASTRK01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

  printf("s52csny : PASTRK01 ERROR no conditional symbology for: %s\n",
         rzRules->LUP->OBCL);
  return NULL;
}

static void *QUALIN01(void *param);
static void *QUAPNT01(void *param);

static void *QUAPOS01(void *param)
// Remarks: The attribute QUAPOS, which identifies low positional accuracy, is
// attached to the spatial object, not the feature object. In OpenCPN
// implementation, QUAPOS of Point Objects has been converted to QUALTY
// attribute of object.
//
// This procedure passes the object to procedure QUALIN01 or QUAPNT01,
// which traces back to the spatial object, retrieves any QUAPOS attributes,
// and returns the appropriate symbolization to QUAPOS01.
{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  wxString *q = NULL;

  if (GEO_LINE == obj->Primitive_type)
    q = CSQUALIN01(obj);

  else
    q = CSQUAPNT01(obj);

  char *r = (char *)malloc(q->Len() + 1);
  strcpy(r, q->mb_str());

  delete q;

  return r;
}

static void *QUALIN01(void *param)
// Remarks: The attribute QUAPOS, which identifies low positional accuracy, is
// attached only to the spatial component(s) of an object.
//
// A line object may be composed of more than one spatial object.
//
// This procedure looks at each of the spatial
// objects, and symbolizes the line according to the positional accuracy.
{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  wxString *q = CSQUALIN01(obj);
  char *r = (char *)malloc(q->Len() + 1);
  strcpy(r, q->mb_str());

  delete q;
  return r;
}

wxString *CSQUALIN01(S57Obj *obj)
// Remarks: The attribute QUAPOS, which identifies low positional accuracy, is
// attached only to the spatial component(s) of an object.
//
// A line object may be composed of more than one spatial object.
//
// This procedure looks at each of the spatial
// objects, and symbolizes the line according to the positional accuracy.
{
  wxString qualino1;
  int quapos = 0;
  bool bquapos = GetIntAttr(obj, "QUAPOS", quapos);
  const char *line = NULL;

  if (bquapos) {
    if (2 <= quapos && quapos < 10) line = "LC(LOWACC21)";
  } else {
    if (!strncmp("COALNE", obj->FeatureName, 6)) {
      int conrad;
      bool bconrad = GetIntAttr(obj, "CONRAD", conrad);

      if (bconrad) {
        if (1 == conrad)
          line = "LS(SOLD,3,CHMGF);LS(SOLD,1,CSTLN)";
        else
          line = "LS(SOLD,1,CSTLN)";
      } else
        line = "LS(SOLD,1,CSTLN)";

    } else  // LNDARE
      line = "LS(SOLD,1,CSTLN)";
  }

  if (NULL != line) qualino1.Append(wxString(line, wxConvUTF8));

  qualino1.Append('\037');

  wxString *r = new wxString(qualino1);

  /*    char *r = (char *)malloc(qualino1.Len() + 1);
      strcpy(r, qualino1.mb_str());
  */
  return r;
}

static void *QUAPNT01(void *param)
// Remarks: The attribute QUAPOS, which identifies low positional accuracy, is
// attached only to the spatial component(s) of an object.
//
// This procedure retrieves any QUALTY (ne QUAPOS) attributes, and returns the
// appropriate symbols to the calling procedure.

{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  wxString *q = CSQUAPNT01(obj);

  char *r = (char *)malloc(q->Len() + 1);
  strcpy(r, q->mb_str());

  return r;
}

wxString *CSQUAPNT01(S57Obj *obj)
// Remarks: The attribute QUAPOS, which identifies low positional accuracy, is
// attached only to the spatial component(s) of an object.
//
// This procedure retrieves any QUALTY (ne QUAPOS) attributes, and returns the
// appropriate symbols to the calling procedure.

{
  wxString quapnt01;
  int accurate = TRUE;
  int qualty = 10;
  bool bquapos = GetIntAttr(obj, "QUAPOS", qualty);

  if (bquapos) {
    if (2 <= qualty && qualty < 10) accurate = FALSE;
  }

  if (!accurate) {
    switch (qualty) {
      case 4:
        quapnt01.Append(_T(";SY(QUAPOS01)"));
        break;  // "PA"
      case 5:
        quapnt01.Append(_T(";SY(QUAPOS02)"));
        break;  // "PD"
      case 7:
      case 8:
        quapnt01.Append(_T(";SY(QUAPOS03)"));
        break;  // "REP"
      default:
        quapnt01.Append(_T(";SY(LOWACC03)"));
        break;  // "?"
    }
  }

  quapnt01.Append('\037');

  wxString *r = new wxString;

  *r = quapnt01;

  /*    char *r = (char *)malloc(quapnt01.Len() + 1);
      strcpy(r, quapnt01.mb_str());
  */
  return r;
}

static void *SLCONS03(void *param)

// Remarks: Shoreline construction objects which have a QUAPOS attribute on
// their
// spatial component indicating that their position is unreliable are symbolized
// by a special linestyle in the place of the varied linestyles normally used.
// Otherwise this procedure applies the normal symbolization.
{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  wxString slcons03;

  bool bvalstr;
  int ival;

  const char *cmdw = NULL;  // command word

  int quapos;
  bool bquapos = GetIntAttr(obj, "QUAPOS", quapos);

  if (GEO_POINT == obj->Primitive_type) {
    if (bquapos) {
      if (2 <= quapos && quapos < 10) cmdw = "SY(LOWACC01)";
    }
  } else {
    // This instruction not found in PLIB 3.4, but seems to appear in later PLIB
    // implementations by commercial ECDIS providers, so.....
    if (GEO_AREA == obj->Primitive_type) {
      slcons03 = _T("AP(CROSSX01);");
    }

    // GEO_LINE and GEO_AREA are the same
    if (bquapos) {
      if (2 <= quapos && quapos < 10) cmdw = "LC(LOWACC01)";
    } else {
      bvalstr = GetIntAttr(obj, "CONDTN", ival);

      if (bvalstr && (1 == ival || 2 == ival))
        cmdw = "LS(DASH,1,CSTLN)";
      else {
        ival = 0;
        bvalstr = GetIntAttr(obj, "CATSLC", ival);

        if (bvalstr &&
            (6 == ival || 15 == ival || 16 == ival))  // Some sort of wharf
          cmdw = "LS(SOLD,4,CSTLN)";
        else {
          bvalstr = GetIntAttr(obj, "WATLEV", ival);

          if (bvalstr && 2 == ival)
            cmdw = "LS(SOLD,2,CSTLN)";
          else if (bvalstr && (3 == ival || 4 == ival))
            cmdw = "LS(DASH,2,CSTLN)";
          else
            cmdw = "LS(SOLD,2,CSTLN)";  // default
        }
      }
    }
  }

  // WARNING: not explicitly specified in S-52 !!
  // FIXME:this is to put AC(DEPIT) --intertidal area
  // Could this be bug in OGR ?
  /*
  if (AREAS_T == S57_getObjtype(geo)) {
      GString    *seabed01  = NULL;
      GString    *drval1str = S57_getAttVal(geo, "DRVAL1");
      double      drval1    = (NULL == drval1str)? -UNKNOWN :
  atof(drval1str->str); GString    *drval2str = S57_getAttVal(geo, "DRVAL2");
      double      drval2    = (NULL == drval2str)? -UNKNOWN :
  atof(drval2str->str);
      // NOTE: change sign of infinity (minus) to get out of bound in seabed01


      PRINTF("***********drval1=%f drval2=%f \n", drval1, drval2);
      seabed01 = _SEABED01(drval1, drval2);
      slcons03 = g_string_new(seabed01->str);
      g_string_free(seabed01, TRUE);

  }
  */

  if (NULL != cmdw) slcons03.Append(wxString(cmdw, wxConvUTF8));

  //      Match CM93 CMAPECS presentation?
  /*
      if (GEO_AREA == obj->Primitive_type)
            slcons03.Append(_T(";AC(LANDA)"));
  */

  slcons03.Append('\037');

  char *r = (char *)malloc(slcons03.Len() + 1);
  strcpy(r, slcons03.mb_str());

  return r;
}

static void *RESARE02(void *param)
// Remarks: A list-type attribute is used because an area of the object class
// RESARE may have more than one category (CATREA). For example an inshore
// traffic zone might also have fishing and anchoring prohibition and a
// prohibited area might also be a bird sanctuary or a mine field.
//
// This conditional procedure is set up to ensure that the categories of most
// importance to safe navigation are prominently symbolized, and to pass on
// all given information with minimum clutter. Only the most significant
// restriction is symbolized, and an indication of further limitations is given
// by a subscript "!" or "I". Further details are given under conditional
// symbology procedure RESTRN01
//
// Other object classes affected by attribute RESTRN are handled by
// conditional symbology procedure RESTRN01.
{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  wxString resare02;

  wxString *restrnstr = GetStringAttrWXS(obj, "RESTRN");
  //    GString *restrnstr        = S57_getAttVal(geo, "RESTRN");

  char restrn[LISTSIZE] = {'\0'};
  //    GString *catreastr        = S57_getAttVal(geo, "CATREA");
  wxString *catreastr = GetStringAttrWXS(obj, "CATREA");

  char catrea[LISTSIZE] = {'\0'};
  wxString symb;
  wxString line;
  wxString prio;

  if (NULL != catreastr)
    _parseList(catreastr->mb_str(), catrea, sizeof(catrea));

  if (NULL != restrnstr) {
    _parseList(restrnstr->mb_str(), restrn, sizeof(restrn));

    if (strpbrk(restrn, "\007\010\016")) {  // entry restrictions
      // Continuation A
      if (strpbrk(restrn,
                  "\001\002\003\004\005\006"))  // anchoring, fishing, trawling
        symb = _T(";SY(ENTRES61)");
      else {
        if (NULL != catreastr &&
            strpbrk(catrea, "\001\010\011\014\016\023\025\031"))
          symb = _T(";SY(ENTRES61)");
        else {
          if (strpbrk(restrn, "\011\012\013\014\015"))
            symb = _T(";SY(ENTRES71)");
          else {
            if (NULL != catreastr &&
                strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
              symb = _T(";SY(ENTRES71)");
            else
              symb = _T(";SY(ENTRES51)");
          }
        }
      }

      if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
        line = _T(";LC(RESARE51)");
      else
        line = _T( ";LS(DASH,2,CHMGD)");

      prio = _T(";OP(6---)");  // display prio set to 6

    } else {
      if (strpbrk(restrn, "\001\002")) {  // anchoring
        // Continuation B
        if (strpbrk(restrn, "\003\004\005\006"))
          symb = _T(";SY(ACHRES61)");
        else {
          if (NULL != catreastr &&
              strpbrk(catrea, "\001\010\011\014\016\023\025\031"))
            symb = _T(";SY(ACHRES61)");
          else {
            if (strpbrk(restrn, "\011\012\013\014\015"))
              symb = _T(";SY(ACHRES71)");
            else {
              if (NULL != catreastr &&
                  strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
                symb = _T( ";SY(ACHRES71)");
              else
                symb = _T(";SY(RESTRN51)");
            }
          }
        }

        if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
          line = _T(";LC(RESARE51)");  // could be ACHRES51 when _drawLC is
                                       // implemented fully
        else
          line = _T(";LS(DASH,2,CHMGD)");

        prio = _T(";OP(6---)");  // display prio set to 6

      } else {
        if (strpbrk(restrn, "\003\004\005\006")) {  // fishing/trawling
          // Continuation C
          if (NULL != catreastr &&
              strpbrk(catrea, "\001\010\011\014\016\023\025\031"))
            symb = _T(";SY(FSHRES51)");
          else {
            if (strpbrk(restrn, "\011\012\013\014\015"))
              symb = _T(";SY(FSHRES71)");
            else {
              if (NULL != catreastr &&
                  strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
                symb = _T(";SY(FSHRES71)");
              else
                symb = _T(";SY(FSHRES51)");
            }
          }

          if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
            line = _T(";LC(FSHRES51)");
          else
            line = _T(";LS(DASH,2,CHMGD)");

          prio = _T(";OP(6---)");  // display prio set to 6

        } else {
          if (strpbrk(restrn,
                      "\011\012\013\014\015"))  // diving, dredging, waking...
            symb = _T(";SY(INFARE51)");
          else
            symb = _T(";SY(RSRDEF51)");

          if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
            line = _T(";LC(CTYARE51)");
          else
            line = _T(";LS(DASH,2,CHMGD)");
        }
        //  Todo more for s57 3.1  Look at caris catalog ATTR::RESARE
      }
    }
  } else {
    // Continuation D
    if (NULL != catreastr) {
      if (strpbrk(catrea, "\001\010\011\014\016\023\025\031")) {
        if (strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
          symb = _T(";SY(CTYARE71)");
        else
          symb = _T(";SY(CTYARE51)");
      } else {
        if (strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
          symb = _T(";SY(INFARE51)");
        else
          symb = _T(";SY(RSRDEF51)");
      }
    } else
      symb = _T(";SY(RSRDEF51)");

    if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
      line = _T(";LC(CTYARE51)");
    else
      line = _T(";LS(DASH,2,CHMGD)");
  }

  // create command word
  if (prio.Len()) resare02.Append(prio);
  resare02.Append(line);
  resare02.Append(symb);

  resare02.Append('\037');

  char *r = (char *)malloc(resare02.Len() + 1);
  strcpy(r, resare02.mb_str());

  delete restrnstr;
  delete catreastr;

  return r;
}

static void *_RESCSP01(void *param);
static void *RESTRN01(void *param)
// Remarks: Objects subject to RESTRN01 are actually symbolised in sub-process
// RESCSP01, since the latter can also be accessed from other conditional
// symbology procedures. RESTRN01 merely acts as a "signpost" for
// RESCSP01.
//
// Object class RESARE is symbolised for the effect of attribute RESTRN in a
// separate conditional symbology procedure called RESARE02.
//
// Since many of the areas concerned cover shipping channels, the number of
// symbols used is minimised to reduce clutter. To do this, values of RESTRN are
// ranked for significance as follows: "Traffic Restriction" values of RESTRN:
// (1) RESTRN 7,8: entry prohibited or restricted
//     RESTRN 14: IMO designated "area to be avoided" part of a TSS
// (2) RESTRN 1,2: anchoring prohibited or restricted
// (3) RESTRN 3,4,5,6: fishing or trawling prohibited or restricted
// (4) "Other Restriction" values of RESTRN are:
//     RESTRN 9, 10: dredging prohibited or restricted,
//     RESTRN 11,12: diving prohibited or restricted,
//     RESTRN 13   : no wake area.
{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  wxString *restrnstr = GetStringAttrWXS(obj, "RESTRN");

  //    GString *restrn01str = S57_getAttVal(geo, "RESTRN");
  char *restrn01 = NULL;

  if (NULL != restrnstr)
    restrn01 = (char *)_RESCSP01(param);
  else
    restrn01 = NULL;

  delete restrnstr;
  return restrn01;
}

static void *_RESCSP01(void *param)
// Remarks: See procedure RESTRN01
{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  wxString rescsp01;
  //    char *rescsp01         = NULL;
  wxString *restrnstr = GetStringAttrWXS(obj, "RESTRN");
  //    GString *restrnstr        = S57_getAttVal(geo, "RESTRN");
  char restrn[LISTSIZE] = {'\0'};  // restriction list
  wxString symb;
  char *r = NULL;

  if (restrnstr->Len()) {
    _parseList(restrnstr->mb_str(), restrn, sizeof(restrn));

    if (strpbrk(restrn, "\007\010\016")) {
      // continuation A
      if (strpbrk(restrn, "\001\002\003\004\005\006"))
        symb = _T(";SY(ENTRES61)");
      else {
        if (strpbrk(restrn, "\011\012\013\014\015"))
          symb = _T(";SY(ENTRES71)");
        else
          symb = _T(";SY(ENTRES51)");
      }
    } else {
      if (strpbrk(restrn, "\001\002")) {
        // continuation B
        if (strpbrk(restrn, "\003\004\005\006"))
          symb = _T(";SY(ACHRES61)");
        else {
          if (strpbrk(restrn, "\011\012\013\014\015"))
            symb = _T( ";SY(ACHRES71)");
          else
            symb = _T(";SY(ACHRES51)");
        }

      } else {
        if (strpbrk(restrn, "\003\004\005\006")) {
          // continuation C
          if (strpbrk(restrn, "\011\012\013\014\015"))
            symb = _T(";SY(FSHRES71)");
          else
            symb = _T(";SY(FSHRES51)");

        } else {
          if (strpbrk(restrn, "\011\012\013\014\015"))
            symb = _T(";SY(INFARE51)");
          else
            symb = _T(";SY(RSRDEF51)");
        }
      }
    }

    rescsp01.Append(symb);
    rescsp01.Append('\037');

    r = (char *)malloc(rescsp01.Len() + 1);
    strcpy(r, rescsp01.mb_str());

    delete restrnstr;
  }

  return r;
}

static void *SEABED01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

//   CPLError((CPLErr)0, 0,
//            "s52csny : SEABED01 ERROR no conditional symbology for: %s\n",
//            rzRules->LUP->OBCL);
  return NULL;
}

/*
static void *SNDFRM02(void *param)
{
        ObjRazRules *rzRules = (ObjRazRules *)param;
//      S57Obj *obj = rzRules->obj;

        CPLError((CPLErr)0, 0,"s52csny : SNDFRM02 ERROR no conditional symbology
for: %s\n",rzRules->LUP->OBCL); return NULL;
}
*/

wxString SNDFRM02(S57Obj *obj, double depth_value);

static void *SOUNDG02(void *param)
// Remarks: In S-57 soundings are elements of sounding arrays rather than
// individual objects. Thus the conditional symbology methodology must examine
// each sounding of a sounding array one by one. To symbolize the depth values
// it calls the procedure SNDFRM02 which in turn translates the depth values
// into a set of symbols to be shown at the soundings position.
{
  // Shortcut.  This CS method causes a branch to an S52plib method
  // which splits multi-point soundings into separate point objects,
  // and then calls CS(SOUNDG03) on successive points below.
  char *r = (char *)malloc(6);
  strcpy(r, "MP();");

  return r;
}

static void *SOUNDG03(void *param)
// Remarks:  SOUNDG03 is a private conditional symbology,
// called to render individual points of a multi-point sounding set.
{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  wxString s = SNDFRM02(obj, obj->z);

  char *r = (char *)malloc(s.Len() + 1);
  strcpy(r, s.mb_str());

  return r;
}

wxString SNDFRM02(S57Obj *obj, double depth_value_in)
// Remarks: Soundings differ from plain text because they have to be readable
// under all circumstances and their digits are placed according to special
// rules. This conditional symbology procedure accesses a set of carefully
// designed sounding symbols provided by the symbol library and composes them to
// sounding labels. It symbolizes swept depth and it also symbolizes for low
// reliability as indicated by attributes QUASOU and QUAPOS.
{
  wxString sndfrm02;
  char temp_str[LISTSIZE] = {'\0'};
  wxString symbol_prefix;

  char symbol_prefix_a[200];

  wxString *tecsoustr = GetStringAttrWXS(obj, "TECSOU");
  char tecsou[LISTSIZE] = {'\0'};

  wxString *quasoustr = GetStringAttrWXS(obj, "QUASOU");
  char quasou[LISTSIZE] = {'\0'};

  wxString *statusstr = GetStringAttrWXS(obj, "STATUS");
  char status[LISTSIZE] = {'\0'};

  double leading_digit = 0.0;

  double safety_depth = S52_getMarinerParam(S52_MAR_SAFETY_DEPTH);

  //      Do the math to convert soundings to ft/metres/fathoms on request
  double depth_value = depth_value_in;

  //      If the sounding value from the ENC (or SENC) is bogus, so state
  if (depth_value_in > 40000.) depth_value = 99999.;
  if (depth_value_in < -1000.) depth_value = 0.;

  switch (ps52plib->m_nDepthUnitDisplay) {
    case 0:
      depth_value = depth_value * 3 * 39.37 / 36;  // feet
      safety_depth = safety_depth * 3 * 39.37 / 36;
      break;
    case 2:
      depth_value = depth_value * 3 * 39.37 / (36 * 6);  // fathoms
      safety_depth = safety_depth * 3 * 39.37 / (36 * 6);
      break;
    default:
      break;
  }

  // FIXME: test to fix the rounding error (!?)
  depth_value += (depth_value > 0.0) ? 0.01 : -0.01;
  leading_digit = (int)fabs(depth_value);

  if (depth_value <= safety_depth)  // S52_getMarinerParam(S52_MAR_SAFETY_DEPTH)
    symbol_prefix = _T("SOUNDS");
  else
    symbol_prefix = _T("SOUNDG");

  strcpy(symbol_prefix_a, symbol_prefix.mb_str());

  if (NULL != tecsoustr) {
    _parseList(tecsoustr->mb_str(), tecsou, sizeof(tecsou));
    if (strpbrk(tecsou, "\006")) {
      chk_snprintf(temp_str, LISTSIZE, ";SY(%sB1)", symbol_prefix_a);
      sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    }
  }

  if (NULL != quasoustr)
    _parseList(quasoustr->mb_str(), quasou, sizeof(quasou));
  if (NULL != statusstr)
    _parseList(statusstr->mb_str(), status, sizeof(status));

  if (strpbrk(quasou, "\003\004\005\010\011") || strpbrk(status, "\022")) {
    chk_snprintf(temp_str, LISTSIZE, ";SY(%sC2)", symbol_prefix_a);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
  } else {
    int quapos = 0;
    GetIntAttr(obj, "QUAPOS", quapos);
    if (0 != quapos) {
      if (2 <= quapos && quapos < 10) {
        chk_snprintf(temp_str, LISTSIZE, ";SY(%sC2)", symbol_prefix_a);
        sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      }
    }
  }

  // Continuation A
  if (fabs(depth_value) < 10.0) {
    //      If showing as "feet", round off to one digit only
    if ((ps52plib->m_nDepthUnitDisplay == 0) && (depth_value > 0)) {
      double r1 = depth_value;
      depth_value = wxRound(r1);
      leading_digit = (int)depth_value;
    }

    if (depth_value < 10.0) {
      // can be above water (negative)
      int fraction = (int)abs((fabs(depth_value) - leading_digit) * 10);

      chk_snprintf(temp_str, LISTSIZE, ";SY(%s1%1i)", symbol_prefix_a,
                   (int)abs(leading_digit));
      sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      if (fraction > 0) {
        chk_snprintf(temp_str, LISTSIZE, ";SY(%s5%1i)", symbol_prefix_a,
                     fraction);
        sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      }

      // above sea level (negative)
      if (depth_value < 0.0) {
        chk_snprintf(temp_str, LISTSIZE, ";SY(%sA1)", symbol_prefix_a);
        sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      }
      goto return_point;
    }
  }

  if (fabs(depth_value) < 31.0) {
    bool b_2digit = false;
    double depth_value_pos = fabs(depth_value);

    //      If showing as "feet", round off to two digits only
    if ((ps52plib->m_nDepthUnitDisplay == 0) && (depth_value_pos > 0)) {
      double r1 = depth_value;
      depth_value = wxRound(r1);
      leading_digit = (int)depth_value_pos;
      b_2digit = true;
    }

    double fraction = fabs(depth_value_pos - floor(leading_digit));

    if (fraction != 0.0) {
      fraction = fraction * 10;
      if (leading_digit >= 10.0) {
        chk_snprintf(temp_str, LISTSIZE, ";SY(%s2%1i)", symbol_prefix_a,
                     (int)leading_digit / 10);
        sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      }

      double first_digit = floor(leading_digit / 10);
      int secnd_digit = (int)(floor(leading_digit - (first_digit * 10)));
      chk_snprintf(temp_str, LISTSIZE, ";SY(%s1%1i)", symbol_prefix_a,
                   secnd_digit /*(int)leading_digit*/);
      sndfrm02.Append(wxString(temp_str, wxConvUTF8));

      if (!b_2digit) {
        if ((int)fraction > 0) {
          chk_snprintf(temp_str, LISTSIZE, ";SY(%s5%1i)", symbol_prefix_a,
                       (int)fraction);
          sndfrm02.Append(wxString(temp_str, wxConvUTF8));
        }
      }

      if (depth_value < 0.0) {
        chk_snprintf(temp_str, LISTSIZE, ";SY(%sA1)", symbol_prefix_a);
        sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      }

      goto return_point;
    }
  }

  // Continuation B
  if (fabs(depth_value) < 100.0) {
    leading_digit = fabs(leading_digit);

    double first_digit = floor(leading_digit / 10);
    double secnd_digit = floor(leading_digit - (first_digit * 10));

    if (depth_value < 0.0) {
      chk_snprintf(temp_str, LISTSIZE, ";SY(%s2%1i)", symbol_prefix_a,
                   (int)first_digit);
      sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      chk_snprintf(temp_str, LISTSIZE, ";SY(%s1%1i)", symbol_prefix_a,
                   (int)secnd_digit);
      sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      chk_snprintf(temp_str, LISTSIZE, ";SY(%sA1)", symbol_prefix_a);
      sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    } else {
      chk_snprintf(temp_str, LISTSIZE, ";SY(%s1%1i)", symbol_prefix_a,
                   (int)first_digit);
      sndfrm02.Append(wxString(temp_str, wxConvUTF8));
      chk_snprintf(temp_str, LISTSIZE, ";SY(%s0%1i)", symbol_prefix_a,
                   (int)secnd_digit);
      sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    }
    goto return_point;
  }

  if (depth_value < 1000.0) {
    double first_digit = floor(leading_digit / 100);
    double secnd_digit = floor((leading_digit - (first_digit * 100)) / 10);
    double third_digit =
        floor(leading_digit - (first_digit * 100) - (secnd_digit * 10));

    chk_snprintf(temp_str, LISTSIZE, ";SY(%s2%1i)", symbol_prefix_a,
                 (int)first_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s1%1i)", symbol_prefix_a,
                 (int)secnd_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s0%1i)", symbol_prefix_a,
                 (int)third_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));

    goto return_point;
  }

  if (depth_value < 10000.0) {
    double first_digit = floor(leading_digit / 1000);
    double secnd_digit = floor((leading_digit - (first_digit * 1000)) / 100);
    double third_digit = floor(
        (leading_digit - (first_digit * 1000) - (secnd_digit * 100)) / 10);
    double last_digit = floor(leading_digit - (first_digit * 1000) -
                              (secnd_digit * 100) - (third_digit * 10));

    chk_snprintf(temp_str, LISTSIZE, ";SY(%s2%1i)", symbol_prefix_a,
                 (int)first_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s1%1i)", symbol_prefix_a,
                 (int)secnd_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s0%1i)", symbol_prefix_a,
                 (int)third_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s4%1i)", symbol_prefix_a,
                 (int)last_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));

    goto return_point;
  }

  // Continuation C
  {
    double first_digit = floor(leading_digit / 10000);
    double secnd_digit = floor((leading_digit - (first_digit * 10000)) / 1000);
    double third_digit = floor(
        (leading_digit - (first_digit * 10000) - (secnd_digit * 1000)) / 100);
    double fourth_digit = floor((leading_digit - (first_digit * 10000) -
                                 (secnd_digit * 1000) - (third_digit * 100)) /
                                10);
    double last_digit =
        floor(leading_digit - (first_digit * 10000) - (secnd_digit * 1000) -
              (third_digit * 100) - (fourth_digit * 10));

    chk_snprintf(temp_str, LISTSIZE, ";SY(%s3%1i)", symbol_prefix_a,
                 (int)first_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s2%1i)", symbol_prefix_a,
                 (int)secnd_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s1%1i)", symbol_prefix_a,
                 (int)third_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s0%1i)", symbol_prefix_a,
                 (int)fourth_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));
    chk_snprintf(temp_str, LISTSIZE, ";SY(%s4%1i)", symbol_prefix_a,
                 (int)last_digit);
    sndfrm02.Append(wxString(temp_str, wxConvUTF8));

    goto return_point;
  }

return_point:
  sndfrm02.Append('\037');

  delete tecsoustr;
  delete quasoustr;
  delete statusstr;

  return sndfrm02;
}

static void *TOPMAR01(void *param)
// Remarks: Topmark objects are to be symbolized through consideration of their
// platforms e.g. a buoy. Therefore this conditional symbology procedure
// searches for platforms by looking for other objects that are located at the
// same position.. Based on the finding whether the platform is rigid or
// floating, the respective upright or sloping symbol is selected and presented
// at the objects location. Buoyf symbols and topmark symbols have been
// carefully designed to fit to each other when combined at the same position.
// The result is a composed symbol that looks like the traditional symbols the
// mariner is used to.
{
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  int top_int = 0;
  bool battr = GetIntAttr(obj, "TOPSHP", top_int);

  wxString sy;

  if (!battr)
    sy = _T(";SY(QUESMRK1)");
  else {
    int floating = FALSE;  // not a floating platform
    int topshp = (!battr) ? 0 : top_int;

    if (TRUE == _atPtPos(obj, GetChartFloatingATONArray(rzRules), false))
       floating = TRUE;

    if (floating) {
      // floating platform
      switch (topshp) {
        case 1:
          sy = _T(";SY(TOPMAR02)");
          break;
        case 2:
          sy = _T(";SY(TOPMAR04)");
          break;
        case 3:
          sy = _T(";SY(TOPMAR10)");
          break;
        case 4:
          sy = _T(";SY(TOPMAR12)");
          break;

        case 5:
          sy = _T(";SY(TOPMAR13)");
          break;
        case 6:
          sy = _T(";SY(TOPMAR14)");
          break;
        case 7:
          sy = _T(";SY(TOPMAR65)");
          break;
        case 8:
          sy = _T(";SY(TOPMAR17)");
          break;

        case 9:
          sy = _T(";SY(TOPMAR16)");
          break;
        case 10:
          sy = _T(";SY(TOPMAR08)");
          break;
        case 11:
          sy = _T(";SY(TOPMAR07)");
          break;
        case 12:
          sy = _T(";SY(TOPMAR14)");
          break;

        case 13:
          sy = _T(";SY(TOPMAR05)");
          break;
        case 14:
          sy = _T(";SY(TOPMAR06)");
          break;
        case 17:
          sy = _T(";SY(TMARDEF2)");
          break;
        case 18:
          sy = _T(";SY(TOPMAR10)");
          break;

        case 19:
          sy = _T(";SY(TOPMAR13)");
          break;
        case 20:
          sy = _T(";SY(TOPMAR14)");
          break;
        case 21:
          sy = _T(";SY(TOPMAR13)");
          break;
        case 22:
          sy = _T(";SY(TOPMAR14)");
          break;

        case 23:
          sy = _T(";SY(TOPMAR14)");
          break;
        case 24:
          sy = _T(";SY(TOPMAR02)");
          break;
        case 25:
          sy = _T(";SY(TOPMAR04)");
          break;
        case 26:
          sy = _T(";SY(TOPMAR10)");
          break;

        case 27:
          sy = _T(";SY(TOPMAR17)");
          break;
        case 28:
          sy = _T(";SY(TOPMAR18)");
          break;
        case 29:
          sy = _T(";SY(TOPMAR02)");
          break;
        case 30:
          sy = _T(";SY(TOPMAR17)");
          break;

        case 31:
          sy = _T(";SY(TOPMAR14)");
          break;
        case 32:
          sy = _T(";SY(TOPMAR10)");
          break;
        case 33:
          sy = _T(";SY(TMARDEF2)");
          break;
        default:
          sy = _T(";SY(TMARDEF2)");
          break;
      }
    } else {
      // not a floating platform
      switch (topshp) {
        case 1:
          sy = _T(";SY(TOPMAR22)");
          break;
        case 2:
          sy = _T(";SY(TOPMAR24)");
          break;
        case 3:
          sy = _T(";SY(TOPMAR30)");
          break;
        case 4:
          sy = _T(";SY(TOPMAR32)");
          break;

        case 5:
          sy = _T(";SY(TOPMAR33)");
          break;
        case 6:
          sy = _T(";SY(TOPMAR34)");
          break;
        case 7:
          sy = _T(";SY(TOPMAR85)");
          break;
        case 8:
          sy = _T(";SY(TOPMAR86)");
          break;

        case 9:
          sy = _T(";SY(TOPMAR36)");
          break;
        case 10:
          sy = _T(";SY(TOPMAR28)");
          break;
        case 11:
          sy = _T(";SY(TOPMAR27)");
          break;
        case 12:
          sy = _T(";SY(TOPMAR14)");
          break;

        case 13:
          sy = _T(";SY(TOPMAR25)");
          break;
        case 14:
          sy = _T(";SY(TOPMAR26)");
          break;
        case 15:
          sy = _T(";SY(TOPMAR88)");
          break;
        case 16:
          sy = _T(";SY(TOPMAR87)");
          break;

        case 17:
          sy = _T(";SY(TMARDEF1)");
          break;
        case 18:
          sy = _T(";SY(TOPMAR30)");
          break;
        case 19:
          sy = _T(";SY(TOPMAR33)");
          break;
        case 20:
          sy = _T(";SY(TOPMAR34)");
          break;

        case 21:
          sy = _T(";SY(TOPMAR33)");
          break;
        case 22:
          sy = _T(";SY(TOPMAR34)");
          break;
        case 23:
          sy = _T(";SY(TOPMAR34)");
          break;
        case 24:
          sy = _T(";SY(TOPMAR22)");
          break;

        case 25:
          sy = _T(";SY(TOPMAR24)");
          break;
        case 26:
          sy = _T(";SY(TOPMAR30)");
          break;
        case 27:
          sy = _T(";SY(TOPMAR86)");
          break;
        case 28:
          sy = _T(";SY(TOPMAR89)");
          break;

        case 29:
          sy = _T(";SY(TOPMAR22)");
          break;
        case 30:
          sy = _T(";SY(TOPMAR86)");
          break;
        case 31:
          sy = _T(";SY(TOPMAR14)");
          break;
        case 32:
          sy = _T(";SY(TOPMAR30)");
          break;
        case 33:
          sy = _T(";SY(TMARDEF1)");
          break;
        default:
          sy = _T(";SY(TMARDEF1)");
          break;
      }
    }
  }

  wxString topmar;
  topmar.Append(sy);
  topmar.Append('\037');

  char *r = (char *)malloc(topmar.Len() + 1);
  strcpy(r, topmar.mb_str());

  return r;
}

static void *UDWHAZ03(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

//   CPLError((CPLErr)0, 0,
//            "s52csny : UDWHAZ03 ERROR no conditional symbology for: %s\n",
//            rzRules->LUP->OBCL);
  return NULL;
}

static void *VESSEL01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

//   CPLError((CPLErr)0, 0,
//            "s52csny : VESSEL01 ERROR no conditional symbology for: %s\n",
//            rzRules->LUP->OBCL);
  return NULL;
}

static void *VRMEBL01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  //      S57Obj *obj = rzRules->obj;

//FIXME plib
//   CPLError((CPLErr)0, 0,
//            "s52csny : VRMEBL01 ERROR no conditional symbology for: %s\n",
//            rzRules->LUP->OBCL);
  return NULL;
}

/*
static void *WRECKS02a(void *param)
{
        ObjRazRules *rzRules = (ObjRazRules *)param;
//      S57Obj *obj = rzRules->obj;

        static int f07;
        if(!f07)
                CPLError((CPLErr)0, 0,"s52csny : WRECKS02 ERROR no conditional
symbology for: %s\n",rzRules->LUP->OBCL); f07++; return NULL;
}
*/

static void *WRECKS02(void *param)
// Remarks: Wrecks of depths less than the safety contour which lie within the
// safe waters defined by the safety contour are to be presented by a specific
// isolated danger symbol and put in IMO category DISPLAYBASE (see (3), App.2,
// 1.3). This task is performed by the sub-procedure "UDWHAZ03" which is
// called by this symbology procedure.
{
  wxString wrecks02str;
  wxString sndfrm02str;
  wxString *udwhaz03str = NULL;
  wxString *quapnt01str = NULL;
  double least_depth = UNKNOWN;
  double depth_value = UNKNOWN;
  //    GString *valsoustr   = S57_getAttVal(geo, "VALSOU");
  double valsou = UNKNOWN;
  bool b_promote = false;

  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;

  GetDoubleAttr(obj, "VALSOU", valsou);

  int watlev = -9;
  GetIntAttr(obj, "WATLEV", watlev);
  int catwrk = -9;
  GetIntAttr(obj, "CATWRK", catwrk);

  int quasou = -9;
  // QUASOU is a list ie a string for us
  wxString *quasoustr = GetStringAttrWXS(obj, "QUASOU");
  char quasouchar[LISTSIZE] = {'\0'};

  double safety_contour = S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR);

  if (UNKNOWN != valsou) {
    depth_value = valsou;
    sndfrm02str = SNDFRM02(obj, depth_value);
  } else {
    if (GEO_AREA == obj->Primitive_type)
      least_depth = _DEPVAL01(obj, least_depth);

    if (least_depth == UNKNOWN)
    /*
            {
                // WARNING: ambiguity removed in WRECKS03 (see update)

                if (-9 == watlev) // default
                    depth_value = -15.0;
                else
                    switch (watlev)
                      { // ambiguous
                        case 1:
                        case 2: depth_value = -15.0 ; break;
                        case 3: depth_value =   0.01; break;
                        case 4: depth_value = -15.0 ; break;
                        case 5: depth_value =   0.0 ; break;
                        case 6: depth_value = -15.0 ; break;
                        default :
                              {
                                  if (-9 != catwrk)
                                  {
                                        switch (catwrk)
                                        {
                                              case 1: depth_value =  20.0;
       break; case 2: depth_value =   0.0; break; case 4: case 5: depth_value =
       -15.0; break;
                                        }
                                   }
                              }
                      }
            }
    */
    ////////////////////////////////////////////////
    //    DSR New Logic Here  (FIXME)
    {
      if (-9 != catwrk) {
        switch (catwrk) {
          case 1:
            depth_value = 20.0;
            break;  // safe
          case 2:
            depth_value = 0.0;
            break;  // dangerous
          case 4:
          case 5:
            depth_value = -15.0;
            break;
        }
      } else {
        if (-9 == watlev)  // default
          depth_value = -15.0;
        else
          switch (watlev) {
            case 1:
            case 2:
              depth_value = -15.0;
              break;
            case 3:
              depth_value = 0.01;
              break;
            case 4:
              depth_value = -15.0;
              break;
            case 5:
              depth_value = 0.0;
              break;
            case 6:
              depth_value = -15.0;
              break;
          }
      }

    }

    else
      depth_value = least_depth;
  }
  if (NULL != quasoustr)
    _parseList(quasoustr->mb_str(), quasouchar, sizeof(quasouchar));

  if (quasouchar[0] == 0 || NULL == strpbrk(quasouchar, "\07")) {
    // Fixes FS 165   XXX where it is?
    // 7 is 'least depth unknown, safe clearance at value shown'
    udwhaz03str = _UDWHAZ03(obj, depth_value, rzRules, &b_promote);

  } else {
    quasou = 7;
    udwhaz03str = new wxString();
  }
  quapnt01str = CSQUAPNT01(obj);

  if (GEO_POINT == obj->Primitive_type) {
    if (0 != udwhaz03str->Len()) {
      wrecks02str = wxString(*udwhaz03str);

      wrecks02str.Append(*quapnt01str);

    } else {
      // Continuation A (POINT_T)
      if (UNKNOWN != valsou) {
        ///////////////////////////////////////////
        //    DSR New logic here, FIXME check s52 specs

        /*
                        if (valsou <= 20.0)
                        {
                            wrecks02str = wxString(";SY(DANGER51)");
                            if (NULL != sndfrm02str)
                                wrecks02str.Append(sndfrm02str);
                        }
                        else
                            wrecks02str = wxString(";SY(DANGER52)");
        */
        if ((valsou <
             safety_contour) /* || (2 == catwrk)*/)  // maybe redundant, seems
                                                     // like wrecks with valsou
                                                     // < 20 are always coded as
                                                     // "dangerous wrecks"
                                                     // Excluding (2 == catwrk)
                                                     // matches Caris logic
          wrecks02str = wxString(_T(";SY(DANGER51)"));
        else
          wrecks02str = wxString(_T(";SY(DANGER52)"));
        wrecks02str.Append(_T(";TX('Wk',2,1,2,'15110',1,0,CHBLK,21)"));
        if (7 == quasou)  // Fixes FS 165
          wrecks02str.Append(_T(";SY(WRECKS07)"));

        wrecks02str.Append(sndfrm02str);  // always show valsou depth
        ///////////////////////////////////////////

        wrecks02str.Append(*udwhaz03str);
        wrecks02str.Append(*quapnt01str);

      } else {
        wxString sym;

        if (-9 != catwrk && -9 != watlev) {
          if (1 == catwrk && 3 == watlev)
            sym = _T(";SY(WRECKS04)");
          else {
            if (2 == catwrk && 3 == watlev)
              sym = _T(";SY(WRECKS05)");
            else {
              if (4 == catwrk || 5 == catwrk)
                sym = _T(";SY(WRECKS01)");
              else {
                if (1 == watlev || 2 == watlev || 5 == watlev || 4 == watlev) {
                  sym = _T(";SY(WRECKS01)");
                } else
                  sym = _T(";SY(WRECKS05)");  // default
              }
            }
          }
        }

        wrecks02str = sym;
        if (NULL != quapnt01str) wrecks02str.Append(*quapnt01str);
      }
    }

  } else {
    // Continuation B (AREAS_T)
    int quapos = 0;
    GetIntAttr(obj, "QUAPOS", quapos);

    wxString line;

    if (2 <= quapos && quapos < 10)
      line = _T(";LC(LOWACC41)");
    else {
      if (0 != udwhaz03str->Len())
        line = _T(";LS(DOTT,2,CHBLK)");
      else {
        if (UNKNOWN != valsou) {
          if (valsou <= 20)
            line = _T(";LS(DOTT,2,CHBLK)");
          else
            line = _T(";LS(DASH,2,CHBLK)");
        } else {
          if (-9 == watlev)
            line = _T(";LS(DOTT,2,CSTLN)");
          else {
            switch (watlev) {
              case 1:
              case 2:
                line = _T(";LS(SOLD,2,CSTLN)");
                break;
              case 4:
                line = _T(";LS(DASH,2,CSTLN)");
                break;
              case 3:
              case 5:

              default:
                line = _T(";LS(DOTT,2,CSTLN)");
                break;
            }
          }
        }
      }
    }
    wrecks02str = wxString(line);

    if (UNKNOWN != valsou) {
      if (valsou <= 20) {
        wrecks02str.Append(*udwhaz03str);
        wrecks02str.Append(*quapnt01str);
        wrecks02str.Append(sndfrm02str);

      } else {
        // NOTE: ??? same as above ???
        wrecks02str.Append(*udwhaz03str);
        wrecks02str.Append(*quapnt01str);
      }
    } else {
      wxString ac;

      if (-9 == watlev)
        ac = _T(";AC(DEPVS)");
      else
        switch (watlev) {
          case 1:
          case 2:
            ac = _T(";AC(CHBRN)");
            break;
          case 4:
            ac = _T(";AC(DEPIT)");
            break;
          case 5:
          case 3:
          default:
            ac = _T(";AC(DEPVS)");
            break;
        }

      wrecks02str.Append(ac);

      wrecks02str.Append(*udwhaz03str);
      wrecks02str.Append(*quapnt01str);
    }
  }

  wrecks02str.Append('\037');

  char *r = (char *)malloc(wrecks02str.Len() + 1);
  strcpy(r, wrecks02str.mb_str());

  delete udwhaz03str;
  delete quapnt01str;
  delete quasoustr;
  return r;
}

static wxString _LITDSN01(S57Obj *obj)
// Remarks: In S-57 the light characteristics are held as a series of attributes
// values. The mariner may wish to see a light description text string displayed
// on the screen similar to the string commonly found on a paper chart. This
// conditional procedure, reads the attribute values from the above list of
// attributes and composes a light description string which can be displayed.
// This procedure is provided as a C function which has as input, the above
// listed attribute values and as output, the light description.
{
  // CATLIT, LITCHR, COLOUR, HEIGHT, LITCHR, SIGGRP, SIGPER, STATUS, VALNMR

  char colist[20];
  wxString return_value;
#if 0
      // XXX CATLIT
      int catlit = -9;
      GetIntAttr(obj, "CATLIT", catlit);

      if(-9 != catlit)
      {
      }
#endif

  /*
    1: directional function  IP 30.1-3;  475.7;
    2: rear/upper light
    3: front/lower light
    4: leading light           IP 20.1-3;      475.6;
    5: aero light                  IP 60;      476.1;
    6: air obstruction light IP 61;      476.2;
    7: fog detector light        IP 62;  477;
    8: flood light                 IP 63;      478.2;
    9: strip light                 IP 64;      478.5;
    10: subsidiary light          IP 42;  471.8;
    11: spotlight
    12: front
    13: rear
    14: lower
    15: upper
    16: moire' effect           IP 31;    475.8;
    17: emergency
    18: bearing light                   478.1;
    19: horizontally disposed
    20: vertically disposed
  */

  // LITCHR
  int litchr = -9;
  wxString spost(_T(""));
  GetIntAttr(obj, "LITCHR", litchr);

  bool b_grp2 = false;  // 2 GRP attributes expected
  if (-9 != litchr) {
    switch (litchr) {
        /*
                          case 1:   return_value.Append(_T("F"));    break;
                          case 2:   return_value.Append(_T("Fl"));   break;
                          case 3:   return_value.Append(_T("Fl"));   break;
                          case 4:   return_value.Append(_T("Q"));    break;
                          case 7:   return_value.Append(_T("Iso"));  break;
                          case 8:   return_value.Append(_T("Occ"));  break;
                          case 12:  return_value.Append(_T("Mo"));   break;
        */

      case 1:
        return_value.Append(_T("F"));
        break;  // fixed     IP 10.1;
      case 2:
        return_value.Append(_T("Fl"));
        break;  // flashing  IP 10.4;
      case 3:
        return_value.Append(_T("LFl"));
        break;  // long-flashing   IP 10.5;
      case 4:
        return_value.Append(_T("Q"));
        break;  // quick-flashing  IP 10.6;
      case 5:
        return_value.Append(_T("VQ"));
        break;  // very quick-flashing   IP 10.7;
      case 6:
        return_value.Append(_T("UQ"));
        break;  // ultra quick-flashing  IP 10.8;
      case 7:
        return_value.Append(_T("Iso"));
        break;  // isophased IP 10.3;
      case 8:
        return_value.Append(_T("Occ"));
        break;  // occulting IP 10.2;
      case 9:
        return_value.Append(_T("IQ"));
        break;  // interrupted quick-flashing  IP 10.6;
      case 10:
        return_value.Append(_T("IVQ"));
        break;  // interrupted very quick-flashing   IP 10.7;
      case 11:
        return_value.Append(_T("IUQ"));
        break;  // interrupted ultra quick-flashing  IP 10.8;
      case 12:
        return_value.Append(_T("Mo"));
        break;  // morse     IP 10.9;
      case 13:
        return_value.Append(_T("F + Fl"));
        b_grp2 = true;
        break;  // fixed/flash     IP 10.10;
      case 14:
        return_value.Append(_T("Fl + LFl"));
        b_grp2 = true;
        break;  // flash/long-flash
      case 15:
        return_value.Append(_T("Occ + Fl"));
        b_grp2 = true;
        break;  // occulting/flash
      case 16:
        return_value.Append(_T("F + LFl"));
        b_grp2 = true;
        break;  // fixed/long-flash
      case 17:
        return_value.Append(_T("Al Occ"));
        break;  // occulting alternating
      case 18:
        return_value.Append(_T("Al LFl"));
        break;  // long-flash alternating
      case 19:
        return_value.Append(_T("Al Fl"));
        break;  // flash alternating
      case 20:
        return_value.Append(_T("Al Grp"));
        break;  // group alternating
      case 21:
        return_value.Append(_T("F"));
        spost = _T(" (vert)");
        break;  // 2 fixed (vertical)
      case 22:
        return_value.Append(_T("F"));
        spost = _T(" (horz)");
        break;  // 2 fixed (horizontal)
      case 23:
        return_value.Append(_T("F"));
        spost = _T(" (vert)");
        break;  // 3 fixed (vertical)
      case 24:
        return_value.Append(_T("F"));
        spost = _T(" (horz)");
        break;  // 3 fixed (horizontal)
      case 25:
        return_value.Append(_T("Q + LFl"));
        b_grp2 = true;
        break;  // quick-flash plus long-flash
      case 26:
        return_value.Append(_T("VQ + LFl"));
        b_grp2 = true;
        break;  // very quick-flash plus long-flash
      case 27:
        return_value.Append(_T("UQ + LFl"));
        b_grp2 = true;
        break;  // ultra quick-flash plus long-flash
      case 28:
        return_value.Append(_T("Alt"));
        break;  // alternating
      case 29:
        return_value.Append(_T("F + Alt"));
        b_grp2 = true;
        break;  // fixed and alternating flashing

      default:
        break;
    }
  }

  int nfirst_grp = -1;
  if (b_grp2) {
    wxString ret_new;
    nfirst_grp = return_value.Find(_T(" "));
    if (wxNOT_FOUND != nfirst_grp) {
      ret_new = return_value.Mid(0, nfirst_grp);
      ret_new.Append(_T("(?)"));
      ret_new.Append(return_value.Mid(nfirst_grp));
      return_value = ret_new;
      nfirst_grp += 1;
    }
  }

  // SIGGRP, (c)(c) ...
  char grp_str[20] = {'\0'};
  GetStringAttr(obj, "SIGGRP", grp_str, 19);
  if (strlen(grp_str)) {
    wxString ss(grp_str, wxConvUTF8);

    if (b_grp2) {
      wxStringTokenizer tkz(ss, _T("()"));

      int n_tok = 0;
      while (tkz.HasMoreTokens() && (n_tok < 2)) {
        wxString s = tkz.GetNextToken();
        if (s.Len()) {
          if ((n_tok == 0) && (nfirst_grp > 0)) {
            return_value[nfirst_grp] = s[0];
          } else {
            if (s != _T("1")) {
              return_value.Append(_T("("));
              return_value.Append(s);
              return_value.Append(_T(")"));
            }
          }

          n_tok++;
        }
      }
    } else {
      if (ss != _T("(1)")) return_value.Append(ss);
    }
  }

  // COLOUR,
  char col_str[20] = {'\0'};

  // Don't show for sectored lights since we are only showing one of the
  // sectors.
  double sectrTest;
  bool hasSectors = GetDoubleAttr(obj, "SECTR1", sectrTest);

  if (!hasSectors) {
    GetStringAttr(obj, "COLOUR", col_str, 19);

    int n_cols = 0;
    if (strlen(col_str)) n_cols = _parseList(col_str, colist, sizeof(colist));

    if (n_cols) return_value.Append(_T(" "));

    for (int i = 0; i < n_cols; i++) {
      switch (colist[i]) {
        case 1:
          return_value.Append(_T("W"));
          break;
        case 3:
          return_value.Append(_T("R"));
          break;
        case 4:
          return_value.Append(_T("G"));
          break;
        case 6:
          return_value.Append(_T("Y"));
          break;
        default:
          break;
      }
    }
  }

  /*
    1: white     IP 11.1;    450.2-3;
    2: black
    3: red IP 11.2;    450.2-3;
    4: green     IP 11.3;    450.2-3;
    5: blue      IP 11.4;    450.2-3;
    6: yellow    IP 11.6;    450.2-3;
    7: grey
    8: brown
    9: amber     IP 11.8;    450.2-3;
    10: violet    IP 11.5;    450.2-3;
    11: orange    IP 11.7;    450.2-3;
    12: magenta
    13: pink
  */

  // SIGPER, xx.xx
  double sigper = UNKNOWN;
  GetDoubleAttr(obj, "SIGPER", sigper);

  if (UNKNOWN != sigper) {
    wxString s;
    if (fabs(wxRound(sigper) - sigper) > 0.01)
      s.Printf(_T("%4.1fs"), sigper);
    else
      s.Printf(_T("%2.0fs"), sigper);

    s.Trim(false);  // remove leading spaces
    s.Prepend(_T(" "));
    return_value.Append(s);
  }

  // HEIGHT, xxx.x
  double height = UNKNOWN;
  GetDoubleAttr(obj, "HEIGHT", height);

  if (UNKNOWN != height) {
    wxString s;
    switch (ps52plib->m_nDepthUnitDisplay) {
      case 0:  // feet
      case 2:  // fathoms
        s.Printf(_T("%3.0fft"), height * 3 * 39.37 / 36);
        break;
      default:
        s.Printf(_T("%3.0fm"), height);
        break;
    }

    s.Trim(false);  // remove leading spaces
    s.Prepend(_T(" "));
    return_value.Append(s);
  }

  // VALNMR, xx.x
  double valnmr = UNKNOWN;
  GetDoubleAttr(obj, "VALNMR", valnmr);

  if (UNKNOWN != valnmr && !hasSectors) {
    wxString s;
    s.Printf(_T("%2.0fNm"), valnmr);
    s.Trim(false);  // remove leading spaces
    s.Prepend(_T(" "));
    return_value.Append(s);
  }

#if 0

    // STATUS,
      gstr = S57_getAttVal(geo, "STATUS");
      if (NULL != gstr)
            g_string_append(litdsn01, gstr->str);

    /*
      1: permanent
      2: occasional      IP 50;      473.2;
      3: recommended     IN 10;      431.1;
      4: not in use      IL 14, 44;  444.7;
      5: periodic/intermittent IC 21; IQ 71;     353.3; 460.5;
      6: reserved  IN 12.9;
      7: temporary IP 54;
      8: private   IQ 70;
      9: mandatory
      10: destroyed/ruined
      11: extinguished
      12: illuminated
      13: historic
      14: public
      15: synchronized
      16: watched
      17: un-watched
      18: existence doubtful
    */

#endif

  return_value.Append(spost);  // add any final modifiers

  return return_value;
}

static void *SYMINS01(void *param) {
  ObjRazRules *rzRules = (ObjRazRules *)param;
  S57Obj *obj = rzRules->obj;
  char symins[80] = {'\0'};
  GetStringAttr(obj, "SYMINS", symins, 79);

  strcat(symins, "\037");
  char *r = (char *)malloc(strlen(symins) + 1);
  strcpy(r, symins);

  return r;
}

//--------------------------------
//
// JUMP TABLE SECTION
//
//--------------------------------
Cond condTable[] = {
    {"CLRLIN01", CLRLIN01},
    {"DATCVR01", DATCVR01},
    {"DATCVR01", DATCVR01},
    {"DEPARE01", DEPARE01},
    {"DEPARE02", DEPARE01},  // new in PLIB 3_3, opencpn defaults to DEPARE01
    {"DEPCNT02", DEPCNT02},
    {"DEPVAL01", DEPVAL01},
    {"LEGLIN02", LEGLIN02},
    {"LIGHTS05", LIGHTS05},  // new in PLIB 3_3, replaces LIGHTS04
    {"LITDSN01", LITDSN01},
    {"OBSTRN04", OBSTRN04},
    {"OWNSHP02", OWNSHP02},
    {"PASTRK01", PASTRK01},
    {"QUAPOS01", QUAPOS01},
    {"QUALIN01", QUALIN01},
    {"QUAPNT01", QUAPNT01},
    {"SLCONS03", SLCONS03},
    {"RESARE02", RESARE02},
    {"RESTRN01", RESTRN01},
    //   {"RESCSP01",RESCSP01},
    {"SEABED01", SEABED01},
    //   {"SNDFRM02",SNDFRM02},
    {"SOUNDG02", SOUNDG02},
    {"TOPMAR01", TOPMAR01},
    {"UDWHAZ03", UDWHAZ03},
    {"VESSEL01", VESSEL01},
    {"VRMEBL01", VRMEBL01},
    {"WRECKS02", WRECKS02},
    {"SOUNDG03", SOUNDG03},  // special case for MPS
    {"SYMINS01", SYMINS01},  //  Container for Virtual AIS ATONS, special case
    {"########", NULL}};

#if 0
// S52CS.c : Conditional Symbologie procedure 3.2 (CS)
//
// Project:  OpENCview

/*
    This file is part of the OpENCview project, a viewer of ENC
    Copyright (C) 2000-2004  Sylvain Duclos sduclos@users.sourceforgue.net

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// NOTE: remarks commenting each CS are extracted from pslb03_2.pdf (sec. 12)

// FIXME: DEPCNT02: call DB for area DEPARE & DRGARE that intersect this line
// FIXME:_DEPVAL01: call DB for area DEPARE & UNSARE that intersect this area
// FIXME:_UDWHAZ03: call DB for area DRGARE & DEPARE that intersect this point/area

#include "S52CS.h"

#include "S52utils.h"  // PRINTF()

#include <stdlib.h>  // atof()
#include <math.h>    // fabsf(), HUGE_VAL
#include <ctype.h>   // isdigit()

#define UNKNOWN 1e6  // HUGE_VAL   // INFINITY/NAN

#define COALNE 30   // Coastline
#define DEPARE 42   // Depth area
#define DEPCNT 43   // Depth contour
#define DRGARE 46   // Dredged area
#define UWTROC 153  // Underwater rock / awash rock
#define WRECKS 159  // Wreck

// point list name
#define LIGHTLIST 0
#define SECTRLIST 1
#define FLOATLIST 2  // floating platform
#define RIGIDLIST 3  // rigid platform
//static GPtrArray   *_lightList = NULL;
//static GPtrArray   *_sectrList = NULL;
//static GPtrArray   *_flaotList = NULL;
static GPtrArray *_ptList[] = {NULL, NULL, NULL, NULL};

int S52_state = 1;

// size of attributes value list buffer
#define LISTSIZE 16  // list size

#define version "3.2.0"
char     *S52_CS_version()
{
    return version;
}

int       S52_CS_init()
{
    _ptList[LIGHTLIST] = g_ptr_array_new();
    _ptList[SECTRLIST] = g_ptr_array_new();
    _ptList[FLOATLIST] = g_ptr_array_new();
    _ptList[RIGIDLIST] = g_ptr_array_new();

    return 1;
}

int       S52_CS_done()
{
    g_ptr_array_free(_ptList[LIGHTLIST], TRUE);
    g_ptr_array_free(_ptList[SECTRLIST], TRUE);
    g_ptr_array_free(_ptList[FLOATLIST], TRUE);
    g_ptr_array_free(_ptList[RIGIDLIST], TRUE);

    return 1;
}

int       S52_CS_setPtPos(S57_geo *geoData, char *name)
{
    printf("name = %s\n", name);

    if (POINT_T == S57_getObjtype(geoData)) {

        // set floating platform
        if ((0==strncmp(name, "LITFLT", 6)) ||
            (0==strncmp(name, "LITVES", 6)) ||
            (0==strncmp(name, "BOY",    3)))
            g_ptr_array_add(_ptList[FLOATLIST], (gpointer) geoData);

        // set rigid platform
        if (0==strncmp(name, "BCN",    3))
            g_ptr_array_add(_ptList[RIGIDLIST], (gpointer) geoData);
    }

    return 1;
}

static int      _atPtPos(S57_geo *geoNew, int listNm)
// return TRUE if there is a light at this position
// or if its an extended arc radius else FALSE
{
    int i;
    GPtrArray *curntList = _ptList[listNm];

    for (i=0; i<curntList->len; i++) {
        S57_geo *geoOld = g_ptr_array_index(curntList, i);

        if (S57_samePtPos(geoNew, geoOld)) {

            if (SECTRLIST != listNm)
                return TRUE;
            else {
                // check for extend arc radius
                GString *Asectr1str = S57_getAttVal(geoOld, "SECTR1");
                GString *Asectr2str = S57_getAttVal(geoOld, "SECTR2");
                GString *Bsectr1str = S57_getAttVal(geoNew, "SECTR1");
                GString *Bsectr2str = S57_getAttVal(geoNew, "SECTR2");

                // check  present
                if (NULL == Asectr1str ||
                    NULL == Asectr1str ||
                    NULL == Asectr1str ||
                    NULL == Asectr1str)
                    return FALSE;

                {
                    double Asectr1 = atof(Asectr1str->str);
                    double Asectr2 = atof(Asectr2str->str);
                    double Bsectr1 = atof(Bsectr1str->str);
                    double Bsectr2 = atof(Bsectr2str->str);
                    double Asweep = (Asectr1 > Asectr2) ?
                        Asectr2-Asectr1+360 : Asectr2-Asectr1;
                    double Bsweep = (Bsectr1 > Bsectr2) ?
                        Bsectr2-Bsectr1+360 : Bsectr2-Bsectr1;

                    // check sector overlap
                    if (Asectr2<=Bsectr1 || Asectr1>=Bsectr2) {
                        if (Asweep == Bsweep) {
                            g_string_truncate(Bsectr2str, 0);
                            g_string_sprintf(Bsectr2str, "%f",Bsectr2-1);
                            S57_setAtt(geoNew, "SECTR2", Bsectr2str->str);
                        }

                        return FALSE;
                    }

                    // check if other sector larger
                    if (Asweep >= Bsweep)
                        return TRUE;
                }
            }
        }
    }

    return FALSE;
}

static int      _setPtPos(S57_geo *geo, int listNm)
// TRUE if set new position of a light
// else FALSE (ie there is a light at this position)
{
    GPtrArray *curntList = _ptList[listNm];

    if (_atPtPos(geo, listNm))
        return 1;
    else
        g_ptr_array_add(curntList, (gpointer) geo);

    return 0;
}

static int      _parseList(const char *str, char *buf)
// Put a string of comma delimited number in an array (buf).
// Return: the number of value in buf.
// Assume: - number < 256,
//         - list size less then LISTSIZE-1 .
// Note: buf is \0 terminated for strpbrk().
{
    int i = 0;

    if (NULL != str && *str != '\0') {
        do {
            if ( i>= LISTSIZE-1) {
                PRINTF("OVERFLOW --value in list lost!!\n");
                break;
            }

            /*
            if (255 <  (unsigned char) atoi(str)) {
                PRINTF("value overflow (>255)\n");
                exit(0);
            }
            */

            buf[i++] = (unsigned char) atoi(str);

            while(isdigit(*str++));   // next
            //while( g_ascii_isdigit(c));   // next

        } while(*str++ != '\0');      // skip ',' or exit
    }

    buf[i] = '\0';

    return i;
}


static GString *CLRLIN01 (S57_geo *geo)
// Remarks: A clearing line shows a single arrow head at one of its ends. The direction
// of the clearing line must be calculated from its line object in order to rotate
// the arrow head symbol and place it at the correct end. This cannot be
// achieved with a complex linestyle since linestyle symbols cannot be sized
// to the length of the clearing line. Instead a linestyle with a repeating pattern
// of arrow symbols had to be used which does not comply with the required
// symbolization.
{

    PRINTF("Mariner's object not drawn\n");

    return NULL;
}

static GString *DATCVR01 (S57_geo *geo)
// Remarks: This conditional symbology procedure describes procedures for:
// - symbolizing the limit of ENC coverage;
// - symbolizing navigational purpose boundaries ("scale boundarie"); and
// - indicating overscale display.
//
// Note that the mandatory meta object CATQUA is symbolized by the look-up table.
//
// Because the methods adopted by an ECDIS to meet the IMO and IHO requirements
// listed on the next page will depend on the manufacturer's software, and cannot be
// described in terms of a flow chart in the same way as other conditional procedures,
// this procedure is in the form of written notes.
{
    GString *datcvr01 = NULL;

    ///////////////////////
    // 1- REQUIREMENT
    // (IMO/IHO specs. explenation)

    ///////////////////////
    // 2- ENC COVERAGE
    //
    // 2.1- Limit of ENC coverage
    //datcvr01 = g_string_new(";OP(3OD11060);LC(HODATA01)");
    // FIXME: get cell extend

    // 2.2- No data areas
    // This can be done outside of CS (ie when clearing the screen in Mesa)
    // FIXME: ";OP(0---);AC(NODATA)"
    // FIXME: set geo to cover earth (!)

    //////////////////////
    // 3- SCALE BOUNDARIES
    //
    // 3.1- Chart scale boundaties
    // FIXME;
    //g_string_append(datcvr01, ";LS(SOLD,1,CHGRD)");
    // -OR- LC(SCLBDYnn) (?)
    //
    // ;OP(3OS21030)

    // 3.2- Graphical index of navigational purpose
    // FIXME: draw extent of available SENC in DB

    //////////////////////
    // 4- OVERSCALE
    //
    // FIXME: get meta date CSCL of DSPM field
    // FIXME: get object M_CSCL or CSCALE
    //
    // 4.1- Overscale indication
    // FIXME: compute, scale = [denominator of the compilation scale] /
    //                         [denominator of the display scale]
    // FIXME: draw overscale indication (ie TX("X%3.1f",scale))
    //
    // 4.2- Ovescale area at a chart scale boundary
    // FIXME: test if next chart is over scale (ie going from large scale chart
    //        to a small scale chart)
    // FIXME: draw AP(OVERSC01) on overscale part of display
    //g_string(";OP(3OS21030)");

    //
    // 4.3- Larger scale data available
    // FIXME: display indication of better scale available (?)


    PRINTF("not computed\n");

    return datcvr01;
}

static GString *_SEABED01(double drval1, double drval2);
static GString *_RESCSP01(S57_geo *geo);
static GString *DEPARE01 (S57_geo *geo)
// Remarks: An object of the class "depth area" is coloured and covered with fill patterns
// according to the mariners selections of shallow contour, safety contour and
// deep contour. This requires a decision making process provided by the sub-procedure
// "SEABED01" which is called by this symbology procedure.
// Objects of the class "dredged area" are handled by this routine as well to
// ensure a consistent symbolization of areas that represent the surface of the
// seabed.
{
    GString *depare01  = NULL;
    int      objl      = 0;
    GString *objlstr   = NULL;
    GString *drval1str = S57_getAttVal(geo, "DRVAL1");
    double   drval1    = UNKNOWN;
    GString *drval2str = S57_getAttVal(geo, "DRVAL2");
    double   drval2    = UNKNOWN;

    if (NULL == drval1str || NULL == drval2str) {
        PRINTF("ERROR: drval1 or drval2 should have the value UNKNOWN\n");
        return NULL;
    }

    drval1 = (NULL == drval1str) ? -1.0 : atof(drval1str->str);
    drval2 = (NULL == drval2str) ? drval1+0.01 : atof(drval2str->str);

    depare01 = _SEABED01(drval1, drval2);

    objlstr = S57_getAttVal(geo, "OBJL");
    objl    = (NULL == objlstr) ? 0 : atoi(objlstr->str);

    if (DRGARE == objl) {
        g_string_append(depare01, ";AP(DRGARE01)");
        g_string_append(depare01, ";LS(DASH,1,CHGRF)");

        if (NULL != S57_getAttVal(geo, "RESTRN")) {
            GString *rescsp01 = _RESCSP01(geo);
            if (NULL != rescsp01) {
                g_string_append(depare01, rescsp01->str);
                g_string_free(rescsp01, TRUE);
            }
        }

    }

    return depare01;
}

static GString *_SNDFRM02(S57_geo *geo, double depth_value);
static GString *DEPCNT02 (S57_geo *geo)
// Remarks: An object of the class "depth contour" or "line depth area" is highlighted and must
// be shown under all circumstances if it matches the safety contour depth value
// entered by the mariner (see IMO PS 3.6). But, while the mariner is free to enter any
// safety contour depth value that he thinks is suitable for the safety of his ship, the
// SENC only contains a limited choice of depth contours. This symbology procedure
// determines whether a contour matches the selected safety contour. If the selected
// safety contour does not exist in the data, the procedure will default to the next deeper
// contour. The contour selected is highlighted as the safety contour and put in
// DISPLAYBASE. The procedure also identifies any line segment of the spatial
// component of the object that has a "QUAPOS" value indicating unreliable
// positioning, and symbolizes it with a double dashed line.
//
// Note: Depth contours are not normally labeled. The ECDIS may provide labels, on demand
// only as with other text, or provide the depth value on cursor picking
{
    GString *depcnt02  = NULL;
    int      safe      = FALSE;     // initialy not a safety contour
    GString *objlstr   = NULL;
    int      objl      = 0;
    GString *quaposstr = NULL;
    int      quapos    = 0;
    double   depth_value;

    objlstr = S57_getAttVal(geo, "OBJL");
    objl    = (NULL == objlstr) ? 0 : atoi(objlstr->str);

    if (DEPARE==objl && LINES_T==S57_getObjtype(geo))
    {
        GString *drval1str = S57_getAttVal(geo, "DRVAL1");
        double   drval1    = (NULL == drval1str) ? 0.0    : atof(drval1str->str);
        GString *drval2str = S57_getAttVal(geo, "DRVAL2");
        double   drval2    = (NULL == drval2str) ? drval1 : atof(drval2str->str);

        if (drval1 <= S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR))
        {
            if (drval2 >= S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR))
                safe = TRUE;
        }
        else
        {
            if (1 == S52_state)
                return NULL;
            else
            {
                S57_geo *geoTmp = geo;

                // get area DEPARE & DRGARE that intersect this line
                while (NULL != (geoTmp = S57_nextObj(geoTmp))) {
                    drval1str = S57_getAttVal(geoTmp, "DRVAL1");
                    drval1    = (NULL == drval1str) ? 0.0 : atof(drval1str->str);

                    if (NULL == drval1str) {
                        safe = TRUE;
                        break;
                    }

                    if (drval1 < S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)) {
                        safe = TRUE;
                        break;
                    }
                }
                // debug trace
                //if (safe) PRINTF("** DEPARE: SAFE FOUND**\n");
            }
        }

        depth_value = drval1;

    }
    else
    {
        // continuation A (DEPCNT)
        GString *valdcostr = S57_getAttVal(geo, "VALDCO");
        double   valdco    = (NULL == valdcostr) ? 0.0 : atof(valdcostr->str);

        depth_value = valdco;

        if (valdco == S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR))
            safe = TRUE;   // this is useless !?!?
        else
        {
            if (valdco > S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)) {
                if (1 == S52_state)
                    return NULL;
                else {
                    S57_geo *geoTmp = geo;

                    // get area DEPARE & DRGARE that intersect this line
                    while (NULL != (geoTmp = S57_nextObj(geoTmp))){
                        GString *drval1str = S57_getAttVal(geoTmp, "DRVAL1");
                        double   drval1    = (NULL == drval1str) ? 0.0 : atof(drval1str->str);

                        if (NULL == drval1str) {
                            safe = TRUE;
                            break;
                        }

                        if (drval1 < S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)) {
                            safe = TRUE;
                            break;
                        }
                    }
                    // debug trace
                    //if (safe) PRINTF("** DEPCN: SAFE FOUND**\n");
                }
            }
        }
    }

    // Continuation B
    quaposstr = S57_getAttVal(geo, "QUAPOS");
    if (NULL != quaposstr) {
        quapos = atoi(quaposstr->str);
        if ( 2 <= quapos && quapos < 10) {
            if (safe)
                depcnt02 = g_string_new(";LS(DASH,2,DEPSC)");
            else
                depcnt02 = g_string_new(";LS(DASH,1,DEPCN)");
        }
    } else {
        if (safe)
            depcnt02 = g_string_new(";LS(SOLD,2,DEPSC)");
        else
            depcnt02 = g_string_new(";LS(SOLD,1,DEPCN)");
    }

    if (safe) {
        S57_setAtt(geo, "SCAMIN", "INFINITE");
        depcnt02 = g_string_prepend(depcnt02, ";OP(8OD13010)");
    } else
        depcnt02 = g_string_prepend(depcnt02, ";OP(---33020)");

    // facultative in S-52
    //if (TRUE == S52_getMarinerParam(S52_MAR_SHOW_TEXT)) {
    //    GString *sndfrm02 = _SNDFRM02(geo, depth_value);
    //    depcnt02 = g_string_append(depcnt02, sndfrm02->str);
    //    g_string_free(sndfrm02, TRUE);
    //}

    // debug
    //PRINTF("depth= %f\n", depth_value);

    S57_unlinkObj(geo);

    return depcnt02;
}

static double   _DEPVAL01(S57_geo *geo, double least_depth)
// Remarks: S-57 Appendix B1 Annex A requires in Section 6 that areas of rocks be
// encoded as area obstruction, and that area OBSTRNs and area WRECKS
// be covered by either group 1 object DEPARE or group 1 object UNSARE.
// If the value of the attribute VALSOU for an area OBSTRN or WRECKS
// is missing, the DRVAL1 of an underlying DEPARE is the preferred default
// for establishing a depth value. This procedure either finds the shallowest
// DRVAL1 of the one or more underlying DEPAREs, or returns an
// "unknown"" depth value to the main procedure for the next default
// procedure.

// NOTE: UNSARE test is useless since least_depth is already UNKNOWN
{
    least_depth = UNKNOWN;


    S57_geo *geoTmp = geo;

    // NOTE: the geo list is unchange (_UDWHAZ03 will unlink geo)
    while (NULL != (geoTmp = S57_nextObj(geoTmp))) {
        GString *objlstr = S57_getAttVal(geoTmp, "OBJL");
        int      objl    = (NULL == objlstr) ? 0 : atoi(objlstr->str);

        // get area DEPARE  that intersect this area
        if (DEPARE==objl && LINES_T==S57_getObjtype(geo)) {
            GString *drval1str = S57_getAttVal(geoTmp, "DRVAL1");
            double   drval1    = (NULL == drval1str) ? 9.0 : atof(drval1str->str);

            if (NULL != drval1str) {
                if (UNKNOWN==least_depth || least_depth<drval1)
                    least_depth = drval1;
            }

        }
    }

    return least_depth;
}

static GString *LEGLIN02 (S57_geo *geo)

// Remarks: The course of a leg is given by its start and end point. Therefore this
// conditional symbology procedure calculates the course and shows it
// alongside the leg. It also places the "distance to run" labels and cares for the
// different presentation of planned & alternate legs.
{
    PRINTF("Mariner's object not drawn\n");
    return NULL;
}

static GString *_LITDSN01(S57_geo *geo);
static GString *LIGHTS05 (S57_geo *geo)
// Remarks: A light is one of the most complex S-57 objects. Its presentation depends on
// whether it is a light on a floating or fixed platform, its range, it's colour and
// so on. This conditional symbology procedure derives the correct
// presentation from these parameters and also generates an area that shows the
// coverage of the light.
//
// Notes on light sectors:
// 1.) The radial leg-lines defining the light sectors are normally drawn to only 25mm
// from the light to avoid clutter (see Part C). However, the mariner should be able to
// select "full light-sector lines" and have the leg-lines extended to the nominal range
// of the light (VALMAR).
//
// 2.) Part C of this procedure symbolizes the sectors at the light itself. In addition,
// it should be possible, upon request, for the mariner to be capable of identifying
// the colour and sector limit lines of the sectors affecting the ship even if the light
// itself is off the display.
// [ed. last sentence in bold]

// NOTE: why is this relationship not already encoded in S57 (ei. C_AGGR or C_STAC) ?

{
    GString *lights05          = NULL;
    GString *valnmrstr         = S57_getAttVal(geo, "VALNMR");
    double   valnmr            = 0.0;
    GString *catlitstr         = S57_getAttVal(geo, "CATLIT");
    char     catlit[LISTSIZE]  = {'\0'};
    int      flare_at_45       = FALSE;
    int      extend_arc_radius = TRUE;
    GString *sectr1str         = NULL;
    GString *sectr2str         = NULL;
    double   sectr1            = 0.0;
    double   sectr2            = 0.0;
    GString *colourstr         = NULL;
    char     colist[LISTSIZE]  = {'\0'};   // colour list
    GString *orientstr         = NULL;
    double   sweep             = 0.0;


    lights05 = g_string_new("");

    valnmr = (NULL == valnmrstr) ? 9.0 : atof(valnmrstr->str);

    if ( NULL != catlitstr) {
        _parseList(catlitstr->str, catlit);

        // FIXME: OR vs AND/OR
        if (strpbrk(catlit, "\010\013")) {
            g_string_append(lights05, ";SY(LIGHTS82)");
            return lights05;
        }

        if (strpbrk(catlit, "\011")) {
            g_string_append(lights05, ";SY(LIGHTS81)");
            return lights05;
        }

        if (strpbrk(catlit, "\001\020")) {
            orientstr = S57_getAttVal(geo, "ORIENT");
            if (NULL != orientstr) {
                // FIXME: create a geo object (!?) LINE of lenght VALNMR
                // using ORIENT (from seaward) & POINT_T position
                g_string_append(lights05, ";LS(DASH,1,CHBLK)");
            }
        }
    }

    // Continuation A
    colourstr = S57_getAttVal(geo, "COLOUR");
    if (NULL != colourstr)
        _parseList(colourstr->str, colist);
    else {
        colist[0] = '\014';  // maganta (12)
        colist[1] = '\000';
    }

    sectr1str = S57_getAttVal(geo, "SECTR1");
    sectr1    = (NULL == sectr1str) ? 0.0 : atof(sectr1str->str);
    sectr2str = S57_getAttVal(geo, "SECTR2");
    sectr2    = (NULL == sectr2str) ? 0.0 : atof(sectr2str->str);

    if (NULL==sectr1str || NULL==sectr2str) {
        // not a sector light
        char *sym;

        if (1==S52_state) {
            _setPtPos(geo, LIGHTLIST);
            g_string_free(lights05, TRUE);
            return NULL;
        } else
            flare_at_45 = _atPtPos(geo, LIGHTLIST);

        sym = _selSYcol(colist);

        if (strpbrk(catlit, "\001\020")) {
            if (NULL != orientstr){
                g_string_append(lights05, sym);
                g_string_sprintfa(lights05, ",%s)", orientstr->str);
                g_string_append(lights05, ";TE('%03.0lf deg','ORIENT',3,3,3,'15110',3,1,CHBLK,23)" );
            } else
                g_string_append(lights05, ";SY(QUSMRK1)");
        } else {
            g_string_append(lights05, sym);
            if (flare_at_45)
                g_string_append(lights05, ",145)");
            else
                g_string_append(lights05, ",135)");
        }

        if (TRUE == S52_getMarinerParam(S52_MAR_SHOW_TEXT)) {
            GString *litdsn01 = _LITDSN01(geo);
            if (NULL != litdsn01){
                g_string_append(lights05, ";TX('");
                g_string_append(lights05, litdsn01->str);
                g_string_free(litdsn01, TRUE);

                if (flare_at_45)
                    g_string_append(lights05, "',3,3,3,'15110',2,-1,CHBLK,23)" );
                else
                    g_string_append(lights05, "',3,2,3,'15110',2,0,CHBLK,23)" );
            }
        }

        return lights05;
    }

    // Continuation B --sector light
    if (NULL == sectr1str) {
        sectr1 = 0.0;
        sectr2 = 0.0;
    } else
        sweep = (sectr1 > sectr2) ? sectr2-sectr1+360 : sectr2-sectr1;


    if (sweep<1.0 || sweep==360.0) {
        // handle all round light
        char *sym = _selSYcol(colist);;

        g_string_append(lights05, sym);
        g_string_append(lights05, ",135)");

        if (TRUE == S52_getMarinerParam(S52_MAR_SHOW_TEXT)) {
            GString *litdsn01 = _LITDSN01(geo);
            if (NULL != litdsn01) {
                g_string_append(lights05, ";TX('");
                g_string_append(lights05, litdsn01->str);
                g_string_append(lights05, "',3,2,3,'15110',2,0,CHBLK,23)" );
                g_string_free(litdsn01, TRUE);
            }

        }

        return lights05;
    }

    // scan for other lights with sector overlap at this position
    // compute light sector radius acording to other sector
    if (1 == S52_state) {
        _setPtPos(geo, SECTRLIST);
        g_string_free(lights05, TRUE);
        return NULL;
    } else {
        extend_arc_radius = _atPtPos(geo, SECTRLIST);

        // passe value via attribs to _renderAC
        if (extend_arc_radius)
            // FIXME: draw radius 25 mm
            S57_setAtt(geo, "extend_arc_radius", "Y");
        else
            // FIXME: draw radius 20 mm
            S57_setAtt(geo, "extend_arc_radius", "N");
    }

    // setup sector
    {
        char litvis[LISTSIZE] = {'\0'};  // list visibility
        GString *litvisstr = S57_getAttVal(geo, "LITVIS");

        // sector leg --logic is _renderLS()
        g_string_append(lights05, ";LS(DASH,1,CHBLK)");

        // get light vis.
        if (NULL != litvisstr) _parseList(litvisstr->str, litvis);

        // faint light
        // FIXME: spec say OR (ie 1 number) the code is AND/OR
        if (strpbrk(litvis, "\003\007\010")) {
            // NOTE: LS(DASH,1,CHBLK)
            // pass flag to _renderAC()
            g_string_append(lights05, ";AC(CHBLK)");
            S57_setAtt(geo, "faint_light", "Y");

        } else {
            // set arc colour
            char *sym = ";AC(CHMGD)";  // other

            // max 1 color
            if ('\0' == colist[1]) {
                if (strpbrk(colist, "\003"))
                    sym = ";AC(LITRD)";
                else if (strpbrk(colist, "\004"))
                    sym = ";AC(LITGN)";
                else if (strpbrk(colist, "\001\006\013"))
                    sym = ";AC(LITYW)";
            } else {
                // max 2 color
                if ('\0' == colist[2]) {
                    if (strpbrk(colist, "\001") && strpbrk(colist, "\003"))
                        sym = ";AC(LITRD)";
                    else if (strpbrk(colist, "\001") && strpbrk(colist, "\004"))
                        sym = ";AC(LITGN)";
                }
            }

            g_string_append(lights05, sym);
        }
    }

    return lights05;
}

static GString *_LITDSN01(S57_geo *geo)
// Remarks: In S-57 the light characteristics are held as a series of attributes values. The
// mariner may wish to see a light description text string displayed on the
// screen similar to the string commonly found on a paper chart. This
// conditional procedure, reads the attribute values from the above list of
// attributes and composes a light description string which can be displayed.
// This procedure is provided as a C function which has as input, the above
// listed attribute values and as output, the light description.
{
    GString *litdsn01 = g_string_new("");
    GString *gstr     = NULL;  // tmp

    // FIXME: need grammar to create light's text

    // CATLIT, LITCHR, COLOUR, HEIGHT, LITCHR, SIGGRP, SIGPER, STATUS, VALNMR

    // CATLIT
    gstr = S57_getAttVal(geo, "CATLIT");
    if (NULL != gstr)
        g_string_append(litdsn01, gstr->str);

    /*
     1: directional function  IP 30.1-3;  475.7;
     2: rear/upper light
     3: front/lower light
     4: leading light           IP 20.1-3;      475.6;
     5: aero light                  IP 60;      476.1;
     6: air obstruction light IP 61;      476.2;
     7: fog detector light        IP 62;  477;
     8: flood light                 IP 63;      478.2;
     9: strip light                 IP 64;      478.5;
    10: subsidiary light          IP 42;  471.8;
    11: spotlight
    12: front
    13: rear
    14: lower
    15: upper
    16: moire' effect           IP 31;    475.8;
    17: emergency
    18: bearing light                   478.1;
    19: horizontally disposed
    20: vertically disposed
     */

    // LITCHR
    gstr = S57_getAttVal(geo, "LITCHR");
    if (NULL != gstr)
        g_string_append(litdsn01, gstr->str);

    /*
     1: fixed     IP 10.1;
     2: flashing  IP 10.4;
     3: long-flashing   IP 10.5;
     4: quick-flashing  IP 10.6;
     5: very quick-flashing   IP 10.7;
     6: ultra quick-flashing  IP 10.8;
     7: isophased IP 10.3;
     8: occulting IP 10.2;
     9: interrupted quick-flashing  IP 10.6;
    10: interrupted very quick-flashing   IP 10.7;
    11: interrupted ultra quick-flashing  IP 10.8;
    12: morse     IP 10.9;
    13: fixed/flash     IP 10.10;
    14: flash/long-flash
    15: occulting/flash
    16: fixed/long-flash
    17: occulting alternating
    18: long-flash alternating
    19: flash alternating
    20: group alternating
    21: 2 fixed (vertical)
    22: 2 fixed (horizontal)
    23: 3 fixed (vertical)
    24: 3 fixed (horizontal)
    25: quick-flash plus long-flash
    26: very quick-flash plus long-flash
    27: ultra quick-flash plus long-flash
    28: alternating
    29: fixed and alternating flashing
    */

    // COLOUR,
    gstr = S57_getAttVal(geo, "COLOUR");
    if (NULL != gstr)
        g_string_append(litdsn01, gstr->str);

    /*
     1: white     IP 11.1;    450.2-3;
     2: black
     3: red IP 11.2;    450.2-3;
     4: green     IP 11.3;    450.2-3;
     5: blue      IP 11.4;    450.2-3;
     6: yellow    IP 11.6;    450.2-3;
     7: grey
     8: brown
     9: amber     IP 11.8;    450.2-3;
    10: violet    IP 11.5;    450.2-3;
    11: orange    IP 11.7;    450.2-3;
    12: magenta
    13: pink
    */

    // HEIGHT, xxx.x
    gstr = S57_getAttVal(geo, "HEIGHT");
    if (NULL != gstr)
        g_string_append(litdsn01, gstr->str);


    // SIGGRP, (c)(c) ...
    gstr = S57_getAttVal(geo, "SIGGRP");
    if (NULL != gstr)
        g_string_append(litdsn01, gstr->str);


    // SIGPER, xx.xx
    gstr = S57_getAttVal(geo, "SIGPER");
    if (NULL != gstr)
        g_string_append(litdsn01, gstr->str);


    // STATUS,
    gstr = S57_getAttVal(geo, "STATUS");
    if (NULL != gstr)
        g_string_append(litdsn01, gstr->str);

    /*
     1: permanent
     2: occasional      IP 50;      473.2;
     3: recommended     IN 10;      431.1;
     4: not in use      IL 14, 44;  444.7;
     5: periodic/intermittent IC 21; IQ 71;     353.3; 460.5;
     6: reserved  IN 12.9;
     7: temporary IP 54;
     8: private   IQ 70;
     9: mandatory
    10: destroyed/ruined
    11: extinguished
    12: illuminated
    13: historic
    14: public
    15: synchronized
    16: watched
    17: un-watched
    18: existence doubtful
    */

    // VALNMR, xx.x
    gstr = S57_getAttVal(geo, "VALNMR");
    if (NULL != gstr)
        g_string_append(litdsn01, gstr->str);


    PRINTF("FIXME: lights description not translated into text\n");

    return litdsn01;
}

static GString *_UDWHAZ03(S57_geo *geo, double depth_value);
static GString *_QUAPNT01(S57_geo *geo);

static GString *OBSTRN04 (S57_geo *geo)
// Remarks: Obstructions or isolated underwater dangers of depths less than the safety
// contour which lie within the safe waters defined by the safety contour are
// to be presented by a specific isolated danger symbol and put in IMO
// category DISPLAYBASE (see (3), App.2, 1.3). This task is performed
// by the sub-procedure "UDWHAZ03" which is called by this symbology
// procedure. Objects of the class "under water rock" are handled by this
// routine as well to ensure a consistent symbolization of isolated dangers on
// the seabed.
{
    GString *obstrn04str = g_string_new("");
    GString *sndfrm02str = NULL;
    GString *udwhaz03str = NULL;
    GString *valsoustr   = S57_getAttVal(geo, "VALSOU");
    double   valsou      = UNKNOWN;
    double   depth_value = UNKNOWN;
    double   least_depth = UNKNOWN;

    // exit if not in drawing state
    if (1 == S52_state)
        return NULL;

    if (NULL != valsoustr) {
        valsou      = atof(valsoustr->str);
        depth_value = valsou;
        sndfrm02str = _SNDFRM02(geo, depth_value);
    } else {
        if (AREAS_T == S57_getObjtype(geo))
            least_depth = _DEPVAL01(geo, least_depth);

        if (UNKNOWN != least_depth) {
            GString *catobsstr = S57_getAttVal(geo, "CATOBS");
            GString *watlevstr = S57_getAttVal(geo, "WATLEV");

            if (NULL != catobsstr && '6' == *catobsstr->str)
                depth_value = 0.01;
            else
                if (NULL == watlevstr) // default
                    depth_value = -15.0;
                else {
                    switch (*watlevstr->str){
                        case '5': depth_value =   0.0 ; break;
                        case '3': depth_value =   0.01; break;
                        case '4':
                        case '1':
                        case '2':
                        default : depth_value = -15.0 ; break;
                    }
                }
        } else
            depth_value = least_depth;
    }

    udwhaz03str = _UDWHAZ03(geo, depth_value);

    if (POINT_T == S57_getObjtype(geo)) {
        // Continuation A
        int      sounding    = FALSE;
        GString *quapnt01str = _QUAPNT01(geo);

        if (NULL != udwhaz03str){
            g_string_append(obstrn04str, udwhaz03str->str);
            if (NULL != quapnt01str)
                g_string_append(obstrn04str, quapnt01str->str);

            if (NULL != udwhaz03str) g_string_free(udwhaz03str, TRUE);
            if (NULL != sndfrm02str) g_string_free(sndfrm02str, TRUE);
            if (NULL != quapnt01str) g_string_free(quapnt01str, TRUE);

            return obstrn04str;
        }

        if (UNKNOWN != valsou) {
            if (valsou <= 20.0) {
                GString *objlstr   = S57_getAttVal(geo, "OBJL");
                int      objl      = (NULL == objlstr)? 0 : atoi(objlstr->str);
                GString *watlevstr = S57_getAttVal(geo, "WATLEV");

                if (UWTROC == objl) {
                    if (NULL == watlevstr) {  // default
                        g_string_append(obstrn04str, ";SY(DANGER01)");
                        sounding = TRUE;
                    } else {
                        switch (*watlevstr->str){
                            case '3': g_string_append(obstrn04str, ";SY(DANGER01)"); sounding = TRUE ; break;
                            case '4':
                            case '5': g_string_append(obstrn04str, ";SY(UWTROC04)"); sounding = FALSE; break;
                            default : g_string_append(obstrn04str, ";SY(DANGER01)"); sounding = TRUE ; break;
                        }
                    }
                } else { // OBSTRN
                    if (NULL == watlevstr) { // default
                        g_string_append(obstrn04str, ";SY(DANGER01)");
                        sounding = TRUE;
                    } else {
                        switch (*watlevstr->str) {
                            case '1':
                            case '2': g_string_append(obstrn04str, ";SY(OBSTRN11)"); sounding = FALSE; break;
                            case '3': g_string_append(obstrn04str, ";SY(DANGER01)"); sounding = TRUE;  break;
                            case '4':
                            case '5': g_string_append(obstrn04str, ";SY(DANGER03)"); sounding = TRUE; break;
                            default : g_string_append(obstrn04str, ";SY(DANGER01)"); sounding = TRUE; break;
                        }
                    }
                }
            } else {  // valsou > 20.0
                g_string_append(obstrn04str, ";SY(DANGER02)");
                sounding = FALSE;
            }

        } else {  // NO valsou
                GString *objlstr   = S57_getAttVal(geo, "OBJL");
                int     objl       = (NULL == objlstr)? 0 : atoi(objlstr->str);
                GString *watlevstr = S57_getAttVal(geo, "WATLEV");

                if (UWTROC == objl) {
                    if (NULL == watlevstr)  // default
                       g_string_append(obstrn04str, ";SY(UWTROC04)");
                    else {
                        if ('3' == *watlevstr->str)
                            g_string_append(obstrn04str, ";SY(UWTROC03)");
                        else
                            g_string_append(obstrn04str, ";SY(UWTROC04)");
                    }

                } else { // OBSTRN
                    if ( NULL == watlevstr) // default
                        g_string_append(obstrn04str, ";SY(OBSTRN01)");
                    else {
                        switch (*watlevstr->str) {
                            case '1':
                            case '2': g_string_append(obstrn04str, ";SY(OBSTRN11)"); break;
                            case '3': g_string_append(obstrn04str, ";SY(OBSTRN01)"); break;
                            case '4':
                            case '5':
                            default : g_string_append(obstrn04str, ";SY(OBSTRN01)"); break;
                        }
                    }
                }

        }

        if (sounding && NULL != sndfrm02str)
            g_string_append(obstrn04str, sndfrm02str->str);

        if (NULL != quapnt01str)
            g_string_append(obstrn04str, quapnt01str->str);

        if (NULL != udwhaz03str) g_string_free(udwhaz03str, TRUE);
        if (NULL != sndfrm02str) g_string_free(sndfrm02str, TRUE);
        if (NULL != quapnt01str) g_string_free(quapnt01str, TRUE);

        return obstrn04str;

    } else {
        if (LINES_T == S57_getObjtype(geo)) {
            // Continuation B
            GString *quaposstr = S57_getAttVal(geo, "QUAPOS");
            int      quapos    = 0;

            if (NULL != quaposstr) {
                quapos = atoi(quaposstr->str);
                if ( 2 <= quapos && quapos < 10){
                    if (NULL != udwhaz03str)
                        g_string_append(obstrn04str, ";LC(LOWACC41)");
                    else
                        g_string_append(obstrn04str, ";LC(LOWACC31)");
                }
            }

            if (NULL != udwhaz03str)
                g_string_append(obstrn04str, ";LS(DOTT,2,CHBLK)");

            if (UNKNOWN != valsou)
                if (valsou <= 20.0)
                    g_string_append(obstrn04str, ";LS(DOTT,2,CHBLK)");
                else
                    g_string_append(obstrn04str, ";LS(DASH,2,CHBLK)");
            else
                g_string_append(obstrn04str, ";LS(DOTT,2,CHBLK)");


            if (NULL != udwhaz03str)
                g_string_append(obstrn04str, udwhaz03str->str);
            else {
                if (UNKNOWN != valsou)
                    if (valsou <= 20.0)
                        g_string_append(obstrn04str, sndfrm02str->str);
            }

            if (NULL != udwhaz03str) g_string_free(udwhaz03str, TRUE);
            if (NULL != sndfrm02str) g_string_free(sndfrm02str, TRUE);

            return obstrn04str;

        } else {
            // Continuation C (AREAS_T)
            GString *quapnt01str = _QUAPNT01(geo);
            if (NULL != udwhaz03str) {
                g_string_append(obstrn04str, ";AC(DEPVS);AP(FOULAR01)");
                g_string_append(obstrn04str, ";LS(DOTT,2,CHBLK)");
                g_string_append(obstrn04str, udwhaz03str->str);
                if (NULL != quapnt01str)
                    g_string_append(obstrn04str, quapnt01str->str);

                if (NULL != udwhaz03str) g_string_free(udwhaz03str, TRUE);
                if (NULL != sndfrm02str) g_string_free(sndfrm02str, TRUE);
                if (NULL != quapnt01str) g_string_free(quapnt01str, TRUE);

                return obstrn04str;
            }

            if (UNKNOWN != valsou) {
                // BUG in CA49995B.000 if we get here because there is no color
                // beside NODATA (ie there is a hole in group 1 area!)
                //g_string_append(obstrn04, ";AC(UINFR)");

                if (valsou <= 20.0)
                    g_string_append(obstrn04str, ";LS(DOTT,2,CHBLK)");
                else
                    g_string_append(obstrn04str, ";LS(DASH,2,CHBLK)");

                g_string_append(obstrn04str, sndfrm02str->str);

            } else {
                GString *watlevstr = S57_getAttVal(geo, "WATLEV");

                if (NULL == watlevstr)   // default
                    g_string_append(obstrn04str, ";AC(DEPVS);LS(DOTT,2,CHBLK)");
                else {
                    if ('3' == *watlevstr->str) {
                        GString *catobsstr = S57_getAttVal(geo, "CATOBS");
                        if (NULL != catobsstr && '6' == *catobsstr->str)
                            g_string_append(obstrn04str, ";AC(DEPVS);AP(FOULAR01);LS(DOTT,2,CHBLK)");
                    } else {
                        switch (*watlevstr->str) {
                            case '1':
                            case '2': g_string_append(obstrn04str, ";AC(CHBRN);LS(SOLD,2,CSTLN)"); break;
                            case '4': g_string_append(obstrn04str, ";AC(DEPIT);LS(DASH,2,CSTLN)"); break;
                            case '5':
                            case '3':
                            default : g_string_append(obstrn04str, ";AC(DEPVS);LS(DOTT,2,CHBLK)");  break;
                        }
                    }
                }
            }

            g_string_append(obstrn04str, quapnt01str->str);

            if (NULL != udwhaz03str) g_string_free(udwhaz03str, TRUE);
            if (NULL != sndfrm02str) g_string_free(sndfrm02str, TRUE);
            if (NULL != quapnt01str) g_string_free(quapnt01str, TRUE);


            return obstrn04str;
        }

    }


    // check if one exit point could do!!!
    return NULL;
}

static GString *OWNSHP02 (S57_geo *geo)
// Remarks:
// 1. CONNING POSITION
//    1.1 When own-ship is drawn to scale, the conning position must be correctly located in
//        relation to the ship's outline. The conning position then serves as the pivot point for
//        the own-ship symbol, to be located by the ECDIS at the correct latitude, longitude
//        for the conning point, as computed from the positioning system, correcting for
//        antenna offset.
//    1.2 In this procedure it is assumed that the heading line, beam bearing line and course
//        and speed vector originate at the conning point. If another point of origin is used,
//        for example to account for the varying position of the ships turning centre, this must
//        be made clear to the mariner.
//
// 2. DISPLAY OPTIONS
//    2.1 Only the ship symbol is mandatory for an ECDIS. The mariner should be prompted
//        to select from the following additional optional features:
//    - display own-ship as:
//        1. symbol, or
//        2. scaled outline
//    - select time period determining vector length for own-ship and other vessel course and speed
//      vectors, (all vectors must be for the same time period),
//    - display own-ship vector,
//    - select ground or water stabilization for all vectors, and select whether to display the type of
//      stabilization, (by arrowhead),
//    - select one-minute or six-minute vector time marks,
//    - select whether to show a heading line, to the edge of the display window,
//    - select whether to show a beam bearing line, and if so what length (default: 10mm total
//      length).
{
    PRINTF("Mariner's object not drawn\n");
    return NULL;
}

static GString *PASTRK01 (S57_geo *geo)
// Remarks: This conditional symbology procedure was designed to allow the mariner
// to select time labels at the pasttrack (see (3) 10.5.11.1). The procedure also
// cares for the presentation of primary and secondary pasttrack.
//
// The manufacturer should define his own data class (spatial primitive) in xyt
// (position and time) in order to represent Pastrk.
{

    PRINTF("Mariner's object not drawn\n");
    return NULL;
}

static GString *_QUALIN01(S57_geo *geo);
static GString *QUAPOS01 (S57_geo *geo)
// Remarks: The attribute QUAPOS, which identifies low positional accuracy, is attached
// to the spatial object, not the feature object.
//
// This procedure passes the object to procedure QUALIN01 or QUAPNT01,
// which traces back to the spatial object, retrieves any QUAPOS attributes,
// and returns the appropriate symbolization to QUAPOS01.
{
    GString *quapos01 = NULL;

    if (LINES_T == S57_getObjtype(geo))
        quapos01 = _QUALIN01(geo);
    else
        quapos01 = _QUAPNT01(geo);

    return quapos01;
}

static GString *_QUALIN01(S57_geo *geo)
// Remarks: The attribute QUAPOS, which identifies low positional accuracy, is attached
// only to the spatial component(s) of an object.
//
// A line object may be composed of more than one spatial object.
//
// This procedure looks at each of the spatial
// objects, and symbolizes the line according to the positional accuracy.
{
    GString *qualino1  = NULL;
    GString *quaposstr = S57_getAttVal(geo, "QUAPOS");
    int      quapos    = 0;
    char    *line      = NULL;

    if (NULL != quaposstr) {
        quapos = atoi(quaposstr->str);
        if ( 2 <= quapos && quapos < 10)
            line = ";LC(LOWACC21)";
    } else {
        GString *objlstr = S57_getAttVal(geo, "OBJL");
        int      objl    = (NULL == objlstr)? 0 : atoi(objlstr->str);

        if (COALNE == objl) {
            GString *conradstr = S57_getAttVal(geo, "CONRAD");

            if (NULL != conradstr) {
                if ('1' == *conradstr->str)
                    line = ";LS(SOLD,3,CHMGF);LS(SOLD,1,CSTLN)";
                else
                    line = ";LS(SOLD,1,CSTLN)";
            } else
                line = ";LS(SOLD,1,CSTLN)";

        } else  //LNDARE
            line = ";LS(SOLD,1,CSTLN)";
    }

    if (NULL != line)
        qualino1 = g_string_new(line);

    return qualino1;
}

static GString *_QUAPNT01(S57_geo *geo)
// Remarks: The attribute QUAPOS, which identifies low positional accuracy, is attached
// only to the spatial component(s) of an object.
//
// This procedure retrieves any QUAPOS attributes, and returns the
// appropriate symbols to the calling procedure.
{
    GString *quapnt01  = NULL;
    int      accurate  = TRUE;
    GString *quaposstr = S57_getAttVal(geo, "QUAPOS");
    int      quapos    = (NULL == quaposstr)? 0 : atoi(quaposstr->str);

    if (NULL != quaposstr) {
        if ( 2 <= quapos && quapos < 10)
            accurate = FALSE;
    }

    if (accurate)
        quapnt01 = g_string_new(";SY(LOWACC01)");

    return quapnt01;
}

static GString *SLCONS03 (S57_geo *geo)
// Remarks: Shoreline construction objects which have a QUAPOS attribute on their
// spatial component indicating that their position is unreliable are symbolized
// by a special linestyle in the place of the varied linestyles normally used.
// Otherwise this procedure applies the normal symbolization.
{
    GString *slcons03  = NULL;
    GString *valstr    = NULL;
    char    *cmdw      = NULL;   // command word
    GString *quaposstr = S57_getAttVal(geo, "QUAPOS");
    int      quapos    = (NULL == quaposstr)? 0 : atoi(quaposstr->str);

    if (POINT_T == S57_getObjtype(geo)) {
        if (NULL != quaposstr) {
            if (2 <= quapos && quapos < 10)
                cmdw =";SY(LOWACC01)";
        }
    } else {
        // LINE_T and AREA_T are the same
        if (NULL != quaposstr) {
            if (2 <= quapos && quapos < 10)
                cmdw =";LC(LOWACC01)";
        } else {
            valstr = S57_getAttVal(geo, "CONDTN");

            if (NULL != valstr && ( '1' == *valstr->str || '2' == *valstr->str))
                    cmdw = ";LS(DASH,1,CSTLN)";
            else {
                int val = 0;
                valstr  = S57_getAttVal(geo, "CATSLC");
                val     = (NULL == valstr)? 0 : atoi(valstr->str);

                if (NULL != valstr && ( 6  == val || 15 == val || 16 == val ))
                        cmdw = ";LS(SOLD,4,CSTLN)";
                else {
                    valstr = S57_getAttVal(geo, "WATLEV");

                    if (NULL != valstr && '2' == *valstr->str)
                            cmdw = ";LS(SOLD,2,CSTLN)";
                    else
                        if (NULL != valstr && ('3' == *valstr->str || '4' == *valstr->str))
                            cmdw = ";LS(DASH,2,CSTLN)";
                        else
                            cmdw = ";LS(SOLD,2,CSTLN)";  // default

                }
            }
        }
    }

    // WARNING: not explicitly specified in S-52 !!
    // FIXME:this is to put AC(DEPIT) --intertidal area
    // Could this be bug in OGR ?
    /*
    if (AREAS_T == S57_getObjtype(geo)) {
        GString    *seabed01  = NULL;
        GString    *drval1str = S57_getAttVal(geo, "DRVAL1");
        double      drval1    = (NULL == drval1str)? -UNKNOWN : atof(drval1str->str);
        GString    *drval2str = S57_getAttVal(geo, "DRVAL2");
        double      drval2    = (NULL == drval2str)? -UNKNOWN : atof(drval2str->str);
        // NOTE: change sign of infinity (minus) to get out of bound in seabed01


        PRINTF("***********drval1=%f drval2=%f \n", drval1, drval2);
        seabed01 = _SEABED01(drval1, drval2);
        slcons03 = g_string_new(seabed01->str);
        g_string_free(seabed01, TRUE);

    }
    */

    if (NULL != cmdw) {
        if (NULL == slcons03)
            slcons03 = g_string_new(cmdw);
        else
            g_string_append(slcons03, cmdw);
    }

    return slcons03;
}

static GString *RESARE02 (S57_geo *geo)
// Remarks: A list-type attribute is used because an area of the object class RESARE may
// have more than one category (CATREA). For example an inshore traffic
// zone might also have fishing and anchoring prohibition and a prohibited
// area might also be a bird sanctuary or a mine field.
//
// This conditional procedure is set up to ensure that the categories of most
// importance to safe navigation are prominently symbolized, and to pass on
// all given information with minimum clutter. Only the most significant
// restriction is symbolized, and an indication of further limitations is given by
// a subscript "!" or "I". Further details are given under conditional
// symbology procedure RESTRN01
//
// Other object classes affected by attribute RESTRN are handled by
// conditional symbology procedure RESTRN01.
{
    GString *resare02         = g_string_new("");
    GString *restrnstr        = S57_getAttVal(geo, "RESTRN");
    char     restrn[LISTSIZE] = {'\0'};
    GString *catreastr        = S57_getAttVal(geo, "CATREA");
    char     catrea[LISTSIZE] = {'\0'};
    char    *symb             = NULL;
    char    *line             = NULL;
    char    *prio             = NULL;

    if ( NULL != restrnstr) {
        _parseList(restrnstr->str, restrn);

        if (NULL != catreastr) _parseList(catreastr->str, catrea);

        if (strpbrk(restrn, "\007\010\016")) {
            // Continuation A
            if (strpbrk(restrn, "\001\002\003\004\005\006"))
                symb = ";SY(ENTRES61)";
            else {
                if (NULL != catreastr && strpbrk(catrea, "\001\010\011\014\016\023\025\031"))
                        symb = ";SY(ENTRES61)";
                else {
                    if (strpbrk(restrn, "\011\012\013\014\015"))
                        symb = ";SY(ENTRES71)";
                    else {
                        if (NULL != catreastr && strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
                            symb = ";SY(ENTRES71)";
                        else
                            symb = ";SY(ENTRES51)";
                    }
                }
            }

            if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
                line = ";LC(CTYARE51)";
            else
                line = ";LS(DASH,2,CHMGD)";

            prio = ";OP(6---)";  // display prio set to 6

        } else {
            if (strpbrk(restrn, "\001\002")) {
                // Continuation B
                if (strpbrk(restrn, "\003\004\005\006"))
                    symb = ";SY(ACHRES61)";
                else {
                    if (NULL != catreastr && strpbrk(catrea, "\001\010\011\014\016\023\025\031"))
                            symb = ";SY(ACHRES61)";
                    else {
                        if (strpbrk(restrn, "\011\012\013\014\015"))
                            symb = ";SY(ACHRES71)";
                        else {
                            if (NULL != catreastr && strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
                                symb = ";SY(ACHRES71)";
                            else
                                symb = ";SY(ACHRES51)";
                        }
                    }
                }

                if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
                    line = ";LC(ACHRE51)";
                else
                    line = ";LS(DASH,2,CHMGD)";

                prio = ";OP(6---)";  // display prio set to 6

            } else {
                if (strpbrk(restrn, "\003\004\005\006")) {
                    // Continuation C
                    if (NULL != catreastr && strpbrk(catrea, "\001\010\011\014\016\023\025\031"))
                            symb = ";SY(FSHRES51)";
                    else {
                        if (strpbrk(restrn, "\011\012\013\014\015"))
                            symb = ";SY(FSHRES71)";
                        else{
                            if (NULL != catreastr && strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
                                symb = ";SY(FSHRES71)";
                            else
                                symb = ";SY(FSHRES51)";
                        }
                    }

                    if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
                        line = ";LC(FSHRES51)";
                    else
                        line = ";LS(DASH,2,CHMGD)";

                    prio = ";OP(6---)";  // display prio set to 6

                } else {
                    if (strpbrk(restrn, "\011\012\013\014\015"))
                        symb = ";SY(INFARE51)";
                    else
                        symb = ";SY(RSRDEF51)";

                    if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
                        line = ";LC(CTYARE51)";
                    else
                        line = ";LS(DASH,2,CHMGD)";

                }
            }
        }

    } else {
        // Continuation D
        if (NULL != catreastr) {
            if (strpbrk(catrea, "\001\010\011\014\016\023\025\031")) {
                if (strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
                    symb = ";SY(CTYARE71)";
                else
                    symb = ";SY(CTYARE51)";
            } else {
                if (strpbrk(catrea, "\004\005\006\007\012\022\024\026\027\030"))
                    symb = ";SY(INFARE71)";
                else
                    symb = ";SY(RSRDEF51)";
            }
        } else
            symb = ";SY(RSRDEF51)";

        if (TRUE == S52_getMarinerParam(S52_MAR_SYMBOLIZED_BND))
            line = ";LC(CTYARE51)";
        else
            line = ";LS(DASH,2,CHMGD)";
    }

    // create command word
    if (NULL != prio)
        g_string_append(resare02, prio);
    g_string_append(resare02, line);
    g_string_append(resare02, symb);

    return resare02;
}

static GString *RESTRN01 (S57_geo *geo)
// Remarks: Objects subject to RESTRN01 are actually symbolised in sub-process
// RESCSP01, since the latter can also be accessed from other conditional
// symbology procedures. RESTRN01 merely acts as a "signpost" for
// RESCSP01.
//
// Object class RESARE is symbolised for the effect of attribute RESTRN in a separate
// conditional symbology procedure called RESARE02.
//
// Since many of the areas concerned cover shipping channels, the number of symbols used
// is minimised to reduce clutter. To do this, values of RESTRN are ranked for significance
// as follows:
// "Traffic Restriction" values of RESTRN:
// (1) RESTRN 7,8: entry prohibited or restricted
//     RESTRN 14: IMO designated "area to be avoided" part of a TSS
// (2) RESTRN 1,2: anchoring prohibited or restricted
// (3) RESTRN 3,4,5,6: fishing or trawling prohibited or restricted
// (4) "Other Restriction" values of RESTRN are:
//     RESTRN 9, 10: dredging prohibited or restricted,
//     RESTRN 11,12: diving prohibited or restricted,
//     RESTRN 13   : no wake area.
{
    GString *restrn01str = S57_getAttVal(geo, "RESTRN");
    GString *restrn01    = NULL;

    if (NULL != restrn01str)
        restrn01 = _RESCSP01(geo);
    else
        restrn01 = g_string_new(";OP(----)");  // return NOOP to silence error msg

    return restrn01;
}

static GString *_RESCSP01(S57_geo *geo)
// Remarks: See procedure RESTRN01
{
    GString *rescsp01         = NULL;
    GString *restrnstr        = S57_getAttVal(geo, "RESTRN");
    char     restrn[LISTSIZE] = {'\0'};   // restriction list
    char    *symb             = NULL;

    if ( NULL != restrnstr) {
        _parseList(restrnstr->str, restrn);

        if (strpbrk(restrn, "\007\010\016")) {
            // continuation A
            if (strpbrk(restrn, "\001\002\003\004\005\006"))
                symb = ";SY(ENTRES61)";
            else {
                if (strpbrk(restrn, "\011\012\013\014\015"))
                    symb = ";SY(ENTRES71)";
                else
                    symb = ";SY(ENTRES51)";

            }
        } else {
            if (strpbrk(restrn, "\001\002")) {
                // continuation B
                if (strpbrk(restrn, "\003\004\005\006"))
                    symb = ";SY(ACHRES61)";
                else {
                    if (strpbrk(restrn, "\011\012\013\014\015"))
                        symb = ";SY(ACHRES71)";
                    else
                        symb = ";SY(ACHRES51)";
                }


            } else {
                if (strpbrk(restrn, "\003\004\005\006")) {
                    // continuation C
                    if (strpbrk(restrn, "\011\012\013\014\015"))
                        symb = ";SY(FSHRES71)";
                    else
                        symb = ";SY(FSHRES51)";


                } else {
                    if (strpbrk(restrn, "\011\012\013\014\015"))
                        symb = ";SY(INFARE51)";
                    else
                        symb = ";SY(RSRDEF51)";

                }
            }
        }

        rescsp01 = g_string_new(symb);
    }

    return rescsp01;
}

static GString *_SEABED01(double drval1, double drval2)
// Remarks: An area object that is part of the seabed is coloured as necessary according
// to the mariners selection of two shades, (shallow contour, safety contour,
// deep contour), or four shades (safety contour only). This requires a decision
// making process provided by this conditional symbology procedure. Note
// that this procedure is called as a sub-procedure by other conditional
// symbology procedures.
//
// Note: The requirement to show four depth shades is not mandatory. Also,
// the requirement to show the shallow pattern is not mandatory. However,
// both these features are strongly recommended.

// return: is never NULL

{
    GString *seabed01 = NULL;
    gboolean shallow  = TRUE;
    char    *arecol   = ";AC(DEPIT)";

    if (drval1 >= 0.0 && drval2 > 0.0)
        arecol  = ";AC(DEPVS)";

    if (TRUE == S52_getMarinerParam(S52_MAR_TWO_SHADES)){
        if (drval1 >= S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)  &&
            drval2 >  S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)) {
            arecol  = ";AC(DEPDW)";
            shallow = FALSE;
        }
    } else {
        if (drval1 >= S52_getMarinerParam(S52_MAR_SHALLOW_CONTOUR) &&
            drval2 >  S52_getMarinerParam(S52_MAR_SHALLOW_CONTOUR))
            arecol  = ";AC(DEPMS)";

            if (drval1 >= S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)  &&
                drval2 >  S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR)) {
            arecol  = ";AC(DEPMD)";
            shallow = FALSE;
        }

            if (drval1 >= S52_getMarinerParam(S52_MAR_DEEP_CONTOUR)  &&
                drval2 >  S52_getMarinerParam(S52_MAR_DEEP_CONTOUR)) {
            arecol  = ";AC(DEPDW)";
            shallow = FALSE;
        }

    }

    seabed01 = g_string_new(arecol);

    if (TRUE==S52_getMarinerParam(S52_MAR_SHALLOW_PATTERN) && TRUE==shallow)
        g_string_append(seabed01, ";AP(DIAMOND1)");

    return seabed01;
}

static GString *SOUNDG02 (S57_geo *geo)
// Remarks: In S-57 soundings are elements of sounding arrays rather than individual
// objects. Thus this conditional symbology procedure examines each
// sounding of a sounding array one by one. To symbolize the depth values it
// calls the procedure SNDFRM02 which in turn translates the depth values
// into a set of symbols to be shown at the soundings position.
{
    guint   npt = 0;
    double *ppt = NULL;

    if (POINT_T != S57_getObjtype(geo)) {
        PRINTF("invalid object type (not M_PNT_T)\n");
        //return NULL;
        exit(0);
    }

    S57_getGeoData(geo, 0, &npt, &ppt);

    return _SNDFRM02(geo, ppt[2]);
}

static GString *_SNDFRM02(S57_geo *geo, double depth_value)
// Remarks: Soundings differ from plain text because they have to be readable under all
// circumstances and their digits are placed according to special rules. This
// conditional symbology procedure accesses a set of carefully designed
// sounding symbols provided by the symbol library and composes them to
// sounding labels. It symbolizes swept depth and it also symbolizes for low
// reliability as indicated by attributes QUASOU and QUAPOS.
{
    GString *sndfrm02         = g_string_new("");
    char    *symbol_prefix    = NULL;
    GString *tecsoustr        = S57_getAttVal(geo, "TECSOU");
    char     tecsou[LISTSIZE] = {'\0'};
    GString *quasoustr        = S57_getAttVal(geo, "QUASOU");
    char     quasou[LISTSIZE] = {'\0'};
    GString *statusstr        = S57_getAttVal(geo, "STATUS");
    char     status[LISTSIZE] = {'\0'};
    double   leading_digit    = 0.0;

    // FIXME: test to fix the rounding error (!?)
    depth_value  += (depth_value > 0.0)? 0.01: -0.01;
    leading_digit = (int) depth_value;

    if (depth_value <= S52_getMarinerParam(S52_MAR_SAFETY_DEPTH))
        symbol_prefix = "SOUNDS";
    else
        symbol_prefix = "SOUNDG";

    if (NULL != tecsoustr) {
        _parseList(tecsoustr->str, tecsou);
        if (strpbrk(tecsou, "\006"))
            g_string_sprintfa(sndfrm02, ";SY(%sB1)", symbol_prefix);
    }

    if (NULL != quasoustr) _parseList(quasoustr->str, quasou);
    if (NULL != statusstr) _parseList(statusstr->str, status);

    if (strpbrk(quasou, "\003\004\005\010\011") || strpbrk(status, "\022"))
            g_string_sprintfa(sndfrm02, ";SY(%sC2)", symbol_prefix);
    else {
        GString *quaposstr = S57_getAttVal(geo, "QUAPOS");
        int      quapos    = (NULL == quaposstr)? 0 : atoi(quaposstr->str);

        if (NULL != quaposstr) {
            if (2 <= quapos && quapos < 10)
                g_string_sprintfa(sndfrm02, ";SY(%sC2)", symbol_prefix);
        }
    }

    // Continuation A
    if (depth_value < 10.0) {
        // can be above water (negative)
        int fraction = (int)ABS((depth_value - leading_digit)*10);

        g_string_sprintfa(sndfrm02, ";SY(%s1%1i)", symbol_prefix, (int)ABS(leading_digit));
        g_string_sprintfa(sndfrm02, ";SY(%s5%1i)", symbol_prefix, fraction);

        // above sea level (negative)
        if (depth_value < 0.0)
            g_string_sprintfa(sndfrm02, ";SY(%sA1)", symbol_prefix);

        return sndfrm02;
    }

    if (depth_value < 31.0) {
        double fraction = depth_value - leading_digit;

        if (fraction != 0.0) {
            fraction = fraction * 10;
            if (leading_digit >= 10.0)
                g_string_sprintfa(sndfrm02, ";SY(%s2%1i)", symbol_prefix, (int)leading_digit/10);

            g_string_sprintfa(sndfrm02, ";SY(%s1%1i)", symbol_prefix, (int)leading_digit);
            g_string_sprintfa(sndfrm02, ";SY(%s5%1i)", symbol_prefix, (int)fraction);

            return sndfrm02;
        }
    }

    // Continuation B
    depth_value = leading_digit;    // truncate to integer
    if (depth_value < 100.0) {
        double first_digit = leading_digit / 10;
        double secnd_digit = leading_digit - (first_digit * 10);

        g_string_sprintfa(sndfrm02, ";SY(%s1%1i)", symbol_prefix, (int)first_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s0%1i)", symbol_prefix, (int)secnd_digit);

        return sndfrm02;
    }

    if (depth_value < 1000.0) {
        double first_digit = leading_digit / 100;
        double secnd_digit = leading_digit - (first_digit * 100);
        double third_digit = leading_digit - (first_digit * 100) - (secnd_digit * 10);

        g_string_sprintfa(sndfrm02, ";SY(%s2%1i)", symbol_prefix, (int)first_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s1%1i)", symbol_prefix, (int)secnd_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s0%1i)", symbol_prefix, (int)third_digit);

        return sndfrm02;
    }

    if (depth_value < 10000.0) {
        double first_digit = leading_digit / 1000;
        double secnd_digit = leading_digit - (first_digit * 1000);
        double third_digit = leading_digit - (first_digit * 1000) - (secnd_digit * 100);
        double last_digit  = leading_digit - (first_digit * 1000) - (secnd_digit * 100) - (third_digit * 100) ;

        g_string_sprintfa(sndfrm02, ";SY(%s2%1i)", symbol_prefix, (int)first_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s1%1i)", symbol_prefix, (int)secnd_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s0%1i)", symbol_prefix, (int)third_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s4%1i)", symbol_prefix, (int)last_digit);

        return sndfrm02;
    }

    // Continuation C
    {
        double first_digit  = leading_digit / 10000;
        double secnd_digit  = leading_digit - (first_digit * 10000);
        double third_digit  = leading_digit - (first_digit * 10000) - (secnd_digit * 1000);
        double fourth_digit = leading_digit - (first_digit * 10000) - (secnd_digit * 1000) - (third_digit * 100) ;
        double last_digit   = leading_digit - (first_digit * 10000) - (secnd_digit * 1000) - (third_digit * 100) - (fourth_digit * 10) ;

        g_string_sprintfa(sndfrm02, ";SY(%s3%1i)", symbol_prefix, (int)first_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s2%1i)", symbol_prefix, (int)secnd_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s1%1i)", symbol_prefix, (int)third_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s0%1i)", symbol_prefix, (int)fourth_digit);
        g_string_sprintfa(sndfrm02, ";SY(%s4%1i)", symbol_prefix, (int)last_digit);

        return sndfrm02;
    }

    return sndfrm02;
}

static GString *TOPMAR01 (S57_geo *geo)
// Remarks: Topmark objects are to be symbolized through consideration of their
// platforms e.g. a buoy. Therefore this conditional symbology procedure
// searches for platforms by looking for other objects that are located at the
// same position.. Based on the finding whether the platform is rigid or
// floating, the respective upright or sloping symbol is selected and presented
// at the objects location. Buoy symbols and topmark symbols have been
// carefully designed to fit to each other when combined at the same position.
// The result is a composed symbol that looks like the traditional symbols the
// mariner is used to.
{
    GString *topshpstr = S57_getAttVal(geo, "TOPSHP");
    GString *topmar    = NULL;
    char    *sy        = NULL;

    if (NULL == topshpstr)
        sy = ";SY(QUESMRK1)";
    else {
        int floating    = FALSE; // not a floating platform
        int topshp      = (NULL==topshpstr) ? 0 : atoi(topshpstr->str);

        if (TRUE == _atPtPos(geo, FLOATLIST))
            floating = TRUE;
        else
            // FIXME: this test is wierd since it doesn't affect 'floating'
            if (TRUE == _atPtPos(geo, RIGIDLIST))
                floating = FALSE;


        if (floating) {
            // floating platform
            switch (topshp) {
                case 1 : sy = ";SY(TOPMAR02)"; break;
                case 2 : sy = ";SY(TOPMAR04)"; break;
                case 3 : sy = ";SY(TOPMAR10)"; break;
                case 4 : sy = ";SY(TOPMAR12)"; break;

                case 5 : sy = ";SY(TOPMAR13)"; break;
                case 6 : sy = ";SY(TOPMAR14)"; break;
                case 7 : sy = ";SY(TOPMAR65)"; break;
                case 8 : sy = ";SY(TOPMAR17)"; break;

                case 9 : sy = ";SY(TOPMAR16)"; break;
                case 10: sy = ";SY(TOPMAR08)"; break;
                case 11: sy = ";SY(TOPMAR07)"; break;
                case 12: sy = ";SY(TOPMAR14)"; break;

                case 13: sy = ";SY(TOPMAR05)"; break;
                case 14: sy = ";SY(TOPMAR06)"; break;
                case 17: sy = ";SY(TMARDEF2)"; break;
                case 18: sy = ";SY(TOPMAR10)"; break;

                case 19: sy = ";SY(TOPMAR13)"; break;
                case 20: sy = ";SY(TOPMAR14)"; break;
                case 21: sy = ";SY(TOPMAR13)"; break;
                case 22: sy = ";SY(TOPMAR14)"; break;

                case 23: sy = ";SY(TOPMAR14)"; break;
                case 24: sy = ";SY(TOPMAR02)"; break;
                case 25: sy = ";SY(TOPMAR04)"; break;
                case 26: sy = ";SY(TOPMAR10)"; break;

                case 27: sy = ";SY(TOPMAR17)"; break;
                case 28: sy = ";SY(TOPMAR18)"; break;
                case 29: sy = ";SY(TOPMAR02)"; break;
                case 30: sy = ";SY(TOPMAR17)"; break;

                case 31: sy = ";SY(TOPMAR14)"; break;
                case 32: sy = ";SY(TOPMAR10)"; break;
                case 33: sy = ";SY(TMARDEF2)"; break;
                default: sy = ";SY(TMARDEF2)"; break;
            }
        } else {
            // not a floating platform
            switch (topshp) {
                case 1 : sy = ";SY(TOPMAR22)"; break;
                case 2 : sy = ";SY(TOPMAR24)"; break;
                case 3 : sy = ";SY(TOPMAR30)"; break;
                case 4 : sy = ";SY(TOPMAR32)"; break;

                case 5 : sy = ";SY(TOPMAR33)"; break;
                case 6 : sy = ";SY(TOPMAR34)"; break;
                case 7 : sy = ";SY(TOPMAR85)"; break;
                case 8 : sy = ";SY(TOPMAR86)"; break;

                case 9 : sy = ";SY(TOPMAR36)"; break;
                case 10: sy = ";SY(TOPMAR28)"; break;
                case 11: sy = ";SY(TOPMAR27)"; break;
                case 12: sy = ";SY(TOPMAR14)"; break;

                case 13: sy = ";SY(TOPMAR25)"; break;
                case 14: sy = ";SY(TOPMAR26)"; break;
                case 15: sy = ";SY(TOPMAR88)"; break;
                case 16: sy = ";SY(TOPMAR87)"; break;

                case 17: sy = ";SY(TMARDEF1)"; break;
                case 18: sy = ";SY(TOPMAR30)"; break;
                case 19: sy = ";SY(TOPMAR33)"; break;
                case 20: sy = ";SY(TOPMAR34)"; break;

                case 21: sy = ";SY(TOPMAR33)"; break;
                case 22: sy = ";SY(TOPMAR34)"; break;
                case 23: sy = ";SY(TOPMAR34)"; break;
                case 24: sy = ";SY(TOPMAR22)"; break;

                case 25: sy = ";SY(TOPMAR24)"; break;
                case 26: sy = ";SY(TOPMAR30)"; break;
                case 27: sy = ";SY(TOPMAR86)"; break;
                case 28: sy = ";SY(TOPMAR89)"; break;

                case 29: sy = ";SY(TOPMAR22)"; break;
                case 30: sy = ";SY(TOPMAR86)"; break;
                case 31: sy = ";SY(TOPMAR14)"; break;
                case 32: sy = ";SY(TOPMAR30)"; break;
                case 33: sy = ";SY(TMARDEF1)"; break;
                default: sy = ";SY(TMARDEF1)"; break;
            }
        }

    }

    topmar = g_string_new(sy);

    return topmar;
}

/*
static GString *_UDWHAZ03(S57_geo *geo, double depth_value)
// Remarks: Obstructions or isolated underwater dangers of depths less than the safety
// contour which lie within the safe waters defined by the safety contour are
// to be presented by a specific isolated danger symbol as hazardous objects
// and put in IMO category DISPLAYBASE (see (3), App.2, 1.3). This task
// is performed by this conditional symbology procedure.
{
    GString *udwhaz03str    = NULL;
    int      danger         = FALSE;
    double   safety_contour = S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR);
    S57_geo *geoTmp         = geo;

    if (depth_value <= safety_contour) {
        // that intersect this point/line/area for OBSTRN04
        // that intersect this point/area      for WRECKS02

        // get area DEPARE & DRGARE that intersect this point/line/area
        while (NULL != (geoTmp = S57_nextObj(geoTmp))) {

            if (LINES_T  == S57_getObjtype(geoTmp)) {
                GString *drval2str = S57_getAttVal(geoTmp, "DRVAL2");
                double   drval2    = (NULL == drval2str) ? 0.0 : atof(drval2str->str);

                if (drval2 < safety_contour) {
                    danger = TRUE;
                    break;
                }

            } else {
                //
                GString *drval1str = S57_getAttVal(geoTmp, "DRVAL1");
                double   drval1    = (NULL == drval1str) ? 0.0 : atof(drval1str->str);

                if (drval1 >= safety_contour) {
                    danger = TRUE;
                    break;
                }
            }

        }


        //danger = TRUE;   // true
        if (TRUE == danger) {
            GString *watlevstr = S57_getAttVal(geo, "WATLEV");
            if (NULL != watlevstr && ('1' == *watlevstr->str || '2' == *watlevstr->str))
                udwhaz03str = g_string_new(";OP(--D14050");
            else {
                udwhaz03str = g_string_new(";OP(8OD14010);SY(ISODGR51)");
                S57_setAtt(geo, "SCAMIN", "INFINITE");
            }
        }
    }

    S57_unlinkObj(geo);

    return udwhaz03str;
}

static GString *VESSEL01 (S57_geo *geo)
// Remarks: The mariner should be prompted to select from the following options:
// - ARPA target or AIS report (overall decision or vessel by vessel) (vesrce)
// - *time-period determining vector-length for all vectors (vecper)
// - whether to show a vector (overall or vessel by vessel) (vestat)
// - *whether to symbolize vector stabilization (vecstb)
// - *whether to show one-minute or six-minute vector time marks (vecmrk)
// - whether to show heading line on AIS vessel reports (headng)
// * Note that the same vector parameters should be used for own-ship and all vessel
// vectors.
{
    PRINTF("Mariner's object not drawn\n");
    return NULL;
}

static GString *VRMEBL01 (S57_geo *geo)
// Remarks: This conditional symbology procedure symbolizes the three cases of range
// circle, bearing line and range/bearing line. VRM's and EBL's can be ship-centred
// or freely movable, and two line-styles are available
{
    PRINTF("Mariner's object not drawn\n");
    return NULL;
}

static GString *WRECKS02 (S57_geo *geo)
// Remarks: Wrecks of depths less than the safety contour which lie within the safe waters
// defined by the safety contour are to be presented by a specific isolated
// danger symbol and put in IMO category DISPLAYBASE (see (3), App.2,
// 1.3). This task is performed by the sub-procedure "UDWHAZ03" which is
// called by this symbology procedure.
{
    GString *wrecks02str = NULL;
    GString *sndfrm02str = NULL;
    GString *udwhaz03str = NULL;
    GString *quapnt01str = NULL;
    double   least_depth = UNKNOWN;
    double   depth_value = UNKNOWN;
    GString *valsoustr   = S57_getAttVal(geo, "VALSOU");
    double   valsou      = UNKNOWN;

    // exit if not in drawing state
    if (1 == S52_state)
        return NULL;

    if (NULL != valsoustr) {
        valsou      = atof(valsoustr->str);
        depth_value = valsou;
        sndfrm02str = _SNDFRM02(geo, depth_value);
    } else {
        if (AREAS_T == S57_getObjtype(geo))
            least_depth = _DEPVAL01(geo, least_depth);

        if (least_depth == UNKNOWN) {
            // WARNING: ambiguity removed in WRECKS03 (see update)
            GString *watlevstr = S57_getAttVal(geo, "WATLEV");
            GString *catwrkstr = S57_getAttVal(geo, "CATWRK");

            if (NULL == watlevstr) // default
                depth_value = -15.0;
            else
                switch (*watlevstr->str) { // ambiguous
                    case '1':
                    case '2': depth_value = -15.0 ; break;
                    case '3': depth_value =   0.01; break;
                    case '4': depth_value = -15.0 ; break;
                    case '5': depth_value =   0.0 ; break;
                    case '6': depth_value = -15.0 ; break;
                    default :{
                        if (NULL != catwrkstr) {
                            switch (*catwrkstr->str) {
                                case '1': depth_value =  20.0; break;
                                case '2': depth_value =   0.0; break;
                                case '4':
                                case '5': depth_value = -15.0; break;
                            }
                        }
                    }
                }
        } else
            depth_value = least_depth;


    }

    udwhaz03str = _UDWHAZ03(geo, depth_value);
    quapnt01str = _QUAPNT01(geo);

    if (POINT_T == S57_getObjtype(geo)) {
        if (NULL != udwhaz03str) {
            wrecks02str = g_string_new(udwhaz03str->str);

            if (NULL != quapnt01str)
                g_string_append(wrecks02str, quapnt01str->str);

        } else {
            // Continuation A (POINT_T)
            if (UNKNOWN != valsou) {

                if (valsou <= 20.0) {
                    wrecks02str = g_string_new(";SY(DANGER01)");
                    if (NULL != sndfrm02str)
                        g_string_append(wrecks02str, sndfrm02str->str);

                } else
                    wrecks02str = g_string_new(";SY(DANGER02)");

                if (NULL != udwhaz03str)
                    g_string_append(wrecks02str, udwhaz03str->str);
                if (NULL != quapnt01str)
                    g_string_append(wrecks02str, quapnt01str->str);

            } else {
                char    *sym       = NULL;
                GString *catwrkstr = S57_getAttVal(geo, "CATWRK");
                GString *watlevstr = S57_getAttVal(geo, "WATLEV");

                if (NULL != catwrkstr && NULL != watlevstr) {
                    if ('1' == *catwrkstr->str && '3' == *watlevstr->str)
                        sym =";SY(WRECKS04)";
                    else {
                        if ('2' == *catwrkstr->str && '3' == *watlevstr->str)
                            sym = ";SY(WRECKS05)";
                        else {
                            if ('4' == *catwrkstr->str || '5' == *catwrkstr->str)
                                sym = ";SY(WRECKS01)";
                            else {
                                if ('1' == *watlevstr->str ||
                                    '2' == *watlevstr->str ||
                                    '5' == *watlevstr->str ||
                                    '4' == *watlevstr->str ){
                                    sym = ";SY(WRECKS01)";
                                } else
                                    sym = ";SY(WRECKS05)"; // default

                            }
                        }
                    }
                }

                wrecks02str = g_string_new(sym);
                if (NULL != quapnt01str)
                    g_string_append(wrecks02str, quapnt01str->str);

            }

        }


    } else {
        // Continuation B (AREAS_T)
        GString *quaposstr = S57_getAttVal(geo, "QUAPOS");
        int      quapos    = (NULL == quaposstr)? 0 : atoi(quaposstr->str);
        char    *line      = NULL;

        if (2 <= quapos && quapos < 10)
            line = ";LC(LOWACC41)";
        else {
            if ( NULL != udwhaz03str)
                line = ";LS(DOTT,2,CHBLK)";
            else {
                 if (UNKNOWN != valsou){
                     if (valsou <= 20)
                         line = ";LS(DOTT,2,CHBLK)";
                     else
                         line = ";LS(DASH,2,CHBLK)";
                 } else {
                     GString  *watlevstr = S57_getAttVal(geo, "WATLEV");

                     if (NULL == watlevstr)
                         line = ";LS(DOTT,2,CSTLN)";
                     else {
                         switch (*watlevstr->str){
                             case '1':
                             case '2': line = ";LS(SOLD,2,CSTLN)"; break;
                             case '4': line = ";LS(DASH,2,CSTLN)"; break;
                             case '3':
                             case '5':

                             default : line = ";LS(DOTT,2,CSTLN)"; break;
                         }
                     }

                 }
            }
        }
        wrecks02str = g_string_new(line);

        if (UNKNOWN != valsou) {
            if (valsou <= 20) {
                if (NULL != udwhaz03str)
                    g_string_append(wrecks02str, udwhaz03str->str);

                if (NULL != quapnt01str)
                    g_string_append(wrecks02str, quapnt01str->str);

                if (NULL != sndfrm02str)
                    g_string_append(wrecks02str, sndfrm02str->str);

            } else {
                // NOTE: ??? same as above ???
                if (NULL != udwhaz03str)
                    g_string_append(wrecks02str, udwhaz03str->str);

                if (NULL != quapnt01str)
                    g_string_append(wrecks02str, quapnt01str->str);
            }
        } else {
            char    *ac        = NULL;
            GString *watlevstr = S57_getAttVal(geo, "WATLEV");

            if (NULL == watlevstr)
                ac = ";AC(DEPVS)";
            else
                switch (*watlevstr->str) {
                    case '1':
                    case '2': ac = ";AC(CHBRN)"; break;
                    case '4': ac = ";AC(DEPIT)"; break;
                    case '5':
                    case '3':
                    default : ac = ";AC(DEPVS)"; break;
                }

            g_string_append(wrecks02str, ac);

            if (NULL != udwhaz03str)
                g_string_append(wrecks02str, udwhaz03str->str);

            if (NULL != quapnt01str)
                g_string_append(wrecks02str, quapnt01str->str);
        }
    }

    if (NULL != sndfrm02str) g_string_free(sndfrm02str, TRUE);
    if (NULL != udwhaz03str) g_string_free(udwhaz03str, TRUE);
    if (NULL != quapnt01str) g_string_free(quapnt01str, TRUE);

    return wrecks02str;
}


//--------------------------------
//
// JUMP TABLE SECTION
//
//--------------------------------


CondSymb condTable[] = {
   // name      call            Sub-Procedure
   {"CLRLIN01", CLRLIN01},   //
   {"DATCVR01", DATCVR01},   //
   {"DEPARE01", DEPARE01},   // _RESCSP01, _SEABED01
   {"DEPCNT02", DEPCNT02},   //
   {"LEGLIN02", LEGLIN02},   //                          str
   {"LIGHTS05", LIGHTS05},   // _LITDSN01
   {"OBSTRN04", OBSTRN04},   // _DEPVAL01, _QUAPNT01, _SNDFRM02, _UDWHAZ03
   {"OWNSHP02", OWNSHP02},   //
   {"PASTRK01", PASTRK01},   //
   {"QUAPOS01", QUAPOS01},   // _QUALIN01, _QUAPNT01
   {"SLCONS03", SLCONS03},   //
   {"RESARE02", RESARE02},   //
   {"RESTRN01", RESTRN01},   // _RESCSP01
   {"SOUNDG02", SOUNDG02},   // _SNDFRM02
   {"TOPMAR01", TOPMAR01},   //
   {"VESSEL01", VESSEL01},   //
   {"VRMEBL01", VRMEBL01},   //
   {"WRECKS02", WRECKS02},   // _DEPVAL01, _QUAPNT01, _SNDFRM02, _UDWHAZ03

   {"########",NULL}
};
*/
#endif
