version 3.10-dev
vtkreader.hh
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3//
4// SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
12#ifndef DUMUX_IO_VTK_VTKREADER_HH
13#define DUMUX_IO_VTK_VTKREADER_HH
14
15#include <iostream>
16#include <iterator>
17#include <algorithm>
18#include <memory>
19#include <type_traits>
20#include <unordered_map>
21#include <utility>
22#include <filesystem>
23
24#include <dune/common/parallel/mpihelper.hh>
25#include <dune/common/exceptions.hh>
26#include <dune/grid/common/capabilities.hh>
27#include <dune/grid/io/file/vtk/common.hh>
28#include <dune/grid/common/gridfactory.hh>
29
30#include <dumux/io/container.hh>
31#include <dumux/io/xml/tinyxml2.h>
32
33namespace Dumux {
34
40{
41public:
45 enum class DataType { cellData, pointData };
46
48 using Data = std::unordered_map<std::string, std::vector<double>>;
49
53 explicit VTKReader(const std::string& fileName)
54 {
55 using namespace tinyxml2;
56 const auto ext = std::filesystem::path(fileName).extension().string();
57 // If in parallel and the file to read is a parallel piece collection (pvtu/pvtp)
58 // read only the piece belonging to the own process. For this to work, the files
59 // have to have exactly the same amount of pieces than processes.
60 fileName_ = Dune::MPIHelper::instance().size() > 1 && ext[1] == 'p' ?
61 getProcessFileName_(fileName) : fileName;
62
63 const auto eResult = doc_.LoadFile(fileName_.c_str());
64 if (eResult != tinyxml2::XML_SUCCESS)
65 DUNE_THROW(Dune::IOError, "Couldn't open XML file " << fileName_ << ".");
66
67 const XMLElement* pieceNode = getPieceNode_();
68 if (pieceNode == nullptr)
69 DUNE_THROW(Dune::IOError, "Couldn't get 'Piece' node in " << fileName_ << ".");
70 }
71
77 bool hasData(const std::string& name, const DataType& type) const
78 {
79 using namespace tinyxml2;
80
81 const XMLElement* pieceNode = getPieceNode_();
82 const XMLElement* dataNode = getDataNode_(pieceNode, type);
83 if (dataNode == nullptr)
84 return false;
85
86 const XMLElement* dataArray = findDataArray_(dataNode, name);
87 if (dataArray == nullptr)
88 return false;
89
90 return true;
91 }
92
99 template<class Container>
100 Container readData(const std::string& name, const DataType& type) const
101 {
102 using namespace tinyxml2;
103
104 const XMLElement* pieceNode = getPieceNode_();
105 const XMLElement* dataNode = getDataNode_(pieceNode, type);
106 if (dataNode == nullptr)
107 DUNE_THROW(Dune::IOError, "Couldn't get 'PointData' or 'CellData' node in " << fileName_ << ".");
108
109 const XMLElement* dataArray = findDataArray_(dataNode, name);
110 if (dataArray == nullptr)
111 DUNE_THROW(Dune::IOError, "Couldn't find the data array " << name << ".");
112
113 return parseDataArray_<Container>(dataArray);
114 }
115
120 template<class Grid>
121 std::unique_ptr<Grid> readGrid(bool verbose = false) const
122 {
123 static_assert(!Dune::Capabilities::isCartesian<Grid>::v, "Grid reader only supports unstructured grid implementations");
124
125 // make a grid factory
126 Dune::GridFactory<Grid> factory;
127
128 // only read on rank 0
129 if (Dune::MPIHelper::instance().rank() == 0)
130 {
131 if (verbose)
132 std::cout << "Reading " << Grid::dimension << "d grid from vtk file " << fileName_ << "." << std::endl;
133 readGrid_(factory, verbose);
134 }
135
136 return std::unique_ptr<Grid>(factory.createGrid());
137 }
138
145 template<class Grid>
146 std::unique_ptr<Grid> readGrid(Dune::GridFactory<Grid>& factory, bool verbose = false) const
147 {
148 static_assert(!Dune::Capabilities::isCartesian<Grid>::v, "Grid reader only supports unstructured grid implementations");
149
150 if (Dune::MPIHelper::instance().rank() == 0)
151 {
152 if (verbose)
153 std::cout << "Reading " << Grid::dimension << "d grid from vtk file " << fileName_ << "." << std::endl;
154 readGrid_(factory, verbose);
155 }
156
157 return std::unique_ptr<Grid>(factory.createGrid());
158 }
159
168 template<class Grid>
169 std::unique_ptr<Grid> readGrid(Dune::GridFactory<Grid>& factory, Data& cellData, Data& pointData, bool verbose = false) const
170 {
171 static_assert(!Dune::Capabilities::isCartesian<Grid>::v, "Grid reader only supports unstructured grid implementations");
172
173 if (Dune::MPIHelper::instance().rank() == 0)
174 {
175 if (verbose)
176 std::cout << "Reading " << Grid::dimension << "d grid from vtk file " << fileName_ << "." << std::endl;
177 readGrid_(factory, verbose);
178 readGridData_(cellData, pointData, verbose);
179 }
180
181 return std::unique_ptr<Grid>(factory.createGrid());
182 }
183
184private:
188 std::string getProcessFileName_(const std::string& pvtkFileName)
189 {
190 using namespace tinyxml2;
191
192 XMLDocument pDoc;
193 const auto eResult = pDoc.LoadFile(pvtkFileName.c_str());
194 if (eResult != XML_SUCCESS)
195 DUNE_THROW(Dune::IOError, "Couldn't open XML file " << pvtkFileName << ".");
196
197 // get the first piece node
198 const XMLElement* pieceNode = getPieceNode_(pDoc, pvtkFileName);
199 const auto myrank = Dune::MPIHelper::instance().rank();
200 for (int rank = 0; rank < myrank; ++rank)
201 {
202 pieceNode = pieceNode->NextSiblingElement("Piece");
203 if (pieceNode == nullptr)
204 DUNE_THROW(Dune::IOError, "Couldn't find 'Piece' node for rank "
205 << rank << " in " << pvtkFileName << ".");
206 }
207
208 const char *vtkFileName = pieceNode->Attribute("Source");
209 if (vtkFileName == nullptr)
210 DUNE_THROW(Dune::IOError, "Couldn't get 'Source' attribute of 'Piece' node no. " << myrank << " in " << pvtkFileName);
211
212 return vtkFileName;
213 }
214
220 template<class Grid>
221 void readGrid_(Dune::GridFactory<Grid>& factory, bool verbose = false) const
222 {
223 using namespace tinyxml2;
224
225 const XMLElement* pieceNode = getPieceNode_();
226 const XMLElement* pointsNode = pieceNode->FirstChildElement("Points")->FirstChildElement("DataArray");
227 if (pointsNode == nullptr)
228 DUNE_THROW(Dune::IOError, "Couldn't get data array of points in " << fileName_ << ".");
229
230 using Point3D = Dune::FieldVector<double, 3>;
231 std::vector<Point3D> points3D;
232 std::stringstream dataStream(pointsNode->GetText());
233 std::istream_iterator<Point3D> it(dataStream);
234 std::copy(it, std::istream_iterator<Point3D>(), std::back_inserter(points3D));
235
236 // adapt point dimensions if grid dimension is smaller than 3
237 auto points = adaptPointDimension_<Grid::dimensionworld>(std::move(points3D));
238
239 if (verbose) std::cout << "Found " << points.size() << " vertices." << std::endl;
240
241 // insert vertices to the grid factory
242 for (auto&& point : points)
243 factory.insertVertex(std::move(point));
244
245 const XMLElement* cellsNode = pieceNode->FirstChildElement("Cells");
246 const XMLElement* linesNode = pieceNode->FirstChildElement("Lines");
247 if (cellsNode)
248 {
249 const XMLElement* connectivityNode = findDataArray_(cellsNode, "connectivity");
250 const XMLElement* offsetsNode = findDataArray_(cellsNode, "offsets");
251 const XMLElement* typesNode = findDataArray_(cellsNode, "types");
252
253 const auto connectivity = parseDataArray_<std::vector<unsigned int>>(connectivityNode);
254 const auto offsets = parseDataArray_<std::vector<unsigned int>>(offsetsNode);
255 const auto types = parseDataArray_<std::vector<unsigned int>>(typesNode);
256
257 if (verbose) std::cout << "Found " << offsets.size() << " elements." << std::endl;
258
259 unsigned int lastOffset = 0;
260 for (unsigned int i = 0; i < offsets.size(); ++i)
261 {
262 const auto geomType = vtkToDuneGeomType_(types[i]);
263 unsigned int offset = offsets[i];
264 std::vector<unsigned int> corners; corners.resize(offset-lastOffset);
265 for (unsigned int j = 0; j < offset-lastOffset; ++j)
266 corners[Dune::VTK::renumber(geomType, j)] = connectivity[lastOffset+j];
267 factory.insertElement(geomType, std::move(corners));
268 lastOffset = offset;
269 }
270 }
271 // for poly data
272 else if (linesNode)
273 {
274 // sanity check
275 if (Grid::dimension != 1)
276 DUNE_THROW(Dune::IOError, "Grid expects dimension " << Grid::dimension
277 << " but " << fileName_ << " contains a 1D grid.");
278
279 const XMLElement* connectivityNode = findDataArray_(linesNode, "connectivity");
280 const XMLElement* offsetsNode = findDataArray_(linesNode, "offsets");
281
282 const auto connectivity = parseDataArray_<std::vector<unsigned int>>(connectivityNode);
283 const auto offsets = parseDataArray_<std::vector<unsigned int>>(offsetsNode);
284
285 if (verbose) std::cout << "Found " << offsets.size() << " polylines." << std::endl;
286
287 unsigned int lastOffset = 0;
288 for (unsigned int i = 0; i < offsets.size(); ++i)
289 {
290 // a polyline can have many points in the VTK format
291 // split the line in segments with two points
292 unsigned int offset = offsets[i];
293 for (unsigned int j = 0; j < offset-lastOffset-1; ++j)
294 factory.insertElement(Dune::GeometryTypes::line,
295 std::vector<unsigned int>({ connectivity[lastOffset+j], connectivity[lastOffset+j+1] }));
296 lastOffset = offset;
297 }
298 }
299 else
300 DUNE_THROW(Dune::IOError, "No Cells or Lines element found in " << fileName_);
301 }
302
309 void readGridData_(Data& cellData, Data& pointData, bool verbose = false) const
310 {
311 using namespace tinyxml2;
312
313 const XMLElement* pieceNode = getPieceNode_();
314 const XMLElement* cellDataNode = getDataNode_(pieceNode, DataType::cellData);
315 if (cellDataNode != nullptr)
316 {
317 const XMLElement* cellsNode = pieceNode->FirstChildElement("Cells");
318 const XMLElement* linesNode = pieceNode->FirstChildElement("Lines");
319
320 if (cellsNode)
321 {
322 const XMLElement* dataArray = cellDataNode->FirstChildElement("DataArray");
323 for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray"))
324 {
325 const char *attributeText = dataArray->Attribute("Name");
326
327 if (attributeText == nullptr)
328 DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a cell data array.");
329
330 cellData[std::string(attributeText)] = parseDataArray_<std::vector<double>>(dataArray);
331
332 if (verbose)
333 std::cout << "Read cell data field " << attributeText << std::endl;
334 }
335 }
336 // for poly data
337 else if (linesNode)
338 {
339 // first parse all the cell data (each cell in this sense can be a polyline)
340 const XMLElement* dataArray = cellDataNode->FirstChildElement("DataArray");
341 if (dataArray)
342 {
343 Data polyLineCellData;
344 for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray"))
345 {
346 const char *attributeText = dataArray->Attribute("Name");
347
348 if (attributeText == nullptr)
349 DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a cell data array.");
350
351 polyLineCellData[std::string(attributeText)] = parseDataArray_<std::vector<double>>(dataArray);
352
353 if (verbose)
354 std::cout << "Read cell data field " << attributeText << std::endl;
355 }
356
357 // a polyline can have many points in the VTK format
358 // we split the line in segments with two points
359 // so we also need to duplicate the cell data to fit the increased line number
360 const XMLElement* offsetsNode = findDataArray_(linesNode, "offsets");
361 const auto offsets = parseDataArray_<std::vector<unsigned int>>(offsetsNode);
362
363 if (offsets.size() != polyLineCellData.begin()->second.size())
364 DUNE_THROW(Dune::IOError, "Expected the same number of cell data entries (is "
365 << polyLineCellData.begin()->second.size()
366 << ") as polylines (" << offsets.size() << ")!");
367
368 // count the number of Dune cells to be able to resize the data vectors
369 unsigned int lastOffset = 0;
370 std::size_t numCells = 0;
371 for (unsigned int i = 0; i < offsets.size(); ++i)
372 {
373 unsigned int offset = offsets[i];
374 for (unsigned int j = 0; j < offset-lastOffset-1; ++j)
375 ++numCells;
376 lastOffset = offset;
377 }
378
379 // create the data arrays
380 for (const auto& dArray : polyLineCellData)
381 {
382 cellData[dArray.first] = std::vector<double>(numCells);
383 auto& cd = cellData[dArray.first];
384 const auto& pd = dArray.second;
385
386 lastOffset = 0;
387 std::size_t cellIdx = 0;
388 for (unsigned int i = 0; i < offsets.size(); ++i)
389 {
390 unsigned int offset = offsets[i];
391 for (unsigned int j = 0; j < offset-lastOffset-1; ++j)
392 cd[cellIdx++] = pd[i];
393 lastOffset = offset;
394 }
395 }
396 }
397 }
398 else
399 DUNE_THROW(Dune::IOError, "No Cells or Lines element found in " << fileName_);
400 }
401
402 const XMLElement* pointDataNode = getDataNode_(pieceNode, DataType::pointData);
403 if (pointDataNode != nullptr)
404 {
405 const XMLElement* dataArray = pointDataNode->FirstChildElement("DataArray");
406 for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray"))
407 {
408 const char *attributeText = dataArray->Attribute("Name");
409
410 if (attributeText == nullptr)
411 DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a point data array.");
412
413 pointData[std::string(attributeText)] = parseDataArray_<std::vector<double>>(dataArray);
414
415 if (verbose)
416 std::cout << "Read point data field " << attributeText << std::endl;
417 }
418 }
419 }
420
425 const tinyxml2::XMLElement* getPieceNode_() const
426 { return getPieceNode_(doc_, fileName_); }
427
434 const tinyxml2::XMLElement* getPieceNode_(const tinyxml2::XMLDocument& doc, const std::string& fileName) const
435 {
436 using namespace tinyxml2;
437
438 const XMLElement* pieceNode = doc.FirstChildElement("VTKFile");
439 if (pieceNode == nullptr)
440 DUNE_THROW(Dune::IOError, "Couldn't get 'VTKFile' node in " << fileName << ".");
441
442 pieceNode = pieceNode->FirstChildElement("UnstructuredGrid");
443 if (pieceNode == nullptr)
444 pieceNode = doc.FirstChildElement("VTKFile")->FirstChildElement("PolyData");
445 if (pieceNode == nullptr)
446 pieceNode = doc.FirstChildElement("VTKFile")->FirstChildElement("PUnstructuredGrid");
447 if (pieceNode == nullptr)
448 pieceNode = doc.FirstChildElement("VTKFile")->FirstChildElement("PPolyData");
449 if (pieceNode == nullptr)
450 DUNE_THROW(Dune::IOError, "Couldn't get 'UnstructuredGrid', 'PUnstructuredGrid', 'PolyData', or 'PPolyData' node in " << fileName << ".");
451
452 return pieceNode->FirstChildElement("Piece");
453 }
454
461 const tinyxml2::XMLElement* getDataNode_(const tinyxml2::XMLElement* pieceNode, const DataType& type) const
462 {
463 using namespace tinyxml2;
464
465 const XMLElement* dataNode = nullptr;
466 if (type == DataType::pointData)
467 dataNode = pieceNode->FirstChildElement("PointData");
468 else if (type == DataType::cellData)
469 dataNode = pieceNode->FirstChildElement("CellData");
470 else
471 DUNE_THROW(Dune::IOError, "Only cell and point data are supported.");
472
473 return dataNode;
474 }
475
482 const tinyxml2::XMLElement* findDataArray_(const tinyxml2::XMLElement* dataNode, const std::string& name) const
483 {
484 using namespace tinyxml2;
485
486 // loop over XML node siblings to find the correct data array
487 const XMLElement* dataArray = dataNode->FirstChildElement("DataArray");
488 for (; dataArray != nullptr; dataArray = dataArray->NextSiblingElement("DataArray"))
489 {
490 const char *attributeText = dataArray->Attribute("Name");
491
492 if (attributeText == nullptr)
493 DUNE_THROW(Dune::IOError, "Couldn't get Name attribute of a data array.");
494
495 if (attributeText == name)
496 break;
497 }
498
499 return dataArray;
500 }
501
507 template<class Container>
508 Container parseDataArray_(const tinyxml2::XMLElement* dataArray) const
509 {
510 std::stringstream dataStream(dataArray->GetText());
511 return readStreamToContainer<Container>(dataStream);
512 }
513
518 Dune::GeometryType vtkToDuneGeomType_(unsigned int vtkCellType) const
519 {
520 switch (vtkCellType)
521 {
522 case Dune::VTK::GeometryType::vertex: return Dune::GeometryTypes::vertex;
524 case Dune::VTK::GeometryType::triangle: return Dune::GeometryTypes::triangle;
525 case Dune::VTK::GeometryType::quadrilateral: return Dune::GeometryTypes::quadrilateral;
526 case Dune::VTK::GeometryType::tetrahedron: return Dune::GeometryTypes::tetrahedron;
527 case Dune::VTK::GeometryType::hexahedron: return Dune::GeometryTypes::hexahedron;
528 case Dune::VTK::GeometryType::prism: return Dune::GeometryTypes::prism;
529 case Dune::VTK::GeometryType::pyramid: return Dune::GeometryTypes::pyramid;
530 default: DUNE_THROW(Dune::NotImplemented, "VTK cell type " << vtkCellType);
531 }
532 }
533
534 template<int dim, std::enable_if_t<(dim < 3), int> = 0>
535 std::vector<Dune::FieldVector<double, dim>>
536 adaptPointDimension_(std::vector<Dune::FieldVector<double, 3>>&& points3D) const
537 {
538 std::vector<Dune::FieldVector<double, dim>> points(points3D.size());
539 for (std::size_t i = 0; i < points.size(); ++i)
540 for (int j = 0; j < dim; ++j)
541 points[i][j] = points3D[i][j];
542 return points;
543 }
544
545 template<int dim, std::enable_if_t<(dim == 3), int> = 0>
546 std::vector<Dune::FieldVector<double, dim>>
547 adaptPointDimension_(std::vector<Dune::FieldVector<double, 3>>&& points3D) const
548 { return std::move(points3D); }
549
550 std::string fileName_;
551 tinyxml2::XMLDocument doc_;
552};
553
554} // end namespace Dumux
555
556#endif
A vtk file reader using tinyxml2 as xml backend.
Definition: vtkreader.hh:40
std::unique_ptr< Grid > readGrid(Dune::GridFactory< Grid > &factory, Data &cellData, Data &pointData, bool verbose=false) const
Read a grid from a vtk/vtu/vtp file, reading all cell and point data.
Definition: vtkreader.hh:169
bool hasData(const std::string &name, const DataType &type) const
Reviews data from the vtk file to check if there is a data array with a specified name.
Definition: vtkreader.hh:77
Container readData(const std::string &name, const DataType &type) const
read data from the vtk file to a container, e.g. std::vector<double>
Definition: vtkreader.hh:100
VTKReader(const std::string &fileName)
The constructor creates a tinyxml2::XMLDocument from file.
Definition: vtkreader.hh:53
std::unique_ptr< Grid > readGrid(Dune::GridFactory< Grid > &factory, bool verbose=false) const
Read a grid from a vtk/vtu/vtp file, ignoring cell and point data.
Definition: vtkreader.hh:146
std::unique_ptr< Grid > readGrid(bool verbose=false) const
Read a grid from a vtk/vtu/vtp file, ignoring cell and point data.
Definition: vtkreader.hh:121
DataType
The data array types.
Definition: vtkreader.hh:45
std::unordered_map< std::string, std::vector< double > > Data
the cell / point data type for point data read from a grid file
Definition: vtkreader.hh:48
Free functions to write and read a sequence container to and from a file.
constexpr Line line
Definition: couplingmanager1d3d_line.hh:31
Definition: adapt.hh:17