1 /******************************************************************************
2  *
3  * Project:  OpenGIS Simple Features Reference Implementation
4  * Purpose:  Implements OGRDGNLayer class.
5  * Author:   Frank Warmerdam, warmerdam@pobox.com
6  *
7  ******************************************************************************
8  * Copyright (c) 2000, Frank Warmerdam (warmerdam@pobox.com)
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "ogr_dgn.h"
30 #include "cpl_conv.h"
31 #include "ogr_featurestyle.h"
32 #include "ogr_api.h"
33 
34 #include <algorithm>
35 #include <cmath>
36 #include <list>
37 
38 CPL_CVSID("$Id: ogrdgnlayer.cpp bed7caf904983fef4beb2a6ce67c26dcc84c9ed4 2021-03-11 11:28:21 +0100 Even Rouault $")
39 
40 /************************************************************************/
41 /*                           OGRDGNLayer()                              */
42 /************************************************************************/
43 
OGRDGNLayer(const char * pszName,DGNHandle hDGNIn,int bUpdateIn)44 OGRDGNLayer::OGRDGNLayer( const char * pszName, DGNHandle hDGNIn,
45                           int bUpdateIn ) :
46     poFeatureDefn(new OGRFeatureDefn( pszName )),
47     iNextShapeId(0),
48     hDGN(hDGNIn),
49     bUpdate(bUpdateIn)
50 {
51 
52 /* -------------------------------------------------------------------- */
53 /*      Work out what link format we are using.                         */
54 /* -------------------------------------------------------------------- */
55     OGRFieldType eLinkFieldType;
56 
57     pszLinkFormat = const_cast<char *>(
58         CPLGetConfigOption( "DGN_LINK_FORMAT", "FIRST" ) );
59 
60     if( EQUAL(pszLinkFormat,"FIRST") )
61         eLinkFieldType = OFTInteger;
62     else if( EQUAL(pszLinkFormat,"LIST") )
63         eLinkFieldType = OFTIntegerList;
64     else if( EQUAL(pszLinkFormat,"STRING") )
65         eLinkFieldType = OFTString;
66     else
67     {
68         CPLError( CE_Warning, CPLE_AppDefined,
69                   "DGN_LINK_FORMAT=%s, but only FIRST, LIST or STRING "
70                   "supported.",
71                   pszLinkFormat );
72         pszLinkFormat = const_cast<char *>( "FIRST" );
73         eLinkFieldType = OFTInteger;
74     }
75     pszLinkFormat = CPLStrdup(pszLinkFormat);
76 
77 /* -------------------------------------------------------------------- */
78 /*      Create the feature definition.                                  */
79 /* -------------------------------------------------------------------- */
80     SetDescription( poFeatureDefn->GetName() );
81     poFeatureDefn->Reference();
82 
83     OGRFieldDefn oField( "", OFTInteger );
84 
85 /* -------------------------------------------------------------------- */
86 /*      Element type                                                    */
87 /* -------------------------------------------------------------------- */
88     oField.SetName( "Type" );
89     oField.SetType( OFTInteger );
90     oField.SetWidth( 2 );
91     oField.SetPrecision( 0 );
92     poFeatureDefn->AddFieldDefn( &oField );
93 
94 /* -------------------------------------------------------------------- */
95 /*      Level number.                                                   */
96 /* -------------------------------------------------------------------- */
97     oField.SetName( "Level" );
98     oField.SetType( OFTInteger );
99     oField.SetWidth( 2 );
100     oField.SetPrecision( 0 );
101     poFeatureDefn->AddFieldDefn( &oField );
102 
103 /* -------------------------------------------------------------------- */
104 /*      graphic group                                                   */
105 /* -------------------------------------------------------------------- */
106     oField.SetName( "GraphicGroup" );
107     oField.SetType( OFTInteger );
108     oField.SetWidth( 4 );
109     oField.SetPrecision( 0 );
110     poFeatureDefn->AddFieldDefn( &oField );
111 
112 /* -------------------------------------------------------------------- */
113 /*      ColorIndex                                                      */
114 /* -------------------------------------------------------------------- */
115     oField.SetName( "ColorIndex" );
116     oField.SetType( OFTInteger );
117     oField.SetWidth( 3 );
118     oField.SetPrecision( 0 );
119     poFeatureDefn->AddFieldDefn( &oField );
120 
121 /* -------------------------------------------------------------------- */
122 /*      Weight                                                          */
123 /* -------------------------------------------------------------------- */
124     oField.SetName( "Weight" );
125     oField.SetType( OFTInteger );
126     oField.SetWidth( 2 );
127     oField.SetPrecision( 0 );
128     poFeatureDefn->AddFieldDefn( &oField );
129 
130 /* -------------------------------------------------------------------- */
131 /*      Style                                                           */
132 /* -------------------------------------------------------------------- */
133     oField.SetName( "Style" );
134     oField.SetType( OFTInteger );
135     oField.SetWidth( 1 );
136     oField.SetPrecision( 0 );
137     poFeatureDefn->AddFieldDefn( &oField );
138 
139 /* -------------------------------------------------------------------- */
140 /*      EntityNum                                                       */
141 /* -------------------------------------------------------------------- */
142     oField.SetName( "EntityNum" );
143     oField.SetType( eLinkFieldType );
144     oField.SetWidth( 0 );
145     oField.SetPrecision( 0 );
146     poFeatureDefn->AddFieldDefn( &oField );
147 
148 /* -------------------------------------------------------------------- */
149 /*      MSLink                                                          */
150 /* -------------------------------------------------------------------- */
151     oField.SetName( "MSLink" );
152     oField.SetType( eLinkFieldType );
153     oField.SetWidth( 0 );
154     oField.SetPrecision( 0 );
155     poFeatureDefn->AddFieldDefn( &oField );
156 
157 /* -------------------------------------------------------------------- */
158 /*      Text                                                            */
159 /* -------------------------------------------------------------------- */
160     oField.SetName( "Text" );
161     oField.SetType( OFTString );
162     oField.SetWidth( 0 );
163     oField.SetPrecision( 0 );
164     poFeatureDefn->AddFieldDefn( &oField );
165 
166 /* -------------------------------------------------------------------- */
167 /*      ULink                                                           */
168 /* -------------------------------------------------------------------- */
169     oField.SetName( "ULink" );
170     oField.SetType( OFTString );
171     oField.SetSubType( OFSTJSON );
172     oField.SetWidth( 0 );
173     oField.SetPrecision( 0 );
174     poFeatureDefn->AddFieldDefn( &oField );
175 
176 /* -------------------------------------------------------------------- */
177 /*      Create template feature for evaluating simple expressions.      */
178 /* -------------------------------------------------------------------- */
179     poEvalFeature = new OGRFeature( poFeatureDefn );
180 
181     /* TODO: I am intending to keep track of simple attribute queries (ones
182        using only FID, Type and Level and short circuiting their operation
183        based on the index.  However, there are some complexities with
184        complex elements, and spatial queries that have caused me to put it
185        off for now.
186     */
187 }
188 
189 /************************************************************************/
190 /*                           ~OGRDGNLayer()                             */
191 /************************************************************************/
192 
~OGRDGNLayer()193 OGRDGNLayer::~OGRDGNLayer()
194 
195 {
196     if( m_nFeaturesRead > 0 )
197     {
198         CPLDebug( "Mem", "%d features read on layer '%s'.",
199                   static_cast<int>( m_nFeaturesRead ),
200                   poFeatureDefn->GetName() );
201     }
202 
203     delete poEvalFeature;
204 
205     poFeatureDefn->Release();
206 
207     CPLFree( pszLinkFormat );
208 }
209 
210 /************************************************************************/
211 /*                          SetSpatialFilter()                          */
212 /************************************************************************/
213 
SetSpatialFilter(OGRGeometry * poGeomIn)214 void OGRDGNLayer::SetSpatialFilter( OGRGeometry * poGeomIn )
215 
216 {
217     if( !InstallFilter(poGeomIn) )
218         return;
219 
220     if( m_poFilterGeom != nullptr )
221     {
222         DGNSetSpatialFilter( hDGN,
223                              m_sFilterEnvelope.MinX,
224                              m_sFilterEnvelope.MinY,
225                              m_sFilterEnvelope.MaxX,
226                              m_sFilterEnvelope.MaxY );
227     }
228     else
229     {
230         DGNSetSpatialFilter( hDGN, 0.0, 0.0, 0.0, 0.0 );
231     }
232 
233     ResetReading();
234 }
235 
236 /************************************************************************/
237 /*                            ResetReading()                            */
238 /************************************************************************/
239 
ResetReading()240 void OGRDGNLayer::ResetReading()
241 
242 {
243     iNextShapeId = 0;
244     DGNRewind( hDGN );
245 }
246 
247 /************************************************************************/
248 /*                             GetFeature()                             */
249 /************************************************************************/
250 
GetFeature(GIntBig nFeatureId)251 OGRFeature *OGRDGNLayer::GetFeature( GIntBig nFeatureId )
252 
253 {
254     if( nFeatureId > INT_MAX || !DGNGotoElement( hDGN, (int)nFeatureId ) )
255         return nullptr;
256 
257     // We should likely clear the spatial search region as it affects
258     // DGNReadElement(), but I will defer that for now.
259 
260     DGNElemCore *psElement = DGNReadElement( hDGN );
261     OGRFeature *poFeature = ElementToFeature( psElement, 0 );
262     DGNFreeElement( hDGN, psElement );
263 
264     if( poFeature == nullptr )
265         return nullptr;
266 
267     if( poFeature->GetFID() != nFeatureId )
268     {
269         delete poFeature;
270         return nullptr;
271     }
272 
273     return poFeature;
274 }
275 
276 /************************************************************************/
277 /*                           ConsiderBrush()                            */
278 /*                                                                      */
279 /*      Method to set the style for a polygon, including a brush if     */
280 /*      appropriate.                                                    */
281 /************************************************************************/
282 
ConsiderBrush(DGNElemCore * psElement,const char * pszPen,OGRFeature * poFeature)283 void OGRDGNLayer::ConsiderBrush( DGNElemCore *psElement, const char *pszPen,
284                                  OGRFeature *poFeature )
285 
286 {
287     int nFillColor = 0;
288     int gv_red = 0;
289     int gv_green = 0;
290     int gv_blue = 0;
291 
292     if( DGNGetShapeFillInfo( hDGN, psElement, &nFillColor )
293         && DGNLookupColor( hDGN, nFillColor,
294                            &gv_red, &gv_green, &gv_blue ) )
295     {
296         char szFullStyle[256];
297         snprintf( szFullStyle, sizeof(szFullStyle),
298                  "BRUSH(fc:#%02x%02x%02x,id:\"ogr-brush-0\")",
299                  gv_red, gv_green, gv_blue );
300 
301         if( nFillColor != psElement->color )
302         {
303             strcat( szFullStyle, ";" );
304             strcat( szFullStyle, pszPen );
305         }
306         poFeature->SetStyleString( szFullStyle );
307     }
308     else
309         poFeature->SetStyleString( pszPen );
310 }
311 
312 /************************************************************************/
313 /*                          ElementToFeature()                          */
314 /************************************************************************/
315 
ElementToFeature(DGNElemCore * psElement,int nRecLevel)316 OGRFeature *OGRDGNLayer::ElementToFeature( DGNElemCore *psElement, int nRecLevel )
317 
318 {
319     OGRFeature  *poFeature = new OGRFeature( poFeatureDefn );
320 
321     poFeature->SetFID( psElement->element_id );
322     poFeature->SetField( "Type", psElement->type );
323     poFeature->SetField( "Level", psElement->level );
324     poFeature->SetField( "GraphicGroup", psElement->graphic_group );
325     poFeature->SetField( "ColorIndex", psElement->color );
326     poFeature->SetField( "Weight", psElement->weight );
327     poFeature->SetField( "Style", psElement->style );
328 
329     m_nFeaturesRead++;
330 
331 /* -------------------------------------------------------------------- */
332 /*      Collect linkage information                                     */
333 /* -------------------------------------------------------------------- */
334     const int MAX_LINK = 100;
335 
336     int anEntityNum[MAX_LINK];
337     anEntityNum[0] = 0;
338 
339     int anMSLink[MAX_LINK];
340     anMSLink[0] = 0;
341 
342     CPLJSONObject uLinkData;
343 
344     int iLink = 0;
345     int nLinkCount = 0;
346     int uLinkCount = 0;
347 
348     int nLinkType = 0;
349     int nLinkSize = 0;
350 
351     // coverity[tained_data]
352     unsigned char *pabyData = DGNGetLinkage( hDGN, psElement, iLink, &nLinkType,
353                               anEntityNum + iLink, anMSLink + iLink, &nLinkSize);
354 
355     while( pabyData )
356     {
357         CPLJSONArray previousValues = uLinkData.GetArray( std::to_string(nLinkType) );
358         if (!previousValues.IsValid() )
359         {
360             uLinkData.Add( std::to_string(nLinkType), CPLJSONArray() );
361             previousValues = uLinkData.GetArray( std::to_string(nLinkType) );
362         }
363         CPLJSONArray rawWords;
364         for( int i=0; i < nLinkSize-1; i+=2 )
365         {
366             rawWords.Add( CPLSPrintf("0x%02x%02x", pabyData[i+1], pabyData[i] ) );
367         }
368         CPLJSONObject theNewObject = CPLJSONObject();
369         theNewObject.Add( "size", nLinkSize );
370         previousValues.Add( theNewObject );
371         switch( nLinkType )
372         {
373             case 24721: // OdDgDBLinkage::kOracle
374             {
375                 theNewObject.Add( "raw", rawWords );
376                 theNewObject.Add( "type", "Oracle" );
377             }
378             break;
379             case 32047: // OdDgDBLinkage::kODBC
380             {
381                 theNewObject.Add( "raw", rawWords );
382                 theNewObject.Add( "type", "ODBC" );
383             }
384             break;
385             case 6549: // 0x1995 Application ID by IPCC/Portugal
386             {
387                 theNewObject.Add( "domain", CPLSPrintf("0x%02x", pabyData[5] ) );
388                 theNewObject.Add( "subdomain", CPLSPrintf("0x%02x", pabyData[4] ) );
389                 theNewObject.Add( "family", CPLSPrintf("0x%02x", pabyData[7] ) );
390                 theNewObject.Add( "object", CPLSPrintf("0x%02x", pabyData[6] ) );
391                 theNewObject.Add( "key", CPLSPrintf("%02x%02x%02x%02x", pabyData[5], pabyData[4], pabyData[7], pabyData[6] ) );
392                 theNewObject.Add( "type", "IPCC/Portugal" );
393             }
394             break;
395             default:
396             {
397                 theNewObject.Add( "raw", rawWords );
398                 theNewObject.Add( "type", "unknown" );
399             }
400             break;
401         }
402 
403         uLinkCount++;
404         iLink++;
405 
406         if( anEntityNum[nLinkCount] != 0 || anMSLink[nLinkCount] != 0 )
407         {
408             nLinkCount++;
409             if( nLinkCount == MAX_LINK )
410             {
411                 break;
412             }
413         }
414 
415         anEntityNum[nLinkCount] = 0;
416         anMSLink[nLinkCount] = 0;
417 
418         // coverity[tained_data]
419         pabyData = DGNGetLinkage( hDGN, psElement, iLink, &nLinkType,
420                                   anEntityNum+nLinkCount, anMSLink+nLinkCount,
421                                   &nLinkSize);
422     }
423 
424 /* -------------------------------------------------------------------- */
425 /*      Apply attribute linkage to feature.                             */
426 /* -------------------------------------------------------------------- */
427     if( uLinkCount > 0 )
428     {
429         poFeature->SetField( "ULink", uLinkData.ToString().c_str() );
430     }
431     if( nLinkCount > 0 )
432     {
433         if( EQUAL(pszLinkFormat,"FIRST") )
434         {
435             poFeature->SetField( "EntityNum", anEntityNum[0] );
436             poFeature->SetField( "MSLink", anMSLink[0] );
437         }
438         else if( EQUAL(pszLinkFormat,"LIST") )
439         {
440             poFeature->SetField( "EntityNum", nLinkCount, anEntityNum );
441             poFeature->SetField( "MSLink", nLinkCount, anMSLink );
442         }
443         else if( EQUAL(pszLinkFormat,"STRING") )
444         {
445             char szEntityList[MAX_LINK * 9];
446             char szMSLinkList[MAX_LINK * 9];
447             int nEntityLen = 0;
448             int nMSLinkLen = 0;
449 
450             for( iLink = 0; iLink < nLinkCount; iLink++ )
451             {
452                 if( iLink != 0 )
453                 {
454                     szEntityList[nEntityLen++] = ',';
455                     szMSLinkList[nMSLinkLen++] = ',';
456                 }
457 
458                 snprintf( szEntityList + nEntityLen,
459                           sizeof(szEntityList) - nEntityLen,
460                           "%d", anEntityNum[iLink]);
461                 snprintf( szMSLinkList + nMSLinkLen,
462                           sizeof(szMSLinkList) - nMSLinkLen,
463                           "%d", anMSLink[iLink] );
464 
465                 nEntityLen +=
466                     static_cast<int>(strlen(szEntityList + nEntityLen ));
467                 nMSLinkLen +=
468                     static_cast<int>(strlen(szMSLinkList + nMSLinkLen ));
469             }
470 
471             poFeature->SetField( "EntityNum", szEntityList );
472             poFeature->SetField( "MSLink", szMSLinkList );
473         }
474     }
475 
476 /* -------------------------------------------------------------------- */
477 /*      Lookup color.                                                   */
478 /* -------------------------------------------------------------------- */
479     int gv_red = 0;
480     int gv_green = 0;
481     int gv_blue = 0;
482 
483     char szFSColor[128] = {};
484     szFSColor[0] = '\0';
485     if( DGNLookupColor( hDGN, psElement->color,
486                         &gv_red, &gv_green, &gv_blue ) )
487     {
488         char gv_color[128];
489         CPLsnprintf( gv_color, sizeof(gv_color), "%f %f %f 1.0",
490                  gv_red / 255.0, gv_green / 255.0, gv_blue / 255.0 );
491 
492         snprintf( szFSColor, sizeof(szFSColor), "c:#%02x%02x%02x",
493                  gv_red, gv_green, gv_blue );
494     }
495 
496 /* -------------------------------------------------------------------- */
497 /*      Generate corresponding PEN style.                               */
498 /* -------------------------------------------------------------------- */
499     char szPen[256];
500     szPen[0] = '\0';
501 
502     if( psElement->style == DGNS_SOLID )
503         snprintf( szPen, sizeof(szPen), "PEN(id:\"ogr-pen-0\"" );
504     else if( psElement->style == DGNS_DOTTED )
505         snprintf( szPen, sizeof(szPen), "PEN(id:\"ogr-pen-5\"" );
506     else if( psElement->style == DGNS_MEDIUM_DASH )
507         snprintf( szPen, sizeof(szPen), "PEN(id:\"ogr-pen-2\"" );
508     else if( psElement->style == DGNS_LONG_DASH )
509         snprintf( szPen, sizeof(szPen), "PEN(id:\"ogr-pen-4\"" );
510     else if( psElement->style == DGNS_DOT_DASH )
511         snprintf( szPen, sizeof(szPen), "PEN(id:\"ogr-pen-6\"" );
512     else if( psElement->style == DGNS_SHORT_DASH )
513         snprintf( szPen, sizeof(szPen), "PEN(id:\"ogr-pen-3\"" );
514     else if( psElement->style == DGNS_DASH_DOUBLE_DOT )
515         snprintf( szPen, sizeof(szPen), "PEN(id:\"ogr-pen-7\"" );
516     else if( psElement->style == DGNS_LONG_DASH_SHORT_DASH )
517         snprintf( szPen, sizeof(szPen), "PEN(p:\"10px 5px 4px 5px\"" );
518     else
519         snprintf( szPen, sizeof(szPen), "PEN(id:\"ogr-pen-0\"" );
520 
521     if( strlen(szFSColor) > 0 )
522         snprintf( szPen+strlen(szPen), sizeof(szPen)-strlen(szPen),
523                   ",%s", szFSColor );
524 
525     if( psElement->weight > 1 )
526         snprintf( szPen+strlen(szPen), sizeof(szPen)-strlen(szPen),
527                   ",w:%dpx", psElement->weight );
528 
529     strcat( szPen, ")" );
530 
531     switch( psElement->stype )
532     {
533       case DGNST_MULTIPOINT:
534         if( psElement->type == DGNT_SHAPE )
535         {
536             OGRLinearRing *poLine = new OGRLinearRing();
537             DGNElemMultiPoint *psEMP
538                 = reinterpret_cast<DGNElemMultiPoint *>( psElement );
539 
540             poLine->setNumPoints( psEMP->num_vertices );
541             for( int i = 0; i < psEMP->num_vertices; i++ )
542             {
543                 poLine->setPoint( i,
544                                   psEMP->vertices[i].x,
545                                   psEMP->vertices[i].y,
546                                   psEMP->vertices[i].z );
547             }
548 
549             OGRPolygon *poPolygon = new OGRPolygon();
550             poPolygon->addRingDirectly( poLine );
551 
552             poFeature->SetGeometryDirectly( poPolygon );
553 
554             ConsiderBrush( psElement, szPen, poFeature );
555         }
556         else if( psElement->type == DGNT_CURVE )
557         {
558             OGRLineString *poLine = new OGRLineString();
559             DGNElemMultiPoint *psEMP
560                 = reinterpret_cast<DGNElemMultiPoint *>( psElement );
561             const int nPoints = 5 * psEMP->num_vertices;
562             DGNPoint *pasPoints = static_cast<DGNPoint *>(
563                 CPLMalloc( sizeof(DGNPoint) * nPoints ) );
564 
565             DGNStrokeCurve( hDGN, psEMP, nPoints, pasPoints );
566 
567             poLine->setNumPoints( nPoints );
568             for( int i = 0; i < nPoints; i++ )
569             {
570                 poLine->setPoint( i,
571                                   pasPoints[i].x,
572                                   pasPoints[i].y,
573                                   pasPoints[i].z );
574             }
575 
576             poFeature->SetGeometryDirectly( poLine );
577             CPLFree( pasPoints );
578 
579             poFeature->SetStyleString( szPen );
580         }
581         else
582         {
583             DGNElemMultiPoint *psEMP = (DGNElemMultiPoint *) psElement;
584 
585             if( psEMP->num_vertices > 0 )
586             {
587                 OGRLineString       *poLine = new OGRLineString();
588                 poLine->setNumPoints( psEMP->num_vertices );
589                 for( int i = 0; i < psEMP->num_vertices; i++ )
590                 {
591                     poLine->setPoint( i,
592                                       psEMP->vertices[i].x,
593                                       psEMP->vertices[i].y,
594                                       psEMP->vertices[i].z );
595                 }
596 
597                 poFeature->SetGeometryDirectly( poLine );
598             }
599 
600             poFeature->SetStyleString( szPen );
601         }
602         break;
603 
604       case DGNST_ARC:
605       {
606           DGNElemArc    *psArc = (DGNElemArc *) psElement;
607           int nPoints = static_cast<int>(
608               std::max(1.0, std::abs(psArc->sweepang) / 5.0) + 1.0);
609           if( nPoints > 90 )
610               nPoints = 90;
611 
612           DGNPoint asPoints[90] = {};
613           DGNStrokeArc( hDGN, psArc, nPoints, asPoints );
614 
615           OGRLineString *poLine = new OGRLineString();
616           poLine->setNumPoints( nPoints );
617           for( int i = 0; i < nPoints; i++ )
618           {
619               poLine->setPoint( i,
620                                 asPoints[i].x,
621                                 asPoints[i].y,
622                                 asPoints[i].z );
623           }
624 
625           poFeature->SetGeometryDirectly( poLine );
626           poFeature->SetStyleString( szPen );
627       }
628       break;
629 
630       case DGNST_TEXT:
631       {
632           OGRPoint *poPoint = new OGRPoint();
633           DGNElemText *psText = reinterpret_cast<DGNElemText *>( psElement );
634 
635           poPoint->setX( psText->origin.x );
636           poPoint->setY( psText->origin.y );
637           poPoint->setZ( psText->origin.z );
638 
639           poFeature->SetGeometryDirectly( poPoint );
640 
641           const size_t nOgrFSLen = strlen(psText->string) + 150;
642           char *pszOgrFS = static_cast<char *>( CPLMalloc(nOgrFSLen) );
643 
644           // setup the basic label.
645           snprintf( pszOgrFS, nOgrFSLen, "LABEL(t:\"%s\"",  psText->string );
646 
647           // set the color if we have it.
648           if( strlen(szFSColor) > 0 )
649               snprintf( pszOgrFS+strlen(pszOgrFS), nOgrFSLen-strlen(pszOgrFS),
650                         ",%s", szFSColor );
651 
652           // Add the size info in ground units.
653           // TODO: std::abs
654           if( std::abs(psText->height_mult) >= 6.0 )
655               CPLsnprintf( pszOgrFS+strlen(pszOgrFS),
656                            nOgrFSLen-strlen(pszOgrFS), ",s:%dg",
657                            static_cast<int>( psText->height_mult ) );
658           else if( std::abs(psText->height_mult) > 0.1 )
659               CPLsnprintf( pszOgrFS+strlen(pszOgrFS),
660                           nOgrFSLen-strlen(pszOgrFS), ",s:%.3fg",
661                            psText->height_mult );
662           else
663               CPLsnprintf( pszOgrFS+strlen(pszOgrFS),
664                           nOgrFSLen-strlen(pszOgrFS), ",s:%.12fg",
665                            psText->height_mult );
666 
667           // Add the font name. Name it MstnFont<FONTNUMBER> if not available
668           // in the font list. #3392
669           static const char * const papszFontList[] =
670           { "STANDARD", "WORKING", "FANCY", "ENGINEERING", "NEWZERO",
671             "STENCEL", // 0-5
672             "USTN_FANCY", "COMPRESSED", "STENCEQ", nullptr, "hand", "ARCH", //6-11
673             "ARCHB", nullptr, nullptr, "IGES1001", "IGES1002", "IGES1003", //12-17
674             "CENTB", "MICROS", nullptr, nullptr, "ISOFRACTIONS", "ITALICS", //18-23
675             "ISO30", nullptr, "GREEK", "ISOREC", "Isoeq", nullptr, //24-29
676             "ISO_FONTLEFT", "ISO_FONTRIGHT", "INTL_ENGINEERING", "INTL_WORKING",
677             "ISOITEQ", nullptr, // 30-35
678             "USTN FONT 26", nullptr, nullptr, nullptr, nullptr, "ARCHITECTURAL", //36-41
679             "BLOCK_OUTLINE", "LOW_RES_FILLED", nullptr, nullptr, nullptr, nullptr, //42-47
680             nullptr, nullptr, "UPPERCASE", nullptr, nullptr, nullptr, //48-53
681             nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, //54-49
682             "FONT060", "din", "dinit", "helvl", "HELVLIT", "helv", //60-65
683             "HELVIT", "cent", "CENTIT", "SCRIPT", nullptr, nullptr, //66-71
684             nullptr, nullptr, nullptr, nullptr, "MICROQ", "dotfont", //72-77
685             "DOTIT", nullptr, nullptr, nullptr, nullptr, nullptr, //78-83
686             nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, //84-89
687             nullptr, nullptr, "FONT092", nullptr, "FONT094", nullptr, //90-95
688             nullptr, nullptr, nullptr, nullptr, "ANSI_SYMBOLS",
689             "FEATURE_CONTROL_SYSMBOLS", // 96-101
690             "SYMB_FAST", nullptr, nullptr, "INTL_ISO", "INTL_ISO_EQUAL",
691             "INTL_ISO_ITALIC", // 102-107
692             "INTL_ISO_ITALIC_EQUAL" }; //108
693 
694           if(psText->font_id <= 108 && papszFontList[psText->font_id] != nullptr )
695           {
696               snprintf( pszOgrFS+strlen(pszOgrFS),
697                         nOgrFSLen-strlen(pszOgrFS), ",f:%s",
698                        papszFontList[psText->font_id] );
699           }
700           else
701           {
702               snprintf( pszOgrFS+strlen(pszOgrFS),
703                         nOgrFSLen-strlen(pszOgrFS), ",f:MstnFont%d",
704                        psText->font_id );
705           }
706 
707           // Add the angle, if not horizontal
708           if( psText->rotation != 0.0 )
709               snprintf( pszOgrFS+strlen(pszOgrFS),
710                         nOgrFSLen-strlen(pszOgrFS), ",a:%d",
711                        (int) (psText->rotation+0.5) );
712 
713           snprintf( pszOgrFS+strlen(pszOgrFS),
714                         nOgrFSLen-strlen(pszOgrFS), ")" );
715 
716           poFeature->SetStyleString( pszOgrFS );
717           CPLFree( pszOgrFS );
718 
719           poFeature->SetField( "Text", psText->string );
720       }
721       break;
722 
723       case DGNST_COMPLEX_HEADER:
724       {
725           DGNElemComplexHeader *psHdr = (DGNElemComplexHeader *) psElement;
726           OGRMultiLineString  oChildren;
727 
728           /* collect subsequent child geometries. */
729           // we should disable the spatial filter ... add later.
730           for( int iChild = 0; iChild < psHdr->numelems &&
731                                nRecLevel < 20; iChild++ )
732           {
733               OGRFeature *poChildFeature = nullptr;
734               DGNElemCore *psChildElement = DGNReadElement( hDGN );
735               // should verify complex bit set, not another header.
736 
737               if( psChildElement != nullptr )
738               {
739                   poChildFeature = ElementToFeature( psChildElement,
740                                                      nRecLevel + 1 );
741                   DGNFreeElement( hDGN, psChildElement );
742               }
743 
744               if( poChildFeature != nullptr
745                   && poChildFeature->GetGeometryRef() != nullptr )
746               {
747                   OGRGeometry *poGeom = poChildFeature->GetGeometryRef();
748                   if( wkbFlatten(poGeom->getGeometryType()) == wkbLineString )
749                       oChildren.addGeometry( poGeom );
750               }
751 
752               if( poChildFeature != nullptr )
753                   delete poChildFeature;
754           }
755 
756           // Try to assemble into polygon geometry.
757           OGRGeometry *poGeom = nullptr;
758 
759           if( psElement->type == DGNT_COMPLEX_SHAPE_HEADER )
760               poGeom = reinterpret_cast<OGRGeometry *>(
761                   OGRBuildPolygonFromEdges(
762                       reinterpret_cast<OGRGeometryH>( &oChildren ),
763                       TRUE, TRUE, 100000, nullptr ) );
764           else
765               poGeom = oChildren.clone();
766 
767           if( poGeom != nullptr )
768               poFeature->SetGeometryDirectly( poGeom );
769 
770           ConsiderBrush( psElement, szPen, poFeature );
771       }
772       break;
773 
774       default:
775         break;
776     }
777 
778 /* -------------------------------------------------------------------- */
779 /*      Fixup geometry dimension.                                       */
780 /* -------------------------------------------------------------------- */
781     if( poFeature->GetGeometryRef() != nullptr )
782         poFeature->GetGeometryRef()->setCoordinateDimension(
783             DGNGetDimension( hDGN ) );
784 
785     return poFeature;
786 }
787 
788 /************************************************************************/
789 /*                           GetNextFeature()                           */
790 /************************************************************************/
791 
GetNextFeature()792 OGRFeature *OGRDGNLayer::GetNextFeature()
793 
794 {
795     DGNGetElementIndex( hDGN, nullptr );
796 
797     DGNElemCore *psElement = nullptr;
798     while( (psElement = DGNReadElement( hDGN )) != nullptr )
799     {
800         if( psElement->deleted )
801         {
802             DGNFreeElement( hDGN, psElement );
803             continue;
804         }
805 
806         OGRFeature *poFeature = ElementToFeature( psElement, 0 );
807         DGNFreeElement( hDGN, psElement );
808 
809         if( poFeature == nullptr )
810             continue;
811 
812         if( poFeature->GetGeometryRef() == nullptr )
813         {
814             delete poFeature;
815             continue;
816         }
817 
818         if( (m_poAttrQuery == nullptr
819              || m_poAttrQuery->Evaluate( poFeature ))
820             && FilterGeometry( poFeature->GetGeometryRef() ) )
821             return poFeature;
822 
823         delete poFeature;
824     }
825 
826     return nullptr;
827 }
828 
829 /************************************************************************/
830 /*                           TestCapability()                           */
831 /************************************************************************/
832 
TestCapability(const char * pszCap)833 int OGRDGNLayer::TestCapability( const char * pszCap )
834 
835 {
836     if( EQUAL(pszCap,OLCRandomRead) )
837         return TRUE;
838 
839     else if( EQUAL(pszCap,OLCSequentialWrite) )
840         return bUpdate;
841     else if( EQUAL(pszCap,OLCRandomWrite) )
842         return FALSE; /* maybe later? */
843 
844     else if( EQUAL(pszCap,OLCFastFeatureCount) )
845         return m_poFilterGeom == nullptr || m_poAttrQuery == nullptr;
846 
847     else if( EQUAL(pszCap,OLCFastSpatialFilter) )
848         return FALSE;
849 
850     else if( EQUAL(pszCap,OLCFastGetExtent) )
851         return TRUE;
852 
853     return FALSE;
854 }
855 
856 /************************************************************************/
857 /*                          GetFeatureCount()                           */
858 /************************************************************************/
859 
GetFeatureCount(int bForce)860 GIntBig OGRDGNLayer::GetFeatureCount( int bForce )
861 
862 {
863 /* -------------------------------------------------------------------- */
864 /*      If any odd conditions are in effect collect the information     */
865 /*      normally.                                                       */
866 /* -------------------------------------------------------------------- */
867     if( m_poFilterGeom != nullptr || m_poAttrQuery != nullptr )
868         return OGRLayer::GetFeatureCount( bForce );
869 
870 /* -------------------------------------------------------------------- */
871 /*      Otherwise scan the index.                                       */
872 /* -------------------------------------------------------------------- */
873     int nElementCount = 0;
874     const DGNElementInfo *pasIndex = DGNGetElementIndex(hDGN, &nElementCount);
875 
876     int nFeatureCount = 0;
877     bool bInComplexShape = false;
878 
879     for( int i = 0; i < nElementCount; i++ )
880     {
881         if( pasIndex[i].flags & DGNEIF_DELETED )
882             continue;
883 
884         switch( pasIndex[i].stype )
885         {
886           case DGNST_MULTIPOINT:
887           case DGNST_ARC:
888           case DGNST_TEXT:
889             if( !(pasIndex[i].flags & DGNEIF_COMPLEX) || !bInComplexShape )
890             {
891                 nFeatureCount++;
892                 bInComplexShape = false;
893             }
894             break;
895 
896           case DGNST_COMPLEX_HEADER:
897             nFeatureCount++;
898             bInComplexShape = true;
899             break;
900 
901           default:
902             break;
903         }
904     }
905 
906     return nFeatureCount;
907 }
908 
909 /************************************************************************/
910 /*                             GetExtent()                              */
911 /************************************************************************/
912 
GetExtent(OGREnvelope * psExtent,int)913 OGRErr OGRDGNLayer::GetExtent( OGREnvelope *psExtent, int /* bForce */ )
914 {
915     double adfExtents[6];
916 
917     if( !DGNGetExtents( hDGN, adfExtents ) )
918         return OGRERR_FAILURE;
919 
920     psExtent->MinX = adfExtents[0];
921     psExtent->MinY = adfExtents[1];
922     psExtent->MaxX = adfExtents[3];
923     psExtent->MaxY = adfExtents[4];
924 
925     return OGRERR_NONE;
926 }
927 
928 /************************************************************************/
929 /*                      LineStringToElementGroup()                      */
930 /*                                                                      */
931 /*      Convert an OGR line string to one or more DGN elements.  If     */
932 /*      the input is too long for a single element (more than 38        */
933 /*      points) we split it into multiple LINE_STRING elements, and     */
934 /*      prefix with a complex group header element.                     */
935 /*                                                                      */
936 /*      This method can create handle creating shapes, or line          */
937 /*      strings for the aggregate object, but the components of a       */
938 /*      complex shape group are always line strings.                    */
939 /************************************************************************/
940 
941 constexpr int MAX_ELEM_POINTS = 38;
942 
LineStringToElementGroup(OGRLineString * poLS,int nGroupType)943 DGNElemCore **OGRDGNLayer::LineStringToElementGroup( OGRLineString *poLS,
944                                                      int nGroupType )
945 
946 {
947     const int nTotalPoints = poLS->getNumPoints();
948     int iGeom = 0;
949     DGNElemCore **papsGroup = static_cast<DGNElemCore **>(
950         CPLCalloc( sizeof(void*), (nTotalPoints/(MAX_ELEM_POINTS-1))+3 ) );
951 
952     for( int iNextPoint = 0; iNextPoint < nTotalPoints;  )
953     {
954         DGNPoint asPoints[MAX_ELEM_POINTS] = {};
955         int nThisCount = 0;
956 
957         // we need to repeat end points of elements.
958         // cppcheck-suppress duplicateExpression
959         if( iNextPoint != 0 )
960             iNextPoint--;
961 
962         for( ; iNextPoint < nTotalPoints && nThisCount < MAX_ELEM_POINTS;
963              iNextPoint++, nThisCount++ )
964         {
965             asPoints[nThisCount].x = poLS->getX( iNextPoint );
966             asPoints[nThisCount].y = poLS->getY( iNextPoint );
967             asPoints[nThisCount].z = poLS->getZ( iNextPoint );
968         }
969 
970         if( nTotalPoints <= MAX_ELEM_POINTS )
971             papsGroup[0] = DGNCreateMultiPointElem( hDGN, nGroupType,
972                                                  nThisCount, asPoints);
973         else
974             papsGroup[++iGeom] =
975                 DGNCreateMultiPointElem( hDGN, DGNT_LINE_STRING,
976                                          nThisCount, asPoints);
977     }
978 
979 /* -------------------------------------------------------------------- */
980 /*      We needed to make into a group.  Create the complex header      */
981 /*      from the rest of the group.                                     */
982 /* -------------------------------------------------------------------- */
983     if( papsGroup[0] == nullptr )
984     {
985         if( nGroupType == DGNT_SHAPE )
986             nGroupType = DGNT_COMPLEX_SHAPE_HEADER;
987         else
988             nGroupType = DGNT_COMPLEX_CHAIN_HEADER;
989 
990         papsGroup[0] =
991             DGNCreateComplexHeaderFromGroup( hDGN, nGroupType,
992                                              iGeom, papsGroup + 1 );
993     }
994 
995     return papsGroup;
996 }
997 
998 /************************************************************************/
999 /*                           TranslateLabel()                           */
1000 /*                                                                      */
1001 /*      Translate LABEL feature.                                        */
1002 /************************************************************************/
1003 
TranslateLabel(OGRFeature * poFeature)1004 DGNElemCore **OGRDGNLayer::TranslateLabel( OGRFeature *poFeature )
1005 
1006 {
1007     OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
1008     const char *pszText = poFeature->GetFieldAsString( "Text" );
1009 
1010     OGRStyleMgr oMgr;
1011     oMgr.InitFromFeature( poFeature );
1012     OGRStyleLabel *poLabel
1013         = reinterpret_cast<OGRStyleLabel *>( oMgr.GetPart( 0 ) );
1014     if( poLabel != nullptr && poLabel->GetType() != OGRSTCLabel )
1015     {
1016         delete poLabel;
1017         poLabel = nullptr;
1018     }
1019 
1020     double dfRotation = 0.0;
1021     double dfCharHeight = 100.0;
1022     int nFontID = 1; // 1 is the default font for DGN. Not 0.
1023 
1024     if( poLabel != nullptr )
1025     {
1026         GBool bDefault;
1027 
1028         if( poLabel->TextString(bDefault) != nullptr && !bDefault )
1029             pszText = poLabel->TextString(bDefault);
1030         dfRotation = poLabel->Angle(bDefault);
1031 
1032         poLabel->Size( bDefault );
1033         if( !bDefault && poLabel->GetUnit() == OGRSTUGround )
1034             dfCharHeight = poLabel->Size(bDefault);
1035         // this part is really kind of bogus.
1036         if( !bDefault && poLabel->GetUnit() == OGRSTUMM )
1037             dfCharHeight = poLabel->Size(bDefault)/1000.0;
1038 
1039         /* get font id */
1040         static const char * const papszFontNumbers[] =
1041           { "STANDARD=0", "WORKING=1", "FANCY=2", "ENGINEERING=3", "NEWZERO=4",
1042             "STENCEL=5", "USTN_FANCY=7", "COMPRESSED=8", "STENCEQ=9", "hand=10",
1043             "ARCH=11", "ARCHB=12", "IGES1001=15", "IGES1002=16", "IGES1003=17",
1044             "CENTB=18", "MICROS=19", "ISOFRACTIONS=22", "ITALICS=23",
1045             "ISO30=24", "GREEK=25", "ISOREC=26", "Isoeq=27", "ISO_FONTLEFT=30",
1046             "ISO_FONTRIGHT=31", "INTL_ENGINEERING=32", "INTL_WORKING=33",
1047             "ISOITEQ=34", "USTN FONT 26=36", "ARCHITECTURAL=41",
1048             "BLOCK_OUTLINE=42", "LOW_RES_FILLED=43", "UPPERCASE50",
1049             "FONT060=60", "din=61", "dinit=62", "helvl=63", "HELVLIT=64",
1050             "helv=65", "HELVIT=66", "cent=67", "CENTIT=68", "SCRIPT=69",
1051             "MICROQ=76", "dotfont=77", "DOTIT=78", "FONT092=92", "FONT094=94",
1052             "ANSI_SYMBOLS=100", "FEATURE_CONTROL_SYSMBOLS=101", "SYMB_FAST=102",
1053             "INTL_ISO=105", "INTL_ISO_EQUAL=106", "INTL_ISO_ITALIC=107",
1054             "INTL_ISO_ITALIC_EQUAL=108", nullptr };
1055 
1056         const char *pszFontName = poLabel->FontName( bDefault );
1057         if( !bDefault && pszFontName != nullptr )
1058         {
1059             const char *pszFontNumber =
1060               CSLFetchNameValue( const_cast<char**>( papszFontNumbers ),
1061                                  pszFontName );
1062 
1063             if( pszFontNumber != nullptr )
1064             {
1065                 nFontID = atoi( pszFontNumber );
1066             }
1067         }
1068     }
1069 
1070     DGNElemCore **papsGroup
1071         = static_cast<DGNElemCore **>( CPLCalloc(sizeof(void*), 2) );
1072     papsGroup[0] =
1073         DGNCreateTextElem( hDGN, pszText, nFontID, DGNJ_LEFT_BOTTOM,
1074                            dfCharHeight, dfCharHeight, dfRotation, nullptr,
1075                            poPoint->getX(),
1076                            poPoint->getY(),
1077                            poPoint->getZ() );
1078 
1079     if( poLabel )
1080         delete poLabel;
1081 
1082     return papsGroup;
1083 }
1084 
1085 /************************************************************************/
1086 /*                           ICreateFeature()                            */
1087 /*                                                                      */
1088 /*      Create a new feature and write to file.                         */
1089 /************************************************************************/
1090 
ICreateFeature(OGRFeature * poFeature)1091 OGRErr OGRDGNLayer::ICreateFeature( OGRFeature *poFeature )
1092 
1093 {
1094     if( !bUpdate )
1095     {
1096         CPLError( CE_Failure, CPLE_AppDefined,
1097                   "Attempt to create feature on read-only DGN file." );
1098         return OGRERR_FAILURE;
1099     }
1100 
1101     if( poFeature->GetGeometryRef() == nullptr )
1102     {
1103         CPLError( CE_Failure, CPLE_AppDefined,
1104                   "Features with empty, geometry collection geometries not\n"
1105                   "supported in DGN format." );
1106         return OGRERR_FAILURE;
1107     }
1108 
1109     return CreateFeatureWithGeom( poFeature, poFeature->GetGeometryRef() );
1110 }
1111 
1112 /************************************************************************/
1113 /*                       CreateFeatureWithGeom()                        */
1114 /*                                                                      */
1115 /*      Create an element or element group from a given geometry and    */
1116 /*      the given feature.  This method recurses to handle              */
1117 /*      collections as essentially independent features.                */
1118 /************************************************************************/
1119 
CreateFeatureWithGeom(OGRFeature * poFeature,OGRGeometry * poGeom)1120 OGRErr OGRDGNLayer::CreateFeatureWithGeom( OGRFeature *poFeature,
1121                                            OGRGeometry *poGeom)
1122 
1123 {
1124 /* -------------------------------------------------------------------- */
1125 /*      Translate the geometry.                                         */
1126 /* -------------------------------------------------------------------- */
1127     DGNElemCore **papsGroup = nullptr;
1128     const char *pszStyle = poFeature->GetStyleString();
1129 
1130     if( wkbFlatten(poGeom->getGeometryType()) == wkbPoint )
1131     {
1132         OGRPoint *poPoint = poGeom->toPoint();
1133         const char *pszText = poFeature->GetFieldAsString("Text");
1134 
1135         if( (pszText == nullptr || strlen(pszText) == 0)
1136             && (pszStyle == nullptr || strstr(pszStyle,"LABEL") == nullptr) )
1137         {
1138             // Treat a non text point as a degenerate line.
1139             DGNPoint asPoints[2] = {};
1140             asPoints[0].x = poPoint->getX();
1141             asPoints[0].y = poPoint->getY();
1142             asPoints[0].z = poPoint->getZ();
1143             asPoints[1] = asPoints[0];
1144 
1145             papsGroup
1146                 = static_cast<DGNElemCore **>( CPLCalloc(sizeof(void*), 2) );
1147             papsGroup[0] = DGNCreateMultiPointElem( hDGN, DGNT_LINE,
1148                                                     2, asPoints );
1149         }
1150         else
1151         {
1152             papsGroup = TranslateLabel( poFeature );
1153         }
1154     }
1155     else if( wkbFlatten(poGeom->getGeometryType()) == wkbLineString )
1156     {
1157         papsGroup = LineStringToElementGroup( poGeom->toLineString(),
1158             DGNT_LINE_STRING );
1159     }
1160     else if( wkbFlatten(poGeom->getGeometryType()) == wkbPolygon )
1161     {
1162         OGRPolygon *poPoly = poGeom->toPolygon();
1163 
1164         DGNElemCore **papsGroupExt = LineStringToElementGroup(
1165                 poPoly->getExteriorRing(), DGNT_SHAPE);
1166 
1167         const int innerRingsCnt = poPoly->getNumInteriorRings();
1168 
1169         if (innerRingsCnt > 0) {
1170             CPLDebug("InnerRings", "there are %d inner rings", innerRingsCnt);
1171             std::list<DGNElemCore*> dgnElements;
1172 
1173             for( int i = 0; papsGroupExt[i] != nullptr; i++) {
1174                 dgnElements.push_back(papsGroupExt[i]);
1175             }
1176             CPLFree(papsGroupExt);
1177 
1178             // get all interior rings and create complex group shape
1179             for (int iRing = 0; iRing < innerRingsCnt; iRing++) {
1180                 DGNElemCore **papsGroupInner = LineStringToElementGroup(
1181                         poPoly->getInteriorRing(iRing), DGNT_SHAPE);
1182                 papsGroupInner[0]->properties |= DGNPF_HOLE;
1183                 DGNUpdateElemCoreExtended(hDGN, papsGroupInner[0]);
1184                 for( int i = 0; papsGroupInner[i] != nullptr; i++ ) {
1185                     dgnElements.push_back(papsGroupInner[i]);
1186                 }
1187                 CPLFree(papsGroupInner);
1188             }
1189             int index = 1;
1190             papsGroup = (DGNElemCore **) CPLCalloc(sizeof(void*),
1191                     dgnElements.size() + 2);
1192             for( std::list<DGNElemCore*>::iterator list_iter
1193                      = dgnElements.begin();
1194                  list_iter != dgnElements.end();
1195                  ++list_iter) {
1196                 papsGroup[index++] = *list_iter;
1197             }
1198 
1199             // papsGroup[0] = DGNCreateComplexHeaderFromGroup(
1200             //     hDGN, DGNT_COMPLEX_SHAPE_HEADER, dgnElements.size(),
1201             //     papsGroup+1 );
1202             DGNPoint asPoints[1] = {};
1203             papsGroup[0] = DGNCreateCellHeaderFromGroup(
1204                 hDGN, "", 1, nullptr,
1205                 static_cast<int>(dgnElements.size()), papsGroup + 1,
1206                 asPoints + 0, 1.0, 1.0, 0.0) ;
1207             DGNAddShapeFillInfo(hDGN, papsGroup[0], 6);
1208         } else {
1209             papsGroup = papsGroupExt;
1210         }
1211     }
1212     else if( wkbFlatten(poGeom->getGeometryType()) == wkbMultiPolygon
1213              || wkbFlatten(poGeom->getGeometryType()) == wkbMultiPoint
1214              || wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString
1215              || wkbFlatten(poGeom->getGeometryType()) == wkbGeometryCollection)
1216     {
1217         for( auto&& poMember: poGeom->toGeometryCollection() )
1218         {
1219             OGRErr eErr = CreateFeatureWithGeom( poFeature, poMember );
1220             if( eErr != OGRERR_NONE )
1221                 return eErr;
1222         }
1223 
1224         return OGRERR_NONE;
1225     }
1226     else
1227     {
1228         CPLError( CE_Failure, CPLE_AppDefined,
1229                   "Unsupported geometry type (%s) for DGN.",
1230                   OGRGeometryTypeToName( poGeom->getGeometryType() ) );
1231         return OGRERR_FAILURE;
1232     }
1233 
1234 /* -------------------------------------------------------------------- */
1235 /*      Add other attributes.                                           */
1236 /* -------------------------------------------------------------------- */
1237     int nLevel = poFeature->GetFieldAsInteger( "Level" );
1238     int nGraphicGroup = poFeature->GetFieldAsInteger( "GraphicGroup" );
1239     int nColor = poFeature->GetFieldAsInteger( "ColorIndex" );
1240     int nWeight = poFeature->GetFieldAsInteger( "Weight" );
1241     int nStyle = poFeature->GetFieldAsInteger( "Style" );
1242     int nMSLink = poFeature->GetFieldAsInteger( "MSLink" );
1243 
1244     // TODO: Use std::max and std::min.
1245     nLevel = std::max(0, std::min(63, nLevel));
1246     nColor = std::max(0, std::min(255, nColor));
1247     nWeight = std::max(0, std::min(31, nWeight));
1248     nStyle = std::max(0, std::min(7, nStyle));
1249     nMSLink = std::max(0, nMSLink);
1250 
1251     DGNUpdateElemCore( hDGN, papsGroup[0], nLevel, nGraphicGroup, nColor,
1252                        nWeight, nStyle );
1253     DGNAddMSLink( hDGN, papsGroup[0], DGNLT_ODBC, 0, nMSLink );
1254 /* -------------------------------------------------------------------- */
1255 /*      Write to file.                                                  */
1256 /* -------------------------------------------------------------------- */
1257     for( int i = 0; papsGroup[i] != nullptr; i++ )
1258     {
1259         DGNWriteElement( hDGN, papsGroup[i] );
1260 
1261         if( i == 0 )
1262             poFeature->SetFID( papsGroup[i]->element_id );
1263 
1264         DGNFreeElement( hDGN, papsGroup[i] );
1265     }
1266 
1267     CPLFree( papsGroup );
1268 
1269     return OGRERR_NONE;
1270 }
1271