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