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

#include "ogr_dwg.h"
#include "cpl_conv.h"

#include "ogrdxf_polyline_smooth.h"

CPL_CVSID("$Id: ogrdwglayer.cpp 901a2011cf840e038937efafb2f9cf133a835894 2021-03-17 16:38:43 +0100 Even Rouault $")

/************************************************************************/
/*                            OGRDWGLayer()                             */
/************************************************************************/

OGRDWGLayer::OGRDWGLayer( OGRDWGDataSource *poDSIn )

{
    this->poDS = poDSIn;

    iNextFID = 0;

    poFeatureDefn = new OGRFeatureDefn( "entities" );
    SetDescription( poFeatureDefn->GetName() );
    poFeatureDefn->Reference();

    poDS->AddStandardFields( poFeatureDefn );

    if( !poDS->InlineBlocks() )
    {
        OGRFieldDefn  oScaleField( "BlockScale", OFTRealList );
        poFeatureDefn->AddFieldDefn( &oScaleField );

        OGRFieldDefn  oBlockAngleField( "BlockAngle", OFTReal );
        poFeatureDefn->AddFieldDefn( &oBlockAngleField );
    }

/* -------------------------------------------------------------------- */
/*      Find the *Paper_Space block, which seems to contain all the     */
/*      regular entities.                                               */
/* -------------------------------------------------------------------- */
    OdDbBlockTablePtr pTable = poDS->GetDB()->getBlockTableId().safeOpenObject();
    OdDbSymbolTableIteratorPtr pBlkIter = pTable->newIterator();

    for (pBlkIter->start(); ! pBlkIter->done(); pBlkIter->step())
    {
        m_poBlock = pBlkIter->getRecordId().safeOpenObject();

        if( EQUAL(m_poBlock->getName(),"*Model_Space") )
            break;
        else
            m_poBlock = nullptr;
    }

    OGRDWGLayer::ResetReading();
}

/************************************************************************/
/*                           ~OGRDWGLayer()                           */
/************************************************************************/

OGRDWGLayer::~OGRDWGLayer()

