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