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