{
    ClearPendingFeatures();
    if( m_nFeaturesRead > 0 && poFeatureDefn != nullptr )
    {
        CPLDebug( "DWG", "%d features read on layer '%s'.",
                  (int) m_nFeaturesRead,
                  poFeatureDefn->GetName() );
    }

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

/************************************************************************/
/*                            TextUnescape()                            */
/************************************************************************/

CPLString OGRDWGLayer::TextUnescape( OdString oString, bool bIsMText )

{
    return ACTextUnescape( (const char *) oString, poDS->GetEncoding(), bIsMText );
}

/************************************************************************/
/*                           SetBlockTable()                            */
/*                                                                      */
/*      Set what block table to read features from.  This layer         */
/*      object is used to read blocks features as well as generic       */
/*      entities.                                                       */
/************************************************************************/

void OGRDWGLayer::SetBlockTable( OdDbBlockTableRecordPtr poNewBlock )

{
    m_poBlock = poNewBlock;

    ResetReading();
}

/************************************************************************/
/*                        ClearPendingFeatures()                        */
/************************************************************************/

void OGRDWGLayer::ClearPendingFeatures()

{
    while( !apoPendingFeatures.empty() )
    {
        delete apoPendingFeatures.front();
        apoPendingFeatures.pop();
    }
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void OGRDWGLayer::ResetReading()

{
    iNextFID = 0;
    ClearPendingFeatures();

    if( !m_poBlock.isNull() )
        poEntIter = m_poBlock->newIterator();
}

/************************************************************************/
/*                     TranslateGenericProperties()                     */
/*                                                                      */
/*      Try and convert entity properties handled similarly for most    */
/*      or all entity types                                             */
/************************************************************************/

void OGRDWGLayer::TranslateGenericProperties( OGRFeature *poFeature,
                                              OdDbEntityPtr poEntity )

{
    poFeature->SetField( "Layer", TextUnescape(poEntity->layer(), false) );
    poFeature->SetField( "Linetype", TextUnescape(poEntity->layer(), false) );

    CPLString osValue;
    osValue.Printf( "%d", (int) poEntity->lineWeight() );
    oStyleProperties["LineWeight"] = osValue;

    OdDbHandle oHandle = poEntity->getDbHandle();
    poFeature->SetField( "EntityHandle", (const char *) oHandle.ascii() );

    if( poEntity->colorIndex() != 256 )
    {
        osValue.Printf( "%d", poEntity->colorIndex() );
        oStyleProperties["Color"] = osValue.c_str();
    }

/* -------------------------------------------------------------------- */
/*      Collect the subclasses.                                         */
/* -------------------------------------------------------------------- */
    CPLString osSubClasses;
    OdRxClass *poClass = poEntity->isA();

    while( poClass != nullptr )
    {
        if( !osSubClasses.empty() )
            osSubClasses = ":" + osSubClasses;

        osSubClasses = ((const char *) poClass->name()) + osSubClasses;
        if( EQUAL(poClass->name(),"AcDbEntity") )
            break;

        poClass = poClass->myParent();
    }

    poFeature->SetField( "SubClasses", osSubClasses.c_str() );

/* -------------------------------------------------------------------- */
/*      Collect Xdata.                                                  */
/* -------------------------------------------------------------------- */
    OdResBufPtr poResBufBase = poEntity->xData();
    OdResBuf *poResBuf = poResBufBase;
    CPLString osFullXData;

    for ( ; poResBuf != nullptr; poResBuf = poResBuf->next() )
    {
        CPLString osXDataItem;

        switch (OdDxfCode::_getType(poResBuf->restype()))
        {
          case OdDxfCode::Name:
          case OdDxfCode::String:
          case OdDxfCode::LayerName:
            osXDataItem = (const char *) poResBuf->getString();
            break;

          case OdDxfCode::Bool:
            if( poResBuf->getBool() )
                osXDataItem = "true";
            else
                osXDataItem = "false";
            break;

          case OdDxfCode::Integer8:
            osXDataItem.Printf( "%d", (int) poResBuf->getInt8() );
            break;

          case OdDxfCode::Integer16:
            osXDataItem.Printf( "%d", (int) poResBuf->getInt16() );
            break;

          case OdDxfCode::Integer32:
            osXDataItem.Printf( "%d", (int) poResBuf->getInt32() );
            break;

          case OdDxfCode::Double:
          case OdDxfCode::Angle:
            osXDataItem.Printf( "%g", poResBuf->getDouble() );
            break;

          case OdDxfCode::Point:
          {
              OdGePoint3d oPoint = poResBuf->getPoint3d();
              osXDataItem.Printf( "(%g,%g,%g)", oPoint.x, oPoint.y, oPoint.z );
          }
          break;

          case OdDxfCode::BinaryChunk:
          {
              OdBinaryData oBinData = poResBuf->getBinaryChunk();
              char *pszAsHex = CPLBinaryToHex( oBinData.size(),
                                               (GByte*) oBinData.asArrayPtr() );
              osXDataItem = pszAsHex;
              CPLFree( pszAsHex );
          }
          break;

          case OdDxfCode::ObjectId:
          case OdDxfCode::SoftPointerId:
          case OdDxfCode::HardPointerId:
          case OdDxfCode::SoftOwnershipId:
          case OdDxfCode::HardOwnershipId:
          case OdDxfCode::Handle:
            osXDataItem = (const char *) poResBuf->getHandle().ascii();
            break;

          default:
            break;
        }

        if( !osFullXData.empty() )
            osFullXData += " ";
        osFullXData += (const char *) osXDataItem;
    }

    poFeature->SetField( "ExtendedEntity", osFullXData );

#ifdef notdef
      // OCS vector.
      case 210:
        oStyleProperties["210_N.dX"] = pszValue;
        break;

      case 220:
        oStyleProperties["220_N.dY"] = pszValue;
        break;

      case 230:
        oStyleProperties["230_N.dZ"] = pszValue;
        break;

      default:
        break;
    }
#endif
}

/************************************************************************/
/*                          PrepareLineStyle()                          */
/************************************************************************/

void OGRDWGLayer::PrepareLineStyle( OGRFeature *poFeature )

{
    CPLString osLayer = poFeature->GetFieldAsString("Layer");

/* -------------------------------------------------------------------- */
/*      Is the layer disabled/hidden/frozen/off?                        */
/* -------------------------------------------------------------------- */
    int bHidden =
        EQUAL(poDS->LookupLayerProperty( osLayer, "Hidden" ), "1");

/* -------------------------------------------------------------------- */
/*      Work out the color for this feature.                            */
/* -------------------------------------------------------------------- */
    int nColor = 256;

    if( oStyleProperties.count("Color") > 0 )
        nColor = atoi(oStyleProperties["Color"]);

    // Use layer color?
    if( nColor < 1 || nColor > 255 )
    {
        const char *pszValue = poDS->LookupLayerProperty( osLayer, "Color" );
        if( pszValue != nullptr )
            nColor = atoi(pszValue);
    }

    if( nColor < 1 || nColor > 255 )
        return;

/* -------------------------------------------------------------------- */
/*      Get line weight if available.                                   */
/* -------------------------------------------------------------------- */
    double dfWeight = 0.0;

    if( oStyleProperties.count("LineWeight") > 0 )
    {
        CPLString osWeight = oStyleProperties["LineWeight"];

        if( osWeight == "-1" )
            osWeight = poDS->LookupLayerProperty(osLayer,"LineWeight");

        dfWeight = CPLAtof(osWeight) / 100.0;
    }

/* -------------------------------------------------------------------- */
/*      Do we have a dash/dot line style?                               */
/* -------------------------------------------------------------------- */
    const char *pszPattern = poDS->LookupLineType(
        poFeature->GetFieldAsString("Linetype") );

/* -------------------------------------------------------------------- */
/*      Format the style string.                                        */
/* -------------------------------------------------------------------- */
    CPLString osStyle;
    const unsigned char *pabyDWGColors = ACGetColorTable();

    osStyle.Printf( "PEN(c:#%02x%02x%02x",
                    pabyDWGColors[nColor*3+0],
                    pabyDWGColors[nColor*3+1],
                    pabyDWGColors[nColor*3+2] );

    if( bHidden )
        osStyle += "00";

    if( dfWeight > 0.0 )
    {
        char szBuffer[64];
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.2g", dfWeight);
        char* pszComma = strchr(szBuffer, ',');
        if (pszComma)
            *pszComma = '.';
        osStyle += CPLString().Printf( ",w:%sg", szBuffer );
    }

    if( pszPattern )
    {
        osStyle += ",p:\"";
        osStyle += pszPattern;
        osStyle += "\"";
    }

    osStyle += ")";

    poFeature->SetStyleString( osStyle );
}

/************************************************************************/
/*                           TranslateMTEXT()                           */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslateMTEXT( OdDbEntityPtr poEntity )

{
    OdDbMTextPtr poMTE = OdDbMText::cast( poEntity );
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Set the location.                                               */
/* -------------------------------------------------------------------- */
    OdGePoint3d oLocation = poMTE->location();

    poFeature->SetGeometryDirectly(
        new OGRPoint( oLocation.x, oLocation.y, oLocation.z ) );

/* -------------------------------------------------------------------- */
/*      Apply text after stripping off any extra terminating newline.   */
/* -------------------------------------------------------------------- */
    CPLString osText = TextUnescape( poMTE->contents(), true );

    if( !osText.empty() && osText.back() == '\n' )
        osText.resize( osText.size() - 1 );

    poFeature->SetField( "Text", osText );

/* -------------------------------------------------------------------- */
/*      We need to escape double quotes with backslashes before they    */
/*      can be inserted in the style string.                            */
/* -------------------------------------------------------------------- */
    if( strchr( osText, '"') != nullptr )
    {
        CPLString osEscaped;
        size_t iC;

        for( iC = 0; iC < osText.size(); iC++ )
        {
            if( osText[iC] == '"' )
                osEscaped += "\\\"";
            else
                osEscaped += osText[iC];
        }
        osText = osEscaped;
    }

/* -------------------------------------------------------------------- */
/*      Work out the color for this feature.                            */
/* -------------------------------------------------------------------- */
    int nColor = 256;

    if( oStyleProperties.count("Color") > 0 )
        nColor = atoi(oStyleProperties["Color"]);

    // Use layer color?
    if( nColor < 1 || nColor > 255 )
    {
        CPLString osLayer = poFeature->GetFieldAsString("Layer");
        const char *pszValue = poDS->LookupLayerProperty( osLayer, "Color" );
        if( pszValue != nullptr )
            nColor = atoi(pszValue);
    }

/* -------------------------------------------------------------------- */
/*      Prepare style string.                                           */
/* -------------------------------------------------------------------- */
    double dfAngle = poMTE->rotation() * 180 / M_PI;
    double dfHeight = poMTE->textHeight();
    int nAttachmentPoint = (int) poMTE->attachment();

    CPLString osStyle;
    char szBuffer[64];
    char* pszComma = nullptr;

    osStyle.Printf("LABEL(f:\"Arial\",t:\"%s\"",osText.c_str());

    if( dfAngle != 0.0 )
    {
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfAngle);
        pszComma = strchr(szBuffer, ',');
        if (pszComma)
            *pszComma = '.';
        osStyle += CPLString().Printf(",a:%s", szBuffer);
    }

    if( dfHeight != 0.0 )
    {
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfHeight);
        pszComma = strchr(szBuffer, ',');
        if (pszComma)
            *pszComma = '.';
        osStyle += CPLString().Printf(",s:%sg", szBuffer);
    }

    if( nAttachmentPoint >= 0 && nAttachmentPoint <= 9 )
    {
        const static int anAttachmentMap[10] =
            { -1, 7, 8, 9, 4, 5, 6, 1, 2, 3 };

        osStyle +=
            CPLString().Printf(",p:%d", anAttachmentMap[nAttachmentPoint]);
    }

    if( nColor > 0 && nColor < 256 )
    {
        const unsigned char *pabyDWGColors = ACGetColorTable();
        osStyle +=
            CPLString().Printf( ",c:#%02x%02x%02x",
                                pabyDWGColors[nColor*3+0],
                                pabyDWGColors[nColor*3+1],
                                pabyDWGColors[nColor*3+2] );
    }

    osStyle += ")";

    poFeature->SetStyleString( osStyle );

    return poFeature;
}

