diff --git a/MeshLib/MeshGenerators/LayeredVolume.cpp b/MeshLib/MeshGenerators/LayeredVolume.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bd4825fa1b6d390777a5c5c376a6e1072b9f82d8 --- /dev/null +++ b/MeshLib/MeshGenerators/LayeredVolume.cpp @@ -0,0 +1,219 @@ +/** + * \file LayeredVolume.cpp + * \author Karsten Rink + * \date 2014-04-11 + * \brief Implementation of the LayeredVolume class. + * + * \copyright + * Copyright (c) 2013, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include "LayeredVolume.h" + +#include <fstream> +#include <numeric> + +#include "Vector3.h" + +#include "GEOObjects.h" +#include "PointVec.h" +#include "Mesh.h" +#include "convertMeshToGeo.h" +#include "Elements/Element.h" +#include "Elements/Tri.h" +#include "Elements/Quad.h" +#include "MeshGenerators/MeshLayerMapper.h" +#include "MeshQuality/MeshValidation.h" +#include "MeshEditing/ElementExtraction.h" + +LayeredVolume::LayeredVolume() +: _invalid_value(-9999), _mesh(nullptr) +{ +} + +bool LayeredVolume::createGeoVolumes(const MeshLib::Mesh &mesh, const std::vector<std::string> &raster_paths, double noDataReplacementValue) +{ + if (mesh.getDimension() != 2 || !allRastersExist(raster_paths)) + return false; + + std::vector<GeoLib::Raster const*const> rasters; + rasters.reserve(raster_paths.size()); + for (auto path = raster_paths.begin(); path != raster_paths.end(); ++path) + rasters.push_back(GeoLib::Raster::getRasterFromASCFile(*path)); + + const bool result = this->createGeoVolumes(mesh, rasters, noDataReplacementValue); + std::for_each(rasters.begin(), rasters.end(), [](GeoLib::Raster const*const raster){ delete raster; }); + return result; +} + + +bool LayeredVolume::createGeoVolumes(const MeshLib::Mesh &mesh, const std::vector<GeoLib::Raster const*const> &rasters, double noDataReplacementValue) +{ + if (mesh.getDimension() != 2) + return false; + + // remove line elements, only tri + quad remain + MeshLib::ElementExtraction ex(mesh); + ex.searchByElementType(MeshElemType::LINE); + MeshLib::Mesh* mesh_layer (ex.removeMeshElements("MeshLayer")); + if (mesh_layer==nullptr) + mesh_layer = new MeshLib::Mesh(mesh); + + // map each layer and attach to subsurface mesh + const std::size_t nRasters (rasters.size()); + std::vector<GeoLib::Point> in_region_points(nRasters-1, GeoLib::Point(0,0,0)); + for (size_t i=0; i<nRasters; ++i) + { + const double replacement_value = (i==0) ? noDataReplacementValue : _invalid_value; + if (!MeshLayerMapper::LayerMapping(*mesh_layer, *rasters[i], 0, 0, _invalid_value)) + { + this->cleanUpOnError(); + return false; + } + this->addLayerToMesh(*mesh_layer, i); + } + // close boundaries between layers + this->addLayerBoundaries(*mesh_layer, nRasters); + delete mesh_layer; + + this->removeCongruentElements(nRasters, mesh.getNElements()); + _mesh = new MeshLib::Mesh("BoundaryMesh", _nodes, _elements); + MeshLib::MeshValidation::removeUnusedMeshNodes(*_mesh); + return true; +} + +void LayeredVolume::addLayerToMesh(const MeshLib::Mesh &mesh_layer, unsigned layer_id) +{ + const std::vector<MeshLib::Node*> &layer_nodes (mesh_layer.getNodes()); + const std::size_t nNodes (layer_nodes.size()); + const std::size_t node_id_offset (_nodes.size()); + const std::size_t last_layer_offset (node_id_offset-nNodes); + + for (std::size_t i=0; i<nNodes; ++i) + { + if (layer_id > 0 && + ((*layer_nodes[i])[2] == _invalid_value || + (*_nodes[last_layer_offset+i])[2]-(*layer_nodes[i])[2] < 1)) + _nodes.push_back(new MeshLib::Node(*_nodes[last_layer_offset+i])); + else + _nodes.push_back(new MeshLib::Node(layer_nodes[i]->getCoords(), _nodes.size())); + } + + const std::vector<MeshLib::Element*> &layer_elements (mesh_layer.getElements()); + for (MeshLib::Element* elem : layer_elements) + { + if (elem->getGeomType() == MeshElemType::TRIANGLE) + { + std::array<MeshLib::Node*,3> tri_nodes = { _nodes[node_id_offset+elem->getNode(0)->getID()], + _nodes[node_id_offset+elem->getNode(1)->getID()], + _nodes[node_id_offset+elem->getNode(2)->getID()] }; + _elements.push_back(new MeshLib::Tri(tri_nodes, layer_id)); + } + else if (elem->getGeomType() == MeshElemType::QUAD) + { + std::array<MeshLib::Node*,4> quad_nodes = { _nodes[node_id_offset+elem->getNode(0)->getID()], + _nodes[node_id_offset+elem->getNode(1)->getID()], + _nodes[node_id_offset+elem->getNode(2)->getID()], + _nodes[node_id_offset+elem->getNode(3)->getID()] }; + _elements.push_back(new MeshLib::Quad(quad_nodes, layer_id)); + } + } +} + +void LayeredVolume::addLayerBoundaries(const MeshLib::Mesh &layer, std::size_t nLayers) +{ + const unsigned nLayerBoundaries (nLayers-1); + const std::size_t nNodes (layer.getNNodes()); + const std::vector<MeshLib::Element*> &layer_elements (layer.getElements()); + for (MeshLib::Element* elem : layer_elements) + { + const std::size_t nElemNodes (elem->getNNodes()); + for (unsigned i=0; i<nElemNodes; ++i) + if (elem->getNeighbor(i) == nullptr) + for (unsigned j=0; j<nLayerBoundaries; ++j) + { + const std::size_t offset (j*nNodes); + MeshLib::Node* n0 = _nodes[offset + elem->getNode(i)->getID()]; + MeshLib::Node* n1 = _nodes[offset + elem->getNode((i+1)%nElemNodes)->getID()]; + MeshLib::Node* n2 = _nodes[offset + nNodes + elem->getNode((i+1)%nElemNodes)->getID()]; + MeshLib::Node* n3 = _nodes[offset + nNodes + elem->getNode(i)->getID()]; + + if (MathLib::Vector3(*n1, *n2).getLength() > std::numeric_limits<double>::epsilon()) + { + const std::array<MeshLib::Node*,3> tri_nodes = { n0, n1, n2 }; + _elements.push_back(new MeshLib::Tri(tri_nodes, nLayers+j)); + } + if (MathLib::Vector3(*n0, *n3).getLength() > std::numeric_limits<double>::epsilon()) + { + const std::array<MeshLib::Node*,3> tri_nodes = { n0, n2, n3 }; + _elements.push_back(new MeshLib::Tri(tri_nodes, nLayers+j)); + } + } + } +} + +void LayeredVolume::removeCongruentElements(std::size_t nLayers, std::size_t nElementsPerLayer) +{ + for (std::size_t i=1; i<nLayers; ++i) + { + const std::size_t upper_offset ((i-1) * nElementsPerLayer); + const std::size_t lower_offset ( i * nElementsPerLayer); + for (std::size_t j=0; j<nElementsPerLayer; ++j) + { + MeshLib::Element const*const high (_elements[upper_offset+j]); + MeshLib::Element *low (_elements[lower_offset+j]); + + unsigned count(0); + const std::size_t nElemNodes (low->getNNodes()); + for (std::size_t k=0; k<nElemNodes; ++k) + if (high->getNode(k)->getID() == low->getNode(k)->getID()) + { + low->setNode(k, _nodes[high->getNodeIndex(k)]); + count++; + } + + if (count == nElemNodes) + { + delete _elements[upper_offset+j]; + _elements[upper_offset+j] = nullptr; + } + else + { + MeshLib::Node attr = high->getCenterOfGravity(); + _attribute_points.push_back(MeshLib::Node(attr[0], attr[1], (attr[2] + low->getCenterOfGravity()[2])/2.0, low->getValue())); + } + } + } + auto elem_vec_end = std::remove(_elements.begin(), _elements.end(), nullptr); + _elements.erase(elem_vec_end, _elements.end()); +} + +bool LayeredVolume::exportToGeometry(GeoLib::GEOObjects &geo_objects) const +{ + if (_mesh == nullptr) + return false; + MeshLib::convertMeshToGeo(*_mesh, geo_objects, std::numeric_limits<double>::min()); + return true; +} + +bool LayeredVolume::allRastersExist(const std::vector<std::string> &raster_paths) const +{ + for (auto raster = raster_paths.begin(); raster != raster_paths.end(); ++raster) + { + std::ifstream file_stream (*raster, std::ifstream::in); + if (!file_stream.good()) + return false; + file_stream.close(); + } + return true; +} + +void LayeredVolume::cleanUpOnError() +{ + std::for_each(_nodes.begin(), _nodes.end(), [](MeshLib::Node *node) { delete node; }); + std::for_each(_elements.begin(), _elements.end(), [](MeshLib::Element *elem) { delete elem; }); +} diff --git a/MeshLib/MeshGenerators/LayeredVolume.h b/MeshLib/MeshGenerators/LayeredVolume.h new file mode 100644 index 0000000000000000000000000000000000000000..3ccd09527dd7a0f9f29b0c341349413472c79f39 --- /dev/null +++ b/MeshLib/MeshGenerators/LayeredVolume.h @@ -0,0 +1,93 @@ +/** + * \file LayeredVolume.h + * \author Karsten Rink + * \date 2014-04-11 + * \brief Definition of the LayeredVolume class + * + * \copyright + * Copyright (c) 2013, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#ifndef LAYEREDVOLUME_H +#define LAYEREDVOLUME_H + +#include <string> +#include <vector> + +#include "Raster.h" +#include "Node.h" + +namespace GeoLib { + class GEOObjects; + class Surface; +} + +namespace MeshLib { + class Mesh; + class Element; +} + +/** + * \brief Creates a volume geometry from 2D mesh layers based on raster data. + */ +class LayeredVolume +{ +public: + LayeredVolume(); + ~LayeredVolume() {} + + /** + * Constructs a subsurface representation of a mesh using only 2D elements (i.e. layer boundaries are represented by surfaces) + * @param mesh The 2D surface mesh that is used as a basis for the subsurface mesh + * @param rasters Containing all the raster-data for the subsurface layers from top to bottom (starting with the DEM) + * @param noDataReplacementValue Default z-coordinate if there are mesh nodes not located on the DEM raster (i.e. raster_paths[0]) + * @result true if the subsurface representation has been created, false if there was an error + */ + bool createGeoVolumes(const MeshLib::Mesh &mesh, const std::vector<GeoLib::Raster const*const> &rasters, double noDataReplacementValue = 0.0); + + /** + * Constructs a subsurface representation of a mesh using only 2D elements (i.e. layer boundaries are represented by surfaces) + * @param mesh The 2D surface mesh that is used as a basis for the subsurface mesh + * @param raster_paths Containing all the raster-file-names for the subsurface layers from top to bottom (starting with the DEM) + * @param noDataReplacementValue Default z-coordinate if there are mesh nodes not located on the DEM raster (i.e. raster_paths[0]) + * @result true if the subsurface representation has been created, false if there was an error + */ + bool createGeoVolumes(const MeshLib::Mesh &mesh, const std::vector<std::string> &raster_paths, double noDataReplacementValue = 0.0); + + /// Returns the subsurface representation that can be used as input for the TetGenInterface + MeshLib::Mesh* getMesh() const { return _mesh; } + + /// Returns the region attribute vector necessary for assigning region attributes via TetGen + std::vector<MeshLib::Node> getAttributePoints() { return _attribute_points; } + + /// Converts the subsurface representation to geometry objects and adds them to the geo storage object + bool exportToGeometry(GeoLib::GEOObjects &geo_objects) const; + +private: + /// Adds another layer to the subsurface mesh + void addLayerToMesh(const MeshLib::Mesh &mesh_layer, unsigned layer_id); + + /// Creates boundary surfaces between the mapped layers to make the volumes watertight + void addLayerBoundaries(const MeshLib::Mesh &layer, std::size_t nLayers); + + /// Removes duplicate 2D elements (possible due to outcroppings) + void removeCongruentElements(std::size_t nLayers, std::size_t nElementsPerLayer); + + /// Checks if all raster files actually exist + bool allRastersExist(const std::vector<std::string> &raster_paths) const; + + /// Cleans up the already created object in case of an error + void cleanUpOnError(); + + const double _invalid_value; + std::vector<MeshLib::Node*> _nodes; + std::vector<MeshLib::Element*> _elements; + std::vector<MeshLib::Node> _attribute_points; + MeshLib::Mesh* _mesh; +}; + +#endif //LAYEREDVOLUME_H