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