/************************************************************************/
/*                           TranslateTEXT()                            */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslateTEXT( OdDbEntityPtr poEntity )

{
    OdDbTextPtr poText = OdDbText::cast( poEntity );
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Set the location.                                               */
/* -------------------------------------------------------------------- */
    OdGePoint3d oLocation = poText->position();

    poFeature->SetGeometryDirectly(
        new OGRPoint( oLocation.x, oLocation.y, oLocation.z ) );

/* -------------------------------------------------------------------- */
/*      Apply text after stripping off any extra terminating newline.   */
/* -------------------------------------------------------------------- */
    CPLString osText = TextUnescape( poText->textString(), false );

    if( !osText.empty() && osText.back() == '\n' )
        osText.resize( osText.size() - 1 );

    poFeature->SetField( "Text", osText );

/* -------------------------------------------------------------------- */
/*      We need to escape double quotes with backslashes before they    */
/*      can be inserted in the style string.                            */
/* -------------------------------------------------------------------- */
    if( strchr( osText, '"') != nullptr )
    {
        CPLString osEscaped;
        size_t iC;

        for( iC = 0; iC < osText.size(); iC++ )
        {
            if( osText[iC] == '"' )
                osEscaped += "\\\"";
            else
                osEscaped += osText[iC];
        }
        osText = osEscaped;
    }

/* -------------------------------------------------------------------- */
/*      Is the layer disabled/hidden/frozen/off?                        */
/* -------------------------------------------------------------------- */
    CPLString osLayer = poFeature->GetFieldAsString("Layer");

    int bHidden =
        EQUAL(poDS->LookupLayerProperty( osLayer, "Hidden" ), "1");

/* -------------------------------------------------------------------- */
/*      Work out the color for this feature.                            */
/* -------------------------------------------------------------------- */
    int nColor = 256;

    if( oStyleProperties.count("Color") > 0 )
        nColor = atoi(oStyleProperties["Color"]);

    // Use layer color?
    if( nColor < 1 || nColor > 255 )
    {
        const char *pszValue = poDS->LookupLayerProperty( osLayer, "Color" );
        if( pszValue != nullptr )
            nColor = atoi(pszValue);
    }

    if( nColor < 1 || nColor > 255 )
        nColor = 8;

/* -------------------------------------------------------------------- */
/*      Prepare style string.                                           */
/* -------------------------------------------------------------------- */
    double dfAngle = poText->rotation() * 180 / M_PI;
    double dfHeight = poText->height();

    CPLString osStyle;
    char szBuffer[64];
    char* pszComma = nullptr;

    osStyle.Printf("LABEL(f:\"Arial\",t:\"%s\"",osText.c_str());

    if( dfAngle != 0.0 )
    {
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfAngle);
        pszComma = strchr(szBuffer, ',');
        if (pszComma)
            *pszComma = '.';
        osStyle += CPLString().Printf(",a:%s", szBuffer);
    }

    if( dfHeight != 0.0 )
    {
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfHeight);
        pszComma = strchr(szBuffer, ',');
        if (pszComma)
            *pszComma = '.';
        osStyle += CPLString().Printf(",s:%sg", szBuffer);
    }

    const unsigned char *pabyDWGColors = ACGetColorTable();

    snprintf( szBuffer, sizeof(szBuffer), ",c:#%02x%02x%02x",
              pabyDWGColors[nColor*3+0],
              pabyDWGColors[nColor*3+1],
              pabyDWGColors[nColor*3+2] );
    osStyle += szBuffer;

    if( bHidden )
        osStyle += "00";

    osStyle += ")";

    poFeature->SetStyleString( osStyle );

    return poFeature;
}

