/*========================================================================= Program: Visualization Toolkit Module: vtkGeoJSONReader.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkGeoJSONReader.h" // VTK Includes #include "vtkAbstractArray.h" #include "vtkBitArray.h" #include "vtkCellArray.h" #include "vtkCellData.h" #include "vtkDoubleArray.h" #include "vtkGeoJSONFeature.h" #include "vtkInformation.h" #include "vtkInformationVector.h" #include "vtkIntArray.h" #include "vtkNew.h" #include "vtkObjectFactory.h" #include "vtkPolyData.h" #include "vtkStringArray.h" #include "vtkTriangleFilter.h" #include "vtk_jsoncpp.h" #include "vtksys/FStream.hxx" // C++ includes #include #include #include vtkStandardNewMacro(vtkGeoJSONReader); //------------------------------------------------------------------------------ class vtkGeoJSONReader::GeoJSONReaderInternal { public: struct GeoJSONProperty_t { std::string Name; vtkVariant Value; }; using GeoJSONProperty = struct GeoJSONProperty_t; // List of property names to read. Property value is used the default std::vector PropertySpecs; // Parse the Json Value corresponding to the root of the geoJSON data from the file void ParseRoot(const Json::Value& root, vtkPolyData* output, bool outlinePolygons, const char* serializedPropertiesArrayName); // Verify if file exists and can be read by the parser // If exists, parse into Jsoncpp data structure int CanParseFile(const char* filename, Json::Value& root); // Verify if string can be read by the parser // If exists, parse into Jsoncpp data structure int CanParseString(char* input, Json::Value& root); // Extract property values from json node void ParseFeatureProperties(const Json::Value& propertiesNode, std::vector& properties, const char* serializedPropertiesArrayName); void InsertFeatureProperties( vtkPolyData* polyData, const std::vector& featureProperties); }; //------------------------------------------------------------------------------ void vtkGeoJSONReader::GeoJSONReaderInternal::ParseRoot(const Json::Value& root, vtkPolyData* output, bool outlinePolygons, const char* serializedPropertiesArrayName) { // Initialize geometry containers vtkNew points; points->SetDataTypeToDouble(); output->SetPoints(points); vtkNew verts; output->SetVerts(verts); vtkNew lines; output->SetLines(lines); vtkNew polys; output->SetPolys(polys); // Initialize feature-id array vtkStringArray* featureIdArray = vtkStringArray::New(); featureIdArray->SetName("feature-id"); output->GetCellData()->AddArray(featureIdArray); featureIdArray->Delete(); // Initialize properties arrays if (serializedPropertiesArrayName) { vtkStringArray* propertiesArray = vtkStringArray::New(); propertiesArray->SetName(serializedPropertiesArrayName); output->GetCellData()->AddArray(propertiesArray); propertiesArray->Delete(); } vtkAbstractArray* array; std::vector::iterator iter = this->PropertySpecs.begin(); for (; iter != this->PropertySpecs.end(); ++iter) { array = nullptr; switch (iter->Value.GetType()) { case VTK_BIT: array = vtkBitArray::New(); break; case VTK_INT: array = vtkIntArray::New(); break; case VTK_DOUBLE: array = vtkDoubleArray::New(); break; case VTK_STRING: array = vtkStringArray::New(); break; default: vtkGenericWarningMacro("unexpected data type " << iter->Value.GetType()); break; } // Skip if array not created for some reason if (!array) { continue; } array->SetName(iter->Name.c_str()); output->GetCellData()->AddArray(array); array->Delete(); } // Check type Json::Value rootType = root["type"]; if (rootType.isNull()) { vtkGenericWarningMacro(<< "ParseRoot: Missing type node"); return; } // Parse features Json::Value rootFeatures; std::string strRootType = rootType.asString(); std::vector properties; if ("FeatureCollection" == strRootType) { rootFeatures = root["features"]; if (rootFeatures.isNull()) { vtkGenericWarningMacro(<< "ParseRoot: Missing \"features\" node"); return; } if (!rootFeatures.isArray()) { vtkGenericWarningMacro(<< "ParseRoot: features node is not an array"); return; } GeoJSONProperty property; for (Json::Value::ArrayIndex i = 0; i < rootFeatures.size(); i++) { // Append extracted geometry to existing outputData Json::Value featureNode = rootFeatures[i]; Json::Value propertiesNode = featureNode["properties"]; this->ParseFeatureProperties(propertiesNode, properties, serializedPropertiesArrayName); vtkNew feature; feature->SetOutlinePolygons(outlinePolygons); feature->ExtractGeoJSONFeature(featureNode, output); this->InsertFeatureProperties(output, properties); } } else if ("Feature" == strRootType) { // Process single feature this->ParseFeatureProperties(root, properties, serializedPropertiesArrayName); vtkNew feature; feature->SetOutlinePolygons(outlinePolygons); // Next call adds (exactly) one cell to the polydata feature->ExtractGeoJSONFeature(root, output); // Next call adds (exactly) one tuple to the polydata's cell data this->InsertFeatureProperties(output, properties); } else { vtkGenericWarningMacro(<< "ParseRoot: do not support root type \"" << strRootType << "\""); } } //------------------------------------------------------------------------------ int vtkGeoJSONReader::GeoJSONReaderInternal::CanParseFile(const char* filename, Json::Value& root) { if (!filename) { vtkGenericWarningMacro(<< "Input filename not specified"); return VTK_ERROR; } vtksys::ifstream file; file.open(filename); if (!file.is_open()) { vtkGenericWarningMacro(<< "Unable to Open File " << filename); return VTK_ERROR; } Json::CharReaderBuilder builder; builder["collectComments"] = false; std::string formattedErrors; // parse the entire geoJSON data into the Json::Value root bool parsedSuccess = parseFromStream(builder, file, &root, &formattedErrors); if (!parsedSuccess) { // Report failures and their locations in the document vtkGenericWarningMacro(<< "Failed to parse JSON" << endl << formattedErrors); return VTK_ERROR; } return VTK_OK; } //------------------------------------------------------------------------------ int vtkGeoJSONReader::GeoJSONReaderInternal::CanParseString(char* input, Json::Value& root) { if (!input) { vtkGenericWarningMacro(<< "Input string is empty"); return VTK_ERROR; } Json::CharReaderBuilder builder; builder["collectComments"] = false; std::unique_ptr reader(builder.newCharReader()); std::string formattedErrors; // parse the entire geoJSON data into the Json::Value root bool parsedSuccess = reader->parse(input, input + strlen(input), &root, &formattedErrors); if (!parsedSuccess) { // Report failures and their locations in the document vtkGenericWarningMacro(<< "Failed to parse JSON" << endl << formattedErrors); return VTK_ERROR; } return VTK_OK; } //------------------------------------------------------------------------------ void vtkGeoJSONReader::GeoJSONReaderInternal::ParseFeatureProperties( const Json::Value& propertiesNode, std::vector& featureProperties, const char* serializedPropertiesArrayName) { featureProperties.clear(); GeoJSONProperty spec; GeoJSONProperty property; std::vector::iterator iter = this->PropertySpecs.begin(); for (; iter != this->PropertySpecs.end(); ++iter) { spec = *iter; property.Name = spec.Name; Json::Value propertyNode = propertiesNode[spec.Name]; if (propertyNode.isNull()) { property.Value = spec.Value; featureProperties.push_back(property); continue; } // (else) switch (spec.Value.GetType()) { case VTK_BIT: property.Value = vtkVariant(propertyNode.asBool()); break; case VTK_DOUBLE: property.Value = vtkVariant(propertyNode.asDouble()); break; case VTK_INT: property.Value = vtkVariant(propertyNode.asInt()); break; case VTK_STRING: property.Value = vtkVariant(propertyNode.asString()); break; } featureProperties.push_back(property); } // Add GeoJSON string if enabled if (serializedPropertiesArrayName) { property.Name = serializedPropertiesArrayName; Json::StreamWriterBuilder builder; builder["commentStyle"] = "None"; builder["indentation"] = ""; std::unique_ptr writer(builder.newStreamWriter()); std::stringstream stream; writer->write(propertiesNode, &stream); std::string propString = stream.str(); if (!propString.empty() && *propString.rbegin() == '\n') { propString.resize(propString.size() - 1); } property.Value = vtkVariant(propString); featureProperties.push_back(property); } } //------------------------------------------------------------------------------ void vtkGeoJSONReader::GeoJSONReaderInternal::InsertFeatureProperties( vtkPolyData* polyData, const std::vector& featureProperties) { std::vector::const_iterator iter = featureProperties.begin(); for (; iter != featureProperties.end(); ++iter) { std::string name = iter->Name; vtkVariant value = iter->Value; vtkAbstractArray* array = polyData->GetCellData()->GetAbstractArray(name.c_str()); switch (array->GetDataType()) { case VTK_BIT: vtkArrayDownCast(array)->InsertNextValue(value.ToChar()); break; case VTK_DOUBLE: vtkArrayDownCast(array)->InsertNextValue(value.ToDouble()); break; case VTK_INT: vtkArrayDownCast(array)->InsertNextValue(value.ToInt()); break; case VTK_STRING: vtkArrayDownCast(array)->InsertNextValue(value.ToString()); break; } } } //------------------------------------------------------------------------------ vtkGeoJSONReader::vtkGeoJSONReader() { this->FileName = nullptr; this->StringInput = nullptr; this->StringInputMode = false; this->TriangulatePolygons = false; this->OutlinePolygons = false; this->SerializedPropertiesArrayName = nullptr; this->SetNumberOfInputPorts(0); this->SetNumberOfOutputPorts(1); this->Internal = new GeoJSONReaderInternal; } //------------------------------------------------------------------------------ vtkGeoJSONReader::~vtkGeoJSONReader() { delete[] FileName; delete[] StringInput; delete Internal; } //------------------------------------------------------------------------------ void vtkGeoJSONReader::AddFeatureProperty(const char* name, vtkVariant& typeAndDefaultValue) { GeoJSONReaderInternal::GeoJSONProperty property; // Traverse internal list checking if name already used std::vector::iterator iter = this->Internal->PropertySpecs.begin(); for (; iter != this->Internal->PropertySpecs.end(); ++iter) { if (iter->Name == name) { vtkGenericWarningMacro(<< "Overwriting property spec for name " << name); property.Name = name; property.Value = typeAndDefaultValue; *iter = property; break; } } // If not found, add to list if (iter == this->Internal->PropertySpecs.end()) { property.Name = name; property.Value = typeAndDefaultValue; this->Internal->PropertySpecs.push_back(property); vtkDebugMacro(<< "Added feature property " << property.Name); } } //------------------------------------------------------------------------------ int vtkGeoJSONReader::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** vtkNotUsed(request), vtkInformationVector* outputVector) { // Get the info object vtkInformation* outInfo = outputVector->GetInformationObject(0); // Get the output vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); // Parse either string input of file, depending on mode Json::Value root; int parseResult = 0; if (this->StringInputMode) { parseResult = this->Internal->CanParseString(this->StringInput, root); } else { parseResult = this->Internal->CanParseFile(this->FileName, root); } if (parseResult != VTK_OK) { return VTK_ERROR; } // If parsed successfully into Json, then convert it // into appropriate vtkPolyData if (root.isObject()) { this->Internal->ParseRoot( root, output, this->OutlinePolygons, this->SerializedPropertiesArrayName); // Convert Concave Polygons to convex polygons using triangulation if (output->GetNumberOfPolys() && this->TriangulatePolygons) { vtkNew filter; filter->SetInputData(output); filter->Update(); output->ShallowCopy(filter->GetOutput()); } } return VTK_OK; } //------------------------------------------------------------------------------ void vtkGeoJSONReader::PrintSelf(ostream& os, vtkIndent indent) { Superclass::PrintSelf(os, indent); os << "vtkGeoJSONReader" << std::endl; os << "Filename: " << this->FileName << std::endl; }