LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/dxf - ogrdxf_dimension.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 184 191 96.3 %
Date: 2024-05-04 12:52:34 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  DXF Translator
       4             :  * Purpose:  Implements translation support for DIMENSION elements as a part
       5             :  *           of the OGRDXFLayer class.
       6             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
      10             :  * Copyright (c) 2010, Even Rouault <even dot rouault at spatialys.com>
      11             :  * Copyright (c) 2017, Alan Thomas <alant@outlook.com.au>
      12             :  *
      13             :  * Permission is hereby granted, free of charge, to any person obtaining a
      14             :  * copy of this software and associated documentation files (the "Software"),
      15             :  * to deal in the Software without restriction, including without limitation
      16             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      17             :  * and/or sell copies of the Software, and to permit persons to whom the
      18             :  * Software is furnished to do so, subject to the following conditions:
      19             :  *
      20             :  * The above copyright notice and this permission notice shall be included
      21             :  * in all copies or substantial portions of the Software.
      22             :  *
      23             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      24             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      25             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      26             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      27             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      28             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      29             :  * DEALINGS IN THE SOFTWARE.
      30             :  ****************************************************************************/
      31             : 
      32             : #include "ogr_dxf.h"
      33             : #include "cpl_conv.h"
      34             : 
      35             : #include <stdexcept>
      36             : 
      37             : /************************************************************************/
      38             : /*                             PointDist()                              */
      39             : /************************************************************************/
      40             : 
      41             : #ifndef PointDist_defined
      42             : #define PointDist_defined
      43             : 
      44          28 : inline static double PointDist(double x1, double y1, double x2, double y2)
      45             : {
      46          28 :     return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
      47             : }
      48             : #endif
      49             : 
      50             : /************************************************************************/
      51             : /*                         TranslateDIMENSION()                         */
      52             : /************************************************************************/
      53             : 
      54          30 : OGRDXFFeature *OGRDXFLayer::TranslateDIMENSION()
      55             : 
      56             : {
      57             :     char szLineBuf[257];
      58          30 :     int nCode = 0;
      59             :     // int  nDimType = 0;
      60          30 :     OGRDXFFeature *poFeature = new OGRDXFFeature(poFeatureDefn);
      61          30 :     double dfArrowX1 = 0.0;
      62          30 :     double dfArrowY1 = 0.0;
      63             :     // double dfArrowZ1 = 0.0;
      64          30 :     double dfTargetX1 = 0.0;
      65          30 :     double dfTargetY1 = 0.0;
      66             :     // double dfTargetZ1 = 0.0;
      67          30 :     double dfTargetX2 = 0.0;
      68          30 :     double dfTargetY2 = 0.0;
      69             :     // double dfTargetZ2 = 0.0;
      70          30 :     double dfTextX = 0.0;
      71          30 :     double dfTextY = 0.0;
      72             :     // double dfTextZ = 0.0;
      73             : 
      74          30 :     bool bReadyForDimstyleOverride = false;
      75             : 
      76          30 :     bool bHaveBlock = false;
      77          60 :     CPLString osBlockName;
      78          60 :     CPLString osText;
      79             : 
      80          60 :     std::map<CPLString, CPLString> oDimStyleProperties;
      81          30 :     poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
      82             : 
      83        1105 :     while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
      84             :     {
      85        1075 :         switch (nCode)
      86             :         {
      87          19 :             case 2:
      88          19 :                 bHaveBlock = true;
      89          19 :                 osBlockName = szLineBuf;
      90          19 :                 break;
      91             : 
      92          28 :             case 3:
      93             :                 // 3 is the dimension style name. We don't need to store it,
      94             :                 // let's just fetch the dimension style properties
      95          28 :                 poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
      96          28 :                 break;
      97             : 
      98          28 :             case 10:
      99          28 :                 dfArrowX1 = CPLAtof(szLineBuf);
     100          28 :                 break;
     101             : 
     102          28 :             case 20:
     103          28 :                 dfArrowY1 = CPLAtof(szLineBuf);
     104          28 :                 break;
     105             : 
     106          28 :             case 30:
     107             :                 /* dfArrowZ1 = CPLAtof(szLineBuf); */
     108          28 :                 break;
     109             : 
     110          28 :             case 11:
     111          28 :                 dfTextX = CPLAtof(szLineBuf);
     112          28 :                 break;
     113             : 
     114          28 :             case 21:
     115          28 :                 dfTextY = CPLAtof(szLineBuf);
     116          28 :                 break;
     117             : 
     118          28 :             case 31:
     119             :                 /* dfTextZ = CPLAtof(szLineBuf); */
     120          28 :                 break;
     121             : 
     122          28 :             case 13:
     123          28 :                 dfTargetX2 = CPLAtof(szLineBuf);
     124          28 :                 break;
     125             : 
     126          28 :             case 23:
     127          28 :                 dfTargetY2 = CPLAtof(szLineBuf);
     128          28 :                 break;
     129             : 
     130          28 :             case 33:
     131             :                 /* dfTargetZ2 = CPLAtof(szLineBuf); */
     132          28 :                 break;
     133             : 
     134          28 :             case 14:
     135          28 :                 dfTargetX1 = CPLAtof(szLineBuf);
     136          28 :                 break;
     137             : 
     138          28 :             case 24:
     139          28 :                 dfTargetY1 = CPLAtof(szLineBuf);
     140          28 :                 break;
     141             : 
     142          28 :             case 34:
     143             :                 /* dfTargetZ1 = CPLAtof(szLineBuf); */
     144          28 :                 break;
     145             : 
     146          26 :             case 70:
     147             :                 /* nDimType = atoi(szLineBuf); */
     148          26 :                 break;
     149             : 
     150          25 :             case 1:
     151          25 :                 osText = szLineBuf;
     152          25 :                 break;
     153             : 
     154          15 :             case 1001:
     155          15 :                 bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
     156          15 :                 break;
     157             : 
     158         160 :             case 1070:
     159         160 :                 if (bReadyForDimstyleOverride)
     160             :                 {
     161             :                     // Store DIMSTYLE override values in the dimension
     162             :                     // style property map. The nInnerCode values match the
     163             :                     // group codes used in the DIMSTYLE table.
     164         160 :                     const int nInnerCode = atoi(szLineBuf);
     165             :                     const char *pszProperty =
     166         160 :                         ACGetDimStylePropertyName(nInnerCode);
     167         160 :                     if (pszProperty)
     168             :                     {
     169          74 :                         nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf));
     170          74 :                         if (nCode == 1005 || nCode == 1040 || nCode == 1070)
     171          74 :                             oDimStyleProperties[pszProperty] = szLineBuf;
     172             :                     }
     173             :                 }
     174         160 :                 break;
     175             : 
     176         466 :             default:
     177         466 :                 TranslateGenericProperty(poFeature, nCode, szLineBuf);
     178         466 :                 break;
     179             :         }
     180             :     }
     181          30 :     if (nCode < 0)
     182             :     {
     183           0 :         DXF_LAYER_READER_ERROR();
     184           0 :         delete poFeature;
     185           0 :         return nullptr;
     186             :     }
     187          30 :     if (nCode == 0)
     188          30 :         poDS->UnreadValue();
     189             : 
     190             :     // If osBlockName (group code 2) refers to a valid block, we can just insert
     191             :     // that block - that should give us the correctly exploded geometry of this
     192             :     // dimension. If this value is missing, or doesn't refer to a valid block,
     193             :     // we will need to use our own logic to generate the dimension lines.
     194          30 :     if (bHaveBlock && osBlockName.length() > 0)
     195             :     {
     196             :         // Always inline the block, because this is an anonymous block that the
     197             :         // user likely doesn't know or care about
     198             :         try
     199             :         {
     200          19 :             OGRDXFFeature *poBlockFeature = InsertBlockInline(
     201          22 :                 CPLGetErrorCounter(), osBlockName, OGRDXFInsertTransformer(),
     202          19 :                 poFeature, apoPendingFeatures, true, false);
     203             : 
     204          16 :             return poBlockFeature;  // may be NULL but that is OK
     205             :         }
     206           3 :         catch (const std::invalid_argument &)
     207             :         {
     208             :         }
     209             :     }
     210             : 
     211             :     // Unpack the dimension style
     212          14 :     const double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
     213          14 :     const double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
     214          14 :     const double dfExtLineExtendLength = CPLAtof(oDimStyleProperties["DIMEXE"]);
     215          14 :     const double dfExtLineOffset = CPLAtof(oDimStyleProperties["DIMEXO"]);
     216          14 :     const bool bWantExtLine1 = atoi(oDimStyleProperties["DIMSE1"]) == 0;
     217          14 :     const bool bWantExtLine2 = atoi(oDimStyleProperties["DIMSE2"]) == 0;
     218          14 :     const double dfTextHeight = CPLAtof(oDimStyleProperties["DIMTXT"]);
     219          14 :     const int nUnitsPrecision = atoi(oDimStyleProperties["DIMDEC"]);
     220             :     const bool bTextSupposedlyCentered =
     221          14 :         atoi(oDimStyleProperties["DIMTAD"]) == 0;
     222          28 :     const CPLString osTextColor = oDimStyleProperties["DIMCLRT"];
     223             : 
     224             :     /*************************************************************************
     225             : 
     226             :        DIMENSION geometry layout
     227             : 
     228             :                       (11,21)(text center point)
     229             :             |          DimText                  |
     230             :     (10,20) X<--------------------------------->X (Arrow2 - computed)
     231             :     (Arrow1)|                                   |
     232             :             |                                   |
     233             :             |                                   X (13,23) (Target2)
     234             :             |
     235             :             X (14,24) (Target1)
     236             : 
     237             :     Given:
     238             :       Locations Arrow1, Target1, and Target2 we need to compute Arrow2.
     239             : 
     240             :     Steps:
     241             :      1) Compute direction vector from Target1 to Arrow1 (Vec1).
     242             :      2) Compute direction vector for arrow as perpendicular to Vec1 (call Vec2).
     243             :      3) Compute Arrow2 location as intersection between line defined by
     244             :         Vec2 and Arrow1 and line defined by Target2 and direction Vec1 (call
     245             :     Arrow2)
     246             : 
     247             :     Then we can draw lines for the various components.
     248             : 
     249             :     Note that Vec1 and Vec2 may be horizontal, vertical or on an angle but
     250             :     the approach is as above in all these cases.
     251             : 
     252             :     *************************************************************************/
     253             : 
     254             :     /* -------------------------------------------------------------------- */
     255             :     /*      Step 1, compute direction vector between Target1 and Arrow1.    */
     256             :     /* -------------------------------------------------------------------- */
     257          14 :     double dfVec1X = dfArrowX1 - dfTargetX1;
     258          14 :     double dfVec1Y = dfArrowY1 - dfTargetY1;
     259             : 
     260             :     // make Vec1 a unit vector
     261          14 :     double dfVec1Length = PointDist(0, 0, dfVec1X, dfVec1Y);
     262          14 :     if (dfVec1Length > 0.0)
     263             :     {
     264          12 :         dfVec1X /= dfVec1Length;
     265          12 :         dfVec1Y /= dfVec1Length;
     266             :     }
     267             : 
     268             :     /* -------------------------------------------------------------------- */
     269             :     /*      Step 2, compute the direction vector from Arrow1 to Arrow2      */
     270             :     /*      as a perpendicular to Vec1.                                     */
     271             :     /* -------------------------------------------------------------------- */
     272          14 :     double dfVec2X = dfVec1Y;
     273          14 :     double dfVec2Y = -dfVec1X;
     274             : 
     275             :     /* -------------------------------------------------------------------- */
     276             :     /*      Step 3, compute intersection of line from target2 along         */
     277             :     /*      direction vector 1, with the line through Arrow1 and            */
     278             :     /*      direction vector 2.                                             */
     279             :     /* -------------------------------------------------------------------- */
     280          14 :     double dfArrowX2 = 0.0;
     281          14 :     double dfArrowY2 = 0.0;
     282             : 
     283             :     // special case if vec1 is zero, which means the arrow and target
     284             :     // points coincide.
     285          14 :     if (dfVec1X == 0.0 && dfVec1Y == 0.0)
     286             :     {
     287           2 :         dfArrowX2 = dfTargetX2;
     288           2 :         dfArrowY2 = dfTargetY2;
     289             :     }
     290             : 
     291             :     // special case if vec1 is vertical.
     292          12 :     else if (dfVec1X == 0.0)
     293             :     {
     294           5 :         dfArrowX2 = dfTargetX2;
     295           5 :         dfArrowY2 = dfArrowY1;
     296             :     }
     297             : 
     298             :     // special case if vec1 is horizontal.
     299           7 :     else if (dfVec1Y == 0.0)
     300             :     {
     301           3 :         dfArrowX2 = dfArrowX1;
     302           3 :         dfArrowY2 = dfTargetY2;
     303             :     }
     304             : 
     305             :     else  // General case for diagonal vectors.
     306             :     {
     307             :         // first convert vec1 + target2 into y = mx + b format: call this L1
     308             : 
     309           4 :         const double dfL1M = dfVec1Y / dfVec1X;
     310           4 :         const double dfL1B = dfTargetY2 - dfL1M * dfTargetX2;
     311             : 
     312             :         // convert vec2 + Arrow1 into y = mx + b format, call this L2
     313             : 
     314           4 :         const double dfL2M = dfVec2Y / dfVec2X;
     315           4 :         const double dfL2B = dfArrowY1 - dfL2M * dfArrowX1;
     316             : 
     317             :         // Compute intersection x = (b2-b1) / (m1-m2)
     318             : 
     319           4 :         dfArrowX2 = (dfL2B - dfL1B) / (dfL1M - dfL2M);
     320           4 :         dfArrowY2 = dfL2M * dfArrowX2 + dfL2B;
     321             :     }
     322             : 
     323             :     /* -------------------------------------------------------------------- */
     324             :     /*      Create geometries for the different components of the           */
     325             :     /*      dimension object.                                               */
     326             :     /* -------------------------------------------------------------------- */
     327          14 :     OGRMultiLineString *poMLS = new OGRMultiLineString();
     328          28 :     OGRLineString oLine;
     329             : 
     330             :     // Main arrow line between Arrow1 and Arrow2.
     331          14 :     oLine.setPoint(0, dfArrowX1, dfArrowY1);
     332          14 :     oLine.setPoint(1, dfArrowX2, dfArrowY2);
     333          14 :     poMLS->addGeometry(&oLine);
     334             : 
     335             :     // Insert default arrowheads.
     336          14 :     InsertArrowhead(poFeature, "", &oLine, dfArrowheadSize * dfScale);
     337          14 :     InsertArrowhead(poFeature, "", &oLine, dfArrowheadSize * dfScale, true);
     338             : 
     339             :     // Dimension line from Target1 to Arrow1 with a small extension.
     340          14 :     oLine.setPoint(0, dfTargetX1 + dfVec1X * dfExtLineOffset,
     341          14 :                    dfTargetY1 + dfVec1Y * dfExtLineOffset);
     342          14 :     oLine.setPoint(1, dfArrowX1 + dfVec1X * dfExtLineExtendLength,
     343          14 :                    dfArrowY1 + dfVec1Y * dfExtLineExtendLength);
     344          14 :     if (bWantExtLine1 && oLine.get_Length() > 0.0)
     345             :     {
     346          12 :         poMLS->addGeometry(&oLine);
     347             :     }
     348             : 
     349             :     // Dimension line from Target2 to Arrow2 with a small extension.
     350          14 :     oLine.setPoint(0, dfTargetX2 + dfVec1X * dfExtLineOffset,
     351          14 :                    dfTargetY2 + dfVec1Y * dfExtLineOffset);
     352          14 :     oLine.setPoint(1, dfArrowX2 + dfVec1X * dfExtLineExtendLength,
     353          14 :                    dfArrowY2 + dfVec1Y * dfExtLineExtendLength);
     354          14 :     if (bWantExtLine2 && oLine.get_Length() > 0.0)
     355             :     {
     356          12 :         poMLS->addGeometry(&oLine);
     357             :     }
     358             : 
     359          14 :     poFeature->SetGeometryDirectly(poMLS);
     360             : 
     361          14 :     PrepareLineStyle(poFeature);
     362             : 
     363             :     /* -------------------------------------------------------------------- */
     364             :     /*      Prepare a new feature to serve as the dimension text label      */
     365             :     /*      feature.  We will push it onto the layer as a pending           */
     366             :     /*      feature for the next feature read.                              */
     367             :     /*                                                                      */
     368             :     /*      The DXF format supports a myriad of options for dimension       */
     369             :     /*      text placement, some of which involve the drawing of            */
     370             :     /*      additional lines and the like.  For now we ignore most of       */
     371             :     /*      those properties and place the text alongside the dimension     */
     372             :     /*      line.                                                           */
     373             :     /* -------------------------------------------------------------------- */
     374             : 
     375             :     // a single space suppresses labeling.
     376          14 :     if (osText == " ")
     377           0 :         return poFeature;
     378             : 
     379          14 :     OGRDXFFeature *poLabelFeature = poFeature->CloneDXFFeature();
     380             : 
     381          14 :     poLabelFeature->SetGeometryDirectly(new OGRPoint(dfTextX, dfTextY));
     382             : 
     383          14 :     if (osText.empty())
     384          13 :         osText = "<>";
     385             : 
     386             :     // Do we need to compute the dimension value?
     387          14 :     size_t nDimensionPos = osText.find("<>");
     388          14 :     if (nDimensionPos == std::string::npos)
     389             :     {
     390           0 :         poLabelFeature->SetField("Text", TextUnescape(osText, true));
     391             :     }
     392             :     else
     393             :     {
     394             :         // Replace the first occurrence of <> with the dimension
     395          14 :         CPLString osDimensionText;
     396          14 :         FormatDimension(osDimensionText,
     397             :                         PointDist(dfArrowX1, dfArrowY1, dfArrowX2, dfArrowY2),
     398             :                         nUnitsPrecision);
     399          14 :         osText.replace(nDimensionPos, 2, osDimensionText);
     400          14 :         poLabelFeature->SetField("Text", TextUnescape(osText, true));
     401             :     }
     402             : 
     403          14 :     CPLString osStyle;
     404             :     char szBuffer[64];
     405             : 
     406             :     osStyle.Printf("LABEL(f:\"Arial\",t:\"%s\"",
     407          14 :                    TextUnescape(osText.c_str(), true).c_str());
     408             : 
     409             :     // If the text is supposed to be centered on the line, we align
     410             :     // it above the line. Drawing it properly would require us to
     411             :     // work out the width of the text, which seems like too much
     412             :     // effort for what is just a fallback renderer.
     413          14 :     if (bTextSupposedlyCentered)
     414           4 :         osStyle += ",p:11";
     415             :     else
     416          10 :         osStyle += ",p:5";
     417             : 
     418             :     // Compute the text angle. Use atan to avoid upside-down text
     419          14 :     const double dfTextAngle =
     420             :         (dfArrowX1 == dfArrowX2)
     421          14 :             ? -90.0
     422          10 :             : atan((dfArrowY1 - dfArrowY2) / (dfArrowX1 - dfArrowX2)) * 180.0 /
     423             :                   M_PI;
     424             : 
     425          14 :     if (dfTextAngle != 0.0)
     426             :     {
     427           9 :         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
     428           9 :         osStyle += CPLString().Printf(",a:%s", szBuffer);
     429             :     }
     430             : 
     431          14 :     if (dfTextHeight != 0.0)
     432             :     {
     433          14 :         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextHeight * dfScale);
     434          14 :         osStyle += CPLString().Printf(",s:%sg", szBuffer);
     435             :     }
     436             : 
     437          14 :     poLabelFeature->oStyleProperties["Color"] = osTextColor;
     438          14 :     osStyle += ",c:";
     439          14 :     osStyle += poLabelFeature->GetColor(poDS, poFeature);
     440             : 
     441          14 :     osStyle += ")";
     442             : 
     443          14 :     poLabelFeature->SetStyleString(osStyle);
     444             : 
     445          14 :     apoPendingFeatures.push(poLabelFeature);
     446             : 
     447          14 :     return poFeature;
     448             : }
     449             : 
     450             : /************************************************************************/
     451             : /*                          FormatDimension()                           */
     452             : /*                                                                      */
     453             : /*      Format a dimension number according to the current files        */
     454             : /*      formatting conventions.                                         */
     455             : /************************************************************************/
     456             : 
     457          14 : void OGRDXFLayer::FormatDimension(CPLString &osText, const double dfValue,
     458             :                                   int nPrecision)
     459             : 
     460             : {
     461          14 :     if (nPrecision < 0)
     462           0 :         nPrecision = 0;
     463          14 :     else if (nPrecision > 20)
     464           0 :         nPrecision = 20;
     465             : 
     466             :     // We could do a significantly more precise formatting if we want
     467             :     // to spend the effort.  See QCAD's rs_dimlinear.cpp and related files
     468             :     // for example.
     469             : 
     470             :     char szFormat[32];
     471          14 :     snprintf(szFormat, sizeof(szFormat), "%%.%df", nPrecision);
     472             : 
     473             :     char szBuffer[64];
     474          14 :     CPLsnprintf(szBuffer, sizeof(szBuffer), szFormat, dfValue);
     475             : 
     476          14 :     osText = szBuffer;
     477          14 : }

Generated by: LCOV version 1.14