Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: DXF Translator
4 : * Purpose: Implements OGRDXFWriterLayer - the OGRLayer class used for
5 : * writing a DXF file.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
10 : * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "ogr_dxf.h"
16 : #include "cpl_conv.h"
17 : #include "cpl_string.h"
18 : #include "ogr_featurestyle.h"
19 :
20 : #include <cstdlib>
21 :
22 : /************************************************************************/
23 : /* OGRDXFWriterLayer() */
24 : /************************************************************************/
25 :
26 59 : OGRDXFWriterLayer::OGRDXFWriterLayer(OGRDXFWriterDS *poDSIn, VSILFILE *fpIn)
27 : : fp(fpIn),
28 : poFeatureDefn(nullptr), // TODO(schwehr): Can I move the new here?
29 59 : poDS(poDSIn)
30 : {
31 59 : nNextAutoID = 1;
32 59 : bWriteHatch = CPLTestBool(CPLGetConfigOption("DXF_WRITE_HATCH", "YES"));
33 :
34 59 : poFeatureDefn = new OGRFeatureDefn("entities");
35 59 : poFeatureDefn->Reference();
36 :
37 59 : OGRDXFDataSource::AddStandardFields(poFeatureDefn, ODFM_IncludeBlockFields);
38 59 : }
39 :
40 : /************************************************************************/
41 : /* ~OGRDXFWriterLayer() */
42 : /************************************************************************/
43 :
44 118 : OGRDXFWriterLayer::~OGRDXFWriterLayer()
45 :
46 : {
47 59 : if (poFeatureDefn)
48 59 : poFeatureDefn->Release();
49 118 : }
50 :
51 : /************************************************************************/
52 : /* ResetFP() */
53 : /* */
54 : /* Redirect output. Mostly used for writing block definitions. */
55 : /************************************************************************/
56 :
57 2 : void OGRDXFWriterLayer::ResetFP(VSILFILE *fpNew)
58 :
59 : {
60 2 : fp = fpNew;
61 2 : }
62 :
63 : /************************************************************************/
64 : /* TestCapability() */
65 : /************************************************************************/
66 :
67 109 : int OGRDXFWriterLayer::TestCapability(const char *pszCap)
68 :
69 : {
70 109 : if (EQUAL(pszCap, OLCStringsAsUTF8))
71 0 : return TRUE;
72 109 : else if (EQUAL(pszCap, OLCSequentialWrite))
73 16 : return TRUE;
74 : else
75 93 : return FALSE;
76 : }
77 :
78 : /************************************************************************/
79 : /* CreateField() */
80 : /* */
81 : /* This is really a dummy as our fields are precreated. */
82 : /************************************************************************/
83 :
84 80 : OGRErr OGRDXFWriterLayer::CreateField(const OGRFieldDefn *poField,
85 : int bApproxOK)
86 :
87 : {
88 80 : if (poFeatureDefn->GetFieldIndex(poField->GetNameRef()) >= 0 && bApproxOK)
89 0 : return OGRERR_NONE;
90 80 : if (EQUAL(poField->GetNameRef(), "OGR_STYLE"))
91 : {
92 0 : poFeatureDefn->AddFieldDefn(poField);
93 0 : return OGRERR_NONE;
94 : }
95 :
96 80 : CPLError(CE_Failure, CPLE_AppDefined,
97 : "DXF layer does not support arbitrary field creation, field '%s' "
98 : "not created.",
99 : poField->GetNameRef());
100 :
101 80 : return OGRERR_FAILURE;
102 : }
103 :
104 : /************************************************************************/
105 : /* WriteValue() */
106 : /************************************************************************/
107 :
108 784 : int OGRDXFWriterLayer::WriteValue(int nCode, const char *pszValue)
109 :
110 : {
111 784 : CPLString osLinePair;
112 :
113 784 : osLinePair.Printf("%3d\n", nCode);
114 :
115 784 : if (strlen(pszValue) < 255)
116 784 : osLinePair += pszValue;
117 : else
118 0 : osLinePair.append(pszValue, 255);
119 :
120 784 : osLinePair += "\n";
121 :
122 784 : return VSIFWriteL(osLinePair.c_str(), 1, osLinePair.size(), fp) ==
123 1568 : osLinePair.size();
124 : }
125 :
126 : /************************************************************************/
127 : /* WriteValue() */
128 : /************************************************************************/
129 :
130 548 : int OGRDXFWriterLayer::WriteValue(int nCode, int nValue)
131 :
132 : {
133 548 : CPLString osLinePair;
134 :
135 548 : osLinePair.Printf("%3d\n%d\n", nCode, nValue);
136 :
137 548 : return VSIFWriteL(osLinePair.c_str(), 1, osLinePair.size(), fp) ==
138 1096 : osLinePair.size();
139 : }
140 :
141 : /************************************************************************/
142 : /* WriteValue() */
143 : /************************************************************************/
144 :
145 800 : int OGRDXFWriterLayer::WriteValue(int nCode, double dfValue)
146 :
147 : {
148 : char szLinePair[64];
149 :
150 800 : CPLsnprintf(szLinePair, sizeof(szLinePair), "%3d\n%.15g\n", nCode, dfValue);
151 800 : size_t nLen = strlen(szLinePair);
152 :
153 800 : return VSIFWriteL(szLinePair, 1, nLen, fp) == nLen;
154 : }
155 :
156 : /************************************************************************/
157 : /* WriteCore() */
158 : /* */
159 : /* Write core fields common to all sorts of elements. */
160 : /************************************************************************/
161 :
162 183 : OGRErr OGRDXFWriterLayer::WriteCore(OGRFeature *poFeature,
163 : const CorePropertiesType &oCoreProperties)
164 :
165 : {
166 : /* -------------------------------------------------------------------- */
167 : /* Write out an entity id. I'm not sure why this is critical, */
168 : /* but it seems that VoloView will just quietly fail to open */
169 : /* dxf files without entity ids set on most/all entities. */
170 : /* Also, for reasons I don't understand these ids seem to have */
171 : /* to start somewhere around 0x50 hex (80 decimal). */
172 : /* -------------------------------------------------------------------- */
173 183 : unsigned int nGotFID = 0;
174 183 : poDS->WriteEntityID(fp, nGotFID, poFeature->GetFID());
175 183 : poFeature->SetFID(nGotFID);
176 :
177 183 : WriteValue(100, "AcDbEntity");
178 :
179 : /* -------------------------------------------------------------------- */
180 : /* For now we assign everything to the default layer - layer */
181 : /* "0" - if there is no layer property on the source features. */
182 : /* -------------------------------------------------------------------- */
183 183 : const char *pszLayer = poFeature->GetFieldAsString("Layer");
184 183 : if (pszLayer == nullptr || strlen(pszLayer) == 0)
185 : {
186 175 : WriteValue(8, "0");
187 : }
188 : else
189 : {
190 16 : CPLString osSanitizedLayer(pszLayer);
191 : // Replaced restricted characters with underscore
192 : // See
193 : // http://docs.autodesk.com/ACD/2010/ENU/AutoCAD%202010%20User%20Documentation/index.html?url=WS1a9193826455f5ffa23ce210c4a30acaf-7345.htm,topicNumber=d0e41665
194 8 : const char achForbiddenChars[] = {'<', '>', '/', '\\', '"', ':',
195 : ';', '?', '*', '|', '=', '\''};
196 104 : for (size_t i = 0; i < CPL_ARRAYSIZE(achForbiddenChars); ++i)
197 : {
198 96 : osSanitizedLayer.replaceAll(achForbiddenChars[i], '_');
199 : }
200 :
201 : // also remove newline characters (#15067)
202 8 : osSanitizedLayer.replaceAll("\r\n", "_");
203 8 : osSanitizedLayer.replaceAll('\r', '_');
204 8 : osSanitizedLayer.replaceAll('\n', '_');
205 :
206 : auto osExists =
207 16 : poDS->oHeaderDS.LookupLayerProperty(osSanitizedLayer, "Exists");
208 15 : if (!osExists &&
209 7 : CSLFindString(poDS->papszLayersToCreate, osSanitizedLayer) == -1)
210 : {
211 6 : poDS->papszLayersToCreate =
212 3 : CSLAddString(poDS->papszLayersToCreate, osSanitizedLayer);
213 : }
214 :
215 8 : WriteValue(8, osSanitizedLayer);
216 : }
217 :
218 200 : for (const auto &oProp : oCoreProperties)
219 : {
220 17 : if (oProp.first == PROP_RGBA_COLOR)
221 : {
222 17 : bool bPerfectMatch = false;
223 17 : const char *pszColor = oProp.second.c_str();
224 17 : const int nColor = ColorStringToDXFColor(pszColor, bPerfectMatch);
225 17 : if (nColor >= 0)
226 : {
227 17 : WriteValue(62, nColor);
228 :
229 17 : unsigned int nRed = 0;
230 17 : unsigned int nGreen = 0;
231 17 : unsigned int nBlue = 0;
232 17 : unsigned int nOpacity = 255;
233 :
234 17 : const int nCount = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
235 : &nGreen, &nBlue, &nOpacity);
236 17 : if (nCount >= 3 && !bPerfectMatch)
237 : {
238 8 : WriteValue(420, static_cast<int>(nBlue | (nGreen << 8) |
239 8 : (nRed << 16)));
240 : }
241 17 : if (nCount == 4)
242 : {
243 2 : WriteValue(440, static_cast<int>(nOpacity | (2 << 24)));
244 : }
245 : }
246 : }
247 : else
248 : {
249 : // If this happens, this is a coding error
250 0 : CPLError(CE_Failure, CPLE_AppDefined,
251 0 : "BUG! Unhandled core property %d", oProp.first);
252 : }
253 : }
254 :
255 183 : return OGRERR_NONE;
256 : }
257 :
258 : /************************************************************************/
259 : /* WriteINSERT() */
260 : /************************************************************************/
261 :
262 7 : OGRErr OGRDXFWriterLayer::WriteINSERT(OGRFeature *poFeature)
263 :
264 : {
265 14 : CorePropertiesType oCoreProperties;
266 :
267 : // Write style symbol color
268 7 : OGRStyleTool *poTool = nullptr;
269 14 : OGRStyleMgr oSM;
270 7 : if (poFeature->GetStyleString() != nullptr)
271 : {
272 0 : oSM.InitFromFeature(poFeature);
273 :
274 0 : if (oSM.GetPartCount() > 0)
275 0 : poTool = oSM.GetPart(0);
276 : }
277 7 : if (poTool && poTool->GetType() == OGRSTCSymbol)
278 : {
279 0 : OGRStyleSymbol *poSymbol = cpl::down_cast<OGRStyleSymbol *>(poTool);
280 : GBool bDefault;
281 0 : const char *pszColor = poSymbol->Color(bDefault);
282 0 : if (pszColor && !bDefault)
283 0 : oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
284 : }
285 7 : delete poTool;
286 :
287 7 : WriteValue(0, "INSERT");
288 7 : WriteCore(poFeature, oCoreProperties);
289 7 : WriteValue(100, "AcDbBlockReference");
290 7 : WriteValue(2, poFeature->GetFieldAsString("BlockName"));
291 :
292 : /* -------------------------------------------------------------------- */
293 : /* Write location in OCS. */
294 : /* -------------------------------------------------------------------- */
295 7 : int nCoordCount = 0;
296 : const double *padfCoords =
297 7 : poFeature->GetFieldAsDoubleList("BlockOCSCoords", &nCoordCount);
298 :
299 7 : if (nCoordCount == 3)
300 : {
301 0 : WriteValue(10, padfCoords[0]);
302 0 : WriteValue(20, padfCoords[1]);
303 0 : if (!WriteValue(30, padfCoords[2]))
304 0 : return OGRERR_FAILURE;
305 : }
306 : else
307 : {
308 : // We don't have an OCS; we will just assume that the location of
309 : // the geometry (in WCS) is the correct insertion point.
310 7 : OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
311 :
312 7 : WriteValue(10, poPoint->getX());
313 7 : if (!WriteValue(20, poPoint->getY()))
314 0 : return OGRERR_FAILURE;
315 :
316 7 : if (poPoint->getGeometryType() == wkbPoint25D)
317 : {
318 0 : if (!WriteValue(30, poPoint->getZ()))
319 0 : return OGRERR_FAILURE;
320 : }
321 : }
322 :
323 : /* -------------------------------------------------------------------- */
324 : /* Write scaling. */
325 : /* -------------------------------------------------------------------- */
326 7 : int nScaleCount = 0;
327 : const double *padfScale =
328 7 : poFeature->GetFieldAsDoubleList("BlockScale", &nScaleCount);
329 :
330 7 : if (nScaleCount == 3)
331 : {
332 1 : WriteValue(41, padfScale[0]);
333 1 : WriteValue(42, padfScale[1]);
334 1 : WriteValue(43, padfScale[2]);
335 : }
336 :
337 : /* -------------------------------------------------------------------- */
338 : /* Write rotation. */
339 : /* -------------------------------------------------------------------- */
340 7 : const double dfAngle = poFeature->GetFieldAsDouble("BlockAngle");
341 :
342 7 : if (dfAngle != 0.0)
343 : {
344 1 : WriteValue(50, dfAngle); // degrees
345 : }
346 :
347 : /* -------------------------------------------------------------------- */
348 : /* Write OCS normal vector. */
349 : /* -------------------------------------------------------------------- */
350 7 : int nOCSCount = 0;
351 : const double *padfOCS =
352 7 : poFeature->GetFieldAsDoubleList("BlockOCSNormal", &nOCSCount);
353 :
354 7 : if (nOCSCount == 3)
355 : {
356 0 : WriteValue(210, padfOCS[0]);
357 0 : WriteValue(220, padfOCS[1]);
358 0 : WriteValue(230, padfOCS[2]);
359 : }
360 :
361 7 : return OGRERR_NONE;
362 : }
363 :
364 : /************************************************************************/
365 : /* WritePOINT() */
366 : /************************************************************************/
367 :
368 117 : OGRErr OGRDXFWriterLayer::WritePOINT(OGRFeature *poFeature)
369 :
370 : {
371 234 : CorePropertiesType oCoreProperties;
372 :
373 : // Write style pen color
374 117 : OGRStyleTool *poTool = nullptr;
375 234 : OGRStyleMgr oSM;
376 117 : if (poFeature->GetStyleString() != nullptr)
377 : {
378 0 : oSM.InitFromFeature(poFeature);
379 :
380 0 : if (oSM.GetPartCount() > 0)
381 0 : poTool = oSM.GetPart(0);
382 : }
383 117 : if (poTool && poTool->GetType() == OGRSTCPen)
384 : {
385 0 : OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
386 : GBool bDefault;
387 0 : const char *pszColor = poPen->Color(bDefault);
388 0 : if (pszColor && !bDefault)
389 0 : oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
390 : }
391 117 : delete poTool;
392 :
393 117 : WriteValue(0, "POINT");
394 117 : WriteCore(poFeature, oCoreProperties);
395 117 : WriteValue(100, "AcDbPoint");
396 :
397 117 : OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
398 :
399 117 : WriteValue(10, poPoint->getX());
400 117 : if (!WriteValue(20, poPoint->getY()))
401 0 : return OGRERR_FAILURE;
402 :
403 117 : if (poPoint->getGeometryType() == wkbPoint25D)
404 : {
405 6 : if (!WriteValue(30, poPoint->getZ()))
406 0 : return OGRERR_FAILURE;
407 : }
408 :
409 117 : return OGRERR_NONE;
410 : }
411 :
412 : /************************************************************************/
413 : /* TextEscape() */
414 : /* */
415 : /* Translate UTF8 to Win1252 and escape special characters like */
416 : /* newline and space with DXF style escapes. Note that */
417 : /* non-win1252 unicode characters are translated using the */
418 : /* unicode escape sequence. */
419 : /************************************************************************/
420 :
421 1 : CPLString OGRDXFWriterLayer::TextEscape(const char *pszInput)
422 :
423 : {
424 1 : CPLString osResult;
425 1 : wchar_t *panInput = CPLRecodeToWChar(pszInput, CPL_ENC_UTF8, CPL_ENC_UCS2);
426 31 : for (int i = 0; panInput[i] != 0; i++)
427 : {
428 30 : if (panInput[i] == '\n')
429 : {
430 0 : osResult += "\\P";
431 : }
432 30 : else if (panInput[i] == ' ')
433 : {
434 2 : osResult += "\\~";
435 : }
436 28 : else if (panInput[i] == '\\')
437 : {
438 0 : osResult += "\\\\";
439 : }
440 28 : else if (panInput[i] == '^')
441 : {
442 1 : osResult += "^ ";
443 : }
444 27 : else if (panInput[i] < ' ')
445 : {
446 1 : osResult += '^';
447 1 : osResult += static_cast<char>(panInput[i] + '@');
448 : }
449 26 : else if (panInput[i] > 255)
450 : {
451 0 : CPLString osUnicode;
452 0 : osUnicode.Printf("\\U+%04x", (int)panInput[i]);
453 0 : osResult += osUnicode;
454 : }
455 : else
456 : {
457 26 : osResult += (char)panInput[i];
458 : }
459 : }
460 :
461 1 : CPLFree(panInput);
462 :
463 1 : return osResult;
464 : }
465 :
466 : /************************************************************************/
467 : /* PrepareTextStyleDefinition() */
468 : /************************************************************************/
469 : std::map<CPLString, CPLString>
470 1 : OGRDXFWriterLayer::PrepareTextStyleDefinition(OGRStyleLabel *poLabelTool)
471 : {
472 : GBool bDefault;
473 :
474 1 : std::map<CPLString, CPLString> oTextStyleDef;
475 :
476 : /* -------------------------------------------------------------------- */
477 : /* Fetch the data for this text style. */
478 : /* -------------------------------------------------------------------- */
479 1 : const char *pszFontName = poLabelTool->FontName(bDefault);
480 1 : if (!bDefault)
481 1 : oTextStyleDef["Font"] = pszFontName;
482 :
483 1 : const GBool bBold = poLabelTool->Bold(bDefault);
484 1 : if (!bDefault)
485 1 : oTextStyleDef["Bold"] = bBold ? "1" : "0";
486 :
487 1 : const GBool bItalic = poLabelTool->Italic(bDefault);
488 1 : if (!bDefault)
489 0 : oTextStyleDef["Italic"] = bItalic ? "1" : "0";
490 :
491 1 : const double dfStretch = poLabelTool->Stretch(bDefault);
492 1 : if (!bDefault)
493 : {
494 1 : oTextStyleDef["Width"] = CPLString().Printf("%f", dfStretch / 100.0);
495 : }
496 :
497 2 : return oTextStyleDef;
498 : }
499 :
500 : /************************************************************************/
501 : /* WriteTEXT() */
502 : /************************************************************************/
503 :
504 1 : OGRErr OGRDXFWriterLayer::WriteTEXT(OGRFeature *poFeature)
505 :
506 : {
507 2 : CorePropertiesType oCoreProperties;
508 :
509 : /* -------------------------------------------------------------------- */
510 : /* Do we have styling information? */
511 : /* -------------------------------------------------------------------- */
512 1 : OGRStyleTool *poTool = nullptr;
513 2 : OGRStyleMgr oSM;
514 :
515 1 : if (poFeature->GetStyleString() != nullptr)
516 : {
517 1 : oSM.InitFromFeature(poFeature);
518 :
519 1 : if (oSM.GetPartCount() > 0)
520 1 : poTool = oSM.GetPart(0);
521 : }
522 :
523 1 : if (poTool && poTool->GetType() == OGRSTCLabel)
524 : {
525 1 : OGRStyleLabel *poLabel = cpl::down_cast<OGRStyleLabel *>(poTool);
526 : GBool bDefault;
527 1 : const char *pszColor = poLabel->ForeColor(bDefault);
528 1 : if (pszColor && !bDefault)
529 1 : oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
530 : }
531 :
532 1 : WriteValue(0, "MTEXT");
533 1 : WriteCore(poFeature, oCoreProperties);
534 1 : WriteValue(100, "AcDbMText");
535 :
536 : /* ==================================================================== */
537 : /* Process the LABEL tool. */
538 : /* ==================================================================== */
539 1 : double dfDx = 0.0;
540 1 : double dfDy = 0.0;
541 :
542 1 : if (poTool && poTool->GetType() == OGRSTCLabel)
543 : {
544 1 : OGRStyleLabel *poLabel = cpl::down_cast<OGRStyleLabel *>(poTool);
545 : GBool bDefault;
546 :
547 : /* --------------------------------------------------------------------
548 : */
549 : /* Angle */
550 : /* --------------------------------------------------------------------
551 : */
552 1 : const double dfAngle = poLabel->Angle(bDefault);
553 :
554 1 : if (!bDefault)
555 1 : WriteValue(50, dfAngle);
556 :
557 : /* --------------------------------------------------------------------
558 : */
559 : /* Height - We need to fetch this in georeferenced units - I'm */
560 : /* doubt the default translation mechanism will be much good. */
561 : /* --------------------------------------------------------------------
562 : */
563 1 : poTool->SetUnit(OGRSTUGround);
564 1 : const double dfHeight = poLabel->Size(bDefault);
565 :
566 1 : if (!bDefault)
567 1 : WriteValue(40, dfHeight);
568 :
569 : /* --------------------------------------------------------------------
570 : */
571 : /* Anchor / Attachment Point */
572 : /* --------------------------------------------------------------------
573 : */
574 1 : const int nAnchor = poLabel->Anchor(bDefault);
575 :
576 1 : if (!bDefault)
577 : {
578 : const static int anAnchorMap[] = {-1, 7, 8, 9, 4, 5, 6,
579 : 1, 2, 3, 7, 8, 9};
580 :
581 0 : if (nAnchor > 0 && nAnchor < 13)
582 0 : WriteValue(71, anAnchorMap[nAnchor]);
583 : }
584 :
585 : /* --------------------------------------------------------------------
586 : */
587 : /* Offset */
588 : /* --------------------------------------------------------------------
589 : */
590 1 : dfDx = poLabel->SpacingX(bDefault);
591 1 : dfDy = poLabel->SpacingY(bDefault);
592 :
593 : /* --------------------------------------------------------------------
594 : */
595 : /* Escape the text, and convert to ISO8859. */
596 : /* --------------------------------------------------------------------
597 : */
598 1 : const char *pszText = poLabel->TextString(bDefault);
599 :
600 1 : if (pszText != nullptr && !bDefault)
601 : {
602 2 : CPLString osEscaped = TextEscape(pszText);
603 1 : while (osEscaped.size() > 250)
604 : {
605 0 : WriteValue(3, osEscaped.substr(0, 250).c_str());
606 0 : osEscaped.erase(0, 250);
607 : }
608 1 : WriteValue(1, osEscaped);
609 : }
610 :
611 : /* --------------------------------------------------------------------
612 : */
613 : /* Store the text style in the map. */
614 : /* --------------------------------------------------------------------
615 : */
616 : std::map<CPLString, CPLString> oTextStyleDef =
617 2 : PrepareTextStyleDefinition(poLabel);
618 2 : CPLString osStyleName;
619 :
620 1 : for (const auto &oPair : oNewTextStyles)
621 : {
622 0 : if (oPair.second == oTextStyleDef)
623 : {
624 0 : osStyleName = oPair.first;
625 0 : break;
626 : }
627 : }
628 :
629 1 : if (osStyleName == "")
630 : {
631 :
632 0 : do
633 : {
634 1 : osStyleName.Printf("AutoTextStyle-%d", nNextAutoID++);
635 1 : } while (poDS->oHeaderDS.TextStyleExists(osStyleName));
636 :
637 1 : oNewTextStyles[osStyleName] = std::move(oTextStyleDef);
638 : }
639 :
640 1 : WriteValue(7, osStyleName);
641 : }
642 :
643 1 : delete poTool;
644 :
645 : /* -------------------------------------------------------------------- */
646 : /* Write the location. */
647 : /* -------------------------------------------------------------------- */
648 1 : OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
649 :
650 1 : WriteValue(10, poPoint->getX() + dfDx);
651 1 : if (!WriteValue(20, poPoint->getY() + dfDy))
652 0 : return OGRERR_FAILURE;
653 :
654 1 : if (poPoint->getGeometryType() == wkbPoint25D)
655 : {
656 1 : if (!WriteValue(30, poPoint->getZ()))
657 0 : return OGRERR_FAILURE;
658 : }
659 :
660 1 : return OGRERR_NONE;
661 : }
662 :
663 : /************************************************************************/
664 : /* PrepareLineTypeDefinition() */
665 : /************************************************************************/
666 : std::vector<double>
667 6 : OGRDXFWriterLayer::PrepareLineTypeDefinition(OGRStylePen *poPen)
668 : {
669 :
670 : /* -------------------------------------------------------------------- */
671 : /* Fetch pattern. */
672 : /* -------------------------------------------------------------------- */
673 : GBool bDefault;
674 6 : const char *pszPattern = poPen->Pattern(bDefault);
675 :
676 6 : if (bDefault || strlen(pszPattern) == 0)
677 1 : return std::vector<double>();
678 :
679 : /* -------------------------------------------------------------------- */
680 : /* Split into pen up / pen down bits. */
681 : /* -------------------------------------------------------------------- */
682 5 : char **papszTokens = CSLTokenizeString(pszPattern);
683 10 : std::vector<double> adfWeightTokens;
684 :
685 15 : for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr; i++)
686 : {
687 10 : const char *pszToken = papszTokens[i];
688 20 : CPLString osAmount;
689 20 : CPLString osDXFEntry;
690 :
691 : // Split amount and unit.
692 10 : const char *pszUnit = pszToken; // Used after for.
693 48 : for (; strchr("0123456789.", *pszUnit) != nullptr; pszUnit++)
694 : {
695 : }
696 :
697 10 : osAmount.assign(pszToken, (int)(pszUnit - pszToken));
698 :
699 : // If the unit is other than 'g' we really should be trying to
700 : // do some type of transformation - but what to do? Pretty hard.
701 :
702 : // Even entries are "pen down" represented as positive in DXF.
703 : // "Pen up" entries (gaps) are represented as negative.
704 10 : if (i % 2 == 0)
705 5 : adfWeightTokens.push_back(CPLAtof(osAmount));
706 : else
707 5 : adfWeightTokens.push_back(-CPLAtof(osAmount));
708 : }
709 :
710 5 : CSLDestroy(papszTokens);
711 :
712 5 : return adfWeightTokens;
713 : }
714 :
715 : /************************************************************************/
716 : /* IsLineTypeProportional() */
717 : /************************************************************************/
718 :
719 7 : static double IsLineTypeProportional(const std::vector<double> &adfA,
720 : const std::vector<double> &adfB)
721 : {
722 : // If they are not the same length, they are not the same linetype
723 7 : if (adfA.size() != adfB.size())
724 0 : return 0.0;
725 :
726 : // Determine the proportion of the first elements
727 7 : const double dfRatio = (adfA[0] != 0.0) ? (adfB[0] / adfA[0]) : 0.0;
728 :
729 : // Check if all elements follow this proportionality
730 9 : for (size_t iIndex = 1; iIndex < adfA.size(); iIndex++)
731 7 : if (fabs(adfB[iIndex] - (adfA[iIndex] * dfRatio)) > 1e-6)
732 5 : return 0.0;
733 :
734 2 : return dfRatio;
735 : }
736 :
737 : /************************************************************************/
738 : /* WritePOLYLINE() */
739 : /************************************************************************/
740 :
741 31 : OGRErr OGRDXFWriterLayer::WritePOLYLINE(OGRFeature *poFeature,
742 : const OGRGeometry *poGeom)
743 :
744 : {
745 : /* -------------------------------------------------------------------- */
746 : /* For now we handle multilinestrings by writing a series of */
747 : /* entities. */
748 : /* -------------------------------------------------------------------- */
749 31 : if (poGeom == nullptr)
750 27 : poGeom = poFeature->GetGeometryRef();
751 :
752 31 : if (poGeom->IsEmpty())
753 : {
754 0 : return OGRERR_NONE;
755 : }
756 :
757 62 : if (wkbFlatten(poGeom->getGeometryType()) == wkbMultiPolygon ||
758 31 : wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString)
759 : {
760 4 : const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
761 4 : OGRErr eErr = OGRERR_NONE;
762 8 : for (auto &&poMember : *poGC)
763 : {
764 4 : eErr = WritePOLYLINE(poFeature, poMember);
765 4 : if (eErr != OGRERR_NONE)
766 0 : break;
767 : }
768 :
769 4 : return eErr;
770 : }
771 :
772 : /* -------------------------------------------------------------------- */
773 : /* Polygons are written with on entity per ring. */
774 : /* -------------------------------------------------------------------- */
775 54 : if (wkbFlatten(poGeom->getGeometryType()) == wkbPolygon ||
776 27 : wkbFlatten(poGeom->getGeometryType()) == wkbTriangle)
777 : {
778 0 : const OGRPolygon *poPoly = poGeom->toPolygon();
779 0 : OGRErr eErr = OGRERR_NONE;
780 0 : for (auto &&poRing : *poPoly)
781 : {
782 0 : eErr = WritePOLYLINE(poFeature, poRing);
783 0 : if (eErr != OGRERR_NONE)
784 0 : break;
785 : }
786 :
787 0 : return eErr;
788 : }
789 :
790 : /* -------------------------------------------------------------------- */
791 : /* Do we now have a geometry we can work with? */
792 : /* -------------------------------------------------------------------- */
793 27 : if (wkbFlatten(poGeom->getGeometryType()) != wkbLineString)
794 0 : return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
795 :
796 27 : const OGRLineString *poLS = poGeom->toLineString();
797 :
798 : /* -------------------------------------------------------------------- */
799 : /* Write as a lightweight polygon, */
800 : /* or as POLYLINE if the line contains different heights */
801 : /* -------------------------------------------------------------------- */
802 27 : int bHasDifferentZ = FALSE;
803 27 : if (poLS->getGeometryType() == wkbLineString25D)
804 : {
805 9 : double z0 = poLS->getZ(0);
806 26 : for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
807 : {
808 18 : if (z0 != poLS->getZ(iVert))
809 : {
810 1 : bHasDifferentZ = TRUE;
811 1 : break;
812 : }
813 : }
814 : }
815 :
816 54 : CorePropertiesType oCoreProperties;
817 :
818 : /* -------------------------------------------------------------------- */
819 : /* Do we have styling information? */
820 : /* -------------------------------------------------------------------- */
821 27 : OGRStyleTool *poTool = nullptr;
822 54 : OGRStyleMgr oSM;
823 :
824 27 : if (poFeature->GetStyleString() != nullptr)
825 : {
826 6 : oSM.InitFromFeature(poFeature);
827 :
828 6 : if (oSM.GetPartCount() > 0)
829 6 : poTool = oSM.GetPart(0);
830 : }
831 :
832 : /* -------------------------------------------------------------------- */
833 : /* Handle a PEN tool to control drawing color and width. */
834 : /* -------------------------------------------------------------------- */
835 27 : if (poTool && poTool->GetType() == OGRSTCPen)
836 : {
837 6 : OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
838 : GBool bDefault;
839 6 : const char *pszColor = poPen->Color(bDefault);
840 6 : if (pszColor && !bDefault)
841 6 : oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
842 : }
843 :
844 27 : WriteValue(0, bHasDifferentZ ? "POLYLINE" : "LWPOLYLINE");
845 27 : WriteCore(poFeature, oCoreProperties);
846 27 : if (bHasDifferentZ)
847 : {
848 1 : WriteValue(100, "AcDb3dPolyline");
849 1 : WriteValue(10, 0.0);
850 1 : WriteValue(20, 0.0);
851 1 : WriteValue(30, 0.0);
852 : }
853 : else
854 26 : WriteValue(100, "AcDbPolyline");
855 27 : if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
856 0 : WriteValue(70, 1 + (bHasDifferentZ ? 8 : 0));
857 : else
858 27 : WriteValue(70, 0 + (bHasDifferentZ ? 8 : 0));
859 27 : if (!bHasDifferentZ)
860 26 : WriteValue(90, poLS->getNumPoints());
861 : else
862 1 : WriteValue(66, "1"); // Vertex Flag
863 :
864 : /* -------------------------------------------------------------------- */
865 : /* Handle a PEN tool to control drawing color and width. */
866 : /* Perhaps one day also dottedness, etc. */
867 : /* -------------------------------------------------------------------- */
868 27 : if (poTool && poTool->GetType() == OGRSTCPen)
869 : {
870 6 : OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
871 : GBool bDefault;
872 :
873 : // we want to fetch the width in ground units.
874 6 : poPen->SetUnit(OGRSTUGround, 1.0);
875 6 : const double dfWidth = poPen->Width(bDefault);
876 :
877 6 : if (!bDefault)
878 5 : WriteValue(370, (int)floor(dfWidth * 100 + 0.5));
879 : }
880 :
881 : /* -------------------------------------------------------------------- */
882 : /* Do we have a Linetype for the feature? */
883 : /* -------------------------------------------------------------------- */
884 54 : CPLString osLineType = poFeature->GetFieldAsString("Linetype");
885 27 : double dfLineTypeScale = 0.0;
886 :
887 27 : bool bGotLinetype = false;
888 :
889 27 : if (!osLineType.empty())
890 : {
891 : std::vector<double> adfLineType =
892 4 : poDS->oHeaderDS.LookupLineType(osLineType);
893 :
894 2 : if (adfLineType.empty() && oNewLineTypes.count(osLineType) > 0)
895 0 : adfLineType = oNewLineTypes[osLineType];
896 :
897 2 : if (!adfLineType.empty())
898 : {
899 1 : bGotLinetype = true;
900 1 : WriteValue(6, osLineType);
901 :
902 : // If the given linetype is proportional to the linetype data
903 : // in the style string, then apply a linetype scale
904 1 : if (poTool != nullptr && poTool->GetType() == OGRSTCPen)
905 : {
906 : std::vector<double> adfDefinition = PrepareLineTypeDefinition(
907 2 : static_cast<OGRStylePen *>(poTool));
908 :
909 1 : if (!adfDefinition.empty())
910 : {
911 : dfLineTypeScale =
912 1 : IsLineTypeProportional(adfLineType, adfDefinition);
913 :
914 1 : if (dfLineTypeScale != 0.0 &&
915 0 : fabs(dfLineTypeScale - 1.0) > 1e-4)
916 : {
917 0 : WriteValue(48, dfLineTypeScale);
918 : }
919 : }
920 : }
921 : }
922 : }
923 :
924 27 : if (!bGotLinetype && poTool != nullptr && poTool->GetType() == OGRSTCPen)
925 : {
926 : std::vector<double> adfDefinition =
927 10 : PrepareLineTypeDefinition(static_cast<OGRStylePen *>(poTool));
928 :
929 5 : if (!adfDefinition.empty())
930 : {
931 : // Is this definition already created and named?
932 7 : for (const auto &oPair : poDS->oHeaderDS.GetLineTypeTable())
933 : {
934 : dfLineTypeScale =
935 4 : IsLineTypeProportional(oPair.second, adfDefinition);
936 4 : if (dfLineTypeScale != 0.0)
937 : {
938 1 : osLineType = oPair.first;
939 1 : break;
940 : }
941 : }
942 :
943 4 : if (dfLineTypeScale == 0.0)
944 : {
945 4 : for (const auto &oPair : oNewLineTypes)
946 : {
947 : dfLineTypeScale =
948 2 : IsLineTypeProportional(oPair.second, adfDefinition);
949 2 : if (dfLineTypeScale != 0.0)
950 : {
951 1 : osLineType = oPair.first;
952 1 : break;
953 : }
954 : }
955 : }
956 :
957 : // If not, create an automatic name for it.
958 4 : if (osLineType == "")
959 : {
960 1 : dfLineTypeScale = 1.0;
961 0 : do
962 : {
963 1 : osLineType.Printf("AutoLineType-%d", nNextAutoID++);
964 1 : } while (poDS->oHeaderDS.LookupLineType(osLineType).size() > 0);
965 : }
966 :
967 : // If it isn't already defined, add it now.
968 7 : if (poDS->oHeaderDS.LookupLineType(osLineType).empty() &&
969 3 : oNewLineTypes.count(osLineType) == 0)
970 : {
971 2 : oNewLineTypes[osLineType] = std::move(adfDefinition);
972 : }
973 :
974 4 : WriteValue(6, osLineType);
975 :
976 4 : if (dfLineTypeScale != 0.0 && fabs(dfLineTypeScale - 1.0) > 1e-4)
977 : {
978 2 : WriteValue(48, dfLineTypeScale);
979 : }
980 : }
981 : }
982 :
983 : /* -------------------------------------------------------------------- */
984 : /* Write the vertices */
985 : /* -------------------------------------------------------------------- */
986 :
987 27 : if (!bHasDifferentZ && poLS->getGeometryType() == wkbLineString25D)
988 : {
989 : // if LWPOLYLINE with Z write it only once
990 8 : if (!WriteValue(38, poLS->getZ(0)))
991 0 : return OGRERR_FAILURE;
992 : }
993 :
994 81 : for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
995 : {
996 54 : if (bHasDifferentZ)
997 : {
998 2 : WriteValue(0, "VERTEX");
999 2 : WriteCore(poFeature, CorePropertiesType());
1000 2 : WriteValue(100, "AcDbVertex");
1001 2 : WriteValue(100, "AcDb3dPolylineVertex");
1002 : }
1003 54 : WriteValue(10, poLS->getX(iVert));
1004 54 : if (!WriteValue(20, poLS->getY(iVert)))
1005 0 : return OGRERR_FAILURE;
1006 :
1007 54 : if (bHasDifferentZ)
1008 : {
1009 2 : if (!WriteValue(30, poLS->getZ(iVert)))
1010 0 : return OGRERR_FAILURE;
1011 2 : WriteValue(70, 32);
1012 : }
1013 : }
1014 :
1015 27 : if (bHasDifferentZ)
1016 : {
1017 1 : WriteValue(0, "SEQEND");
1018 1 : WriteCore(poFeature, CorePropertiesType());
1019 : }
1020 :
1021 27 : delete poTool;
1022 :
1023 27 : return OGRERR_NONE;
1024 :
1025 : #ifdef notdef
1026 : /* -------------------------------------------------------------------- */
1027 : /* Alternate unmaintained implementation as a polyline entity. */
1028 : /* -------------------------------------------------------------------- */
1029 : WriteValue(0, "POLYLINE");
1030 : WriteCore(poFeature);
1031 : WriteValue(100, "AcDbPolyline");
1032 : if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
1033 : WriteValue(70, 1);
1034 : else
1035 : WriteValue(70, 0);
1036 : WriteValue(66, "1");
1037 :
1038 : for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
1039 : {
1040 : WriteValue(0, "VERTEX");
1041 : WriteValue(8, "0");
1042 : WriteValue(10, poLS->getX(iVert));
1043 : if (!WriteValue(20, poLS->getY(iVert)))
1044 : return OGRERR_FAILURE;
1045 :
1046 : if (poLS->getGeometryType() == wkbLineString25D)
1047 : {
1048 : if (!WriteValue(30, poLS->getZ(iVert)))
1049 : return OGRERR_FAILURE;
1050 : }
1051 : }
1052 :
1053 : WriteValue(0, "SEQEND");
1054 : WriteValue(8, "0");
1055 :
1056 : return OGRERR_NONE;
1057 : #endif
1058 : }
1059 :
1060 : /************************************************************************/
1061 : /* WriteHATCH() */
1062 : /************************************************************************/
1063 :
1064 32 : OGRErr OGRDXFWriterLayer::WriteHATCH(OGRFeature *poFeature, OGRGeometry *poGeom)
1065 :
1066 : {
1067 : /* -------------------------------------------------------------------- */
1068 : /* For now we handle multipolygons by writing a series of */
1069 : /* entities. */
1070 : /* -------------------------------------------------------------------- */
1071 32 : if (poGeom == nullptr)
1072 28 : poGeom = poFeature->GetGeometryRef();
1073 :
1074 32 : if (poGeom->IsEmpty())
1075 : {
1076 0 : return OGRERR_NONE;
1077 : }
1078 :
1079 32 : if (wkbFlatten(poGeom->getGeometryType()) == wkbMultiPolygon)
1080 : {
1081 4 : OGRErr eErr = OGRERR_NONE;
1082 8 : for (auto &&poMember : poGeom->toMultiPolygon())
1083 : {
1084 4 : eErr = WriteHATCH(poFeature, poMember);
1085 4 : if (eErr != OGRERR_NONE)
1086 0 : break;
1087 : }
1088 :
1089 4 : return eErr;
1090 : }
1091 :
1092 : /* -------------------------------------------------------------------- */
1093 : /* Do we now have a geometry we can work with? */
1094 : /* -------------------------------------------------------------------- */
1095 29 : if (wkbFlatten(poGeom->getGeometryType()) != wkbPolygon &&
1096 1 : wkbFlatten(poGeom->getGeometryType()) != wkbTriangle)
1097 : {
1098 0 : return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
1099 : }
1100 :
1101 56 : CorePropertiesType oCoreProperties;
1102 :
1103 : /* -------------------------------------------------------------------- */
1104 : /* Do we have styling information? */
1105 : /* -------------------------------------------------------------------- */
1106 28 : OGRStyleTool *poTool = nullptr;
1107 56 : OGRStyleMgr oSM;
1108 :
1109 28 : if (poFeature->GetStyleString() != nullptr)
1110 : {
1111 10 : oSM.InitFromFeature(poFeature);
1112 :
1113 10 : if (oSM.GetPartCount() > 0)
1114 10 : poTool = oSM.GetPart(0);
1115 : }
1116 : // Write style brush fore color
1117 56 : std::string osBrushId;
1118 28 : std::string osBackgroundColor;
1119 28 : double dfSize = 1.0;
1120 28 : double dfAngle = 0.0;
1121 28 : if (poTool && poTool->GetType() == OGRSTCBrush)
1122 : {
1123 10 : OGRStyleBrush *poBrush = cpl::down_cast<OGRStyleBrush *>(poTool);
1124 : GBool bDefault;
1125 :
1126 10 : const char *pszBrushId = poBrush->Id(bDefault);
1127 10 : if (pszBrushId && !bDefault)
1128 9 : osBrushId = pszBrushId;
1129 :
1130 : // null brush (transparent - no fill, irrespective of fc or bc values
1131 10 : if (osBrushId == "ogr-brush-1")
1132 : {
1133 1 : oCoreProperties.emplace_back(PROP_RGBA_COLOR, "#00000000");
1134 : }
1135 : else
1136 : {
1137 9 : const char *pszColor = poBrush->ForeColor(bDefault);
1138 9 : if (pszColor != nullptr && !bDefault)
1139 9 : oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
1140 :
1141 9 : const char *pszBGColor = poBrush->BackColor(bDefault);
1142 9 : if (pszBGColor != nullptr && !bDefault)
1143 8 : osBackgroundColor = pszBGColor;
1144 : }
1145 :
1146 10 : double dfStyleSize = poBrush->Size(bDefault);
1147 10 : if (!bDefault)
1148 1 : dfSize = dfStyleSize;
1149 :
1150 10 : double dfStyleAngle = poBrush->Angle(bDefault);
1151 10 : if (!bDefault)
1152 1 : dfAngle = dfStyleAngle;
1153 : }
1154 28 : delete poTool;
1155 :
1156 : /* -------------------------------------------------------------------- */
1157 : /* Write as a hatch. */
1158 : /* -------------------------------------------------------------------- */
1159 28 : WriteValue(0, "HATCH");
1160 28 : WriteCore(poFeature, oCoreProperties);
1161 28 : WriteValue(100, "AcDbHatch");
1162 :
1163 : // Figure out "average" elevation
1164 28 : OGREnvelope3D oEnv;
1165 28 : poGeom->getEnvelope(&oEnv);
1166 28 : WriteValue(10, 0); // elevation point X = 0
1167 28 : WriteValue(20, 0); // elevation point Y = 0
1168 : // elevation point Z = constant elevation
1169 28 : WriteValue(30, oEnv.MinZ + (oEnv.MaxZ - oEnv.MinZ) / 2);
1170 :
1171 28 : WriteValue(210, 0); // extrusion direction X
1172 28 : WriteValue(220, 0); // extrusion direction Y
1173 28 : WriteValue(230, 1.0); // extrusion direction Z
1174 :
1175 28 : const char *pszPatternName = "SOLID";
1176 28 : double dfPatternRotation = 0;
1177 :
1178 : // Cf https://ezdxf.readthedocs.io/en/stable/tutorials/hatch.html#predefined-hatch-pattern
1179 : // for DXF standard hatch pattern names
1180 :
1181 28 : if (osBrushId.empty() || osBrushId == "ogr-brush-0")
1182 : {
1183 : // solid fill pattern
1184 : }
1185 9 : else if (osBrushId == "ogr-brush-2")
1186 : {
1187 : // horizontal line.
1188 1 : pszPatternName = "ANSI31";
1189 1 : dfPatternRotation = -45;
1190 : }
1191 8 : else if (osBrushId == "ogr-brush-3")
1192 : {
1193 : // vertical line.
1194 1 : pszPatternName = "ANSI31";
1195 1 : dfPatternRotation = 45;
1196 : }
1197 7 : else if (osBrushId == "ogr-brush-4")
1198 : {
1199 : // top-left to bottom-right diagonal hatch.
1200 1 : pszPatternName = "ANSI31";
1201 1 : dfPatternRotation = 90;
1202 : }
1203 6 : else if (osBrushId == "ogr-brush-5")
1204 : {
1205 : // bottom-left to top-right diagonal hatch
1206 1 : pszPatternName = "ANSI31";
1207 1 : dfPatternRotation = 0;
1208 : }
1209 5 : else if (osBrushId == "ogr-brush-6")
1210 : {
1211 : // cross hatch
1212 1 : pszPatternName = "ANSI37";
1213 1 : dfPatternRotation = 45;
1214 : }
1215 4 : else if (osBrushId == "ogr-brush-7")
1216 : {
1217 : // diagonal cross hatch
1218 3 : pszPatternName = "ANSI37";
1219 3 : dfPatternRotation = 0;
1220 : }
1221 : else
1222 : {
1223 : // solid fill pattern as a fallback
1224 : }
1225 :
1226 28 : dfPatternRotation += dfAngle;
1227 :
1228 28 : WriteValue(2, pszPatternName);
1229 :
1230 28 : if (EQUAL(pszPatternName, "ANSI31") || EQUAL(pszPatternName, "ANSI37"))
1231 : {
1232 8 : WriteValue(70, 0); // pattern fill
1233 : }
1234 : else
1235 : {
1236 20 : WriteValue(70, 1); // solid fill
1237 : }
1238 28 : WriteValue(71, 0); // associativity
1239 :
1240 : /* -------------------------------------------------------------------- */
1241 : /* Handle a PEN tool to control drawing color and width. */
1242 : /* Perhaps one day also dottedness, etc. */
1243 : /* -------------------------------------------------------------------- */
1244 : #ifdef notdef
1245 : if (poTool && poTool->GetType() == OGRSTCPen)
1246 : {
1247 : OGRStylePen *poPen = (OGRStylePen *)poTool;
1248 : GBool bDefault;
1249 :
1250 : if (poPen->Color(bDefault) != NULL && !bDefault)
1251 : WriteValue(62, ColorStringToDXFColor(poPen->Color(bDefault)));
1252 :
1253 : double dfWidthInMM = poPen->Width(bDefault);
1254 :
1255 : if (!bDefault)
1256 : WriteValue(370, (int)floor(dfWidthInMM * 100 + 0.5));
1257 : }
1258 :
1259 : /* -------------------------------------------------------------------- */
1260 : /* Do we have a Linetype for the feature? */
1261 : /* -------------------------------------------------------------------- */
1262 : CPLString osLineType = poFeature->GetFieldAsString("Linetype");
1263 :
1264 : if (!osLineType.empty() &&
1265 : (poDS->oHeaderDS.LookupLineType(osLineType) != NULL ||
1266 : oNewLineTypes.count(osLineType) > 0))
1267 : {
1268 : // Already define -> just reference it.
1269 : WriteValue(6, osLineType);
1270 : }
1271 : else if (poTool != NULL && poTool->GetType() == OGRSTCPen)
1272 : {
1273 : CPLString osDefinition = PrepareLineTypeDefinition(poFeature, poTool);
1274 :
1275 : if (osDefinition != "" && osLineType == "")
1276 : {
1277 : // Is this definition already created and named?
1278 : std::map<CPLString, CPLString>::iterator it;
1279 :
1280 : for (it = oNewLineTypes.begin(); it != oNewLineTypes.end(); it++)
1281 : {
1282 : if ((*it).second == osDefinition)
1283 : {
1284 : osLineType = (*it).first;
1285 : break;
1286 : }
1287 : }
1288 :
1289 : // create an automatic name for it.
1290 : if (osLineType == "")
1291 : {
1292 : do
1293 : {
1294 : osLineType.Printf("AutoLineType-%d", nNextAutoID++);
1295 : } while (poDS->oHeaderDS.LookupLineType(osLineType) != NULL);
1296 : }
1297 : }
1298 :
1299 : // If it isn't already defined, add it now.
1300 : if (osDefinition != "" && oNewLineTypes.count(osLineType) == 0)
1301 : {
1302 : oNewLineTypes[osLineType] = osDefinition;
1303 : WriteValue(6, osLineType);
1304 : }
1305 : }
1306 : delete poTool;
1307 : #endif
1308 :
1309 : /* -------------------------------------------------------------------- */
1310 : /* Process the loops (rings). */
1311 : /* -------------------------------------------------------------------- */
1312 28 : const OGRPolygon *poPoly = poGeom->toPolygon();
1313 :
1314 28 : WriteValue(91, poPoly->getNumInteriorRings() + 1);
1315 :
1316 57 : for (auto &&poLR : *poPoly)
1317 : {
1318 29 : WriteValue(92, 2); // Polyline
1319 29 : WriteValue(72, 0); // has bulge
1320 29 : WriteValue(73, 1); // is closed
1321 29 : WriteValue(93, poLR->getNumPoints());
1322 :
1323 170 : for (int iVert = 0; iVert < poLR->getNumPoints(); iVert++)
1324 : {
1325 141 : WriteValue(10, poLR->getX(iVert));
1326 141 : WriteValue(20, poLR->getY(iVert));
1327 : }
1328 :
1329 29 : WriteValue(97, 0); // 0 source boundary objects
1330 : }
1331 :
1332 28 : WriteValue(75, 0); // hatch style = Hatch "odd parity" area (Normal style)
1333 28 : WriteValue(76, 1); // hatch pattern type = predefined
1334 :
1335 24 : const auto roundIfClose = [](double x)
1336 : {
1337 24 : if (std::fabs(x - std::round(x)) < 1e-12)
1338 8 : x = std::round(x);
1339 24 : return x == 0 ? 0 : x; // make sure we return positive zero
1340 : };
1341 :
1342 28 : if (EQUAL(pszPatternName, "ANSI31"))
1343 : {
1344 : // Single line. With dfPatternRotation=0, this is a bottom-left to top-right diagonal hatch
1345 :
1346 4 : WriteValue(52, dfPatternRotation); // Hatch pattern angle
1347 4 : WriteValue(41, dfSize); // Hatch pattern scale or spacing
1348 4 : WriteValue(77, 0); // Hatch pattern double flag : 0 = not double
1349 :
1350 4 : WriteValue(78, 1); // Number of pattern definition lines
1351 :
1352 4 : const double angle = dfPatternRotation + 45.0;
1353 4 : WriteValue(53, angle); // Pattern line angle
1354 4 : WriteValue(43, 0.0); // Pattern line base point, X component
1355 4 : WriteValue(44, 0.0); // Pattern line base point, Y component
1356 4 : WriteValue(45, dfSize * 3.175 *
1357 4 : roundIfClose(
1358 4 : cos((angle + 90.0) / 180 *
1359 : M_PI))); // Pattern line offset, X component
1360 4 : WriteValue(46, dfSize * 3.175 *
1361 4 : roundIfClose(
1362 4 : sin((angle + 90.0) / 180 *
1363 : M_PI))); // Pattern line offset, Y component
1364 4 : WriteValue(79, 0); // Number of dash items
1365 : }
1366 24 : else if (EQUAL(pszPatternName, "ANSI37"))
1367 : {
1368 : // cross hatch. With dfPatternRotation=0, lines are diagonals
1369 :
1370 4 : WriteValue(52, dfPatternRotation); // Hatch pattern angle
1371 4 : WriteValue(41, dfSize); // Hatch pattern scale or spacing
1372 4 : WriteValue(77, 0); // Hatch pattern double flag : 0 = not double
1373 :
1374 4 : WriteValue(78, 2); // Number of pattern definition lines
1375 :
1376 4 : const double angle1 = dfPatternRotation + 45;
1377 4 : WriteValue(53, angle1); // Pattern line angle
1378 4 : WriteValue(43, 0.0); // Pattern line base point, X component
1379 4 : WriteValue(44, 0.0); // Pattern line base point, Y component
1380 4 : WriteValue(45, dfSize * 3.175 *
1381 4 : roundIfClose(
1382 4 : cos((angle1 + 90.0) / 180 *
1383 : M_PI))); // Pattern line offset, X component
1384 4 : WriteValue(46, dfSize * 3.175 *
1385 4 : roundIfClose(
1386 4 : sin((angle1 + 90.0) / 180 *
1387 : M_PI))); // Pattern line offset, Y component
1388 4 : WriteValue(79, 0); // Number of dash items
1389 :
1390 4 : const double angle2 = dfPatternRotation + 135;
1391 4 : WriteValue(53, angle2); // Pattern line angle
1392 4 : WriteValue(43, 0.0); // Pattern line base point, X component
1393 4 : WriteValue(44, 0.0); // Pattern line base point, Y component
1394 4 : WriteValue(45, dfSize * 3.175 *
1395 4 : roundIfClose(
1396 4 : cos((angle2 + 90.0) / 180 *
1397 : M_PI))); // Pattern line offset, X component
1398 4 : WriteValue(46, dfSize * 3.175 *
1399 4 : roundIfClose(
1400 4 : sin((angle2 + 90.0) / 180 *
1401 : M_PI))); // Pattern line offset, Y component
1402 4 : WriteValue(79, 0); // Number of dash items
1403 : }
1404 :
1405 28 : WriteValue(98, 0); // 0 seed points
1406 :
1407 : // Deal with brush background color
1408 28 : if (!osBackgroundColor.empty())
1409 : {
1410 8 : bool bPerfectMatch = false;
1411 : int nColor =
1412 8 : ColorStringToDXFColor(osBackgroundColor.c_str(), bPerfectMatch);
1413 8 : if (nColor >= 0)
1414 : {
1415 8 : WriteValue(1001, "HATCHBACKGROUNDCOLOR");
1416 8 : if (bPerfectMatch)
1417 : {
1418 : // C3 is top 8 bit means an indexed color
1419 1 : unsigned nRGBColorUnsigned =
1420 1 : (static_cast<unsigned>(0xC3) << 24) |
1421 1 : ((nColor & 0xff) << 0);
1422 : // Convert to signed (negative) value
1423 : int nRGBColorSigned;
1424 1 : memcpy(&nRGBColorSigned, &nRGBColorUnsigned,
1425 : sizeof(nRGBColorSigned));
1426 1 : WriteValue(1071, nRGBColorSigned);
1427 : }
1428 : else
1429 : {
1430 7 : unsigned int nRed = 0;
1431 7 : unsigned int nGreen = 0;
1432 7 : unsigned int nBlue = 0;
1433 7 : unsigned int nOpacity = 255;
1434 :
1435 : const int nCount =
1436 7 : sscanf(osBackgroundColor.c_str(), "#%2x%2x%2x%2x", &nRed,
1437 : &nGreen, &nBlue, &nOpacity);
1438 7 : if (nCount >= 3)
1439 : {
1440 : // C2 is top 8 bit means a true color
1441 7 : unsigned nRGBColorUnsigned =
1442 : (static_cast<unsigned>(0xC2) << 24) |
1443 7 : ((nRed & 0xff) << 16) | ((nGreen & 0xff) << 8) |
1444 7 : ((nBlue & 0xff) << 0);
1445 : // Convert to signed (negative) value
1446 : int nRGBColorSigned;
1447 7 : memcpy(&nRGBColorSigned, &nRGBColorUnsigned,
1448 : sizeof(nRGBColorSigned));
1449 7 : WriteValue(1071, nRGBColorSigned);
1450 : }
1451 : }
1452 : }
1453 : }
1454 :
1455 28 : return OGRERR_NONE;
1456 :
1457 : #ifdef notdef
1458 : /* -------------------------------------------------------------------- */
1459 : /* Alternate unmaintained implementation as a polyline entity. */
1460 : /* -------------------------------------------------------------------- */
1461 : WriteValue(0, "POLYLINE");
1462 : WriteCore(poFeature);
1463 : WriteValue(100, "AcDbPolyline");
1464 : if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
1465 : WriteValue(70, 1);
1466 : else
1467 : WriteValue(70, 0);
1468 : WriteValue(66, "1");
1469 :
1470 : for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
1471 : {
1472 : WriteValue(0, "VERTEX");
1473 : WriteValue(8, "0");
1474 : WriteValue(10, poLS->getX(iVert));
1475 : if (!WriteValue(20, poLS->getY(iVert)))
1476 : return OGRERR_FAILURE;
1477 :
1478 : if (poLS->getGeometryType() == wkbLineString25D)
1479 : {
1480 : if (!WriteValue(30, poLS->getZ(iVert)))
1481 : return OGRERR_FAILURE;
1482 : }
1483 : }
1484 :
1485 : WriteValue(0, "SEQEND");
1486 : WriteValue(8, "0");
1487 :
1488 : return OGRERR_NONE;
1489 : #endif
1490 : }
1491 :
1492 : /************************************************************************/
1493 : /* ICreateFeature() */
1494 : /************************************************************************/
1495 :
1496 227 : OGRErr OGRDXFWriterLayer::ICreateFeature(OGRFeature *poFeature)
1497 :
1498 : {
1499 227 : OGRGeometry *poGeom = poFeature->GetGeometryRef();
1500 227 : OGRwkbGeometryType eGType = wkbNone;
1501 :
1502 227 : if (poGeom != nullptr)
1503 : {
1504 194 : if (!poGeom->IsEmpty())
1505 : {
1506 192 : OGREnvelope sEnvelope;
1507 192 : poGeom->getEnvelope(&sEnvelope);
1508 192 : poDS->UpdateExtent(&sEnvelope);
1509 : }
1510 194 : eGType = wkbFlatten(poGeom->getGeometryType());
1511 : }
1512 :
1513 227 : if (eGType == wkbPoint)
1514 : {
1515 125 : const char *pszBlockName = poFeature->GetFieldAsString("BlockName");
1516 :
1517 : // We don't want to treat as a blocks ref if the block is not defined
1518 250 : if (pszBlockName &&
1519 125 : poDS->oHeaderDS.LookupBlock(pszBlockName) == nullptr)
1520 : {
1521 130 : if (poDS->poBlocksLayer == nullptr ||
1522 7 : poDS->poBlocksLayer->FindBlock(pszBlockName) == nullptr)
1523 118 : pszBlockName = nullptr;
1524 : }
1525 :
1526 125 : if (pszBlockName != nullptr)
1527 7 : return WriteINSERT(poFeature);
1528 :
1529 119 : else if (poFeature->GetStyleString() != nullptr &&
1530 1 : STARTS_WITH_CI(poFeature->GetStyleString(), "LABEL"))
1531 1 : return WriteTEXT(poFeature);
1532 : else
1533 117 : return WritePOINT(poFeature);
1534 : }
1535 102 : else if (eGType == wkbLineString || eGType == wkbMultiLineString)
1536 27 : return WritePOLYLINE(poFeature);
1537 :
1538 75 : else if (eGType == wkbPolygon || eGType == wkbTriangle ||
1539 : eGType == wkbMultiPolygon)
1540 : {
1541 28 : if (bWriteHatch)
1542 28 : return WriteHATCH(poFeature);
1543 : else
1544 0 : return WritePOLYLINE(poFeature);
1545 : }
1546 :
1547 : // Explode geometry collections into multiple entities.
1548 47 : else if (eGType == wkbGeometryCollection || eGType == wkbMultiPoint)
1549 : {
1550 : OGRGeometryCollection *poGC =
1551 13 : poFeature->StealGeometry()->toGeometryCollection();
1552 37 : for (auto &&poMember : poGC)
1553 : {
1554 25 : poFeature->SetGeometry(poMember);
1555 :
1556 25 : OGRErr eErr = CreateFeature(poFeature);
1557 :
1558 25 : if (eErr != OGRERR_NONE)
1559 : {
1560 1 : delete poGC;
1561 1 : return eErr;
1562 : }
1563 : }
1564 :
1565 12 : poFeature->SetGeometryDirectly(poGC);
1566 12 : return OGRERR_NONE;
1567 : }
1568 : else
1569 : {
1570 34 : CPLError(CE_Failure, CPLE_AppDefined,
1571 : "No known way to write feature with geometry '%s'.",
1572 : OGRGeometryTypeToName(eGType));
1573 34 : return OGRERR_FAILURE;
1574 : }
1575 : }
1576 :
1577 : /************************************************************************/
1578 : /* ColorStringToDXFColor() */
1579 : /************************************************************************/
1580 :
1581 25 : int OGRDXFWriterLayer::ColorStringToDXFColor(const char *pszRGB,
1582 : bool &bPerfectMatch)
1583 :
1584 : {
1585 25 : bPerfectMatch = false;
1586 :
1587 : /* -------------------------------------------------------------------- */
1588 : /* Parse the RGB string. */
1589 : /* -------------------------------------------------------------------- */
1590 25 : if (pszRGB == nullptr)
1591 0 : return -1;
1592 :
1593 25 : unsigned int nRed = 0;
1594 25 : unsigned int nGreen = 0;
1595 25 : unsigned int nBlue = 0;
1596 25 : unsigned int nOpacity = 255;
1597 :
1598 : const int nCount =
1599 25 : sscanf(pszRGB, "#%2x%2x%2x%2x", &nRed, &nGreen, &nBlue, &nOpacity);
1600 :
1601 25 : if (nCount < 3)
1602 0 : return -1;
1603 :
1604 : /* -------------------------------------------------------------------- */
1605 : /* Find near color in DXF palette. */
1606 : /* -------------------------------------------------------------------- */
1607 25 : const unsigned char *pabyDXFColors = ACGetColorTable();
1608 25 : int nMinDist = 768;
1609 25 : int nBestColor = -1;
1610 :
1611 3865 : for (int i = 1; i < 256; i++)
1612 : {
1613 3850 : const int nDist =
1614 3850 : std::abs(static_cast<int>(nRed) - pabyDXFColors[i * 3 + 0]) +
1615 3850 : std::abs(static_cast<int>(nGreen) - pabyDXFColors[i * 3 + 1]) +
1616 3850 : std::abs(static_cast<int>(nBlue) - pabyDXFColors[i * 3 + 2]);
1617 :
1618 3850 : if (nDist < nMinDist)
1619 : {
1620 205 : nBestColor = i;
1621 205 : nMinDist = nDist;
1622 205 : if (nMinDist == 0)
1623 : {
1624 10 : bPerfectMatch = true;
1625 10 : break;
1626 : }
1627 : }
1628 : }
1629 :
1630 25 : return nBestColor;
1631 : }
1632 :
1633 : /************************************************************************/
1634 : /* GetDataset() */
1635 : /************************************************************************/
1636 :
1637 17 : GDALDataset *OGRDXFWriterLayer::GetDataset()
1638 : {
1639 17 : return poDS;
1640 : }
|