OGS
MeshLayerMapper.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "MeshLayerMapper.h"
5
6#include <algorithm>
7#include <range/v3/view/iota.hpp>
8#include <range/v3/view/join.hpp>
9#include <range/v3/view/repeat_n.hpp>
10#include <range/v3/view/transform.hpp>
11
12#include "BaseLib/Logging.h"
13#include "GeoLib/Raster.h"
14#include "MathLib/MathTools.h"
19#include "MeshLib/Properties.h"
21
22namespace MeshToolsLib
23{
25 MeshLib::Mesh const& mesh, std::vector<float> const& layer_thickness_vector,
26 std::string const& mesh_name)
27{
28 std::vector<float> thickness;
29 for (std::size_t i = 0; i < layer_thickness_vector.size(); ++i)
30 {
31 if (layer_thickness_vector[i] > std::numeric_limits<float>::epsilon())
32 {
33 thickness.push_back(layer_thickness_vector[i]);
34 }
35 else
36 {
37 WARN("Ignoring layer {:d} with thickness {:f}.", i,
38 layer_thickness_vector[i]);
39 }
40 }
41
42 const std::size_t nLayers(thickness.size());
43 if (nLayers < 1 || mesh.getDimension() != 2)
44 {
45 ERR("MeshLayerMapper::createStaticLayers(): A 2D mesh with nLayers > 0 "
46 "is required as input.");
47 return nullptr;
48 }
49
50 const std::size_t nNodes = mesh.getNumberOfNodes();
51 // count number of 2d elements in the original mesh
52 const std::size_t nElems(
53 std::count_if(mesh.getElements().begin(), mesh.getElements().end(),
54 [](MeshLib::Element const* elem)
55 { return (elem->getDimension() == 2); }));
56
57 const std::size_t nOrgElems(mesh.getNumberOfElements());
58 const std::vector<MeshLib::Node*>& nodes = mesh.getNodes();
59 const std::vector<MeshLib::Element*>& elems = mesh.getElements();
60 std::vector<MeshLib::Node*> new_nodes(nNodes + (nLayers * nNodes));
61 std::vector<MeshLib::Element*> new_elems;
62 new_elems.reserve(nElems * nLayers);
63 MeshLib::Properties properties;
64 auto* const materials = properties.createNewPropertyVector<int>(
65 "MaterialIDs", MeshLib::MeshItemType::Cell);
66 if (!materials)
67 {
68 ERR("Could not create PropertyVector object 'MaterialIDs'.");
69 return nullptr;
70 }
71
72 auto layer_to_material_id = [&](unsigned const layer_id)
73 { return ranges::views::repeat_n(nLayers - layer_id, nOrgElems); };
74 materials->assign(ranges::views::iota(0u, nLayers + 1) |
75 ranges::views::transform(layer_to_material_id) |
76 ranges::views::join);
77
78 double z_offset(0.0);
79
80 for (unsigned layer_id = 0; layer_id <= nLayers; ++layer_id)
81 {
82 // add nodes for new layer
83 unsigned node_offset(nNodes * layer_id);
84 if (layer_id > 0)
85 {
86 z_offset += thickness[layer_id - 1];
87 }
88
89 std::transform(nodes.cbegin(), nodes.cend(),
90 new_nodes.begin() + node_offset,
91 [&z_offset](MeshLib::Node* node) {
92 return new MeshLib::Node((*node)[0], (*node)[1],
93 (*node)[2] - z_offset);
94 });
95
96 // starting with 2nd layer create prism or hex elements connecting the
97 // last layer with the current one
98 if (layer_id == 0)
99 {
100 continue;
101 }
102
103 node_offset -= nNodes;
104
105 for (unsigned i = 0; i < nOrgElems; ++i)
106 {
107 const MeshLib::Element* sfc_elem(elems[i]);
108 if (sfc_elem->getDimension() < 2)
109 { // ignore line-elements
110 continue;
111 }
112
113 const unsigned nElemNodes(sfc_elem->getNumberOfBaseNodes());
114 auto** e_nodes = new MeshLib::Node*[2 * nElemNodes];
115
116 for (unsigned j = 0; j < nElemNodes; ++j)
117 {
118 const unsigned node_id =
119 sfc_elem->getNode(j)->getID() + node_offset;
120 e_nodes[j] = new_nodes[node_id + nNodes];
121 e_nodes[j + nElemNodes] = new_nodes[node_id];
122 }
124 {
125 // extrude triangles to prism
126 new_elems.push_back(new MeshLib::Prism(e_nodes));
127 }
128 else if (sfc_elem->getGeomType() == MeshLib::MeshElemType::QUAD)
129 {
130 // extrude quads to hexes
131 new_elems.push_back(new MeshLib::Hex(e_nodes));
132 }
133 else
134 {
135 OGS_FATAL("MeshLayerMapper: Unknown element type to extrude.");
136 }
137 }
138 }
139 return new MeshLib::Mesh(mesh_name, new_nodes, new_elems,
140 true /* compute_element_neighbors */, properties);
141}
142
144 MeshLib::Mesh const& mesh,
145 std::vector<GeoLib::Raster const*> const& rasters,
146 double minimum_thickness,
147 double noDataReplacementValue)
148{
149 const std::size_t nLayers(rasters.size());
150 if (nLayers < 2 || mesh.getDimension() != 2)
151 {
152 ERR("MeshLayerMapper::createRasterLayers(): A 2D mesh and at least two "
153 "rasters required as input.");
154 return false;
155 }
156
157 auto top = std::make_unique<MeshLib::Mesh>(mesh);
158 if (!layerMapping(*top, *rasters.back(), noDataReplacementValue))
159 {
160 return false;
161 }
162
163 auto bottom = std::make_unique<MeshLib::Mesh>(mesh);
164 if (!layerMapping(*bottom, *rasters[0], 0))
165 {
166 return false;
167 }
168
169 this->_minimum_thickness = minimum_thickness;
170 std::size_t const nNodes = mesh.getNumberOfNodes();
171 _nodes.reserve(nLayers * nNodes);
172
173 // number of triangles in the original mesh
174 std::size_t const nElems(std::count_if(
175 mesh.getElements().begin(), mesh.getElements().end(),
176 [](MeshLib::Element const* elem)
177 { return (elem->getGeomType() == MeshLib::MeshElemType::TRIANGLE); }));
178 _elements.reserve(nElems * (nLayers - 1));
179 _materials.reserve(nElems * (nLayers - 1));
180
181 // add bottom layer
182 std::vector<MeshLib::Node*> const& nodes = bottom->getNodes();
183 std::transform(nodes.begin(), nodes.end(), std::back_inserter(_nodes),
184 [](auto const* node) { return new MeshLib::Node(*node); });
185
186 // add the other layers
187 for (std::size_t i = 0; i < nLayers - 1; ++i)
188 {
189 addLayerToMesh(*top, i, *rasters[i + 1]);
190 }
191
192 return true;
193}
194
196 unsigned layer_id,
197 GeoLib::Raster const& raster)
198{
199 const unsigned pyramid_base[3][4] = {
200 {1, 3, 4, 2}, // Point 4 missing
201 {2, 4, 3, 0}, // Point 5 missing
202 {0, 3, 4, 1}, // Point 6 missing
203 };
204
205 std::size_t const nNodes = dem_mesh.getNumberOfNodes();
206 std::vector<MeshLib::Node*> const& top_nodes = dem_mesh.getNodes();
207 int const last_layer_node_offset = layer_id * nNodes;
208
209 // add nodes for new layer
210 for (std::size_t i = 0; i < nNodes; ++i)
211 {
212 _nodes.push_back(getNewLayerNode(*top_nodes[i],
213 *_nodes[last_layer_node_offset + i],
214 raster, _nodes.size()));
215 }
216
217 std::vector<MeshLib::Element*> const& elems = dem_mesh.getElements();
218 std::size_t const nElems(dem_mesh.getNumberOfElements());
219
220 for (std::size_t i = 0; i < nElems; ++i)
221 {
222 MeshLib::Element* elem(elems[i]);
224 {
225 continue;
226 }
227 unsigned node_counter(3);
228 unsigned missing_idx(0);
229 std::array<MeshLib::Node*, 6> new_elem_nodes{};
230 for (unsigned j = 0; j < 3; ++j)
231 {
232 new_elem_nodes[j] =
233 _nodes[_nodes[last_layer_node_offset + getNodeIndex(*elem, j)]
234 ->getID()];
235 new_elem_nodes[node_counter] =
236 (_nodes[last_layer_node_offset + getNodeIndex(*elem, j) +
237 nNodes]);
238 if (new_elem_nodes[j]->getID() !=
239 new_elem_nodes[node_counter]->getID())
240 {
241 node_counter++;
242 }
243 else
244 {
245 missing_idx = j;
246 }
247 }
248
249 switch (node_counter)
250 {
251 case 6:
252 _elements.push_back(new MeshLib::Prism(new_elem_nodes));
253 _materials.push_back(layer_id);
254 break;
255 case 5:
256 {
257 std::array<MeshLib::Node*, 5> pyramid_nodes{};
258 pyramid_nodes[0] = new_elem_nodes[pyramid_base[missing_idx][0]];
259 pyramid_nodes[1] = new_elem_nodes[pyramid_base[missing_idx][1]];
260 pyramid_nodes[2] = new_elem_nodes[pyramid_base[missing_idx][2]];
261 pyramid_nodes[3] = new_elem_nodes[pyramid_base[missing_idx][3]];
262 pyramid_nodes[4] = new_elem_nodes[missing_idx];
263 _elements.push_back(new MeshLib::Pyramid(pyramid_nodes));
264 _materials.push_back(layer_id);
265 break;
266 }
267 case 4:
268 {
269 std::array<MeshLib::Node*, 4> tet_nodes{};
270 std::copy(new_elem_nodes.begin(),
271 new_elem_nodes.begin() + node_counter,
272 tet_nodes.begin());
273 _elements.push_back(new MeshLib::Tet(tet_nodes));
274 _materials.push_back(layer_id);
275 break;
276 }
277 default:
278 continue;
279 }
280 }
281}
282
284 GeoLib::Raster const& raster,
285 double const nodata_replacement,
286 bool const ignore_nodata)
287{
288 if (mesh.getDimension() != 2)
289 {
290 ERR("MeshLayerMapper::layerMapping() - requires 2D mesh");
291 return false;
292 }
293
294 GeoLib::RasterHeader const& header(raster.getHeader());
295 const double x0(header.origin[0]);
296 const double y0(header.origin[1]);
297 const double delta(header.cell_size);
298
299 const std::pair<double, double> xDim(
300 x0, x0 + header.n_cols * delta); // extension in x-dimension
301 const std::pair<double, double> yDim(
302 y0, y0 + header.n_rows * delta); // extension in y-dimension
303
304 const std::size_t nNodes(mesh.getNumberOfNodes());
305 const std::vector<MeshLib::Node*>& nodes = mesh.getNodes();
306 for (unsigned i = 0; i < nNodes; ++i)
307 {
308 if (!ignore_nodata && !raster.isPntOnRaster(*nodes[i]))
309 {
310 // use either default value or elevation from layer above
311 (*nodes[i])[2] = nodata_replacement;
312 continue;
313 }
314
315 double elevation(raster.getValueAtPoint(*nodes[i]));
316 constexpr double eps = std::numeric_limits<double>::epsilon();
317 if (std::abs(elevation - header.no_data) < eps)
318 {
319 if (ignore_nodata)
320 {
321 continue;
322 }
323 elevation = nodata_replacement;
324 }
325 else
326 {
327 elevation = raster.interpolateValueAtPoint(*nodes[i]);
328 }
329 (*nodes[i])[2] = elevation;
330 }
331
332 return true;
333}
334
336 double const value)
337{
338 if (mesh.getDimension() != 2)
339 {
340 ERR("MshLayerMapper::mapToStaticValue() - requires 2D mesh");
341 return false;
342 }
343
344 std::vector<MeshLib::Node*> const& nodes(mesh.getNodes());
345 for (MeshLib::Node* node : nodes)
346 {
347 (*node)[2] = value;
348 }
349 return true;
350}
351
352} // namespace MeshToolsLib
#define OGS_FATAL(...)
Definition Error.h:19
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:34
Class Raster is used for managing raster data.
Definition Raster.h:39
double getValueAtPoint(const MathLib::Point3d &pnt) const
Definition Raster.cpp:46
bool isPntOnRaster(MathLib::Point3d const &pnt) const
Definition Raster.cpp:141
double interpolateValueAtPoint(const MathLib::Point3d &pnt) const
Definition Raster.cpp:75
RasterHeader const & getHeader() const
Returns the complete header information.
Definition Raster.h:75
std::vector< int > _materials
MeshLib::Node * getNewLayerNode(MeshLib::Node const &dem_node, MeshLib::Node const &last_layer_node, GeoLib::Raster const &raster, std::size_t new_node_id) const
std::vector< MeshLib::Node * > _nodes
std::vector< MeshLib::Element * > _elements
std::size_t getID() const
virtual MeshElemType getGeomType() const =0
virtual unsigned getNumberOfBaseNodes() const =0
virtual const Node * getNode(unsigned idx) const =0
virtual constexpr unsigned getDimension() const =0
Get dimension of the mesh element.
std::vector< Node * > const & getNodes() const
Get the nodes-vector for the mesh.
Definition Mesh.h:97
std::vector< Element * > const & getElements() const
Get the element-vector for the mesh.
Definition Mesh.h:100
unsigned getDimension() const
Returns the dimension of the mesh (determined by the maximum dimension over all elements).
Definition Mesh.h:79
std::size_t getNumberOfNodes() const
Get the number of nodes.
Definition Mesh.h:91
std::size_t getNumberOfElements() const
Get the number of elements.
Definition Mesh.h:88
Property manager on mesh items. Class Properties manages scalar, vector or matrix properties....
PropertyVector< T > * createNewPropertyVector(std::string_view name, MeshItemType mesh_item_type, std::size_t n_components=1)
constexpr void assign(R &&r)
bool createRasterLayers(MeshLib::Mesh const &mesh, std::vector< GeoLib::Raster const * > const &rasters, double minimum_thickness, double noDataReplacementValue=0.0) override
static MeshLib::Mesh * createStaticLayers(MeshLib::Mesh const &mesh, std::vector< float > const &layer_thickness_vector, std::string const &mesh_name="SubsurfaceMesh")
void addLayerToMesh(const MeshLib::Mesh &dem_mesh, unsigned layer_id, GeoLib::Raster const &raster) override
Adds another layer to a subsurface mesh.
static bool layerMapping(MeshLib::Mesh const &mesh, const GeoLib::Raster &raster, double nodata_replacement=0.0, bool const ignore_nodata=false)
static bool mapToStaticValue(MeshLib::Mesh const &mesh, double value)
Maps the elevation of all mesh nodes to the specified static value.
TemplateElement< MeshLib::TetRule4 > Tet
Definition Tet.h:14
TemplateElement< MeshLib::PyramidRule5 > Pyramid
Definition Pyramid.h:14
TemplateElement< MeshLib::PrismRule6 > Prism
Definition Prism.h:14
TemplateElement< MeshLib::HexRule8 > Hex
Definition Hex.h:14
Contains the relevant information when storing a geoscientific raster data.
Definition Raster.h:18
MathLib::Point3d origin
Definition Raster.h:22
std::size_t n_cols
Definition Raster.h:19
std::size_t n_rows
Definition Raster.h:20