/************************************************************************/
/*                           TranslatePOINT()                           */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslatePOINT( OdDbEntityPtr poEntity )

{
    OdDbPointPtr poPE = OdDbPoint::cast( poEntity );
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );

    TranslateGenericProperties( poFeature, poEntity );

    OdGePoint3d oPoint = poPE->position();

    poFeature->SetGeometryDirectly( new OGRPoint( oPoint.x, oPoint.y, oPoint.z ) );

    return poFeature;
}

/************************************************************************/
/*                        TranslateLWPOLYLINE()                         */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslateLWPOLYLINE( OdDbEntityPtr poEntity )

{
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );
    OdDbPolylinePtr poPL = OdDbPolyline::cast( poEntity );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Collect polyline details.                                       */
/* -------------------------------------------------------------------- */
    DXFSmoothPolyline   oSmoothPolyline;

    for (unsigned int i = 0; i < poPL->numVerts(); i++)
    {
        OdGePoint3d oPoint;
        poPL->getPointAt( i, oPoint );

        oSmoothPolyline.AddPoint( oPoint.x, oPoint.y, 0.0,
                                 poPL->getBulgeAt( i ) );
    }

    if(oSmoothPolyline.IsEmpty())
    {
        delete poFeature;
        return nullptr;
    }

    if( poPL->isClosed() )
        oSmoothPolyline.Close();

    poFeature->SetGeometryDirectly(
        oSmoothPolyline.Tessellate() );

    PrepareLineStyle( poFeature );

    return poFeature;
}

/************************************************************************/
/*                        Translate2DPOLYLINE()                         */
/************************************************************************/

OGRFeature *OGRDWGLayer::Translate2DPOLYLINE( OdDbEntityPtr poEntity )

{
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );
    OdDb2dPolylinePtr poPL = OdDb2dPolyline::cast( poEntity );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Create a polyline geometry from the vertices.                   */
/* -------------------------------------------------------------------- */
    OGRLineString *poLS = new OGRLineString();
    OdDbObjectIteratorPtr poIter = poPL->vertexIterator();

    while( !poIter->done() )
    {
        OdDb2dVertexPtr poVertex = poIter->entity();
        OdGePoint3d oPoint = poPL->vertexPosition( *poVertex );
        poLS->addPoint( oPoint.x, oPoint.y, oPoint.z );
        poIter->step();
    }

    poFeature->SetGeometryDirectly( poLS );

    PrepareLineStyle( poFeature );

    return poFeature;
}

/************************************************************************/
/*                        Translate3DPOLYLINE()                         */
/************************************************************************/

OGRFeature *OGRDWGLayer::Translate3DPOLYLINE( OdDbEntityPtr poEntity )

