(root)/
libredwg-0.13/
src/
out_geojson.c
       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