ryujin 2.1.1 revision 336b16a72e829721302c626ec7071b92032b8248
initial_state_geotiff.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2024 - 2024 by the ryujin authors
4//
5
6#pragma once
7
9#include <lazy.h>
10
11#include <deal.II/base/function_parser.h>
12
13
14#ifdef WITH_GDAL
15#include <cpl_conv.h>
16#include <gdal.h>
17#include <gdal_priv.h>
18#endif
19
20namespace ryujin
21{
22 namespace ShallowWaterInitialStates
23 {
31 template <typename Description, int dim, typename Number>
32 class GeoTIFF : public InitialState<Description, dim, Number>
33 {
34 public:
36 using View =
37 typename Description::template HyperbolicSystemView<dim, Number>;
38 using state_type = typename View::state_type;
39
40
41 GeoTIFF(const HyperbolicSystem &hyperbolic_system,
42 const std::string subsection)
43 : InitialState<Description, dim, Number>("geotiff", subsection)
44 , hyperbolic_system_(hyperbolic_system)
45 {
46 filename_ = "ryujin.tif";
47 this->add_parameter(
48 "filename", filename_, "GeoTIFF: image file to load");
49
50 transformation_ = {0., 0.01, 0., 0., 0., 0.01};
51 this->add_parameter(
52 "transformation",
53 transformation_,
54 "Array \"t[]\" describing an affine transformation between image "
55 "space (indices i and j from bottom left) and real coordinates (x "
56 "and y): x = t[0] + t[1] * i + t[2] * j, and y = t[3] + t[4] * i + "
57 "t[5] * j. (This transformation sets the origin of the image space "
58 "into the bottom left corner with index i to the right and index j "
59 "up)");
60
61 transformation_use_geotiff_ = true;
62 this->add_parameter("transformation use geotiff",
63 transformation_use_geotiff_,
64 "GeoTIFF: read in transformation from GeoTIFF for "
65 "constructing the affine transformation. If set to "
66 "false the manually specified transformation "
67 "parameters will be used instead.");
68
69 transformation_use_geotiff_origin_ = false;
70 this->add_parameter(
71 "transformation use geotiff origin",
72 transformation_use_geotiff_origin_,
73 "GeoTIFF: read in affine shift (i.e., position of "
74 "lower left corner) from GeoTIFF for constructing "
75 "the affine transformation. If set to false the origin specified "
76 "in the transformation parameter will be used instead.");
77
78 height_expression_ = "1.4";
79 this->add_parameter(
80 "water height expression",
81 height_expression_,
82 "A function expression describing the initial total water height");
83
84 velocity_expression_ = "0.0";
85 this->add_parameter(
86 "velocity expression",
87 velocity_expression_,
88 "A function expression describing the initial velocity");
89
90 const auto set_up = [this] {
91#ifdef WITH_GDAL
92 /* Initial GDAL and reset all data: */
93 GDALAllRegister();
94 driver_name = "";
95 driver_projection = "";
96 affine_transformation = {0, 0, 0, 0, 0, 0};
97 inverse_affine_transformation = {0, 0, 0, 0, 0, 0};
98 raster_offset = {0, 0};
99 raster_size = {0, 0};
100 raster.clear();
101#endif
102
103 using FP = dealii::FunctionParser<dim>;
104 /*
105 * This variant of the constructor initializes the function
106 * parser with support for a time-dependent description involving
107 * a variable »t«:
108 */
109 height_function_ = std::make_unique<FP>(height_expression_);
110 velocity_function_ = std::make_unique<FP>(velocity_expression_);
111 };
112
113 set_up();
114 this->parse_parameters_call_back.connect(set_up);
115 }
116
117 state_type compute(const dealii::Point<dim> &point, Number t) final
118 {
119 const auto z = compute_bathymetry(point);
120
121 dealii::Tensor<1, 2, Number> primitive;
122
123 height_function_->set_time(t);
124 primitive[0] = std::max(0., height_function_->value(point) - z);
125
126 velocity_function_->set_time(t);
127 primitive[1] = velocity_function_->value(point);
128
129 const auto view = hyperbolic_system_.template view<dim, Number>();
130 return view.from_initial_state(primitive);
131 }
132
133 auto initial_precomputations(const dealii::Point<dim> &point) ->
135 initial_precomputed_type final
136 {
137 /* Compute bathymetry: */
138 return {compute_bathymetry(point)};
139 }
140
141 private:
142 const HyperbolicSystem &hyperbolic_system_;
143
144
145 void read_in_raster() const
146 {
147#ifdef WITH_GDAL
148 auto dataset_handle = GDALOpen(filename_.c_str(), GA_ReadOnly);
149 AssertThrow(dataset_handle,
150 dealii::ExcMessage("GDAL error: file not found"));
151
152 auto dataset = GDALDataset::FromHandle(dataset_handle);
153 Assert(dataset, dealii::ExcInternalError());
154
155 const auto driver = dataset->GetDriver();
156
157 driver_name = driver->GetMetadataItem(GDAL_DMD_LONGNAME);
158 if (dataset->GetProjectionRef() != nullptr)
159 driver_projection = dataset->GetProjectionRef();
160
161 /* For now we support one raster in the dataset: */
162
163 AssertThrow(
164 dataset->GetRasterCount() == 1,
165 dealii::ExcMessage(
166 "GDAL driver error: currently we only support one raster"));
167
168 const auto raster_band = dataset->GetRasterBand(1);
169
170 AssertThrow(dataset->GetRasterXSize() == raster_band->GetXSize() &&
171 dataset->GetRasterYSize() == raster_band->GetYSize(),
172 dealii::ExcMessage(
173 "GDAL driver error: the raster band has a different "
174 "dimension than the (global) raster dimension of the "
175 "geotiff image. This is not supported."));
176
177 /*
178 * FIXME: For now, we simply read in the entire geotiff file on
179 * each rank. In order to save memory for very large files it would
180 * be possible to create a bounding box for the all active cells of
181 * the triangulation and then only read in a small region for which
182 * we actually need data.
183 */
184
185 raster_offset = {0, 0};
186 raster_size = {dataset->GetRasterXSize(), dataset->GetRasterYSize()};
187
188 raster.resize(raster_size[0] * raster_size[1]);
189 const auto error_code = raster_band->RasterIO(
190 GF_Read,
191 raster_offset[0], /* x-offset of image region */
192 raster_offset[1], /* y-offset of image region */
193 raster_size[0], /* x-size of image region */
194 raster_size[1], /* y-size of image region */
195 raster.data(),
196 raster_size[0], /* x-size of target buffer */
197 raster_size[1], /* y-size of target buffer */
198 GDT_Float32,
199 0,
200 0);
201
202 AssertThrow(error_code == 0,
203 dealii::ExcMessage(
204 "GDAL driver error: error reading in geotiff file"));
205
206 /*
207 * Read in the affine transformation from the geotiff image.
208 *
209 * Note that this transformation differs from the one we use in the
210 * parameter file: GDAL uses typical image orientation: the origin
211 * of the dataset is in the "top left" corner (instead of bottom
212 * left) and the first (column) index goes to the right and the
213 * second (row) index goes down.
214 */
215
216 if (transformation_use_geotiff_) {
217 const auto success =
218 dataset->GetGeoTransform(affine_transformation.data()) == CE_None;
219 AssertThrow(success,
220 dealii::ExcMessage("GDAL driver error: no geo transform "
221 "present in geotiff file"));
222 } else {
223 affine_transformation = transformation_;
224 /* Flip sign for j index (y-coordinate): */
225 affine_transformation[2] *= -1.;
226 affine_transformation[5] *= -1.;
227 }
228
229 /*
230 * Ensure that (i=0, j=raster_size[1]-1) corresponds to the user
231 * supplied (transformation_[0], transformation_[3]).
232 */
233 if (transformation_use_geotiff_ == false ||
234 transformation_use_geotiff_origin_ == false) {
235 const auto j_max = raster_size[1] - 1;
236 affine_transformation[0] =
237 transformation_[0] - j_max * affine_transformation[2];
238 affine_transformation[3] =
239 transformation_[3] - j_max * affine_transformation[5];
240 }
241
242 /*
243 * Compute inverse transformation of
244 *
245 * x = t[0] + t[1] * i + t[2] * j, y = t[3] + t[4] * i + t[5] * j.
246 *
247 * namely:
248 *
249 * i = it[1] * (x - it[0]) + it[2] * (y - it[3])
250 * j = it[4] * (x - it[0]) + it[5] * (y - it[3])
251 */
252 inverse_affine_transformation[0] = affine_transformation[0];
253 inverse_affine_transformation[3] = affine_transformation[3];
254
255 const auto determinant =
256 affine_transformation[1] * affine_transformation[5] -
257 affine_transformation[2] * affine_transformation[4];
258 const auto inv = 1. / determinant;
259 inverse_affine_transformation[1] = inv * affine_transformation[5];
260 inverse_affine_transformation[2] = inv * (-affine_transformation[2]);
261 inverse_affine_transformation[4] = inv * (-affine_transformation[4]);
262 inverse_affine_transformation[5] = inv * affine_transformation[1];
263
264 GDALClose(dataset_handle);
265
266#ifdef DEBUG_OUTPUT
267 std::cout << std::setprecision(16);
268 std::cout << "GDAL: driver name = " << driver_name;
269 std::cout << "\nGDAL: projection = " << driver_projection;
270 std::cout << "\nGDAL: transformation =";
271 for (const auto &it : affine_transformation)
272 std::cout << " " << it;
273 std::cout << "\nGDAL: inverse trafo =";
274 for (const auto &it : inverse_affine_transformation)
275 std::cout << " " << it;
276 std::cout << "\nGDAL: raster offset =";
277 for (const auto &it : raster_offset)
278 std::cout << " " << it;
279 std::cout << "\nGDAL: raster size =";
280 for (const auto &it : raster_size)
281 std::cout << " " << it;
282 std::cout << std::endl;
283#endif
284
285#else
286 static constexpr auto message =
287 "ryujin has to be configured with GDAL support in order to read in "
288 "GeoTIFF images";
289 AssertThrow(false, dealii::ExcMessage(message));
290 __builtin_trap();
291#endif
292 }
293
294
295 DEAL_II_ALWAYS_INLINE inline std::array<double, 2>
296 apply_transformation(const double i, const double j) const
297 {
298 const auto &at = affine_transformation;
299 const double x = at[0] + at[1] * i + at[2] * j;
300 const double y = at[3] + at[4] * i + at[5] * j;
301 return {x, y};
302 }
303
304
305 DEAL_II_ALWAYS_INLINE inline std::array<double, 2>
306 apply_inverse_transformation(const double x, const double y) const
307 {
308 const auto &iat = inverse_affine_transformation;
309 const double i = iat[1] * (x - iat[0]) + iat[2] * (y - iat[3]);
310 const double j = iat[4] * (x - iat[0]) + iat[5] * (y - iat[3]);
311 return {i, j};
312 }
313
314
315 DEAL_II_ALWAYS_INLINE inline Number
316 compute_bathymetry(const dealii::Point<dim> &point) const
317 {
318 geotiff.ensure_initialized([&]() {
319 read_in_raster();
320 return true;
321 });
322
323 const double x = point[0];
324 double y = 0;
325 if constexpr (dim >= 2)
326 y = point[1];
327 const auto &[di, dj] = apply_inverse_transformation(x, y);
328
329 /*
330 * Use a simple bilinear interpolation:
331 */
332
333 const auto i_left = static_cast<unsigned int>(std::floor(di));
334 const auto i_right = static_cast<unsigned int>(std::ceil(di));
335 const auto j_left = static_cast<unsigned int>(std::floor(dj));
336 const auto j_right = static_cast<unsigned int>(std::ceil(dj));
337
338 const bool in_bounds =
339 i_left <= i_right &&
340 i_right < static_cast<unsigned int>(raster_size[0]) &&
341 j_left <= j_right &&
342 j_right < static_cast<unsigned int>(raster_size[1]);
343
344 AssertThrow(
345 in_bounds,
346 dealii::ExcMessage("Raster error: The requested point is outside "
347 "the image boundary of the geotiff file"));
348
349 const double i_ratio = std::fmod(di, 1.);
350 const double j_ratio = std::fmod(dj, 1.);
351
352 const auto v_iljl = raster[i_left + j_left * raster_size[0]];
353 const auto v_irjl = raster[i_right + j_left * raster_size[0]];
354
355 const auto v_iljr = raster[i_left + j_right * raster_size[0]];
356 const auto v_irjr = raster[i_right + j_right * raster_size[0]];
357
358 const auto v_jl = v_iljl * (1. - i_ratio) + v_irjl * i_ratio;
359 const auto v_jr = v_iljr * (1. - i_ratio) + v_irjr * i_ratio;
360
361 return v_jl * (1. - j_ratio) + v_jr * j_ratio;
362 }
363
364
365 /* Runtime parameters: */
366
367 std::string filename_;
368
369 std::array<double, 6> transformation_;
370 bool transformation_use_geotiff_;
371 bool transformation_use_geotiff_origin_;
372
373 std::string height_expression_;
374 std::string velocity_expression_;
375
376 /* GDAL data structures: */
377
378 //
379 // We use a Lazy<t> wrapper for lazy initialization with efficient
380 // Schmidt's double checking. We simply ignore the bool type here.
381 //
382 Lazy<bool> geotiff;
383 mutable std::string driver_name;
384 mutable std::string driver_projection;
385 mutable std::array<double, 6> affine_transformation;
386 mutable std::array<double, 6> inverse_affine_transformation;
387 mutable std::array<int, 2> raster_offset;
388 mutable std::array<int, 2> raster_size;
389 mutable std::vector<float> raster;
390
391 /* Fields for muparser support for water height and velocity: */
392
393 std::unique_ptr<dealii::FunctionParser<dim>> height_function_;
394 std::unique_ptr<dealii::FunctionParser<dim>> velocity_function_;
395 };
396 } // namespace ShallowWaterInitialStates
397} // namespace ryujin
void ensure_initialized(const Callable &creator) const
typename Description::HyperbolicSystem HyperbolicSystem
auto initial_precomputations(const dealii::Point< dim > &point) -> typename InitialState< Description, dim, Number >::initial_precomputed_type final
state_type compute(const dealii::Point< dim > &point, Number t) final
GeoTIFF(const HyperbolicSystem &hyperbolic_system, const std::string subsection)
typename Description::template HyperbolicSystemView< dim, Number > View
Euler::HyperbolicSystem HyperbolicSystem
Definition: description.h:32