{
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );
    OdDb3dPolylinePtr poPL = OdDb3dPolyline::cast( poEntity );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Create a polyline geometry from the vertices.                   */
/* -------------------------------------------------------------------- */
    OGRLineString *poLS = new OGRLineString();
    OdDbObjectIteratorPtr poIter = poPL->vertexIterator();

    while( !poIter->done() )
    {
        OdDb3dPolylineVertexPtr poVertex = poIter->entity();
        OdGePoint3d oPoint = poVertex->position();
        poLS->addPoint( oPoint.x, oPoint.y, oPoint.z );
        poIter->step();
    }

    poFeature->SetGeometryDirectly( poLS );

    PrepareLineStyle( poFeature );

    return poFeature;
}

/************************************************************************/
/*                           TranslateLINE()                            */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslateLINE( OdDbEntityPtr poEntity )

{
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );
    OdDbLinePtr poPL = OdDbLine::cast( poEntity );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Create a polyline geometry from the vertices.                   */
/* -------------------------------------------------------------------- */
    OGRLineString *poLS = new OGRLineString();
    OdGePoint3d oPoint;

    poPL->getStartPoint( oPoint );
    poLS->addPoint( oPoint.x, oPoint.y, oPoint.z );

    poPL->getEndPoint( oPoint );
    poLS->addPoint( oPoint.x, oPoint.y, oPoint.z );

    poFeature->SetGeometryDirectly( poLS );

    PrepareLineStyle( poFeature );

    return poFeature;
}

/************************************************************************/
/*                          TranslateCIRCLE()                           */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslateCIRCLE( OdDbEntityPtr poEntity )

{
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );
    OdDbCirclePtr poC = OdDbCircle::cast( poEntity );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Get geometry information.                                       */
/* -------------------------------------------------------------------- */
    OdGePoint3d oCenter = poC->center();
    double dfRadius = poC->radius();

/* -------------------------------------------------------------------- */
/*      Create geometry                                                 */
/* -------------------------------------------------------------------- */
    OGRGeometry *poCircle =
        OGRGeometryFactory::approximateArcAngles(
            oCenter.x, oCenter.y, oCenter.z,
            dfRadius, dfRadius, 0.0, 0.0, 360.0, 0.0 );

    poFeature->SetGeometryDirectly( poCircle );
    PrepareLineStyle( poFeature );

    return poFeature;
}

/************************************************************************/
/*                            AngleCorrect()                            */
/*                                                                      */
/*      Convert from a "true" angle on the ellipse as returned by       */
/*      the DWG API to an angle of rotation on the ellipse as if the    */
/*      ellipse were actually circular.                                 */
/************************************************************************/

double OGRDWGLayer::AngleCorrect( double dfTrueAngle, double dfRatio )

{
    double dfRotAngle;
    double dfDeltaX, dfDeltaY;

    dfTrueAngle *= (M_PI / 180); // convert to radians.

    dfDeltaX = cos(dfTrueAngle);
    dfDeltaY = sin(dfTrueAngle);

    dfRotAngle = atan2( dfDeltaY, dfDeltaX * dfRatio);

    dfRotAngle *= (180 / M_PI); // convert to degrees.

    if( dfTrueAngle < 0 && dfRotAngle > 0 )
        dfRotAngle -= 360.0;

    if( dfTrueAngle > 360 && dfRotAngle < 360 )
        dfRotAngle += 360.0;

    return dfRotAngle;
}

/************************************************************************/
/*                          TranslateELLIPSE()                          */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslateELLIPSE( OdDbEntityPtr poEntity )

{
    OdDbEllipsePtr poEE = OdDbEllipse::cast( poEntity );
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Get some details.                                               */
/* -------------------------------------------------------------------- */
    double dfStartAngle, dfEndAngle, dfRatio;
    OdGePoint3d oCenter;
    OdGeVector3d oMajorAxis, oUnitNormal;

    // note we reverse start and end angles to account for ogr orientation.
    poEE->get( oCenter, oUnitNormal, oMajorAxis,
               dfRatio, dfEndAngle, dfStartAngle );

    dfStartAngle = -1 * dfStartAngle * 180 / M_PI;
    dfEndAngle   = -1 * dfEndAngle * 180 / M_PI;

/* -------------------------------------------------------------------- */
/*      The DWG SDK expresses the angles as the angle to a real         */
/*      point on the ellipse while DXF and the OGR "arc angles" API     */
/*      work in terms of an angle of rotation on the ellipse as if      */
/*      the ellipse were actually circular.  So we need to "correct"    */
/*      for the ratio.                                                  */
/* -------------------------------------------------------------------- */
    dfStartAngle = AngleCorrect( dfStartAngle, dfRatio );
    dfEndAngle = AngleCorrect( dfEndAngle, dfRatio );

    if( dfStartAngle > dfEndAngle )
        dfEndAngle += 360.0;

/* -------------------------------------------------------------------- */
/*      Compute primary and secondary axis lengths, and the angle of    */
/*      rotation for the ellipse.                                       */
/* -------------------------------------------------------------------- */
    double dfPrimaryRadius, dfSecondaryRadius;
    double dfRotation;

    dfPrimaryRadius = sqrt( oMajorAxis.x * oMajorAxis.x
                            + oMajorAxis.y * oMajorAxis.y
                            + oMajorAxis.z * oMajorAxis.z );

    dfSecondaryRadius = dfRatio * dfPrimaryRadius;

    dfRotation = -1 * atan2( oMajorAxis.y, oMajorAxis.x ) * 180 / M_PI;

/* -------------------------------------------------------------------- */
/*      Create geometry                                                 */
/* -------------------------------------------------------------------- */
    OGRGeometry *poEllipse =
        OGRGeometryFactory::approximateArcAngles(
            oCenter.x, oCenter.y, oCenter.z,
            dfPrimaryRadius, dfSecondaryRadius, dfRotation,
            dfStartAngle, dfEndAngle, 0.0 );

    poFeature->SetGeometryDirectly( poEllipse );

    PrepareLineStyle( poFeature );

    return poFeature;
}

