1 /******************************************************************************
2  *
3  * Project:  DXF Translator
4  * Purpose:  Implements translation support for LEADER and MULTILEADER
5  *           elements as a part of the OGRDXFLayer class.
6  * Author:   Alan Thomas, alant@outlook.com.au
7  *
8  ******************************************************************************
9  * Copyright (c) 2017, Alan Thomas <alant@outlook.com.au>
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  ****************************************************************************/
29 
30 #include "ogr_dxf.h"
31 #include "cpl_conv.h"
32 #include "../../../alg/gdallinearsystem.h"
33 #include <stdexcept>
34 #include <algorithm>
35 
36 CPL_CVSID("$Id: ogrdxf_leader.cpp 5f6d7d1878fd9ad64fb72525d1db843ae99856e1 2020-06-27 14:09:28 +0200 Even Rouault $")
37 
38 static void InterpolateSpline( OGRLineString* const poLine,
39     const DXFTriple& oEndTangentDirection );
40 
41 /************************************************************************/
42 /*                             PointDist()                              */
43 /************************************************************************/
44 
PointDist(double x1,double y1,double x2,double y2)45 inline static double PointDist( double x1, double y1, double x2, double y2 )
46 {
47     return sqrt( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) );
48 }
49 
PointDist(double x1,double y1,double z1,double x2,double y2,double z2)50 inline static double PointDist( double x1, double y1, double z1, double x2,
51     double y2, double z2 )
52 {
53     return sqrt( (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) +
54         (z2 - z1) * (z2 - z1) );
55 }
56 
57 /************************************************************************/
58 /*                          TranslateLEADER()                           */
59 /************************************************************************/
60 
TranslateLEADER()61 OGRDXFFeature *OGRDXFLayer::TranslateLEADER()
62 
63 {
64     char szLineBuf[257];
65     int nCode;
66     OGRDXFFeature *poFeature = new OGRDXFFeature( poFeatureDefn );
67 
68     OGRLineString *poLine = new OGRLineString();
69     bool bHaveX = false;
70     bool bHaveY = false;
71     bool bHaveZ = false;
72     double dfCurrentX = 0.0;
73     double dfCurrentY = 0.0;
74     double dfCurrentZ = 0.0;
75     int nNumVertices = 0;
76 
77     bool bHorizontalDirectionFlip = true;
78     double dfHorizontalDirectionX = 1.0;
79     double dfHorizontalDirectionY = 0.0;
80     double dfHorizontalDirectionZ = 0.0;
81     bool bHasTextAnnotation = false;
82     double dfTextAnnotationWidth = 0.0;
83     bool bIsSpline = false;
84 
85     // spec is silent as to default, but AutoCAD assumes true
86     bool bWantArrowhead = true;
87 
88     bool bReadyForDimstyleOverride = false;
89 
90     std::map<CPLString,CPLString> oDimStyleProperties;
91     poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
92 
93     while( (nCode = poDS->ReadValue(szLineBuf,sizeof(szLineBuf))) > 0 )
94     {
95         switch( nCode )
96         {
97           case 3:
98             // 3 is the dimension style name. We don't need to store it,
99             // let's just fetch the dimension style properties
100             poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
101             break;
102 
103           case 10:
104             // add the previous point onto the linestring
105             if( bHaveX && bHaveY && bHaveZ ) {
106                 poLine->setPoint( nNumVertices++,
107                     dfCurrentX, dfCurrentY, dfCurrentZ );
108                 bHaveY = bHaveZ = false;
109             }
110             dfCurrentX = CPLAtof(szLineBuf);
111             bHaveX = true;
112             break;
113 
114           case 20:
115             // add the previous point onto the linestring
116             if( bHaveX && bHaveY && bHaveZ ) {
117                 poLine->setPoint( nNumVertices++,
118                     dfCurrentX, dfCurrentY, dfCurrentZ );
119                 bHaveX = bHaveZ = false;
120             }
121             dfCurrentY = CPLAtof(szLineBuf);
122             bHaveY = true;
123             break;
124 
125           case 30:
126             // add the previous point onto the linestring
127             if( bHaveX && bHaveY && bHaveZ ) {
128                 poLine->setPoint( nNumVertices++,
129                     dfCurrentX, dfCurrentY, dfCurrentZ );
130                 bHaveX = bHaveY = false;
131             }
132             dfCurrentZ = CPLAtof(szLineBuf);
133             bHaveZ = true;
134             break;
135 
136           case 41:
137             dfTextAnnotationWidth = CPLAtof(szLineBuf);
138             break;
139 
140           case 71:
141             bWantArrowhead = atoi(szLineBuf) != 0;
142             break;
143 
144           case 72:
145             bIsSpline = atoi(szLineBuf) != 0;
146             break;
147 
148           case 73:
149             bHasTextAnnotation = atoi(szLineBuf) == 0;
150             break;
151 
152           case 74:
153             // DXF spec seems to have this backwards. A value of 0 actually
154             // indicates no flipping occurs, and 1 (flip) is the default
155             bHorizontalDirectionFlip = atoi(szLineBuf) != 0;
156             break;
157 
158           case 211:
159             dfHorizontalDirectionX = CPLAtof(szLineBuf);
160             break;
161 
162           case 221:
163             dfHorizontalDirectionY = CPLAtof(szLineBuf);
164             break;
165 
166           case 231:
167             dfHorizontalDirectionZ = CPLAtof(szLineBuf);
168             break;
169 
170           case 1001:
171             bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
172             break;
173 
174           case 1070:
175             if( bReadyForDimstyleOverride )
176             {
177                 // Store DIMSTYLE override values in the dimension
178                 // style property map. The nInnerCode values match the
179                 // group codes used in the DIMSTYLE table.
180                 const int nInnerCode = atoi(szLineBuf);
181                 const char* pszProperty = ACGetDimStylePropertyName(nInnerCode);
182                 if( pszProperty )
183                 {
184                     nCode = poDS->ReadValue(szLineBuf,sizeof(szLineBuf));
185                     if( nCode == 1005 || nCode == 1040 || nCode == 1070 )
186                         oDimStyleProperties[pszProperty] = szLineBuf;
187                 }
188             }
189             break;
190 
191           default:
192             TranslateGenericProperty( poFeature, nCode, szLineBuf );
193             break;
194         }
195     }
196 
197     if( nCode == 0 )
198         poDS->UnreadValue();
199 
200     if( bHaveX && bHaveY && bHaveZ )
201         poLine->setPoint( nNumVertices++, dfCurrentX, dfCurrentY, dfCurrentZ );
202 
203     // Unpack the dimension style
204     bool bWantExtension = atoi(oDimStyleProperties["DIMTAD"]) > 0;
205     double dfTextOffset = CPLAtof(oDimStyleProperties["DIMGAP"]);
206     double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
207     double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
208     int nLeaderColor = atoi(oDimStyleProperties["DIMCLRD"]);
209     // DIMLDRBLK is the entity handle of the BLOCK_RECORD table entry that
210     // corresponds to the arrowhead block.
211     CPLString osArrowheadBlockHandle = oDimStyleProperties["DIMLDRBLK"];
212 
213     // Zero scale has a special meaning which we aren't interested in,
214     // so we can change it to 1.0
215     if( dfScale == 0.0 )
216         dfScale = 1.0;
217 
218     // Use the color from the dimension style if it is not ByBlock
219     if( nLeaderColor > 0 )
220         poFeature->oStyleProperties["Color"] = oDimStyleProperties["DIMCLRD"];
221 
222 /* -------------------------------------------------------------------- */
223 /*      Add an arrowhead to the start of the leader line.               */
224 /* -------------------------------------------------------------------- */
225 
226     if( bWantArrowhead && nNumVertices >= 2 )
227     {
228         InsertArrowhead( poFeature, osArrowheadBlockHandle, poLine,
229             dfArrowheadSize * dfScale );
230     }
231 
232 
233     if( bHorizontalDirectionFlip )
234     {
235         dfHorizontalDirectionX *= -1;
236         dfHorizontalDirectionX *= -1;
237         dfHorizontalDirectionX *= -1;
238     }
239 
240 /* -------------------------------------------------------------------- */
241 /*      For a spline leader, determine the end tangent direction        */
242 /*      and interpolate the spline vertices.                            */
243 /* -------------------------------------------------------------------- */
244 
245     if( bIsSpline )
246     {
247         DXFTriple oEndTangent;
248         if( bHasTextAnnotation )
249         {
250             oEndTangent = DXFTriple( dfHorizontalDirectionX,
251                 dfHorizontalDirectionY, dfHorizontalDirectionZ );
252         }
253         InterpolateSpline( poLine, oEndTangent );
254     }
255 
256 /* -------------------------------------------------------------------- */
257 /*      Add an extension to the end of the leader line. This is not     */
258 /*      properly documented in the DXF spec, but it is needed to        */
259 /*      replicate the way AutoCAD displays leader objects.              */
260 /*                                                                      */
261 /*      When $DIMTAD (77) is nonzero, the leader line is extended       */
262 /*      under the text annotation. This extension is not stored as an   */
263 /*      additional vertex, so we need to create it ourselves.           */
264 /* -------------------------------------------------------------------- */
265 
266     if( bWantExtension && bHasTextAnnotation && poLine->getNumPoints() >= 2 )
267     {
268         OGRPoint oLastVertex;
269         poLine->getPoint( poLine->getNumPoints() - 1, &oLastVertex );
270 
271         double dfExtensionX = oLastVertex.getX();
272         double dfExtensionY = oLastVertex.getY();
273         double dfExtensionZ = oLastVertex.getZ();
274 
275         double dfExtensionLength = ( dfTextOffset * dfScale ) +
276             dfTextAnnotationWidth;
277         dfExtensionX += dfHorizontalDirectionX * dfExtensionLength;
278         dfExtensionY += dfHorizontalDirectionY * dfExtensionLength;
279         dfExtensionZ += dfHorizontalDirectionZ * dfExtensionLength;
280 
281         poLine->setPoint( poLine->getNumPoints(), dfExtensionX, dfExtensionY,
282             dfExtensionZ );
283     }
284 
285     poFeature->SetGeometryDirectly( poLine );
286 
287     PrepareLineStyle( poFeature );
288 
289     return poFeature;
290 }
291 
292 /************************************************************************/
293 /*       DXFMLEADERVertex, DXFMLEADERLeaderLine, DXFMLEADERLeader       */
294 /************************************************************************/
295 
296 struct DXFMLEADERVertex {
297     DXFTriple                oCoords;
298     std::vector<std::pair<DXFTriple, DXFTriple>> aoBreaks;
299 
DXFMLEADERVertexDXFMLEADERVertex300     DXFMLEADERVertex( double dfX, double dfY )
301         : oCoords( DXFTriple( dfX, dfY, 0.0 ) ) {}
302 };
303 
304 struct DXFMLEADERLeader {
305     double                   dfLandingX;
306     double                   dfLandingY;
307     double                   dfDoglegVectorX;
308     double                   dfDoglegVectorY;
309     double                   dfDoglegLength;
310     std::vector<std::pair<DXFTriple, DXFTriple>> aoDoglegBreaks;
311     std::vector<std::vector<DXFMLEADERVertex>> aaoLeaderLines;
312 };
313 
314 /************************************************************************/
315 /*                         TranslateMLEADER()                           */
316 /************************************************************************/
317 
TranslateMLEADER()318 OGRDXFFeature *OGRDXFLayer::TranslateMLEADER()
319 
320 {
321     char szLineBuf[257];
322     int nCode = 0;
323 
324     // This is a dummy feature object used to store style properties
325     // and the like. We end up deleting it without returning it
326     OGRDXFFeature *poOverallFeature = new OGRDXFFeature( poFeatureDefn );
327 
328     DXFMLEADERLeader oLeader;
329     std::vector<DXFMLEADERLeader> aoLeaders;
330 
331     std::vector<DXFMLEADERVertex> oLeaderLine;
332     double dfCurrentX = 0.0;
333     double dfCurrentY = 0.0;
334     double dfCurrentX2 = 0.0;
335     double dfCurrentY2 = 0.0;
336     size_t nCurrentVertex = 0;
337 
338     double dfScale = 1.0;
339     bool bHasDogleg = true;
340     CPLString osLeaderColor = "0";
341 
342     CPLString osText;
343     CPLString osTextStyleHandle;
344     double dfTextX = 0.0;
345     double dfTextY = 0.0;
346     int nTextAlignment = 1; // 1 = left, 2 = center, 3 = right
347     double dfTextAngle = 0.0;
348     double dfTextHeight = 4.0;
349 
350     CPLString osBlockHandle;
351     OGRDXFInsertTransformer oBlockTransformer;
352     CPLString osBlockAttributeHandle;
353     // Map of ATTDEF handles to attribute text
354     std::map<CPLString, CPLString> oBlockAttributes;
355 
356     CPLString osArrowheadBlockHandle;
357     double dfArrowheadSize = 4.0;
358 
359     // The different leader line types
360     const int MLT_NONE = 0;
361     const int MLT_STRAIGHT = 1;
362     const int MLT_SPLINE = 2;
363     int nLeaderLineType = MLT_STRAIGHT;
364 
365     // Group codes mean different things in different sections of the
366     // MLEADER entity. We need to keep track of the section we are in.
367     const int MLS_COMMON = 0;
368     const int MLS_CONTEXT_DATA = 1;
369     const int MLS_LEADER = 2;
370     const int MLS_LEADER_LINE = 3;
371     int nSection = MLS_COMMON;
372 
373     // The way the 30x group codes work is missing from the DXF docs.
374     // We assume that the sections are always nested as follows:
375 
376     // ... [this part is identified as MLS_COMMON]
377     // 300 CONTEXT_DATA{
378     //   ...
379     //   302 LEADER{
380     //     ...
381     //     304 LEADER_LINE{
382     //       ...
383     //     305 }
384     //     304 LEADER_LINE{
385     //       ...
386     //     305 }
387     //     ...
388     //   303 }
389     //   302 LEADER{
390     //     ...
391     //   303 }
392     //   ...
393     // 301 }
394     // ... [MLS_COMMON]
395 
396     while( (nCode = poDS->ReadValue(szLineBuf,sizeof(szLineBuf))) > 0 )
397     {
398         switch( nSection )
399         {
400           case MLS_COMMON:
401             switch( nCode )
402             {
403               case 300:
404                 nSection = MLS_CONTEXT_DATA;
405                 break;
406 
407               case 342:
408                 // 342 is the entity handle of the BLOCK_RECORD table entry that
409                 // corresponds to the arrowhead block.
410                 osArrowheadBlockHandle = szLineBuf;
411                 break;
412 
413               case 42:
414                 // TODO figure out difference between 42 and 140 for arrowheadsize
415                 dfArrowheadSize = CPLAtof( szLineBuf );
416                 break;
417 
418               case 330:
419                 osBlockAttributeHandle = szLineBuf;
420                 break;
421 
422               case 302:
423                 if( osBlockAttributeHandle != "" )
424                 {
425                     oBlockAttributes[osBlockAttributeHandle] =
426                         TextUnescape( szLineBuf, true );
427                     osBlockAttributeHandle = "";
428                 }
429                 break;
430 
431               case 91:
432                 osLeaderColor = szLineBuf;
433                 break;
434 
435               case 170:
436                 nLeaderLineType = atoi(szLineBuf);
437                 break;
438 
439               case 291:
440                 bHasDogleg = atoi(szLineBuf) != 0;
441                 break;
442 
443               default:
444                 TranslateGenericProperty( poOverallFeature, nCode, szLineBuf );
445                 break;
446             }
447             break;
448 
449           case MLS_CONTEXT_DATA:
450             switch( nCode )
451             {
452               case 301:
453                 nSection = MLS_COMMON;
454                 break;
455 
456               case 302:
457                 nSection = MLS_LEADER;
458                 break;
459 
460               case 304:
461                 osText = TextUnescape(szLineBuf, true);
462                 break;
463 
464               case 40:
465                 dfScale = CPLAtof( szLineBuf );
466                 break;
467 
468               case 340:
469                 // 340 is the entity handle of the STYLE table entry that
470                 // corresponds to the text style.
471                 osTextStyleHandle = szLineBuf;
472                 break;
473 
474               case 12:
475                 dfTextX = CPLAtof( szLineBuf );
476                 break;
477 
478               case 22:
479                 dfTextY = CPLAtof( szLineBuf );
480                 break;
481 
482               case 41:
483                 dfTextHeight = CPLAtof( szLineBuf );
484                 break;
485 
486               case 42:
487                 dfTextAngle = CPLAtof( szLineBuf ) * 180 / M_PI;
488                 break;
489 
490               case 171:
491                 nTextAlignment = atoi( szLineBuf );
492                 break;
493 
494               case 341:
495                 // 341 is the entity handle of the BLOCK_RECORD table entry that
496                 // corresponds to the block content of this MLEADER.
497                 osBlockHandle = szLineBuf;
498                 break;
499 
500               case 15:
501                 oBlockTransformer.dfXOffset = CPLAtof( szLineBuf );
502                 break;
503 
504               case 25:
505                 oBlockTransformer.dfYOffset = CPLAtof( szLineBuf );
506                 break;
507 
508               case 16:
509                 oBlockTransformer.dfXScale = CPLAtof( szLineBuf );
510                 break;
511 
512               case 26:
513                 oBlockTransformer.dfYScale = CPLAtof( szLineBuf );
514                 break;
515 
516               case 46:
517                 oBlockTransformer.dfAngle = CPLAtof( szLineBuf );
518                 break;
519             }
520             break;
521 
522           case MLS_LEADER:
523             switch( nCode )
524             {
525               case 303:
526                 nSection = MLS_CONTEXT_DATA;
527                 aoLeaders.push_back( oLeader );
528                 oLeader = DXFMLEADERLeader();
529                 break;
530 
531               case 304:
532                 nSection = MLS_LEADER_LINE;
533                 break;
534 
535               case 10:
536                 oLeader.dfLandingX = CPLAtof(szLineBuf);
537                 break;
538 
539               case 20:
540                 oLeader.dfLandingY = CPLAtof(szLineBuf);
541                 break;
542 
543               case 11:
544                 oLeader.dfDoglegVectorX = CPLAtof(szLineBuf);
545                 break;
546 
547               case 21:
548                 oLeader.dfDoglegVectorY = CPLAtof(szLineBuf);
549                 break;
550 
551               case 12:
552                 dfCurrentX = CPLAtof(szLineBuf);
553                 break;
554 
555               case 22:
556                 dfCurrentY = CPLAtof(szLineBuf);
557                 break;
558 
559               case 13:
560                 dfCurrentX2 = CPLAtof(szLineBuf);
561                 break;
562 
563               case 23:
564                 dfCurrentY2 = CPLAtof(szLineBuf);
565                 oLeader.aoDoglegBreaks.push_back( std::make_pair(
566                     DXFTriple( dfCurrentX, dfCurrentY, 0.0 ),
567                     DXFTriple( dfCurrentX2, dfCurrentY2, 0.0 ) ) );
568                 break;
569 
570               case 40:
571                 oLeader.dfDoglegLength = CPLAtof(szLineBuf);
572                 break;
573             }
574             break;
575 
576           case MLS_LEADER_LINE:
577             switch( nCode )
578             {
579               case 305:
580                 nSection = MLS_LEADER;
581                 oLeader.aaoLeaderLines.push_back( oLeaderLine );
582                 oLeaderLine.clear();
583                 break;
584 
585               case 10:
586                 dfCurrentX = CPLAtof(szLineBuf);
587                 break;
588 
589               case 20:
590                 dfCurrentY = CPLAtof(szLineBuf);
591                 oLeaderLine.push_back(
592                     DXFMLEADERVertex( dfCurrentX, dfCurrentY ) );
593                 break;
594 
595               case 90:
596                 nCurrentVertex = atoi(szLineBuf);
597                 if( nCurrentVertex >= oLeaderLine.size() )
598                 {
599                     CPLError( CE_Warning, CPLE_AppDefined,
600                         "Wrong group code 90 in LEADER_LINE: %s", szLineBuf );
601                     DXF_LAYER_READER_ERROR();
602                     delete poOverallFeature;
603                     return nullptr;
604                 }
605                 break;
606 
607               case 11:
608                 dfCurrentX = CPLAtof(szLineBuf);
609                 break;
610 
611               case 21:
612                 dfCurrentY = CPLAtof(szLineBuf);
613                 break;
614 
615               case 12:
616                 dfCurrentX2 = CPLAtof(szLineBuf);
617                 break;
618 
619               case 22:
620                 if( nCurrentVertex >= oLeaderLine.size() )
621                 {
622                     CPLError( CE_Warning, CPLE_AppDefined,
623                         "Misplaced group code 22 in LEADER_LINE" );
624                     DXF_LAYER_READER_ERROR();
625                     delete poOverallFeature;
626                     return nullptr;
627                 }
628                 dfCurrentY2 = CPLAtof(szLineBuf);
629                 oLeaderLine[nCurrentVertex].aoBreaks.push_back( std::make_pair(
630                     DXFTriple( dfCurrentX, dfCurrentY, 0.0 ),
631                     DXFTriple( dfCurrentX2, dfCurrentY2, 0.0 ) ) );
632                 break;
633             }
634             break;
635         }
636     }
637 
638     if( nCode < 0 )
639     {
640         DXF_LAYER_READER_ERROR();
641         delete poOverallFeature;
642         return nullptr;
643     }
644     if( nCode == 0 )
645         poDS->UnreadValue();
646 
647     // Convert the block handle to a block name. If there is no block,
648     // osBlockName will remain empty.
649     CPLString osBlockName;
650 
651     if( osBlockHandle != "" )
652         osBlockName = poDS->GetBlockNameByRecordHandle( osBlockHandle );
653 
654 /* -------------------------------------------------------------------- */
655 /*      Add the landing and arrowhead onto each leader line, and add    */
656 /*      the dogleg, if present, onto the leader.                        */
657 /* -------------------------------------------------------------------- */
658     OGRDXFFeature* poLeaderFeature = poOverallFeature->CloneDXFFeature();
659     poLeaderFeature->oStyleProperties["Color"] = osLeaderColor;
660 
661     OGRMultiLineString *poMLS = new OGRMultiLineString();
662 
663     // Arrowheads should be the same color as the leader line. If the leader
664     // line is ByBlock or ByLayer then the arrowhead should be "owned" by the
665     // overall feature for styling purposes.
666     OGRDXFFeature* poArrowheadOwningFeature = poLeaderFeature;
667     if( ( atoi(osLeaderColor) & 0xC2000000 ) == 0xC0000000 )
668         poArrowheadOwningFeature = poOverallFeature;
669 
670     for( std::vector<DXFMLEADERLeader>::iterator oIt = aoLeaders.begin();
671          nLeaderLineType != MLT_NONE && oIt != aoLeaders.end();
672          ++oIt )
673     {
674         const bool bLeaderHasDogleg = bHasDogleg &&
675             nLeaderLineType != MLT_SPLINE &&
676             oIt->dfDoglegLength != 0.0 &&
677             ( oIt->dfDoglegVectorX != 0.0 || oIt->dfDoglegVectorY != 0.0 );
678 
679         // We assume that the dogleg vector in the DXF is a unit vector.
680         // Safe assumption? Who knows. The documentation is so bad.
681         const double dfDoglegX = oIt->dfLandingX +
682             oIt->dfDoglegVectorX * oIt->dfDoglegLength;
683         const double dfDoglegY = oIt->dfLandingY +
684             oIt->dfDoglegVectorY * oIt->dfDoglegLength;
685 
686         // When the dogleg is turned off or we are in spline mode, it seems
687         // that the dogleg and landing data are still present in the DXF file,
688         // but they are not supposed to be drawn.
689         if( !bHasDogleg || nLeaderLineType == MLT_SPLINE )
690         {
691             oIt->dfLandingX = dfDoglegX;
692             oIt->dfLandingY = dfDoglegY;
693         }
694 
695         // Iterate through each leader line
696         for( const auto& aoLineVertices : oIt->aaoLeaderLines )
697         {
698             if( aoLineVertices.empty() )
699                 continue;
700 
701             OGRLineString* poLeaderLine = new OGRLineString();
702 
703             // Get the first line segment for arrowhead purposes
704             poLeaderLine->addPoint(
705                 aoLineVertices[0].oCoords.dfX,
706                 aoLineVertices[0].oCoords.dfY );
707 
708             if( aoLineVertices.size() > 1 )
709             {
710                 poLeaderLine->addPoint(
711                     aoLineVertices[1].oCoords.dfX,
712                     aoLineVertices[1].oCoords.dfY );
713             }
714             else
715             {
716                 poLeaderLine->addPoint( oIt->dfLandingX, oIt->dfLandingY );
717             }
718 
719             // Add an arrowhead if required
720             InsertArrowhead( poArrowheadOwningFeature,
721                 osArrowheadBlockHandle, poLeaderLine,
722                 dfArrowheadSize * dfScale );
723 
724             poLeaderLine->setNumPoints( 1 );
725 
726             // Go through the vertices of the leader line, adding them,
727             // as well as break start and end points, to the linestring.
728             for( size_t iVertex = 0; iVertex < aoLineVertices.size();
729                  iVertex++ )
730             {
731                 if( iVertex > 0 )
732                 {
733                     poLeaderLine->addPoint(
734                         aoLineVertices[iVertex].oCoords.dfX,
735                         aoLineVertices[iVertex].oCoords.dfY );
736                 }
737 
738                 // Breaks are ignored for spline leaders
739                 if( nLeaderLineType != MLT_SPLINE )
740                 {
741                     for( const auto& oBreak :
742                          aoLineVertices[iVertex].aoBreaks )
743                     {
744                         poLeaderLine->addPoint( oBreak.first.dfX,
745                             oBreak.first.dfY );
746 
747                         poMLS->addGeometryDirectly( poLeaderLine );
748                         poLeaderLine = new OGRLineString();
749 
750                         poLeaderLine->addPoint( oBreak.second.dfX,
751                             oBreak.second.dfY );
752                     }
753                 }
754             }
755 
756             // Add the final vertex (the landing) to the end of the line
757             poLeaderLine->addPoint( oIt->dfLandingX, oIt->dfLandingY );
758 
759             // Make the spline geometry for spline leaders
760             if( nLeaderLineType == MLT_SPLINE )
761             {
762                 DXFTriple oEndTangent;
763                 if( osBlockName.empty() )
764                 {
765                     oEndTangent = DXFTriple( oIt->dfDoglegVectorX,
766                         oIt->dfDoglegVectorY, 0 );
767                 }
768                 InterpolateSpline( poLeaderLine, oEndTangent );
769             }
770 
771             poMLS->addGeometryDirectly( poLeaderLine );
772         }
773 
774         // Add the dogleg as a separate line in the MLS
775         if( bLeaderHasDogleg )
776         {
777             OGRLineString *poDoglegLine = new OGRLineString();
778             poDoglegLine->addPoint( oIt->dfLandingX, oIt->dfLandingY );
779 
780             // Interrupt the dogleg line at breaks
781             for( const auto& oBreak : oIt->aoDoglegBreaks )
782             {
783                 poDoglegLine->addPoint( oBreak.first.dfX,
784                     oBreak.first.dfY );
785 
786                 poMLS->addGeometryDirectly( poDoglegLine );
787                 poDoglegLine = new OGRLineString();
788 
789                 poDoglegLine->addPoint( oBreak.second.dfX,
790                     oBreak.second.dfY );
791             }
792 
793             poDoglegLine->addPoint( dfDoglegX, dfDoglegY );
794             poMLS->addGeometryDirectly( poDoglegLine );
795         }
796     }
797 
798     poLeaderFeature->SetGeometryDirectly( poMLS );
799 
800     PrepareLineStyle( poLeaderFeature, poOverallFeature );
801 
802 /* -------------------------------------------------------------------- */
803 /*      If we have block content, insert that block.                    */
804 /* -------------------------------------------------------------------- */
805 
806     if( osBlockName != "" )
807     {
808         oBlockTransformer.dfXScale *= dfScale;
809         oBlockTransformer.dfYScale *= dfScale;
810 
811         DXFBlockDefinition *poBlock = poDS->LookupBlock( osBlockName );
812 
813         std::map<OGRDXFFeature *, CPLString> oBlockAttributeValues;
814 
815         // If we have block attributes and will need to output them,
816         // go through all the features on this block, looking for
817         // ATTDEFs whose handle is in our list of attribute handles
818         if( poBlock && !oBlockAttributes.empty() &&
819             ( poDS->InlineBlocks() ||
820             poOverallFeature->GetFieldIndex( "BlockAttributes" ) != -1 ) )
821         {
822             for( std::vector<OGRDXFFeature *>::iterator oIt =
823                 poBlock->apoFeatures.begin();
824                 oIt != poBlock->apoFeatures.end();
825                 ++oIt )
826             {
827                 const char* pszHandle =
828                     (*oIt)->GetFieldAsString( "EntityHandle" );
829 
830                 if( pszHandle && oBlockAttributes.count( pszHandle ) > 0 )
831                     oBlockAttributeValues[*oIt] = oBlockAttributes[pszHandle];
832             }
833         }
834 
835         OGRDXFFeature *poBlockFeature = poOverallFeature->CloneDXFFeature();
836 
837         // If not inlining the block, insert a reference and add attributes
838         // to this feature.
839         if( !poDS->InlineBlocks() )
840         {
841             poBlockFeature = InsertBlockReference( osBlockName,
842                 oBlockTransformer, poBlockFeature );
843 
844             if( !oBlockAttributes.empty() &&
845                 poOverallFeature->GetFieldIndex( "BlockAttributes" ) != -1 )
846             {
847                 std::vector<char *> apszAttribs;
848 
849                 for( std::map<OGRDXFFeature *, CPLString>::iterator oIt =
850                     oBlockAttributeValues.begin();
851                     oIt != oBlockAttributeValues.end();
852                     ++oIt )
853                 {
854                     // Store the attribute tag and the text value as
855                     // a space-separated entry in the BlockAttributes field
856                     CPLString osAttribString = oIt->first->osAttributeTag;
857                     osAttribString += " ";
858                     osAttribString += oIt->second;
859 
860                     apszAttribs.push_back(
861                         new char[osAttribString.length() + 1] );
862                     CPLStrlcpy( apszAttribs.back(), osAttribString.c_str(),
863                         osAttribString.length() + 1 );
864                 }
865 
866                 apszAttribs.push_back( nullptr );
867 
868                 poBlockFeature->SetField( "BlockAttributes", &apszAttribs[0] );
869             }
870 
871             apoPendingFeatures.push( poBlockFeature );
872         }
873         else
874         {
875             // Insert the block inline.
876             OGRDXFFeatureQueue apoExtraFeatures;
877             try
878             {
879                 poBlockFeature = InsertBlockInline(
880                     CPLGetErrorCounter(), osBlockName,
881                     oBlockTransformer, poBlockFeature, apoExtraFeatures,
882                     true, poDS->ShouldMergeBlockGeometries() );
883             }
884             catch( const std::invalid_argument& )
885             {
886                 // Block doesn't exist
887                 delete poBlockFeature;
888                 poBlockFeature = nullptr;
889             }
890 
891             // Add the block geometries to the pending feature stack.
892             if( poBlockFeature )
893             {
894                 apoPendingFeatures.push( poBlockFeature );
895             }
896             while( !apoExtraFeatures.empty() )
897             {
898                 apoPendingFeatures.push( apoExtraFeatures.front() );
899                 apoExtraFeatures.pop();
900             }
901 
902             // Also add any attributes to the pending feature stack.
903             for( std::map<OGRDXFFeature *, CPLString>::iterator oIt =
904                      oBlockAttributeValues.begin();
905                  oIt != oBlockAttributeValues.end();
906                  ++oIt )
907             {
908                 OGRDXFFeature *poAttribFeature = oIt->first->CloneDXFFeature();
909 
910                 poAttribFeature->SetField( "Text", oIt->second );
911 
912                 // Replace text in the style string
913                 const char* poStyleString = poAttribFeature->GetStyleString();
914                 if( poStyleString && STARTS_WITH(poStyleString, "LABEL(") )
915                 {
916                     CPLString osNewStyle = poStyleString;
917                     const size_t nTextStartPos = osNewStyle.find( ",t:\"" );
918                     if( nTextStartPos != std::string::npos )
919                     {
920                         size_t nTextEndPos = nTextStartPos + 4;
921                         while( nTextEndPos < osNewStyle.size() &&
922                             osNewStyle[nTextEndPos] != '\"' )
923                         {
924                             nTextEndPos++;
925                             if( osNewStyle[nTextEndPos] == '\\' )
926                                 nTextEndPos++;
927                         }
928 
929                         if( nTextEndPos < osNewStyle.size() )
930                         {
931                             osNewStyle.replace( nTextStartPos + 4,
932                                 nTextEndPos - ( nTextStartPos + 4 ),
933                                 oIt->second );
934                             poAttribFeature->SetStyleString( osNewStyle );
935                         }
936                     }
937                 }
938 
939                 // The following bits are copied from
940                 // OGRDXFLayer::InsertBlockInline
941                 if( poAttribFeature->GetGeometryRef() )
942                 {
943                     poAttribFeature->GetGeometryRef()->transform(
944                         &oBlockTransformer );
945                 }
946 
947                 if( EQUAL( poAttribFeature->GetFieldAsString( "Layer" ), "0" ) &&
948                     !EQUAL( poOverallFeature->GetFieldAsString( "Layer" ), "" ) )
949                 {
950                     poAttribFeature->SetField( "Layer",
951                         poOverallFeature->GetFieldAsString( "Layer" ) );
952                 }
953 
954                 PrepareFeatureStyle( poAttribFeature, poOverallFeature );
955 
956                 ACAdjustText( oBlockTransformer.dfAngle * 180 / M_PI,
957                     oBlockTransformer.dfXScale, oBlockTransformer.dfYScale,
958                     poAttribFeature );
959 
960                 if ( !EQUAL( poOverallFeature->GetFieldAsString(
961                     "EntityHandle" ), "" ) )
962                 {
963                     poAttribFeature->SetField( "EntityHandle",
964                         poOverallFeature->GetFieldAsString( "EntityHandle" ) );
965                 }
966 
967                 apoPendingFeatures.push( poAttribFeature );
968             }
969         }
970     }
971 
972 /* -------------------------------------------------------------------- */
973 /*      Prepare a new feature to serve as the leader text label         */
974 /*      refeature.  We will push it onto the layer as a pending           */
975 /*      feature for the next feature read.                              */
976 /* -------------------------------------------------------------------- */
977 
978     if( osText.empty() || osText == " " )
979     {
980         delete poOverallFeature;
981         return poLeaderFeature;
982     }
983 
984     OGRDXFFeature *poLabelFeature = poOverallFeature->CloneDXFFeature();
985 
986     poLabelFeature->SetField( "Text", osText );
987     poLabelFeature->SetGeometryDirectly( new OGRPoint( dfTextX, dfTextY ) );
988 
989     CPLString osStyle;
990     char szBuffer[64];
991 
992     const CPLString osStyleName =
993         poDS->GetTextStyleNameByHandle( osTextStyleHandle );
994 
995     // Font name
996     osStyle.Printf("LABEL(f:\"");
997 
998     // Preserve legacy behavior of specifying "Arial" as a default font name.
999     osStyle += poDS->LookupTextStyleProperty( osStyleName, "Font", "Arial" );
1000 
1001     osStyle += "\"";
1002 
1003     // Bold, italic
1004     if( EQUAL( poDS->LookupTextStyleProperty( osStyleName,
1005         "Bold", "0" ), "1" ) )
1006     {
1007         osStyle += ",bo:1";
1008     }
1009     if( EQUAL( poDS->LookupTextStyleProperty( osStyleName,
1010         "Italic", "0" ), "1" ) )
1011     {
1012         osStyle += ",it:1";
1013     }
1014 
1015     osStyle += CPLString().Printf(",t:\"%s\",p:%d", osText.c_str(),
1016         nTextAlignment + 6); // 7,8,9: vertical align top
1017 
1018     if( dfTextAngle != 0.0 )
1019     {
1020         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
1021         osStyle += CPLString().Printf(",a:%s", szBuffer);
1022     }
1023 
1024     if( dfTextHeight != 0.0 )
1025     {
1026         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextHeight);
1027         osStyle += CPLString().Printf(",s:%sg", szBuffer);
1028     }
1029 
1030     const char *pszWidthFactor = poDS->LookupTextStyleProperty( osStyleName,
1031         "Width", "1" );
1032     if( pszWidthFactor && CPLAtof( pszWidthFactor ) != 1.0 )
1033     {
1034         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.4g",
1035             CPLAtof( pszWidthFactor ) * 100.0);
1036         osStyle += CPLString().Printf(",w:%s", szBuffer);
1037     }
1038 
1039     // Color
1040     osStyle += ",c:";
1041     osStyle += poLabelFeature->GetColor( poDS );
1042 
1043     osStyle += ")";
1044 
1045     poLabelFeature->SetStyleString( osStyle );
1046 
1047     apoPendingFeatures.push( poLabelFeature );
1048 
1049     delete poOverallFeature;
1050     return poLeaderFeature;
1051 }
1052 
1053 /************************************************************************/
1054 /*                     GenerateDefaultArrowhead()                       */
1055 /*                                                                      */
1056 /*      Generates the default DWG/DXF arrowhead (a filled triangle      */
1057 /*      with a 3:1 aspect ratio) on the end of the line segment         */
1058 /*      defined by the two points.                                      */
1059 /************************************************************************/
GenerateDefaultArrowhead(OGRDXFFeature * const poArrowheadFeature,const OGRPoint & oPoint1,const OGRPoint & oPoint2,const double dfArrowheadScale)1060 static void GenerateDefaultArrowhead( OGRDXFFeature* const poArrowheadFeature,
1061     const OGRPoint& oPoint1, const OGRPoint& oPoint2,
1062     const double dfArrowheadScale )
1063 
1064 {
1065     // calculate the baseline to be expanded out into arrowheads
1066     const double dfParallelPartX = dfArrowheadScale *
1067         (oPoint2.getX() - oPoint1.getX());
1068     const double dfParallelPartY = dfArrowheadScale *
1069         (oPoint2.getY() - oPoint1.getY());
1070     // ...and drop a perpendicular
1071     const double dfPerpPartX = dfParallelPartY;
1072     const double dfPerpPartY = -dfParallelPartX;
1073 
1074     OGRLinearRing *poLinearRing = new OGRLinearRing();
1075     poLinearRing->setPoint( 0,
1076         oPoint1.getX() + dfParallelPartX + dfPerpPartX/6,
1077         oPoint1.getY() + dfParallelPartY + dfPerpPartY/6,
1078         oPoint1.getZ() );
1079     poLinearRing->setPoint( 1,
1080         oPoint1.getX(),
1081         oPoint1.getY(),
1082         oPoint1.getZ() );
1083     poLinearRing->setPoint( 2,
1084         oPoint1.getX() + dfParallelPartX - dfPerpPartX/6,
1085         oPoint1.getY() + dfParallelPartY - dfPerpPartY/6,
1086         oPoint1.getZ() );
1087     poLinearRing->closeRings();
1088 
1089     OGRPolygon* poPoly = new OGRPolygon();
1090     poPoly->addRingDirectly( poLinearRing );
1091 
1092     poArrowheadFeature->SetGeometryDirectly( poPoly );
1093 }
1094 
1095 /************************************************************************/
1096 /*                          InsertArrowhead()                           */
1097 /*                                                                      */
1098 /*      Inserts the specified arrowhead block at the start of the       */
1099 /*      first segment of the given line string (or the end of the       */
1100 /*      last segment if bReverse is false).  2D only.                   */
1101 /*                                                                      */
1102 /*      The first (last) point of the line string may be updated.       */
1103 /************************************************************************/
InsertArrowhead(OGRDXFFeature * const poFeature,const CPLString & osBlockHandle,OGRLineString * const poLine,const double dfArrowheadSize,const bool bReverse)1104 void OGRDXFLayer::InsertArrowhead( OGRDXFFeature* const poFeature,
1105     const CPLString& osBlockHandle, OGRLineString* const poLine,
1106     const double dfArrowheadSize, const bool bReverse /* = false */ )
1107 {
1108     OGRPoint oPoint1, oPoint2;
1109     poLine->getPoint( bReverse ? poLine->getNumPoints() - 1 : 0, &oPoint1 );
1110     poLine->getPoint( bReverse ? poLine->getNumPoints() - 2 : 1, &oPoint2 );
1111 
1112     const double dfFirstSegmentLength = PointDist( oPoint1.getX(),
1113         oPoint1.getY(), oPoint2.getX(), oPoint2.getY() );
1114 
1115     // AutoCAD only displays an arrowhead if the length of the arrowhead
1116     // is less than or equal to half the length of the line segment
1117     if( dfArrowheadSize == 0.0 || dfFirstSegmentLength == 0.0 ||
1118         dfArrowheadSize > 0.5 * dfFirstSegmentLength )
1119     {
1120         return;
1121     }
1122 
1123     OGRDXFFeature *poArrowheadFeature = poFeature->CloneDXFFeature();
1124 
1125     // Convert the block handle to a block name.
1126     CPLString osBlockName = "";
1127 
1128     if( osBlockHandle != "" )
1129         osBlockName = poDS->GetBlockNameByRecordHandle( osBlockHandle );
1130 
1131     OGRDXFFeatureQueue apoExtraFeatures;
1132 
1133     // If the block doesn't exist, we need to fall back to the
1134     // default arrowhead.
1135     if( osBlockName == "" )
1136     {
1137         GenerateDefaultArrowhead( poArrowheadFeature, oPoint1, oPoint2,
1138             dfArrowheadSize / dfFirstSegmentLength );
1139 
1140         PrepareBrushStyle( poArrowheadFeature );
1141     }
1142     else
1143     {
1144         // Build a transformer to insert the arrowhead block with the
1145         // required location, angle and scale.
1146         OGRDXFInsertTransformer oTransformer;
1147         oTransformer.dfXOffset = oPoint1.getX();
1148         oTransformer.dfYOffset = oPoint1.getY();
1149         oTransformer.dfZOffset = oPoint1.getZ();
1150         // Arrowhead blocks always point to the right (--->)
1151         oTransformer.dfAngle = atan2( oPoint2.getY() - oPoint1.getY(),
1152             oPoint2.getX() - oPoint1.getX() ) + M_PI;
1153         oTransformer.dfXScale = oTransformer.dfYScale =
1154             oTransformer.dfZScale = dfArrowheadSize;
1155 
1156         // Insert the block.
1157         try
1158         {
1159             poArrowheadFeature = InsertBlockInline(
1160                 CPLGetErrorCounter(), osBlockName,
1161                 oTransformer, poArrowheadFeature, apoExtraFeatures,
1162                 true, false );
1163         }
1164         catch( const std::invalid_argument& )
1165         {
1166             // Supposedly the block doesn't exist. But what has probably
1167             // happened is that the block exists in the DXF, but it contains
1168             // no entities, so the data source didn't read it in.
1169             // In this case, no arrowhead is required.
1170             delete poArrowheadFeature;
1171             poArrowheadFeature = nullptr;
1172         }
1173     }
1174 
1175     // Add the arrowhead geometries to the pending feature stack.
1176     if( poArrowheadFeature )
1177     {
1178         apoPendingFeatures.push( poArrowheadFeature );
1179     }
1180     while( !apoExtraFeatures.empty() )
1181     {
1182         apoPendingFeatures.push( apoExtraFeatures.front() );
1183         apoExtraFeatures.pop();
1184     }
1185 
1186     // Move the endpoint of the line out of the way of the arrowhead.
1187     // We assume that arrowheads are 1 unit long, except for a list
1188     // of specific block names which are treated as having no length
1189 
1190     static const char* apszSpecialArrowheads[] = {
1191         "_ArchTick",
1192         "_DotSmall",
1193         "_Integral",
1194         "_None",
1195         "_Oblique",
1196         "_Small"
1197     };
1198 
1199     if( std::find( apszSpecialArrowheads, apszSpecialArrowheads + 6,
1200         osBlockName ) == ( apszSpecialArrowheads + 6 ) )
1201     {
1202         oPoint1.setX( oPoint1.getX() + dfArrowheadSize *
1203             ( oPoint2.getX() - oPoint1.getX() ) / dfFirstSegmentLength );
1204         oPoint1.setY( oPoint1.getY() + dfArrowheadSize *
1205             ( oPoint2.getY() - oPoint1.getY() ) / dfFirstSegmentLength );
1206 
1207         poLine->setPoint( bReverse ? poLine->getNumPoints() - 1 : 0,
1208             &oPoint1 );
1209     }
1210 }
1211 
1212 /************************************************************************/
1213 /*                        basis(), rbspline2()                          */
1214 /*                                                                      */
1215 /*      Spline calculation functions defined in intronurbs.cpp.         */
1216 /************************************************************************/
1217 void basis( int c, double t, int npts, double x[], double N[] );
1218 void rbspline2( int npts,int k,int p1,double b[],double h[],
1219     bool bCalculateKnots, double x[], double p[] );
1220 
1221 
1222 #if defined(__GNUC__) && __GNUC__ >= 6
1223 #pragma GCC diagnostic push
1224 #pragma GCC diagnostic ignored "-Wnull-dereference"
1225 #endif
1226 
1227 namespace {
setRow(GDALMatrix & m,int row,DXFTriple const & t)1228     inline void setRow(GDALMatrix & m, int row, DXFTriple const & t)
1229     {
1230         m(row, 0) = t.dfX;
1231         m(row, 1) = t.dfY;
1232         m(row, 2) = t.dfZ;
1233     }
1234 }
1235 
1236 /************************************************************************/
1237 /*                      GetBSplineControlPoints()                       */
1238 /*                                                                      */
1239 /*      Evaluates the control points for the B-spline of given degree   */
1240 /*      that interpolates the given data points, using the given        */
1241 /*      parameters, start tangent and end tangent.  The parameters      */
1242 /*      and knot vector must be increasing sequences with first         */
1243 /*      element 0 and last element 1.  Given n data points, there       */
1244 /*      must be n parameters and n + nDegree + 3 knots.                 */
1245 /*                                                                      */
1246 /*      It is recommended to match AutoCAD by generating a knot         */
1247 /*      vector from the parameters as follows:                          */
1248 /*              0 0 ... 0 adfParameters 1 1 ... 1                       */
1249 /*        (nDegree zeros)               (nDegree ones)                  */
1250 /*      To fully match AutoCAD's behavior, a chord-length              */
1251 /*      parameterisation should be used, and the start and end          */
1252 /*      tangent vectors should be multiplied by the total chord         */
1253 /*      length of all chords.                                           */
1254 /*                                                                      */
1255 /*      Reference: Piegl, L., Tiller, W. (1995), The NURBS Book,        */
1256 /*      2nd ed. (Springer), sections 2.2 and 9.2.                       */
1257 /*      Although this book contains implementations of algorithms,      */
1258 /*      this function is an original implementation based on the        */
1259 /*      concepts discussed in the book and was written without          */
1260 /*      reference to Piegl and Tiller's implementations.                */
1261 /************************************************************************/
GetBSplineControlPoints(const std::vector<double> & adfParameters,const std::vector<double> & adfKnots,const std::vector<DXFTriple> & aoDataPoints,const int nDegree,DXFTriple oStartTangent,DXFTriple oEndTangent)1262 static std::vector<DXFTriple> GetBSplineControlPoints(
1263     const std::vector<double>& adfParameters,
1264     const std::vector<double>& adfKnots,
1265     const std::vector<DXFTriple>& aoDataPoints, const int nDegree,
1266     DXFTriple oStartTangent, DXFTriple oEndTangent )
1267 {
1268     CPLAssert( nDegree > 1 );
1269 
1270     // Count the number of data points
1271     // Note: The literature often sets n to one less than the number of data
1272     // points for some reason, but we don't do that here
1273     const int nPoints = static_cast<int>( aoDataPoints.size() );
1274 
1275     CPLAssert( nPoints > 0 );
1276     CPLAssert( nPoints == static_cast<int>( adfParameters.size() ) );
1277 
1278     // RAM consumption is quadratic in the number of control points.
1279     if( nPoints > atoi(CPLGetConfigOption(
1280                         "DXF_MAX_BSPLINE_CONTROL_POINTS", "2000")) )
1281     {
1282         CPLError(CE_Failure, CPLE_AppDefined,
1283                  "Too many control points (%d) for spline leader. "
1284                  "Set DXF_MAX_BSPLINE_CONTROL_POINTS configuration "
1285                  "option to a higher value to remove this limitation "
1286                  "(at the cost of significant RAM consumption)", nPoints);
1287         return std::vector<DXFTriple>();
1288     }
1289 
1290     // We want to solve the linear system NP=D for P, where N is a coefficient
1291     // matrix made up of values of the basis functions at each parameter
1292     // value, with two additional rows for the endpoint tangent information.
1293     // Each row relates to a different parameter.
1294 
1295     // Set up D as a matrix consisting initially of the data points
1296     GDALMatrix D(nPoints + 2, 3);
1297 
1298     setRow(D, 0, aoDataPoints[0]);
1299     for( int iIndex = 1; iIndex < nPoints - 1; iIndex++ )
1300         setRow(D, iIndex + 1, aoDataPoints[iIndex]);
1301     setRow(D, nPoints + 1, aoDataPoints[nPoints - 1]);
1302 
1303 
1304     const double dfStartMultiplier = adfKnots[nDegree + 1] / nDegree;
1305     oStartTangent *= dfStartMultiplier;
1306     setRow(D, 1, oStartTangent);
1307 
1308     const double dfEndMultiplier = ( 1.0 - adfKnots[nPoints + 1] ) / nDegree;
1309     oEndTangent *= dfEndMultiplier;
1310     setRow(D, nPoints, oEndTangent);
1311 
1312     GDALMatrix N(nPoints + 2, nPoints + 2);
1313     // First control point will be the first data point
1314     N(0,0) = 1.0;
1315 
1316     // Start tangent determines the second control point
1317     N(1,0) = -1.0;
1318     N(1,1) = 1.0;
1319 
1320     // Fill the middle rows of the matrix with basis function values. We
1321     // have to use a temporary vector, because intronurbs' basis function
1322     // requires an additional nDegree entries for temporary storage.
1323     std::vector<double> adfTempRow( nPoints + 2 + nDegree, 0.0 );
1324     for( int iRow = 2; iRow < nPoints; iRow++ )
1325     {
1326         basis( nDegree + 1, adfParameters[iRow - 1], nPoints + 2,
1327             const_cast<double *>( &adfKnots[0] ) - 1, &adfTempRow[0] - 1 );
1328         for(int iCol = 0; iCol < nPoints + 2; ++iCol)
1329             N(iRow, iCol) = adfTempRow[iCol];
1330     }
1331 
1332     // End tangent determines the second-last control point
1333     N(nPoints,nPoints) = -1.0;
1334     N(nPoints,nPoints + 1) = 1.0;
1335 
1336     // Last control point will be the last data point
1337     N(nPoints + 1,nPoints + 1) = 1.0;
1338 
1339     // Solve the linear system
1340     GDALMatrix P(nPoints + 2, 3);
1341     GDALLinearSystemSolve( N, D, P );
1342 
1343     std::vector<DXFTriple> aoControlPoints( nPoints + 2 );
1344     for( int iRow = 0; iRow < nPoints + 2; iRow++ )
1345     {
1346         aoControlPoints[iRow].dfX = P(iRow, 0);
1347         aoControlPoints[iRow].dfY = P(iRow, 1);
1348         aoControlPoints[iRow].dfZ = P(iRow, 2);
1349     }
1350 
1351     return aoControlPoints;
1352 }
1353 
1354 #if defined(__GNUC__) && __GNUC__ >= 6
1355 #pragma GCC diagnostic pop
1356 #endif
1357 
1358 
1359 /************************************************************************/
1360 /*                         InterpolateSpline()                          */
1361 /*                                                                      */
1362 /*      Interpolates a cubic spline between the data points of the      */
1363 /*      given line string. The line string is updated with the new      */
1364 /*      spline geometry.                                                */
1365 /*                                                                      */
1366 /*      If an end tangent of (0,0,0) is given, the direction vector     */
1367 /*      of the last chord (line segment) is used.                       */
1368 /************************************************************************/
InterpolateSpline(OGRLineString * const poLine,const DXFTriple & oEndTangentDirection)1369 static void InterpolateSpline( OGRLineString* const poLine,
1370     const DXFTriple& oEndTangentDirection )
1371 {
1372     int nDataPoints = static_cast<int>( poLine->getNumPoints() );
1373     if ( nDataPoints < 2 )
1374         return;
1375 
1376     // Transfer line vertices into DXFTriple objects
1377     std::vector<DXFTriple> aoDataPoints;
1378     OGRPoint oPrevPoint;
1379     for( int iIndex = 0; iIndex < nDataPoints; iIndex++ )
1380     {
1381         OGRPoint oPoint;
1382         poLine->getPoint( iIndex, &oPoint );
1383 
1384         // Remove sequential duplicate points
1385         if( iIndex > 0 && oPrevPoint.Equals( &oPoint ) )
1386             continue;
1387 
1388         aoDataPoints.push_back( DXFTriple( oPoint.getX(), oPoint.getY(),
1389             oPoint.getZ() ) );
1390         oPrevPoint = oPoint;
1391     }
1392     nDataPoints = static_cast<int>( aoDataPoints.size() );
1393     if( nDataPoints < 2 )
1394         return;
1395 
1396     // Work out the chord length parameterisation
1397     std::vector<double> adfParameters;
1398     adfParameters.push_back( 0.0 );
1399     for( int iIndex = 1; iIndex < nDataPoints; iIndex++ )
1400     {
1401         const double dfParameter = adfParameters[iIndex - 1] +
1402             PointDist( aoDataPoints[iIndex - 1].dfX,
1403                 aoDataPoints[iIndex - 1].dfY,
1404                 aoDataPoints[iIndex - 1].dfZ,
1405                 aoDataPoints[iIndex].dfX,
1406                 aoDataPoints[iIndex].dfY,
1407                 aoDataPoints[iIndex].dfZ );
1408 
1409         // Bail out in pathological cases. This will happen when
1410         // some lengths are very large (above 10^16) and others are
1411         // very small (such as 1)
1412         if( dfParameter == adfParameters[iIndex - 1] )
1413             return;
1414 
1415         adfParameters.push_back( dfParameter );
1416     }
1417 
1418     const double dfTotalChordLength = adfParameters[adfParameters.size() - 1];
1419 
1420     // Start tangent can be worked out from the first chord
1421     DXFTriple oStartTangent( aoDataPoints[1].dfX - aoDataPoints[0].dfX,
1422         aoDataPoints[1].dfY - aoDataPoints[0].dfY,
1423         aoDataPoints[1].dfZ - aoDataPoints[0].dfZ );
1424     oStartTangent *= dfTotalChordLength / adfParameters[1];
1425 
1426     // If end tangent is zero, it is worked out from the last chord
1427     DXFTriple oEndTangent = oEndTangentDirection;
1428     if( oEndTangent.dfX == 0.0 && oEndTangent.dfY == 0.0 &&
1429         oEndTangent.dfZ == 0.0 )
1430     {
1431         oEndTangent = DXFTriple(
1432             aoDataPoints[nDataPoints - 1].dfX -
1433                 aoDataPoints[nDataPoints - 2].dfX,
1434             aoDataPoints[nDataPoints - 1].dfY -
1435                 aoDataPoints[nDataPoints - 2].dfY,
1436             aoDataPoints[nDataPoints - 1].dfZ -
1437                 aoDataPoints[nDataPoints - 2].dfZ );
1438         oEndTangent /= dfTotalChordLength - adfParameters[nDataPoints - 2];
1439     }
1440 
1441     // End tangent direction is multiplied by total chord length
1442     oEndTangent *= dfTotalChordLength;
1443 
1444     // Normalise the parameter vector
1445     for( int iIndex = 1; iIndex < nDataPoints; iIndex++ )
1446         adfParameters[iIndex] /= dfTotalChordLength;
1447 
1448     // Generate a knot vector
1449     const int nDegree = 3;
1450     std::vector<double> adfKnots( aoDataPoints.size() + nDegree + 3, 0.0 );
1451     std::copy( adfParameters.begin(), adfParameters.end(),
1452         adfKnots.begin() + nDegree );
1453     std::fill( adfKnots.end() - nDegree, adfKnots.end(), 1.0 );
1454 
1455     // Calculate the spline control points
1456     std::vector<DXFTriple> aoControlPoints = GetBSplineControlPoints(
1457         adfParameters, adfKnots, aoDataPoints, nDegree,
1458         oStartTangent, oEndTangent );
1459     const int nControlPoints = static_cast<int>( aoControlPoints.size() );
1460 
1461     if( nControlPoints == 0 )
1462         return;
1463 
1464     // Interpolate the spline using the intronurbs code
1465     int nWantedPoints = nControlPoints * 8;
1466     std::vector<double> adfWeights( nControlPoints, 1.0 );
1467     std::vector<double> adfPoints( 3 * nWantedPoints, 0.0 );
1468 
1469     rbspline2( nControlPoints, nDegree + 1, nWantedPoints,
1470         reinterpret_cast<double*>( &aoControlPoints[0] ) - 1,
1471         &adfWeights[0] - 1, false, &adfKnots[0] - 1, &adfPoints[0] - 1 );
1472 
1473     // Preserve 2D/3D status as we add the interpolated points to the line
1474     const int bIs3D = poLine->Is3D();
1475     poLine->empty();
1476     for( int iIndex = 0; iIndex < nWantedPoints; iIndex++ )
1477     {
1478         poLine->addPoint( adfPoints[iIndex * 3], adfPoints[iIndex * 3 + 1],
1479             adfPoints[iIndex * 3 + 2] );
1480     }
1481     if( !bIs3D )
1482         poLine->flattenTo2D();
1483 }
1484