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