/************************************************************************/
/*                            TranslateARC()                            */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslateARC( OdDbEntityPtr poEntity )

{
    OdDbArcPtr poAE = OdDbArc::cast( poEntity );
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Collect parameters.                                             */
/* -------------------------------------------------------------------- */
    double dfEndAngle = -1 * poAE->startAngle() * 180 / M_PI;
    double dfStartAngle = -1 * poAE->endAngle() * 180 / M_PI;
    double dfRadius = poAE->radius();
    OdGePoint3d oCenter = poAE->center();

/* -------------------------------------------------------------------- */
/*      Create geometry                                                 */
/* -------------------------------------------------------------------- */
    if( dfStartAngle > dfEndAngle )
        dfEndAngle += 360.0;

    OGRGeometry *poArc =
        OGRGeometryFactory::approximateArcAngles(
            oCenter.x, oCenter.y, oCenter.z,
            dfRadius, dfRadius, 0.0, dfStartAngle, dfEndAngle, 0.0 );

    poFeature->SetGeometryDirectly( poArc );

    PrepareLineStyle( poFeature );

    return poFeature;
}

/************************************************************************/
/*                          TranslateSPLINE()                           */
/************************************************************************/

void rbspline(int npts,int k,int p1,double b[],double h[], double p[]);
void rbsplinu(int npts,int k,int p1,double b[],double h[], double p[]);

OGRFeature *OGRDWGLayer::TranslateSPLINE( OdDbEntityPtr poEntity )

{
    OdDbSplinePtr poSpline = OdDbSpline::cast( poEntity );
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );
    std::vector<double> adfControlPoints;
    int nDegree, i;

    TranslateGenericProperties( poFeature, poEntity );

    nDegree = poSpline->degree();

/* -------------------------------------------------------------------- */
/*      Collect the control points in our vector.                       */
/* -------------------------------------------------------------------- */
    int nControlPoints = poSpline->numControlPoints();

    adfControlPoints.push_back( 0.0 ); // some sort of control info.

    for( i = 0; i < nControlPoints; i++ )
    {
        OdGePoint3d oCP;

        poSpline->getControlPointAt( i, oCP );

        adfControlPoints.push_back( oCP.x );
        adfControlPoints.push_back( oCP.y );
        adfControlPoints.push_back( 0.0 );
    }

/* -------------------------------------------------------------------- */
/*      Process values.                                                 */
/* -------------------------------------------------------------------- */
    if( poSpline->isClosed() )
    {
        for( i = 0; i < nDegree; i++ )
        {
            adfControlPoints.push_back( adfControlPoints[i*3+1] );
            adfControlPoints.push_back( adfControlPoints[i*3+2] );
            adfControlPoints.push_back( adfControlPoints[i*3+3] );
        }
    }

/* -------------------------------------------------------------------- */
/*      Interpolate spline                                              */
/* -------------------------------------------------------------------- */
    std::vector<double> h, p;

    h.push_back(1.0);
    for( i = 0; i < nControlPoints; i++ )
        h.push_back( 1.0 );

    // resolution:
    //int p1 = getGraphicVariableInt("$SPLINESEGS", 8) * npts;
    int p1 = nControlPoints * 8;

    p.push_back( 0.0 );
    for( i = 0; i < 3*p1; i++ )
        p.push_back( 0.0 );

    if( poSpline->isClosed() )
        rbsplinu( nControlPoints, nDegree+1, p1, &(adfControlPoints[0]),
                  &(h[0]), &(p[0]) );
    else
        rbspline( nControlPoints, nDegree+1, p1, &(adfControlPoints[0]),
                  &(h[0]), &(p[0]) );

/* -------------------------------------------------------------------- */
/*      Turn into OGR geometry.                                         */
/* -------------------------------------------------------------------- */
    OGRLineString *poLS = new OGRLineString();

    poLS->setNumPoints( p1 );
    for( i = 0; i < p1; i++ )
        poLS->setPoint( i, p[i*3+1], p[i*3+2] );

    poFeature->SetGeometryDirectly( poLS );

    PrepareLineStyle( poFeature );

    return poFeature;
}

/************************************************************************/
/*                      GeometryInsertTransformer                       */
/************************************************************************/

class GeometryInsertTransformer : public OGRCoordinateTransformation
{
public:
    GeometryInsertTransformer() :
            dfXOffset(0),dfYOffset(0),dfZOffset(0),
            dfXScale(1.0),dfYScale(1.0),dfZScale(1.0),
            dfAngle(0.0) {}

