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