1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkGeoJSONWriter.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 
16 #include "vtkGeoJSONWriter.h"
17 
18 #include "vtkCellArray.h"
19 #include "vtkInformation.h"
20 #include "vtkLookupTable.h"
21 #include "vtkMath.h"
22 #include "vtkObjectFactory.h"
23 #include "vtkPointData.h"
24 #include "vtkPolyData.h"
25 
26 #include <sstream>
27 
28 vtkStandardNewMacro(vtkGeoJSONWriter);
29 
30 #define VTK_GJWRITER_MAXPOINTS 32000
31 
32 class vtkGeoJSONWriter::Internals
33 {
34 public:
Internals()35   Internals()
36   {
37     this->MaxBufferSize = 128;
38     this->Buffer = new char[this->MaxBufferSize];
39     this->Top = this->Buffer;
40   };
~Internals()41   ~Internals()
42   {
43     delete[] this->Buffer;
44   }
GetSize()45   inline size_t GetSize()
46   {
47     return this->Top-this->Buffer;
48   }
Clear()49   void Clear()
50   {
51     this->Top = this->Buffer;
52   }
Grow()53   inline void Grow()
54   {
55     this->MaxBufferSize*=2;
56     //cerr << "GROW " << this->MaxBufferSize << endl;
57     char *biggerBuffer = new char[this->MaxBufferSize];
58     size_t curSize = this->Top-this->Buffer;
59     memcpy(biggerBuffer, this->Buffer, curSize);
60     delete[] this->Buffer;
61     this->Buffer = biggerBuffer;
62     this->Top = this->Buffer+curSize;
63   }
append(const char * newcontent)64   inline void append(const char *newcontent)
65   {
66     while (this->Top+strlen(newcontent)>=this->Buffer+this->MaxBufferSize)
67     {
68       this->Grow();
69     }
70     int nchars = snprintf(this->Top, this->MaxBufferSize, "%s", newcontent);
71     this->Top+=nchars;
72   }
append(const double newcontent)73   inline void append(const double newcontent)
74   {
75     snprintf(this->NumBuffer, 64, "%g", newcontent);
76     while (this->Top+strlen(NumBuffer)>=this->Buffer+this->MaxBufferSize)
77     {
78       this->Grow();
79     }
80      int nchars = snprintf(this->Top, this->MaxBufferSize, "%s", this->NumBuffer);
81      this->Top+=nchars;
82   }
83   char *Buffer;
84   char *Top;
85   size_t MaxBufferSize;
86   char NumBuffer[64];
87 };
88 
89  //------------------------------------------------------------------------------
vtkGeoJSONWriter()90  vtkGeoJSONWriter::vtkGeoJSONWriter()
91  {
92    this->FileName = nullptr;
93    this->OutputString = nullptr;
94    this->SetNumberOfOutputPorts(0);
95    this->WriteToOutputString = false;
96    this->ScalarFormat = 2;
97    this->LookupTable = nullptr;
98    this->WriterHelper = new vtkGeoJSONWriter::Internals();
99  }
100 
101  //------------------------------------------------------------------------------
~vtkGeoJSONWriter()102  vtkGeoJSONWriter::~vtkGeoJSONWriter()
103  {
104    this->SetFileName(nullptr);
105    delete[] this->OutputString;
106    this->SetLookupTable(nullptr);
107    delete this->WriterHelper;
108  }
109 
110  //------------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)111  void vtkGeoJSONWriter::PrintSelf(ostream & os, vtkIndent indent)
112  {
113    this->Superclass::PrintSelf(os, indent);
114    os << indent << "FileName: "
115       << (this->FileName?this->FileName:"NONE") << endl;
116    os << indent << "WriteToOutputString: "
117       << (this->WriteToOutputString?"True":"False") << endl;
118    os << indent << "ScalarFormat: " << this->ScalarFormat << endl;
119  }
120 
121  //------------------------------------------------------------------------------
FillInputPortInformation(int port,vtkInformation * info)122  int vtkGeoJSONWriter::FillInputPortInformation(int port, vtkInformation *info)
123  {
124    if (port == 0)
125    {
126      info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData");
127    }
128    return 1;
129  }
130 
131  //------------------------------------------------------------------------------
OpenFile()132  ostream *vtkGeoJSONWriter::OpenFile()
133  {
134    vtkDebugMacro(<<"Opening file\n");
135 
136    ostream *fptr;
137 
138    if (!this->WriteToOutputString)
139    {
140      if (!this->FileName)
141      {
142        vtkErrorMacro(<< "No FileName specified! Can't write!");
143        return nullptr;
144      }
145 
146      fptr = new ofstream(this->FileName, ios::out);
147    }
148    else
149    {
150      // Get rid of any old output string.
151      if (this->OutputString)
152      {
153        delete [] this->OutputString;
154        this->OutputString = nullptr;
155        this->OutputStringLength = 0;
156      }
157      fptr = new std::ostringstream;
158    }
159 
160    if (fptr->fail())
161    {
162      vtkErrorMacro(<< "Unable to open file: "<< this->FileName);
163      delete fptr;
164      return nullptr;
165    }
166 
167    return fptr;
168  }
169 
170  //------------------------------------------------------------------------------
CloseFile(ostream * fp)171  void vtkGeoJSONWriter::CloseFile(ostream *fp)
172  {
173    vtkDebugMacro(<<"Closing file\n");
174 
175    if ( fp != nullptr )
176    {
177      if (this->WriteToOutputString)
178      {
179        std::ostringstream *ostr =
180          static_cast<std::ostringstream*>(fp);
181 
182        delete [] this->OutputString;
183        this->OutputStringLength = static_cast<int>(ostr->str().size());
184        //+1's account for null terminator
185        this->OutputString = new char[ostr->str().size()+1];
186        memcpy(this->OutputString, ostr->str().c_str(),
187          this->OutputStringLength+1);
188      }
189 
190      delete fp;
191    }
192  }
193 
194  //------------------------------------------------------------------------------
ConditionalComma(vtkIdType cnt,vtkIdType limit)195  void vtkGeoJSONWriter::ConditionalComma(
196    vtkIdType cnt, vtkIdType limit)
197  {
198    if (cnt+1 != limit)
199    {
200      this->WriterHelper->append(",");
201    }
202  }
203 
204  //------------------------------------------------------------------------------
WriteScalar(vtkDataArray * da,vtkIdType ptId)205  void vtkGeoJSONWriter::WriteScalar(
206    vtkDataArray *da, vtkIdType ptId)
207  {
208    if (this->ScalarFormat == 0)
209    {
210      return;
211    }
212    if (da)
213    {
214      double b = da->GetTuple1(ptId);
215      if (this->ScalarFormat == 1)
216      {
217        vtkLookupTable *lut = this->GetLookupTable();
218        if (!lut)
219        {
220          lut = vtkLookupTable::New();
221          lut->SetNumberOfColors(256);
222          lut->SetHueRange(0.0,0.667);
223          lut->SetRange(da->GetRange());
224          lut->Build();
225          this->SetLookupTable(lut);
226          lut->Delete();
227        }
228        const unsigned char *color = lut->MapValue(b);
229        this->WriterHelper->append(",");
230        this->WriterHelper->append((double)color[0]/255.0);
231        this->WriterHelper->append(",");
232        this->WriterHelper->append((double)color[1]/255.0);
233        this->WriterHelper->append(",");
234        this->WriterHelper->append((double)color[2]/255.0);
235      }
236      else
237      {
238        if (vtkMath::IsNan(b))
239        {
240          this->WriterHelper->append(",null");
241        }
242        else
243        {
244          this->WriterHelper->append(",");
245          this->WriterHelper->append(b);
246        }
247      }
248    }
249  }
250 
251  //------------------------------------------------------------------------------
WriteData()252  void vtkGeoJSONWriter::WriteData()
253  {
254    ostream *fp;
255    vtkPolyData *input = vtkPolyData::SafeDownCast(this->GetInput());
256 
257    vtkDebugMacro(<<"Writing vtk polygonal data to geojson file...");
258    fp=this->OpenFile();
259    if ( !fp )
260    {
261      return;
262    }
263 
264    this->WriterHelper->append("{\n");
265    this->WriterHelper->append("\"type\": \"Feature\",\n");
266    vtkDataArray *da = input->GetPointData()->GetScalars();
267    if (!da)
268    {
269      da = input->GetPointData()->GetArray(0);
270    }
271    if (da)
272    {
273      switch (this->ScalarFormat)
274      {
275        case 0:
276          this->WriterHelper->append("\"properties\": {\"ScalarFormat\": \"none\"},\n");
277          break;
278        case 1:
279          this->WriterHelper->append("\"properties\": {\"ScalarFormat\": \"rgb\"},\n");
280          break;
281        case 2:
282          double rng[2];
283          da->GetRange(rng);
284          this->WriterHelper->append("\"properties\": {\"ScalarFormat\": \"values\", \"ScalarRange\": [");
285          this->WriterHelper->append(rng[0]);
286          this->WriterHelper->append(",");
287          this->WriterHelper->append(rng[1]);
288          this->WriterHelper->append("] },\n");
289          break;
290      }
291    }
292    else
293    {
294      this->WriterHelper->append("\"properties\": {\"ScalarFormat\": \"none\"},\n");
295    }
296    this->WriterHelper->append("\"geometry\":\n");
297    this->WriterHelper->append("{\n");
298    this->WriterHelper->append("\"type\": \"GeometryCollection\",\n");
299    this->WriterHelper->append("\"geometries\":\n");
300    this->WriterHelper->append("[\n");
301 
302    vtkIdType cellLoc = 0;
303    vtkIdType *cellPts = nullptr;
304    vtkIdType cellSize = 0;
305    vtkIdType numlines, numpolys;
306    numlines = input->GetLines()->GetNumberOfCells();
307    numpolys = input->GetPolys()->GetNumberOfCells();
308 
309    //VERTS
310    vtkCellArray *ca;
311    ca = input->GetVerts();
312    if (ca && ca->GetNumberOfCells())
313    {
314      bool done = false;
315      vtkIdType inCell = 0;
316      vtkIdType ptCnt = 0;
317      do //loop to break into sections with < VTK_GJWRITER_MAXPOINTS points
318      {
319        this->WriterHelper->append("{\n");
320        this->WriterHelper->append("\"type\": \"MultiPoint\",\n");
321        this->WriterHelper->append("\"coordinates\":\n");
322        this->WriterHelper->append("[\n");
323        for (; inCell < ca->GetNumberOfCells() && ptCnt < VTK_GJWRITER_MAXPOINTS; inCell++)
324        {
325          ca->GetCell(cellLoc, cellSize, cellPts);
326          cellLoc += cellSize+1;
327          ptCnt += cellSize;
328          vtkIdType inPt;
329          for (inPt = 0; inPt < cellSize; inPt++)
330          {
331            double coords[3];
332            input->GetPoint(cellPts[inPt], coords);
333            this->WriterHelper->append("[");
334            for (int i=0; i<3; i++)
335            {
336              if (vtkMath::IsNan(coords[i]))
337              {
338                this->WriterHelper->append("null");
339              }
340              else
341              {
342                this->WriterHelper->append(coords[i]);
343              }
344              if (i!=2)
345              {
346                this->WriterHelper->append(",");
347              }
348            }
349            this->WriteScalar(da, cellPts[inPt]);
350            this->WriterHelper->append("]");
351            this->ConditionalComma(inPt, cellSize);
352          }
353          if (ptCnt<VTK_GJWRITER_MAXPOINTS)
354          {
355            this->ConditionalComma(inCell, ca->GetNumberOfCells());
356          }
357          this->WriterHelper->append("\n");
358        }
359        this->WriterHelper->append("]"); //coordinates for this cell array
360        if (inCell < ca->GetNumberOfCells())
361        {
362          ptCnt = 0;
363          this->WriterHelper->append(",\n");
364        }
365        else
366        {
367          if (numlines || numpolys)
368          {
369            this->WriterHelper->append(",");
370          }
371          done = true;
372        }
373      } while (!done);
374    }
375 
376    //lines
377    ca = input->GetLines();
378    if (ca && ca->GetNumberOfCells())
379    {
380      bool done = false;
381      vtkIdType inCell = 0;
382      vtkIdType ptCnt = 0;
383      do //loop to break into sections with < VTK_GJWRITER_MAXPOINTS points
384      {
385        this->WriterHelper->append("{\n");
386        this->WriterHelper->append("\"type\": \"MultiLineString\",\n");
387        this->WriterHelper->append("\"coordinates\":\n");
388        this->WriterHelper->append("[\n");
389        for (; inCell < ca->GetNumberOfCells() && ptCnt < VTK_GJWRITER_MAXPOINTS; inCell++)
390        {
391          this->WriterHelper->append("[ "); //one cell
392          ca->GetCell(cellLoc, cellSize, cellPts);
393          cellLoc += cellSize+1;
394          ptCnt += cellSize;
395          vtkIdType inPt;
396          for (inPt = 0; inPt < cellSize; inPt++)
397          {
398            double coords[3];
399            input->GetPoint(cellPts[inPt], coords);
400            this->WriterHelper->append("[");
401            for (int i =0; i<3; i++)
402            {
403              if (vtkMath::IsNan(coords[i]))
404              {
405                this->WriterHelper->append("null");
406              }
407              else
408              {
409                this->WriterHelper->append(coords[i]);
410              }
411              if (i!=2)
412              {
413                this->WriterHelper->append(",");
414              }
415            }
416            this->WriteScalar(da, cellPts[inPt]);
417            this->WriterHelper->append("]");
418            this->ConditionalComma(inPt, cellSize);
419          }
420          this->WriterHelper->append("]");//one cell
421          if (ptCnt<VTK_GJWRITER_MAXPOINTS)
422          {
423            this->ConditionalComma(inCell, ca->GetNumberOfCells());
424          }
425          this->WriterHelper->append("\n");
426        }
427        this->WriterHelper->append("]"); //coordinates for this cell array
428        this->WriterHelper->append("\n");
429        this->WriterHelper->append("}\n"); //this cell array
430        if (inCell < ca->GetNumberOfCells())
431        {
432          ptCnt = 0;
433          this->WriterHelper->append(",\n");
434        }
435        else
436        {
437          if (numpolys)
438          {
439            this->WriterHelper->append(",");
440          }
441          done = true;
442        }
443      } while (!done);
444    }
445    //polygons
446    ca = input->GetPolys();
447    if (ca && ca->GetNumberOfCells())
448    {
449      bool done = false;
450      vtkIdType inCell = 0;
451      vtkIdType ptCnt = 0;
452      do //loop to break into sections with < VTK_GJWRITER_MAXPOINTS points
453      {
454        this->WriterHelper->append("{\n");
455        this->WriterHelper->append("\"type\": \"MultiPolygon\",\n");
456        this->WriterHelper->append("\"coordinates\":\n");
457        this->WriterHelper->append("[\n");
458        for (; inCell < ca->GetNumberOfCells() && ptCnt < VTK_GJWRITER_MAXPOINTS; inCell++)
459        {
460          this->WriterHelper->append("[[ "); //one cell
461          ca->GetCell(cellLoc, cellSize, cellPts);
462          cellLoc += cellSize+1;
463          ptCnt += cellSize;
464          vtkIdType inPt;
465          for (inPt = 0; inPt < cellSize; inPt++)
466          {
467            double coords[3];
468            input->GetPoint(cellPts[inPt], coords);
469            this->WriterHelper->append("[");
470            for (int i =0; i<3; i++)
471            {
472              if (vtkMath::IsNan(coords[i]))
473              {
474                this->WriterHelper->append("null");
475              }
476              else
477              {
478                this->WriterHelper->append(coords[i]);
479              }
480              if (i!=2)
481              {
482                this->WriterHelper->append(",");
483              }
484            }
485            this->WriteScalar(da, cellPts[inPt]);
486            this->WriterHelper->append("]");
487            this->ConditionalComma(inPt, cellSize);
488          }
489          this->WriterHelper->append(" ]]");//one cell
490          if (ptCnt<VTK_GJWRITER_MAXPOINTS)
491          {
492            this->ConditionalComma(inCell, ca->GetNumberOfCells());
493          }
494          this->WriterHelper->append("\n");
495        }
496        this->WriterHelper->append("]"); //coordinates for this cell array
497        this->WriterHelper->append("\n");
498        this->WriterHelper->append("}\n"); //this cell array
499        if (inCell < ca->GetNumberOfCells())
500        {
501          ptCnt = 0;
502          this->WriterHelper->append(",\n");
503        }
504        else
505        {
506          done = true;
507        }
508      } while (!done);
509    }
510 
511    this->WriterHelper->append("]\n");//feature.geometry.GeometryCollection.geometries
512    this->WriterHelper->append("}\n");//feature.geometry
513    this->WriterHelper->append("}\n");//feature
514 
515    fp->write(this->WriterHelper->Buffer, this->WriterHelper->GetSize());
516    this->WriterHelper->Clear();
517 
518    fp->flush();
519    if (fp->fail())
520    {
521      vtkErrorMacro("Problem writing result check disk space.");
522      delete fp;
523      fp = nullptr;
524    }
525 
526    this->CloseFile(fp);
527  }
528 
529 //------------------------------------------------------------------------------
RegisterAndGetOutputString()530 char *vtkGeoJSONWriter::RegisterAndGetOutputString()
531 {
532   char *tmp = this->OutputString;
533 
534   this->OutputString = nullptr;
535   this->OutputStringLength = 0;
536 
537   return tmp;
538 }
539 
540 //------------------------------------------------------------------------------
GetOutputStdString()541 vtkStdString vtkGeoJSONWriter::GetOutputStdString()
542 {
543   return vtkStdString(this->OutputString, this->OutputStringLength);
544 }
545 
546 //------------------------------------------------------------------------------
547 vtkCxxSetObjectMacro(vtkGeoJSONWriter, LookupTable, vtkLookupTable)
548