    double dfXOffset;
    double dfYOffset;
    double dfZOffset;
    double dfXScale;
    double dfYScale;
    double dfZScale;
    double dfAngle;

    OGRSpatialReference *GetSourceCS() override { return nullptr; }
    OGRSpatialReference *GetTargetCS() override { return nullptr; }

    OGRCoordinateTransformation* Clone() const override { return new GeometryInsertTransformer(*this); }

    virtual OGRCoordinateTransformation* GetInverse() const override { return nullptr; }

    int Transform( int nCount,
                     double *x, double *y, double *z = nullptr,
                     double * /*t*/ = nullptr,
                     int *pabSuccess = nullptr ) override
        {
            int i;
            for( i = 0; i < nCount; i++ )
            {
                double dfXNew, dfYNew;

                x[i] *= dfXScale;
                y[i] *= dfYScale;
                if( z )
                    z[i] *= dfZScale;

                dfXNew = x[i] * cos(dfAngle) - y[i] * sin(dfAngle);
                dfYNew = x[i] * sin(dfAngle) + y[i] * cos(dfAngle);

                x[i] = dfXNew;
                y[i] = dfYNew;

                x[i] += dfXOffset;
                y[i] += dfYOffset;
                if( z )
                    z[i] += dfZOffset;

                if( pabSuccess )
                    pabSuccess[i] = TRUE;
            }
            return TRUE;
        }
};

/************************************************************************/
/*                          TranslateINSERT()                           */
/************************************************************************/

OGRFeature *OGRDWGLayer::TranslateINSERT( OdDbEntityPtr poEntity )

{
    OdDbBlockReferencePtr poRef = OdDbBlockReference::cast( poEntity );
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );

    TranslateGenericProperties( poFeature, poEntity );

/* -------------------------------------------------------------------- */
/*      Collect parameters from the object.                             */
/* -------------------------------------------------------------------- */
    GeometryInsertTransformer oTransformer;
    CPLString osBlockName;
    double dfAngle = poRef->rotation() * 180 / M_PI;
    OdGePoint3d oPosition = poRef->position();
    OdGeScale3d oScale = poRef->scaleFactors();

    oTransformer.dfXOffset = oPosition.x;
    oTransformer.dfYOffset = oPosition.y;
    oTransformer.dfZOffset = oPosition.z;

    oTransformer.dfXScale = oScale.sx;
    oTransformer.dfYScale = oScale.sy;
    oTransformer.dfZScale = oScale.sz;

    oTransformer.dfAngle = poRef->rotation();

    OdDbBlockTableRecordPtr poBlockRec = poRef->blockTableRecord().openObject();
    if (poBlockRec.get())
        osBlockName = (const char *) poBlockRec->getName();

/* -------------------------------------------------------------------- */
/*      In the case where we do not inlined blocks we just capture      */
/*      info on a point feature.                                        */
/* -------------------------------------------------------------------- */
    if( !poDS->InlineBlocks() )
    {
        poFeature->SetGeometryDirectly(
            new OGRPoint( oTransformer.dfXOffset,
                          oTransformer.dfYOffset,
                          oTransformer.dfZOffset ) );

        poFeature->SetField( "BlockName", osBlockName );

        poFeature->SetField( "BlockAngle", dfAngle );
        poFeature->SetField( "BlockScale", 3, &(oTransformer.dfXScale) );

        return poFeature;
    }

/* -------------------------------------------------------------------- */
/*      Lookup the block.                                               */
/* -------------------------------------------------------------------- */
    DWGBlockDefinition *poBlock = poDS->LookupBlock( osBlockName );

    if( poBlock == nullptr )
    {
        delete poFeature;
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Transform the geometry.                                         */
/* -------------------------------------------------------------------- */
    if( poBlock->poGeometry != nullptr )
    {
        OGRGeometry *poGeometry = poBlock->poGeometry->clone();

        poGeometry->transform( &oTransformer );

        poFeature->SetGeometryDirectly( poGeometry );
    }

/* -------------------------------------------------------------------- */
/*      If we have complete features associated with the block, push    */
/*      them on the pending feature stack copying over key override     */
/*      information.                                                    */
/*                                                                      */
/*      Note that while we transform the geometry of the features we    */
/*      don't adjust subtle things like text angle.                     */
/* -------------------------------------------------------------------- */
    unsigned int iSubFeat;

    for( iSubFeat = 0; iSubFeat < poBlock->apoFeatures.size(); iSubFeat++ )
    {
        OGRFeature *poSubFeature = poBlock->apoFeatures[iSubFeat]->Clone();
        CPLString osCompEntityId;

        if( poSubFeature->GetGeometryRef() != nullptr )
            poSubFeature->GetGeometryRef()->transform( &oTransformer );

        ACAdjustText( dfAngle, oScale.sx, oScale.sy, poSubFeature );

#ifdef notdef
        osCompEntityId = poSubFeature->GetFieldAsString( "EntityHandle" );
        osCompEntityId += ":";
#endif
        osCompEntityId += poFeature->GetFieldAsString( "EntityHandle" );

        poSubFeature->SetField( "EntityHandle", osCompEntityId );

        apoPendingFeatures.push( poSubFeature );
    }

/* -------------------------------------------------------------------- */
/*      If we have attributes, insert them on the stack at this         */
/*      point too.                                                      */
/* -------------------------------------------------------------------- */
    OdDbObjectIteratorPtr pIter = poRef->attributeIterator();
    for (; !pIter->done(); pIter->step())
    {
        OdDbAttributePtr pAttr = pIter->entity();
        if (!pAttr.isNull())
        {
            oStyleProperties.clear();

            OGRFeature *poAttrFeat = TranslateTEXT( pAttr );

            if( poAttrFeat )
                apoPendingFeatures.push( poAttrFeat );
        }
    }

/* -------------------------------------------------------------------- */
/*      Return the working feature if we had geometry, otherwise        */
/*      return NULL and let the machinery find the rest of the          */
/*      features in the pending feature stack.                          */
/* -------------------------------------------------------------------- */
    if( poBlock->poGeometry == nullptr )
    {
        delete poFeature;
        return nullptr;
    }
    else
    {
        return poFeature;
    }
}

