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