1 /*****************************************************************************/
2 /*  LibreDWG - free implementation of the DWG file format                    */
3 /*                                                                           */
4 /*  Copyright (C) 2018-2021 Free Software Foundation, Inc.                   */
5 /*                                                                           */
6 /*  This library is free software, licensed under the terms of the GNU       */
7 /*  General Public License as published by the Free Software Foundation,     */
8 /*  either version 3 of the License, or (at your option) any later version.  */
9 /*  You should have received a copy of the GNU General Public License        */
10 /*  along with this program.  If not, see <http://www.gnu.org/licenses/>.    */
11 /*****************************************************************************/
12 
13 /*
14  * out_geojson.c: write as GeoJSON
15  * written by Reini Urban
16  */
17 /* TODO: Arc, Circle, Ellipsis, Bulge (Curve) tessellation.
18  *       ocs/ucs transforms, explode of inserts?
19  *       NOCOMMA:
20  *         We really have to add the comma before, not after, and special case
21  *         the first field, not the last to omit the comma.
22  *       GeoJSON 2008 or newer RFC7946 https://tools.ietf.org/html/rfc7946#appendix-B
23  *       For the new format we need to follow the right-hand rule for orientation
24  *       (counterclockwise polygons).
25  */
26 
27 #include "config.h"
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <math.h>
33 #include <assert.h>
34 
35 #define IS_PRINT
36 #include "common.h"
37 #include "bits.h"
38 #include "myalloca.h"
39 #include "dwg.h"
40 #include "decode.h"
41 #include "out_json.h"
42 
43 #define DWG_LOGLEVEL DWG_LOGLEVEL_NONE
44 #include "logging.h"
45 #include "dwg_api.h"
46 
47 /* the current version per spec block */
48 static unsigned int cur_ver = 0;
49 
50 /* https://tools.ietf.org/html/rfc7946#section-11.2 */
51 // The default %f is good enough
52 //#undef FORMAT_RD
53 //#define FORMAT_RD %.6f
54 
55 /*--------------------------------------------------------------------------------
56  * See http://geojson.org/geojson-spec.html
57  * Arc, AttributeDefinition, BlockReference, Ellipse, Hatch, Line,
58    MText, Point, Polyline, Spline, Text =>
59  * Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon
60  * { "type": "FeatureCollection",
61      "features": [
62        { "type": "Feature",
63          "properties":
64            { "Layer": "SomeLayer",
65              "SubClasses": "AcDbEntity:AcDbLine",
66              "ExtendedEntity": null,
67              "Linetype": null,
68              "EntityHandle": "8B",
69              "Text": null
70            },
71          "geometry":
72            { "type": "LineString",
73              "coordinates": [
74                [ 370.858611, 730.630303 ],
75                [ 450.039756, 619.219273 ]
76              ]
77            }
78        },
79      ], ...
80    }
81  *
82  * MACROS
83  */
84 
85 #define ACTION geojson
86 
87 #define PREFIX                                                                \
88   for (int _i = 0; _i < dat->bit; _i++)                                       \
89     {                                                                         \
90       fprintf (dat->fh, "  ");                                                \
91     }
92 #define ARRAY                                                                 \
93   {                                                                           \
94     PREFIX fprintf (dat->fh, "[\n");                                          \
95     dat->bit++;                                                               \
96   }
97 #define SAMEARRAY                                                             \
98   {                                                                           \
99     PREFIX fprintf (dat->fh, "[");                                            \
100     dat->bit++;                                                               \
101   }
102 #define ENDARRAY                                                              \
103   {                                                                           \
104     dat->bit--;                                                               \
105     PREFIX fprintf (dat->fh, "],\n");                                         \
106   }
107 #define LASTENDARRAY                                                          \
108   {                                                                           \
109     dat->bit--;                                                               \
110     PREFIX fprintf (dat->fh, "]\n");                                          \
111   }
112 #define HASH                                                                  \
113   {                                                                           \
114     PREFIX fprintf (dat->fh, "{\n");                                          \
115     dat->bit++;                                                               \
116   }
117 #define SAMEHASH                                                              \
118   {                                                                           \
119     fprintf (dat->fh, "{\n");                                                 \
120     dat->bit++;                                                               \
121   }
122 #define ENDHASH                                                               \
123   {                                                                           \
124     dat->bit--;                                                               \
125     PREFIX fprintf (dat->fh, "},\n");                                         \
126   }
127 #define LASTENDHASH                                                           \
128   {                                                                           \
129     dat->bit--;                                                               \
130     PREFIX fprintf (dat->fh, "}\n");                                          \
131   }
132 #define SECTION(name)                                                         \
133   {                                                                           \
134     PREFIX fprintf (dat->fh, "\"%s\": [\n", #name);                           \
135     dat->bit++;                                                               \
136   }
137 #define ENDSEC() ENDARRAY
138 #define OLD_NOCOMMA fseek (dat->fh, -2, SEEK_CUR)
139 #define NOCOMMA assert(0 = "NOCOMMA")
140 // guaranteed non-null str
141 #define PAIR_Sc(name, str)                                                    \
142   {                                                                           \
143     const int len = strlen (str);                                             \
144     if (len < 4096 / 6)                                                       \
145       {                                                                       \
146         const int _len = 6 * len + 1;                                         \
147         char *_buf = (char *)alloca (_len);                                   \
148         PREFIX fprintf (dat->fh, "\"" #name "\": \"%s\",\n",                  \
149                         json_cquote (_buf, str, _len));                       \
150         freea (_buf);                                                         \
151       }                                                                       \
152     else                                                                      \
153       {                                                                       \
154         const int _len = 6 * len + 1;                                         \
155         char *_buf = (char *)malloc (_len);                                   \
156         PREFIX fprintf (dat->fh, "\"" #name "\": \"%s\",\n",                  \
157                         json_cquote (_buf, str, _len));                       \
158         free (_buf);                                                          \
159       }                                                                       \
160   }
161 #define PAIR_S(name, str)                                                     \
162   if (str)                                                                    \
163     PAIR_Sc (name, str)
164 #define PAIR_D(name, value)                                                   \
165   {                                                                           \
166     PREFIX fprintf (dat->fh, "\"" #name "\": %d,\n", value);                  \
167   }
168 // guaranteed non-null str
169 #define LASTPAIR_Sc(name, value)                                              \
170   {                                                                           \
171     PREFIX fprintf (dat->fh, "\"" #name "\": \"%s\"\n", value);               \
172   }
173 #define LASTPAIR_S(name, value)                                               \
174   if (value) {                                                                \
175     PREFIX fprintf (dat->fh, "\"" #name "\": \"%s\"\n", value);               \
176   }
177 #define PAIR_NULL(name)                                                       \
178   {                                                                           \
179     PREFIX fprintf (dat->fh, "\"" #name "\": null,\n");                       \
180   }
181 #define LASTPAIR_NULL(name)                                                   \
182   {                                                                           \
183     PREFIX fprintf (dat->fh, "\"" #name "\": null\n");                        \
184   }
185 #define KEY(name)                                                             \
186   {                                                                           \
187     PREFIX fprintf (dat->fh, "\"" #name "\": ");                              \
188   }
189 #define GEOMETRY(name)                                                        \
190   {                                                                           \
191     KEY (geometry);                                                           \
192     SAMEHASH;                                                                 \
193     PAIR_S (type, #name)                                                      \
194   }
195 #define ENDGEOMETRY LASTENDHASH
196 
197 //#define VALUE(value,type,dxf)
198 //    fprintf(dat->fh, FORMAT_##type, value)
199 //#define VALUE_RC(value,dxf) VALUE(value, RC, dxf)
200 
201 #define FIELD(name, type, dxf)
202 #define _FIELD(name, type, value)
203 #define ENT_FIELD(name, type, value)
204 #define FIELD_CAST(name, type, cast, dxf) FIELD (name, cast, dxf)
205 #define FIELD_TRACE(name, type)
206 #define FIELD_TEXT(name, str)
207 #define FIELD_TEXT_TU(name, wstr)
208 
209 #define FIELD_VALUE(name) _obj->name
210 #define ANYCODE -1
211 // todo: only the name, not the ref
212 #define FIELD_HANDLE(name, handle_code, dxf)
213 #define FIELD_DATAHANDLE(name, code, dxf)
214 #define FIELD_HANDLE_N(name, vcount, handle_code, dxf)
215 #define FIELD_B(name, dxf) FIELD (name, B, dxf)
216 #define FIELD_BB(name, dxf) FIELD (name, BB, dxf)
217 #define FIELD_3B(name, dxf) FIELD (name, 3B, dxf)
218 #define FIELD_BS(name, dxf) FIELD (name, BS, dxf)
219 #define FIELD_BL(name, dxf) FIELD (name, BL, dxf)
220 #define FIELD_BLL(name, dxf) FIELD (name, BLL, dxf)
221 #define FIELD_BD(name, dxf)
222 #define FIELD_RC(name, dxf) FIELD (name, RC, dxf)
223 #define FIELD_RS(name, dxf) FIELD (name, RS, dxf)
224 #define FIELD_RD(name, dxf) FIELD_BD (name, dxf)
225 #define FIELD_RL(name, dxf) FIELD (name, RL, dxf)
226 #define FIELD_RLL(name, dxf) FIELD (name, RLL, dxf)
227 #define FIELD_MC(name, dxf) FIELD (name, MC, dxf)
228 #define FIELD_MS(name, dxf) FIELD (name, MS, dxf)
229 #define FIELD_TF(name, len, dxf) FIELD_TEXT (name, _obj->name)
230 #define FIELD_TFF(name, len, dxf) FIELD_TEXT (name, _obj->name)
231 #define FIELD_TV(name, dxf) FIELD_TEXT (name, _obj->name)
232 #define FIELD_TU(name, dxf) FIELD_TEXT_TU (name, (BITCODE_TU)_obj->name)
233 #define FIELD_T(name, dxf)
234 //  { if (dat->version >= R_2007) { FIELD_TU(name, dxf); }
235 //    else                        { FIELD_TV(name, dxf); } }
236 #define FIELD_BT(name, dxf) FIELD (name, BT, dxf)
237 #define FIELD_4BITS(name, dxf) FIELD (name, 4BITS, dxf)
238 #define FIELD_BE(name, dxf) FIELD_3RD (name, dxf)
239 #define FIELD_2DD(name, def, dxf)
240 #define FIELD_3DD(name, def, dxf)
241 #define FIELD_2RD(name, dxf)
242 #define FIELD_2BD(name, dxf)
243 #define FIELD_2BD_1(name, dxf)
244 #define FIELD_3RD(name, dxf) ;
245 #define FIELD_3BD(name, dxf)
246 #define FIELD_3BD_1(name, dxf)
247 #define FIELD_DD(name, _default, dxf)
248 
249 #define _VALUE_RD(value) fprintf (dat->fh, FORMAT_RD, value)
250 #ifdef IS_RELEASE
251 #  define VALUE_RD(value)                                                     \
252   {                                                                           \
253     if (bit_isnan (value))                                                    \
254       _VALUE_RD (0.0);                                                        \
255     else                                                                      \
256       _VALUE_RD (value);                                                      \
257    }
258 #else
259 #  define VALUE_RD(value) _VALUE_RD (value)
260 #endif
261 #define VALUE_2DPOINT(px, py)                                                 \
262   {                                                                           \
263     PREFIX fprintf (dat->fh, "[ ");                                           \
264     VALUE_RD (px);                                                            \
265     fprintf (dat->fh, ", ");                                                  \
266     VALUE_RD (py);                                                            \
267     fprintf (dat->fh, " ],\n");                                               \
268   }
269 #define LASTVALUE_2DPOINT(px, py)                                             \
270   {                                                                           \
271     PREFIX fprintf (dat->fh, "[ ");                                           \
272     VALUE_RD (px);                                                            \
273     fprintf (dat->fh, ", ");                                                  \
274     VALUE_RD (py);                                                            \
275     fprintf (dat->fh, " ]\n");                                                \
276   }
277 #define FIELD_2DPOINT(name) VALUE_2DPOINT (_obj->name.x, _obj->name.y)
278 #define LASTFIELD_2DPOINT(name) LASTVALUE_2DPOINT (_obj->name.x, _obj->name.y)
279 #define VALUE_3DPOINT(px, py, pz)                                          \
280   {                                                                           \
281     PREFIX fprintf (dat->fh, "[ ");                                           \
282     VALUE_RD (px);                                                            \
283     fprintf (dat->fh, ", ");                                                  \
284     VALUE_RD (py);                                                            \
285     if (pz != 0.0)                                                            \
286       {                                                                       \
287         fprintf (dat->fh, ", ");                                              \
288         VALUE_RD (pz);                                                        \
289       }                                                                       \
290     fprintf (dat->fh, " ],\n");                                               \
291   }
292 #define LASTVALUE_3DPOINT(px, py, pz)                                         \
293   {                                                                           \
294     PREFIX fprintf (dat->fh, "[ ");                                           \
295     VALUE_RD (px);                                                            \
296     fprintf (dat->fh, ", ");                                                  \
297     VALUE_RD (py);                                                            \
298     if (pz != 0.0)                                                            \
299       {                                                                       \
300         fprintf (dat->fh, ", ");                                              \
301         VALUE_RD (pz);                                                        \
302       }                                                                       \
303     fprintf (dat->fh, " ]\n");                                                \
304   }
305 #define FIELD_3DPOINT(name)                                                   \
306   {                                                                           \
307     if (_obj->name.z != 0.0)                                                  \
308       VALUE_3DPOINT (_obj->name.x, _obj->name.y, _obj->name.z)                \
309     else                                                                      \
310       FIELD_2DPOINT(name)                                                     \
311   }
312 #define LASTFIELD_3DPOINT(name)                                               \
313   {                                                                           \
314     if (_obj->name.z != 0.0)                                                  \
315       LASTVALUE_3DPOINT (_obj->name.x, _obj->name.y, _obj->name.z)            \
316     else                                                                      \
317       LASTFIELD_2DPOINT (name)                                                \
318   }
319 
320 #define FIELD_CMC(name, dxf1, dxf2)
321 #define FIELD_TIMEBLL(name, dxf)
322 
323 // FIELD_VECTOR_N(name, type, size):
324 // reads data of the type indicated by 'type' 'size' times and stores
325 // it all in the vector called 'name'.
326 #define FIELD_VECTOR_N(name, type, size, dxf)                                 \
327   ARRAY;                                                                      \
328   for (vcount = 0; vcount < (BITCODE_BL)size; vcount++)                       \
329     {                                                                         \
330       PREFIX fprintf (dat->fh, "\"" #name "\": " FORMAT_##type "%s\n",        \
331                       _obj->name[vcount],                                     \
332                       vcount == (BITCODE_BL)size - 1 ? "" : ",");             \
333     }                                                                         \
334   ENDARRAY;
335 
336 #define FIELD_VECTOR_T(name, type, size, dxf)                                 \
337   ARRAY;                                                                      \
338   if (!(IS_FROM_TU (dat)))                                                    \
339   {                                                                           \
340     for (vcount = 0; vcount < (BITCODE_BL)_obj->size; vcount++)               \
341       {                                                                       \
342         PREFIX fprintf (dat->fh, "\"" #name "\": \"%s\"%s\n",                 \
343                         _obj->name[vcount],                                   \
344                         vcount == (BITCODE_BL)_obj->size - 1 ? "" : ",");     \
345       }                                                                       \
346   }                                                                           \
347   else                                                                        \
348   {                                                                           \
349     for (vcount = 0; vcount < (BITCODE_BL)_obj->size; vcount++)               \
350       FIELD_TEXT_TU (name, _obj->name[vcount]);                               \
351   }                                                                           \
352   ENDARRAY;
353 
354 #define FIELD_VECTOR(name, type, size, dxf)                                   \
355   FIELD_VECTOR_N (name, type, _obj->size, dxf)
356 
357 #define FIELD_2RD_VECTOR(name, size, dxf)
358 #define FIELD_2DD_VECTOR(name, size, dxf)
359 
360 #define FIELD_3DPOINT_VECTOR(name, size, dxf)                                 \
361   ARRAY;                                                                      \
362   for (vcount = 0; vcount < (BITCODE_BL)_obj->size; vcount++)                 \
363     {                                                                         \
364       if (vcount == (BITCODE_BL)_obj->size - 1)                               \
365         LASTFIELD_3DPOINT (name[vcount], dxf)                                 \
366       else                                                                    \
367         FIELD_3DPOINT (name[vcount], dxf)                                     \
368     }                                                                         \
369   ENDARRAY;
370 
371 #define WARN_UNSTABLE_CLASS                                                   \
372   LOG_WARN ("Unstable Class %s %d %s (0x%x%s) -@%ld",                         \
373             is_entity ? "entity" : "object", klass->number, dxfname,          \
374             klass->proxyflag, klass->is_zombie ? "is_zombie" : "",          \
375             obj->address + obj->size)
376 
377 // ensure counter-clockwise orientation of a closed polygon. 2d only.
378 static int
normalize_polygon_orient(BITCODE_BL numpts,dwg_point_2d ** const pts_p)379 normalize_polygon_orient (BITCODE_BL numpts, dwg_point_2d **const pts_p)
380 {
381   double sum = 0.0;
382   dwg_point_2d *pts = *pts_p;
383   // check orientation
384   for (unsigned i = 0; i < numpts - 1; i++)
385     {
386       sum += (pts[i+1].x - pts[i].x) * (pts[i+1].y + pts[i].y);
387     }
388   if (sum > 0.0) // if clockwise
389     {
390       // reverse and return a copy
391       unsigned last = numpts - 1;
392       dwg_point_2d *new = malloc (numpts * sizeof (BITCODE_2RD));
393       //fprintf (stderr, "%u pts, sum %f: reverse orient\n", numpts, sum);
394       for (unsigned i = 0; i < numpts; i++)
395         {
396           new[i].x = pts[last-i].x;
397           new[i].y = pts[last-i].y;
398         }
399       *pts_p = new;
400       return 1;
401     }
402   else
403     {
404       //fprintf (stderr, "%u pts, sum %f: keep orient\n", numpts, sum);
405       return 0;
406     }
407 }
408 
409 // common properties
410 static void
dwg_geojson_feature(Bit_Chain * restrict dat,Dwg_Object * restrict obj,const char * restrict subclass)411 dwg_geojson_feature (Bit_Chain *restrict dat, Dwg_Object *restrict obj,
412                      const char *restrict subclass)
413 {
414   int error;
415   char *name;
416   char tmp[64];
417 
418   PAIR_Sc (type, "Feature");
419   sprintf (tmp, "%lX", obj->handle.value);
420   PAIR_Sc (id, tmp);
421   KEY (properties);
422   SAMEHASH;
423   PAIR_S (SubClasses, subclass);
424   if (obj->supertype == DWG_SUPERTYPE_ENTITY)
425     {
426       Dwg_Object *layer
427           = obj->tio.entity->layer ? obj->tio.entity->layer->obj : NULL;
428       if (layer
429           && (layer->type == DWG_TYPE_LAYER
430               || layer->type == DWG_TYPE_DICTIONARY))
431         {
432           name = dwg_obj_table_get_name (layer, &error);
433           if (!error)
434             {
435               PAIR_S (Layer, name);
436               if (IS_FROM_TU (dat))
437                 free (name);
438             }
439         }
440 
441       // See #95: index as int or rgb as hexstring
442       if (dat->version >= R_2004
443           && (obj->tio.entity->color.method == 0xc3     // Truecolor
444               || obj->tio.entity->color.method == 0xc2) // Entity
445           && obj->tio.entity->color.index == 256)
446         {
447           sprintf (tmp, "#%06X", obj->tio.entity->color.rgb & 0xffffff);
448           PAIR_Sc (Color, tmp);
449         }
450       else if ((obj->tio.entity->color.index != 256)
451                || (dat->version >= R_2004
452                    && obj->tio.entity->color.method != 0xc0 // ByLayer
453                    && obj->tio.entity->color.method != 0xc1 // ByBlock
454                    && obj->tio.entity->color.method != 0xc8 // none
455                    ))
456         {
457           // no names for the first palette entries yet.
458           PAIR_D (Color, obj->tio.entity->color.index);
459         }
460 
461       name = dwg_ent_get_ltype_name (obj->tio.entity, &error);
462       if (!error && strNE (name, "ByLayer")) // skip the default
463         {
464           PAIR_S (Linetype, name);
465           if (IS_FROM_TU (dat))
466             free (name);
467         }
468     }
469 
470   // if has notes and opt. an mtext frame_text
471   if (obj->type == DWG_TYPE_GEOPOSITIONMARKER)
472     {
473       Dwg_Entity_GEOPOSITIONMARKER *_obj
474           = obj->tio.entity->tio.GEOPOSITIONMARKER;
475       if (IS_FROM_TU (dat))
476         {
477           char *utf8 = bit_convert_TU ((BITCODE_TU)_obj->notes);
478           PAIR_S (Text, utf8)
479           free (utf8);
480         }
481       else
482         {
483           PAIR_S (Text, _obj->notes)
484         }
485     }
486   else if (obj->type == DWG_TYPE_TEXT)
487     {
488       Dwg_Entity_TEXT *_obj = obj->tio.entity->tio.TEXT;
489       if (IS_FROM_TU (dat))
490         {
491           char *utf8 = bit_convert_TU ((BITCODE_TU)_obj->text_value);
492           PAIR_S (Text, utf8)
493           free (utf8);
494         }
495       else
496         {
497           PAIR_S (Text, _obj->text_value)
498         }
499     }
500   else if (obj->type == DWG_TYPE_MTEXT)
501     {
502       Dwg_Entity_MTEXT *_obj = obj->tio.entity->tio.MTEXT;
503       if (IS_FROM_TU (dat))
504         {
505           char *utf8 = bit_convert_TU ((BITCODE_TU)_obj->text);
506           PAIR_S (Text, utf8)
507           free (utf8);
508         }
509       else
510         {
511           PAIR_S (Text, _obj->text)
512         }
513     }
514   else if (obj->type == DWG_TYPE_INSERT)
515     {
516       Dwg_Entity_INSERT *_obj = obj->tio.entity->tio.INSERT;
517       Dwg_Object *hdr = dwg_ref_get_object (_obj->block_header, &error);
518       if (!error && hdr && hdr->type == DWG_TYPE_BLOCK_HEADER)
519         {
520           Dwg_Object_BLOCK_HEADER *_hdr = hdr->tio.object->tio.BLOCK_HEADER;
521           char *text;
522           if (IS_FROM_TU (dat))
523             text = bit_convert_TU ((BITCODE_TU)_hdr->name);
524           else
525             text = _hdr->name;
526           if (text)
527             {
528               PAIR_S (name, text);
529               if (IS_FROM_TU (dat))
530                 free (text);
531             }
532         }
533     }
534   else if (obj->type == DWG_TYPE_MINSERT)
535     {
536       Dwg_Entity_MINSERT *_obj = obj->tio.entity->tio.MINSERT;
537       Dwg_Object *hdr = dwg_ref_get_object (_obj->block_header, &error);
538       if (!error && hdr && hdr->type == DWG_TYPE_BLOCK_HEADER)
539         {
540           Dwg_Object_BLOCK_HEADER *_hdr = hdr->tio.object->tio.BLOCK_HEADER;
541           char *text;
542           if (IS_FROM_TU (dat))
543             text = bit_convert_TU ((BITCODE_TU)_hdr->name);
544           else
545             text = _hdr->name;
546           if (text)
547             {
548               PAIR_S (name, text);
549               if (IS_FROM_TU (dat))
550                 free (text);
551             }
552         }
553     }
554   // PAIR_NULL(ExtendedEntity);
555   sprintf (tmp, "%lX", obj->handle.value);
556   LASTPAIR_Sc (EntityHandle, tmp);
557   ENDHASH;
558 }
559 
560 #define FEATURE(subclass, obj)                                                \
561   HASH;                                                                       \
562   dwg_geojson_feature (dat, obj, #subclass)
563 #define ENDFEATURE                                                            \
564   if (is_last)                                                                \
565     LASTENDHASH                                                               \
566   else                                                                        \
567     ENDHASH
568 
569 static int
dwg_geojson_LWPOLYLINE(Bit_Chain * restrict dat,Dwg_Object * restrict obj,int is_last)570 dwg_geojson_LWPOLYLINE (Bit_Chain *restrict dat, Dwg_Object *restrict obj, int is_last)
571 {
572   BITCODE_BL j, last_j;
573   Dwg_Entity_LWPOLYLINE *_obj = obj->tio.entity->tio.LWPOLYLINE;
574   dwg_point_2d *pts = (dwg_point_2d*)_obj->points;
575   if (!_obj->points)
576     return 1;
577 
578   FEATURE (AcDbEntity : AcDbLwPolyline, obj);
579   // if closed and num_points > 3 use a Polygon
580   if (_obj->flag & 512 && _obj->num_points > 3)
581     {
582       int changed = normalize_polygon_orient (_obj->num_points, &pts); // RFC7946
583       GEOMETRY (Polygon)
584       KEY (coordinates);
585       ARRAY;
586       ARRAY;
587       for (j = 0; j < _obj->num_points; j++)
588         VALUE_2DPOINT (pts[j].x, pts[j].y)
589       LASTVALUE_2DPOINT (pts[0].x, pts[0].y);
590       LASTENDARRAY;
591       LASTENDARRAY;
592       if (changed)
593         free (pts);
594     }
595   else
596     {
597       GEOMETRY (LineString)
598       KEY (coordinates);
599       ARRAY;
600       last_j = _obj->num_points - 1;
601       for (j = 0; j < last_j; j++)
602         VALUE_2DPOINT (pts[j].x, pts[j].y);
603       LASTVALUE_2DPOINT (pts[last_j].x, pts[last_j].y);
604       LASTENDARRAY;
605     }
606   ENDGEOMETRY;
607   ENDFEATURE;
608   return 1;
609 }
610 
611 /* returns 0 if object could be printed
612  */
613 static int
dwg_geojson_variable_type(Dwg_Data * restrict dwg,Bit_Chain * restrict dat,Dwg_Object * restrict obj,int is_last)614 dwg_geojson_variable_type (Dwg_Data *restrict dwg, Bit_Chain *restrict dat,
615                            Dwg_Object *restrict obj, int is_last)
616 {
617   int i;
618   char *dxfname;
619   Dwg_Class *klass;
620   int is_entity;
621 
622   i = obj->type - 500;
623   if (i < 0 || i >= (int)dwg->num_classes)
624     return 0;
625 
626   klass = &dwg->dwg_class[i];
627   if (!klass || !klass->dxfname)
628     return DWG_ERR_INTERNALERROR;
629   dxfname = klass->dxfname;
630   // almost always false
631   is_entity = dwg_class_is_entity (klass);
632 
633   if (strEQc (dxfname, "LWPOLYLINE"))
634     {
635       return dwg_geojson_LWPOLYLINE (dat, obj, is_last);
636     }
637   /*
638   if (strEQc (dxfname, "GEODATA"))
639     {
640       Dwg_Object_GEODATA *_obj = obj->tio.object->tio.GEODATA;
641       WARN_UNSTABLE_CLASS;
642       FEATURE (AcDbObject : AcDbGeoData, obj);
643       // which fields? transformation for the world-coordinates?
644       // crs links of type proj4, ogcwkt, esriwkt or such?
645       ENDFEATURE;
646       return 0;
647     }
648   */
649   if (strEQc (dxfname, "GEOPOSITIONMARKER"))
650     {
651       Dwg_Entity_GEOPOSITIONMARKER *_obj
652           = obj->tio.entity->tio.GEOPOSITIONMARKER;
653       WARN_UNSTABLE_CLASS;
654       // now even with text
655       FEATURE (AcDbEntity : AcDbGeoPositionMarker, obj);
656       GEOMETRY (Point);
657       KEY (coordinates);
658       if (fabs (_obj->position.z) > 0.000001)
659         VALUE_3DPOINT (_obj->position.x, _obj->position.y, _obj->position.z)
660       else
661         VALUE_2DPOINT (_obj->position.x, _obj->position.y);
662       ENDGEOMETRY;
663       ENDFEATURE;
664       return 1;
665     }
666 
667   return 0;
668 }
669 
670 static int
dwg_geojson_object(Bit_Chain * restrict dat,Dwg_Object * restrict obj,int is_last)671 dwg_geojson_object (Bit_Chain *restrict dat, Dwg_Object *restrict obj, int is_last)
672 {
673   switch (obj->type)
674     {
675     case DWG_TYPE_INSERT: // Just the insertion point yet
676       {
677         Dwg_Entity_INSERT *_obj = obj->tio.entity->tio.INSERT;
678         FEATURE (AcDbEntity : AcDbBlockReference, obj);
679         // TODO: explode insert into a GeometryCollection
680         GEOMETRY (Point);
681         KEY (coordinates);
682         LASTFIELD_3DPOINT (ins_pt);
683         ENDGEOMETRY;
684         ENDFEATURE;
685         return 1;
686       }
687     case DWG_TYPE_MINSERT:
688       // a grid of INSERT's (named points)
689       // dwg_geojson_MINSERT(dat, obj);
690       LOG_TRACE ("MINSERT not yet supported")
691       break;
692     case DWG_TYPE_POLYLINE_2D:
693       {
694         int error;
695         BITCODE_BL j, numpts;
696         bool is_polygon = false;
697         int changed = 0;
698         dwg_point_2d *pts, *orig;
699         Dwg_Entity_POLYLINE_2D *_obj = obj->tio.entity->tio.POLYLINE_2D;
700         numpts = dwg_object_polyline_2d_get_numpoints (obj, &error);
701         if (error || !numpts)
702           return 0;
703         pts = dwg_object_polyline_2d_get_points (obj, &error);
704         if (error || !pts)
705           return 0;
706         // if closed and num_points > 3 use a Polygon
707         FEATURE (AcDbEntity : AcDbPolyline, obj);
708         if (_obj->flag & 512 && numpts > 3)
709           {
710             orig = pts; // pts is already a new copy
711             changed = normalize_polygon_orient (numpts, &pts); // RFC7946
712             if (changed)
713               free (orig);
714             GEOMETRY (Polygon)
715             KEY (coordinates);
716             ARRAY;
717             ARRAY;
718             for (j = 0; j < numpts; j++)
719               VALUE_2DPOINT (pts[j].x, pts[j].y)
720             LASTVALUE_2DPOINT (pts[0].x, pts[0].y);
721             LASTENDARRAY;
722             LASTENDARRAY;
723             if (changed)
724               free (pts);
725           }
726         else
727           {
728             GEOMETRY (LineString)
729             KEY (coordinates);
730             ARRAY;
731             for (j = 0; j < numpts; j++)
732               {
733                 if (j == numpts - 1)
734                   LASTVALUE_2DPOINT (pts[j].x, pts[j].y)
735                 else
736                   VALUE_2DPOINT (pts[j].x, pts[j].y);
737               }
738             free (pts);
739             LASTENDARRAY;
740           }
741         ENDGEOMETRY;
742         ENDFEATURE;
743         return 1;
744       }
745     case DWG_TYPE_POLYLINE_3D:
746       {
747         int error;
748         BITCODE_BL j, numpts;
749         dwg_point_3d *pts;
750         numpts = dwg_object_polyline_3d_get_numpoints (obj, &error);
751         if (error || !numpts)
752           return 0;
753         pts = dwg_object_polyline_3d_get_points (obj, &error);
754         if (error || !pts)
755           return 0;
756         FEATURE (AcDbEntity : AcDbPolyline, obj);
757         GEOMETRY (LineString);
758         KEY (coordinates);
759         ARRAY;
760         for (j = 0; j < numpts; j++)
761           {
762             if (j == numpts - 1)
763               {
764                 LASTVALUE_3DPOINT (pts[j].x, pts[j].y, pts[j].z);
765               }
766             else
767               {
768                 VALUE_3DPOINT (pts[j].x, pts[j].y, pts[j].z);
769               }
770           }
771         free (pts);
772         LASTENDARRAY;
773         ENDGEOMETRY;
774         ENDFEATURE;
775         return 1;
776       }
777     case DWG_TYPE_ARC:
778       // needs explosion of arc into lines
779       // dwg_geojson_ARC(dat, obj);
780       LOG_TRACE ("ARC not yet supported")
781       break;
782     case DWG_TYPE_CIRCLE:
783       // needs explosion of arc into lines
784       // dwg_geojson_CIRCLE(dat, obj);
785       LOG_TRACE ("CIRCLE not yet supported")
786       break;
787     case DWG_TYPE_LINE:
788       {
789         Dwg_Entity_LINE *_obj = obj->tio.entity->tio.LINE;
790         FEATURE (AcDbEntity : AcDbLine, obj);
791         GEOMETRY (LineString);
792         KEY (coordinates);
793         ARRAY;
794         FIELD_3DPOINT (start);
795         LASTFIELD_3DPOINT (end);
796         LASTENDARRAY;
797         ENDGEOMETRY;
798         ENDFEATURE;
799         return 1;
800       }
801     case DWG_TYPE_POINT:
802       {
803         Dwg_Entity_POINT *_obj = obj->tio.entity->tio.POINT;
804         FEATURE (AcDbEntity : AcDbPoint, obj);
805         GEOMETRY (Point);
806         KEY (coordinates);
807         if (fabs (_obj->z) > 0.000001)
808           {
809             LASTVALUE_3DPOINT (_obj->x, _obj->y, _obj->z);
810           }
811         else
812           {
813             LASTVALUE_2DPOINT (_obj->x, _obj->y);
814           }
815         ENDGEOMETRY;
816         ENDFEATURE;
817         return 1;
818       }
819     case DWG_TYPE__3DFACE:
820       // really a Polygon
821       // dwg_geojson__3DFACE(dat, obj);
822       LOG_TRACE ("3DFACE not yet supported")
823       break;
824     case DWG_TYPE_POLYLINE_PFACE:
825       // dwg_geojson_POLYLINE_PFACE(dat, obj);
826       LOG_TRACE ("POLYLINE_PFACE not yet supported")
827       break;
828     case DWG_TYPE_POLYLINE_MESH:
829       // dwg_geojson_POLYLINE_MESH(dat, obj);
830       LOG_TRACE ("POLYLINE_MESH not yet supported")
831       break;
832     case DWG_TYPE_SOLID:
833       // dwg_geojson_SOLID(dat, obj);
834       LOG_TRACE ("SOLID not yet supported")
835       break;
836     case DWG_TYPE_TRACE:
837       // dwg_geojson_TRACE(dat, obj);
838       LOG_TRACE ("TRACE not yet supported")
839       break;
840     case DWG_TYPE_ELLIPSE:
841       // dwg_geojson_ELLIPSE(dat, obj);
842       LOG_TRACE ("ELLIPSE not yet supported")
843       break;
844     case DWG_TYPE_SPLINE:
845       // dwg_geojson_SPLINE(dat, obj);
846       LOG_TRACE ("SPLINE not yet supported")
847       break;
848     case DWG_TYPE_HATCH:
849       // dwg_geojson_HATCH(dat, obj);
850       break;
851     case DWG_TYPE__3DSOLID:
852       // dwg_geojson__3DSOLID(dat, obj);
853       break;
854     case DWG_TYPE_REGION:
855       // dwg_geojson_REGION(dat, obj);
856       break;
857     case DWG_TYPE_BODY:
858       // dwg_geojson_BODY(dat, obj);
859       break;
860     case DWG_TYPE_RAY:
861       // dwg_geojson_RAY(dat, obj);
862       LOG_TRACE ("RAY not yet supported")
863       break;
864     case DWG_TYPE_XLINE:
865       // dwg_geojson_XLINE(dat, obj);
866       LOG_TRACE ("XLINE not yet supported")
867       break;
868     case DWG_TYPE_TEXT:
869       {
870         // add Text property to a point
871         Dwg_Entity_TEXT *_obj = obj->tio.entity->tio.TEXT;
872         FEATURE (AcDbEntity : AcDbText, obj);
873         GEOMETRY (Point);
874         KEY (coordinates);
875         LASTFIELD_2DPOINT (ins_pt);
876         ENDGEOMETRY;
877         ENDFEATURE;
878         return 1;
879       }
880     case DWG_TYPE_MTEXT:
881       {
882         // add Text property to a point
883         Dwg_Entity_MTEXT *_obj = obj->tio.entity->tio.MTEXT;
884         FEATURE (AcDbEntity : AcDbMText, obj);
885         GEOMETRY (Point);
886         KEY (coordinates);
887         LASTFIELD_3DPOINT (ins_pt);
888         ENDGEOMETRY;
889         ENDFEATURE;
890         return 1;
891       }
892     case DWG_TYPE_MLINE:
893       // dwg_geojson_MLINE(dat, obj);
894       LOG_TRACE ("MLINE not yet supported")
895       break;
896     case DWG_TYPE_LWPOLYLINE:
897       return dwg_geojson_LWPOLYLINE (dat, obj, is_last);
898     default:
899       if (obj->parent && obj->type != obj->parent->layout_type)
900         return dwg_geojson_variable_type (obj->parent, dat, obj, is_last);
901     }
902   return 0;
903 }
904 
905 static int
geojson_entities_write(Bit_Chain * restrict dat,Dwg_Data * restrict dwg)906 geojson_entities_write (Bit_Chain *restrict dat, Dwg_Data *restrict dwg)
907 {
908   BITCODE_BL i;
909   int success;
910   SECTION (features);
911   for (i = 0; i < dwg->num_objects; i++)
912     {
913       int is_last = i == dwg->num_objects - 1;
914       Dwg_Object *obj = &dwg->object[i];
915       success = dwg_geojson_object (dat, obj, is_last);
916       if (is_last && !success) // needed for the LASTFEATURE comma. end with an empty dummy
917         {
918           HASH
919           PAIR_Sc (type, "Feature");
920           PAIR_NULL (properties);
921           LASTPAIR_NULL (geometry);
922           LASTENDHASH;
923         }
924     }
925   ENDSEC (); // because afterwards is always the final geocoding object
926   return 0;
927 }
928 
929 EXPORT int
dwg_write_geojson(Bit_Chain * restrict dat,Dwg_Data * restrict dwg)930 dwg_write_geojson (Bit_Chain *restrict dat, Dwg_Data *restrict dwg)
931 {
932   // const int minimal = dwg->opts & DWG_OPTS_MINIMAL;
933   char date[12] = "YYYY-MM-DD";
934   time_t rawtime;
935 
936   if (!dwg->num_objects)
937     goto fail;
938 
939   HASH;
940   PAIR_Sc (type, "FeatureCollection");
941 
942   // array of features
943   if (geojson_entities_write (dat, dwg))
944     goto fail;
945 
946   KEY (geocoding);
947   HASH;
948   time (&rawtime);
949   strftime (date, 12, "%Y-%m-%d", localtime (&rawtime));
950   PAIR_Sc (creation_date, date);
951   KEY (generator);
952   HASH;
953   KEY (author);
954   HASH;
955   LASTPAIR_Sc (name, "dwgread");
956   ENDHASH;
957   PAIR_Sc (package, PACKAGE_NAME);
958   LASTPAIR_Sc (version, PACKAGE_VERSION);
959   LASTENDHASH;
960   // PAIR_S(license, "?");
961   LASTENDHASH;
962 
963   LASTENDHASH;
964   return 0;
965 fail:
966   return 1;
967 }
968 
969 #undef IS_PRINT
970