/************************************************************************/
/*                      GetNextUnfilteredFeature()                      */
/************************************************************************/

OGRFeature *OGRDWGLayer::GetNextUnfilteredFeature()

{
    OGRFeature *poFeature = nullptr;

/* -------------------------------------------------------------------- */
/*      If we have pending features, return one of them.                */
/* -------------------------------------------------------------------- */
    if( !apoPendingFeatures.empty() )
    {
        poFeature = apoPendingFeatures.front();
        apoPendingFeatures.pop();

        poFeature->SetFID( iNextFID++ );
        return poFeature;
    }

/* -------------------------------------------------------------------- */
/*      Fetch the next entity.                                          */
/* -------------------------------------------------------------------- */
    while( poFeature == nullptr && !poEntIter->done() )
    {

        OdDbObjectId oId = poEntIter->objectId();
        OdDbEntityPtr poEntity = OdDbEntity::cast( oId.openObject() );

        if (poEntity.isNull())
            return nullptr;

/* -------------------------------------------------------------------- */
/*      What is the class name for this entity?                         */
/* -------------------------------------------------------------------- */
        OdRxClass *poClass = poEntity->isA();
        const OdString osName = poClass->name();
        const char *pszEntityClassName = (const char *) osName;

/* -------------------------------------------------------------------- */
/*      Handle the entity.                                              */
/* -------------------------------------------------------------------- */
        oStyleProperties.clear();

        if( EQUAL(pszEntityClassName,"AcDbPoint") )
        {
            poFeature = TranslatePOINT( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbLine") )
        {
            poFeature = TranslateLINE( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbPolyline") )
        {
            poFeature = TranslateLWPOLYLINE( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDb2dPolyline") )
        {
            poFeature = Translate2DPOLYLINE( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDb3dPolyline") )
        {
            poFeature = Translate3DPOLYLINE( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbEllipse") )
        {
            poFeature = TranslateELLIPSE( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbArc") )
        {
            poFeature = TranslateARC( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbMText") )
        {
            poFeature = TranslateMTEXT( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbText")
                 || EQUAL(pszEntityClassName,"AcDbAttributeDefinition") )
        {
            poFeature = TranslateTEXT( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbAlignedDimension")
                 || EQUAL(pszEntityClassName,"AcDbRotatedDimension") )
        {
            poFeature = TranslateDIMENSION( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbCircle") )
        {
            poFeature = TranslateCIRCLE( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbSpline") )
        {
            poFeature = TranslateSPLINE( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbHatch") )
        {
            poFeature = TranslateHATCH( poEntity );
        }
        else if( EQUAL(pszEntityClassName,"AcDbBlockReference") )
        {
            poFeature = TranslateINSERT( poEntity );
            if( poFeature == nullptr && !apoPendingFeatures.empty() )
            {
                poFeature = apoPendingFeatures.front();
                apoPendingFeatures.pop();
            }
        }
        else
        {
            if( oIgnoredEntities.count(pszEntityClassName) == 0 )
            {
                oIgnoredEntities.insert( pszEntityClassName );
                CPLDebug( "DWG", "Ignoring one or more of entity '%s'.",
                          pszEntityClassName );
            }
        }

        poEntIter->step();
    }

/* -------------------------------------------------------------------- */
/*      Set FID.                                                        */
/* -------------------------------------------------------------------- */
    if( poFeature != nullptr )
    {
        poFeature->SetFID( iNextFID++ );
        m_nFeaturesRead++;
    }

    return poFeature;
}

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature *OGRDWGLayer::GetNextFeature()

{
    while( true )
    {
        OGRFeature *poFeature = GetNextUnfilteredFeature();

        if( poFeature == nullptr )
            return nullptr;

        if( (m_poFilterGeom == nullptr
             || FilterGeometry( poFeature->GetGeometryRef() ) )
            && (m_poAttrQuery == nullptr
                || m_poAttrQuery->Evaluate( poFeature ) ) )
        {
            return poFeature;
        }

        delete poFeature;
    }
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRDWGLayer::TestCapability( const char * pszCap )

{
    if( EQUAL(pszCap,OLCStringsAsUTF8) )
        return TRUE;
    else
        return FALSE;
}
