Quetzal-CoaTL
The Coalescence Template Library
Loading...
Searching...
No Matches
raster.hpp
1// Copyright 2021 Arnaud Becheler <abechele@umich.edu>
2
3/*********************************************************************** * This program is free software; you can
4 *redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free
5 *Software Foundation; either version 2 of the License, or * (at your option) any later version. *
6 * *
7 ***************************************************************************/
8
9#pragma once
10
11#include "coordinates.hpp"
12#include "extent.hpp"
13#include "gdalcpp.hpp"
14#include "resolution.hpp"
15
16#include <range/v3/all.hpp>
17
18#include <algorithm>
19#include <cmath>
20#include <filesystem>
21#include <iostream>
22#include <iterator> // std::distance
23#include <optional>
24#include <stdexcept> // std::invalid_argument
25#include <string>
26#include <type_traits> // std::is_invocable_r
27#include <vector>
28
29namespace quetzal::geography
30{
31
37template <typename Time = int> class raster
38{
39
40 public:
42 using time_type = Time;
43
45 using time_descriptor = std::vector<time_type>::size_type;
46
48 using location_descriptor = int;
49
52
55
58
61
63 using decimal_degree = double;
64
66 using value_type = double;
67
68 private:
69 gdalcpp::Dataset _dataset;
70 std::vector<time_type> _times;
71 std::vector<double> _gT;
72 latlon _origin;
73 int _width;
74 int _height;
75 int _depth;
77 extent<double> _extent;
78
79 value_type _no_data;
80
81 // Pixel size in x and y
82 inline auto compute_resolution(const std::vector<double> &gT) const
83 {
85 }
86
87 // Top left corner and bottom right corner geographic coordinates
88 inline auto compute_extent(const latlon &origin, const quetzal::geography::resolution<decimal_degree> &r) const
89 {
90 const auto [lat_max, lon_min] = origin;
91 const auto lon_max = lon_min + width() * r.lon();
92 const auto lat_min = lat_max + height() * r.lat();
93 return quetzal::geography::extent<decimal_degree>(lat_min, lat_max, lon_min, lon_max);
94 }
95
96 public:
101 explicit raster(const std::filesystem::path &file, const std::vector<time_type> &times)
102 : _dataset(file.string()), _times(times), _gT(_dataset.affine_transformation_coefficients()),
103 _origin(compute_origin(_gT)), _width(_dataset.width()), _height(_dataset.height()), _depth(_dataset.depth()),
104 _resolution(compute_resolution(_gT)), _extent(compute_extent(_origin, _resolution)),
105 _no_data(nodata_first_band())
106 {
107 check_times_equals_depth();
108 }
109
112
117 inline static raster from_file(const std::filesystem::path &file, const std::vector<time_type> &times)
118 {
119 return raster(file, times);
120 }
121
129 template <class Callable>
130 requires(std::is_invocable_r_v<std::optional<value_type>, Callable, location_descriptor, time_type>)
131 inline void to_geotiff(Callable f, time_type start, time_type end, const std::filesystem::path &file) const
132 {
133 export_to_geotiff(f, start, end, file);
134 }
135
139 inline void to_shapefile(std::vector<latlon> points, const std::filesystem::path &file) const
140 {
141 points_to_shapefile(points, file);
142 }
143
147 inline void to_shapefile(std::map<latlon, int> counts, const std::filesystem::path &file) const
148 {
149 counts_to_shapefile(counts, file);
150 }
151
153 std::ostream &write(std::ostream &stream) const
154 {
155 stream << "Origin: " << origin() << "\nWidth: " << width() << "\nHeight: " << height() << "\nDepth: " << depth()
156 << "\nResolution: "
157 << "\n\tLat: " << get_resolution().lat() << "\n\tLon: " << get_resolution().lon() << "\nExtent:"
158 << "\n\tLat min: " << get_extent().lat_min() << "\n\tLat max: " << get_extent().lat_max()
159 << "\n\tLon min: " << get_extent().lon_min() << "\n\tLon max: " << get_extent().lon_max()
160 << "\nNA value: " << NA() << std::endl;
161 return stream;
162 }
163
165
168
170 inline latlon origin() const noexcept
171 {
172 return _origin;
173 }
174
178 {
179 return _resolution;
180 }
181
184 {
185 return _extent;
186 }
187
189 inline int width() const noexcept
190 {
191 return _width;
192 }
193
195 inline int height() const noexcept
196 {
197 return _height;
198 }
199
201 inline int depth() const noexcept
202 {
203 return _depth;
204 }
205
207 inline value_type NA() const noexcept
208 {
209 return _no_data;
210 }
211
213
216
219 inline auto locations() const noexcept
220 {
221 return ranges::views::iota(0, width() * height());
222 }
223
226 inline auto times() const noexcept
227 {
228 // [0 ... depth -1 [
229 return ranges::views::iota(0, depth());
230 }
231
235 inline bool is_valid(location_descriptor x) const noexcept
236 {
237 return ( x >= 0 and x < locations().size());
238 }
239
243 inline bool is_valid(const colrow &x) const noexcept
244 {
245 return x.col >= 0 and x.col < width() and x.row >= 0 and x.row < height() ;
246 }
247
251 inline bool is_valid(const rowcol &x) const noexcept
252 {
253 return x.col >= 0 and x.col < width() and x.row >= 0 and x.row < height() ;
254 }
255
259 inline bool contains(const latlon &x) const noexcept
260 {
261 return is_in_spatial_extent(x);
262 }
263
267 inline bool contains(const lonlat &x) const noexcept
268 {
269 return is_in_spatial_extent(to_latlon(x));
270 }
271
275 inline bool is_valid(time_descriptor t) const noexcept
276 {
277 return t >= 0 and t < _times.size();
278 }
279
283 inline bool is_recorded(const time_type &t) const noexcept
284 {
285 return std::find(_times.begin(), _times.end(), t) != _times.end();
286 }
287
291 inline bool is_in_interval(const time_type &t) const noexcept
292 {
293 return t >= _times.front() and t <= _times.back();
294 }
295
302 inline std::optional<value_type> at(location_descriptor x, time_descriptor t) const noexcept
303 {
304 assert( is_valid(x) );
305 assert( is_valid(t) );
306 const auto colrow = to_colrow(x);
307 return read(colrow.col, colrow.row, t);
308 }
309
315 inline auto to_view() const noexcept
316 {
317 return [this](location_descriptor x, time_descriptor t) { return this->at(x, t); };
318 }
320
323
328 inline location_descriptor to_descriptor(const colrow &x) const noexcept
329 {
330 assert( is_valid(x) );
331 return x.col * width() + x.col;
332 }
333
338 inline location_descriptor to_descriptor(const latlon &x) const noexcept
339 {
340 assert( contains(x) );
341 return to_descriptor( to_colrow(x) );
342 }
343
348 inline colrow to_colrow(const latlon &x) const noexcept
349 {
350 assert(contains(x));
351 const auto [lat, lon] = x;
352 const int col = (lon - _gT[0]) / _gT[1];
353 const int row = (lat - _gT[3]) / _gT[5];
354 return colrow(col, row);
355 }
356
361 inline rowcol to_rowcol(const latlon &x) const noexcept
362 {
363 assert(contains(x));
364 auto colrow = to_colrow(x);
365 std::swap(colrow.col, colrow.row);
366 return colrow;
367 }
368
373 inline colrow to_colrow(location_descriptor x) const noexcept
374 {
375 assert(is_valid(x));
376 const int col = x % width();
377 const int row = x / width();
378 assert(row < height());
379 return colrow(col, row);
380 }
381
386 inline rowcol to_rowcol(location_descriptor x) const noexcept
387 {
388 assert(is_valid(x));
389 auto c = to_colrow(x);
390 return rowcol(c.row, c.col);
391 }
392
397 inline latlon to_latlon(location_descriptor x) const noexcept
398 {
399 assert(is_valid(x));
400 return to_latlon(to_colrow(x));
401 }
402
407 inline lonlat to_lonlat(location_descriptor x) const noexcept
408 {
409 assert(is_valid(x));
410 return to_lonlat(to_colrow(x));
411 }
412
417 inline latlon to_latlon(const colrow &x) const noexcept
418 {
419 assert(is_valid(x));
420 const auto [col, row] = x;
421 const auto lon = _gT[1] * col + _gT[2] * row + _gT[1] * 0.5 + _gT[2] * 0.5 + _gT[0];
422 const auto lat = _gT[4] * col + _gT[5] * row + _gT[4] * 0.5 + _gT[5] * 0.5 + _gT[3];
423 return latlon(lat, lon);
424 }
425
430 inline latlon to_latlon(const rowcol &x) const noexcept
431 {
432 assert(is_valid(x));
433 return to_latlon(colrow(x.col, x.row));
434 }
435
440 inline lonlat to_lonlat(const colrow &x) const noexcept
441 {
442 assert(is_valid(x));
443 const auto [lat, lon] = to_latlon(x);
444 return lonlat(lon, lat);
445 }
446
452 inline latlon to_centroid(const latlon &x) const
453 {
454 assert(this->contains(x) and
455 "latlon does not belong to spatial extent and can not be reprojected to a cell centroid.");
456
457 return to_latlon(to_colrow(x));
458 }
459
461
462 private:
463
467 inline lonlat to_lonlat(const latlon &x) const noexcept
468 {
469 return lonlat(x.lon, x.lat);
470 }
471
475 inline latlon to_latlon(const lonlat &x) const noexcept
476 {
477 return latlon(x.lat, x.lon);
478 }
479
481 inline value_type nodata_first_band() const
482 {
483 return _dataset.get().GetRasterBand(1)->GetNoDataValue();
484 }
485
489 std::optional<double> read(int col, int row, int band) const
490 {
491 const int nXSize = 1;
492 auto line = (float *)CPLMalloc(sizeof(float) * nXSize);
493
494 // band begins at 1
495 const auto err = _dataset.band(band + 1).RasterIO(GF_Read, col, row, 1, 1, line, nXSize, 1, GDT_Float32, 0, 0);
496
497 if (err != CE_None)
498 throw std::invalid_argument("received negative or invalid col row or band index");
499
500 const double val = static_cast<double>(*line);
501 CPLFree(line);
502 if (val != this->NA())
503 return {val};
504 return {};
505 }
506
508 template <typename Callable>
509 void export_to_geotiff(Callable f, time_type start, time_type end, const std::filesystem::path &file) const
510 {
511 // Number of bands to create
512 const auto times = ranges::views::iota(start, end);
513 const int depth = times.size();
514 assert(depth >= 1);
515 GDALDataset *sink =
516 _dataset.get().GetDriver()->Create(file.string().c_str(), width(), height(), depth, GDT_Float32, NULL);
517
518 // Bands begin at 1 in GDAL
519 int band = 1;
520 for (time_descriptor t : times)
521 {
522 auto fun = [f, t, this](location_descriptor x) {
523 const auto o = f(x, t);
524 return o.has_value() ? static_cast<float>(o.value()) : this->NA();
525 };
526
527 auto buffer = locations() | ranges::views::transform(fun) | ranges::to<std::vector>();
528 GDALRasterBand *sink_band = sink->GetRasterBand(band);
529 [[maybe_unused]] const auto err2 = sink_band->RasterIO(GF_Write, 0, 0, width(), height(), buffer.data(),
530 width(), height(), GDT_Float32, 0, 0);
531 sink_band->SetNoDataValue(NA());
532 sink->FlushCache();
533 band++;
534 }
535
536 // write metadata
537 std::vector<double> geo_transform(6);
538 _dataset.get().GetGeoTransform(geo_transform.data());
539 sink->SetGeoTransform(geo_transform.data());
540 sink->SetProjection(_dataset.get().GetProjectionRef());
541
542 GDALClose(static_cast<GDALDatasetH>(sink));
543 }
544
546 void counts_to_shapefile(std::map<latlon, int> counts, const std::filesystem::path &file) const
547 {
548 std::string driver_name = "ESRI Shapefile";
549 GDALDriver *poDriver;
550 GDALAllRegister();
551 poDriver = GetGDALDriverManager()->GetDriverByName(driver_name.c_str());
552 assert(poDriver != NULL);
553
554 GDALDataset *poDS;
555 poDS = poDriver->Create(file.string().c_str(), 0, 0, 0, GDT_Unknown, NULL);
556 assert(poDS != NULL);
557
558 OGRLayer *poLayer;
559 poLayer = poDS->CreateLayer("sample", NULL, wkbPoint, NULL);
560 assert(poLayer != NULL);
561
562 OGRFieldDefn oField("count", OFTInteger);
563 if (poLayer->CreateField(&oField) != OGRERR_NONE)
564 {
565 throw(std::string("Creating sample size field failed."));
566 }
567
568 for (auto const &it : counts)
569 {
570 const auto [y, x] = it.first;
571 int sample_size = it.second;
572
573 OGRFeature *poFeature;
574 poFeature = OGRFeature::CreateFeature(poLayer->GetLayerDefn());
575
576 poFeature->SetField("count", sample_size);
577
578 OGRPoint pt;
579 pt.setX(x);
580 pt.setY(y);
581
582 poFeature->SetGeometry(&pt);
583
584 if (poLayer->CreateFeature(poFeature) != OGRERR_NONE)
585 {
586 throw(std::string("Failed to create feature in shapefile."));
587 }
588 OGRFeature::DestroyFeature(poFeature);
589 }
590 GDALClose(poDS);
591 }
592
594 void points_to_shapefile(std::vector<latlon> coords, const std::filesystem::path &file) const
595 {
596 std::string driver_name = "ESRI Shapefile";
597 GDALDriver *poDriver;
598 GDALAllRegister();
599 poDriver = GetGDALDriverManager()->GetDriverByName(driver_name.c_str());
600 assert(poDriver != NULL);
601
602 GDALDataset *poDS;
603 poDS = poDriver->Create(file.string().c_str(), 0, 0, 0, GDT_Unknown, NULL);
604 assert(poDS != NULL);
605
606 OGRLayer *poLayer;
607 poLayer = poDS->CreateLayer("sample", NULL, wkbPoint, NULL);
608 assert(poLayer != NULL);
609
610 for (auto const [y, x] : coords)
611 {
612 OGRFeature *poFeature;
613 poFeature = OGRFeature::CreateFeature(poLayer->GetLayerDefn());
614
615 OGRPoint pt;
616 pt.setX(x);
617 pt.setY(y);
618
619 poFeature->SetGeometry(&pt);
620
621 if (poLayer->CreateFeature(poFeature) != OGRERR_NONE)
622 {
623 throw(std::string("Failed to create feature in shapefile."));
624 }
625 OGRFeature::DestroyFeature(poFeature);
626 }
627 GDALClose(poDS);
628 }
629
631 bool is_in_spatial_extent(const latlon &x) const noexcept
632 {
633 const auto [lat, lon] = x;
634 bool answer = false;
635 if (lon >= _extent.lon_min() && lon < _extent.lon_max() && lat > _extent.lat_min() && lat <= _extent.lat_max())
636 answer = true;
637 return answer;
638 }
639
641 void check_times_equals_depth()
642 {
643 if (_times.size() != depth())
644 {
645 std::string message(
646 "the size of times argument should be equal to the depth of the given dataset: size is ");
647 message += std::to_string(_times.size());
648 message += ", should be ";
649 message += std::to_string(depth());
650 message += ".";
651 throw std::runtime_error(message.c_str());
652 }
653 }
654
655 // Geographic coordinates of the top left corner.
656 latlon compute_origin(const std::vector<double> &gT) const
657 {
658 return latlon(gT[3], gT[0]);
659 }
660
661}; // end class raster
662
663template <typename Time> std::ostream &operator<<(std::ostream &os, const raster<Time> &r)
664{
665 return r.write(os);
666}
667} // namespace quetzal::geography
extent of a raster grid object
Definition extent.hpp:19
constexpr value_type lon_min() const noexcept
Minimal longitude of the spatial extent.
Definition extent.hpp:89
constexpr value_type lat_min() const noexcept
Minimal latitude of the spatial extent.
Definition extent.hpp:77
constexpr value_type lon_max() const noexcept
Get the maximal longitude of the spatial extent.
Definition extent.hpp:95
constexpr value_type lat_max() const noexcept
Maximal latitude of the spatial extent.
Definition extent.hpp:83
Definition gdalcpp.hpp:229
auto & band(unsigned int i) const
Fetch a band object for a dataset, zeroth-based numbering.
Definition gdalcpp.hpp:317
Discrete spatio-temporal variations of an environmental variable.
Definition raster.hpp:38
geography::extent< decimal_degree > get_extent() const noexcept
Extent of the spatial grid.
Definition raster.hpp:183
static raster from_file(const std::filesystem::path &file, const std::vector< time_type > &times)
Read the raster from an input file in the geotiff format.
Definition raster.hpp:117
bool is_recorded(const time_type &t) const noexcept
Search for the exact time in the list of time points recorded by the raster.
Definition raster.hpp:283
auto times() const noexcept
Time descriptors (unique identifiers) of the dataset bands.
Definition raster.hpp:226
int depth() const noexcept
Depth of the spatial grid.
Definition raster.hpp:201
location_descriptor to_descriptor(const colrow &x) const noexcept
Location descriptor of the cell identified by its column/row.
Definition raster.hpp:328
std::ostream & write(std::ostream &stream) const
Raster is streamable.
Definition raster.hpp:153
latlon to_latlon(const colrow &x) const noexcept
Latitude and longitude of the cell identified by its column/row.
Definition raster.hpp:417
bool is_in_interval(const time_type &t) const noexcept
Check if the exact time point falls between the first and last date of record.
Definition raster.hpp:291
latlon to_latlon(location_descriptor x) const noexcept
Latitude and longitude of the cell to which the given coordinate belongs.
Definition raster.hpp:397
latlon origin() const noexcept
Origin of the spatial grid.
Definition raster.hpp:170
geography::resolution< decimal_degree > get_resolution() const noexcept
Resolution of the spatial grid.
Definition raster.hpp:177
auto to_view() const noexcept
Makes the raster a callable with signature std::optional<value_type> (location_descriptor x,...
Definition raster.hpp:315
rowcol to_rowcol(const latlon &x) const noexcept
Row and column of the cell to which the given coordinate belongs.
Definition raster.hpp:361
colrow to_colrow(location_descriptor x) const noexcept
Column and row of the cell to which the given descriptor belongs.
Definition raster.hpp:373
void to_shapefile(std::map< latlon, int > counts, const std::filesystem::path &file) const
Export geolocalized counts as spatial-points to a shapefile.
Definition raster.hpp:147
int height() const noexcept
Height of the spatial grid.
Definition raster.hpp:195
bool contains(const lonlat &x) const noexcept
Check if the raster contains a coordinate.
Definition raster.hpp:267
auto locations() const noexcept
Location descriptors (unique identifiers) of the grid cells.
Definition raster.hpp:219
value_type NA() const noexcept
NA value, as read in the first band of the dataset.
Definition raster.hpp:207
lonlat to_lonlat(location_descriptor x) const noexcept
Latitude and longitude of the cell to which the given coordinate belongs.
Definition raster.hpp:407
std::optional< value_type > at(location_descriptor x, time_descriptor t) const noexcept
Read the value at location x and time t.
Definition raster.hpp:302
void to_geotiff(Callable f, time_type start, time_type end, const std::filesystem::path &file) const
Export a spatio-temporal raster to a Geotiff file.
Definition raster.hpp:131
raster(const std::filesystem::path &file, const std::vector< time_type > &times)
Constructor.
Definition raster.hpp:101
location_descriptor to_descriptor(const latlon &x) const noexcept
Location descriptor of the cell to which the given coordinate belongs.
Definition raster.hpp:338
bool is_valid(time_descriptor t) const noexcept
Check if the time descriptor is a valid index.
Definition raster.hpp:275
bool is_valid(const colrow &x) const noexcept
Check if the coordinate describes a valid location of the spatial grid.
Definition raster.hpp:243
latlon to_centroid(const latlon &x) const
Reprojects a coordinate to the centroid of the cell it belongs.
Definition raster.hpp:452
rowcol to_rowcol(location_descriptor x) const noexcept
Column and row of the cell to which the given descriptor belongs.
Definition raster.hpp:386
colrow to_colrow(const latlon &x) const noexcept
Column and row of the cell to which the given coordinate belongs.
Definition raster.hpp:348
bool is_valid(const rowcol &x) const noexcept
Check if the coordinate describes a valid location of the spatial grid.
Definition raster.hpp:251
bool is_valid(location_descriptor x) const noexcept
Check if a descriptor describes a valid location of the spatial grid.
Definition raster.hpp:235
lonlat to_lonlat(const colrow &x) const noexcept
Longitude and latitude of the deme identified by its column/row.
Definition raster.hpp:440
bool contains(const latlon &x) const noexcept
Check if the raster contains a coordinate.
Definition raster.hpp:259
latlon to_latlon(const rowcol &x) const noexcept
Latitude and longitude of the cell identified by its row/column.
Definition raster.hpp:430
int width() const noexcept
Width of the spatial grid.
Definition raster.hpp:189
void to_shapefile(std::vector< latlon > points, const std::filesystem::path &file) const
Export spatial points to a shapefile.
Definition raster.hpp:139
Resolution of a spatial grid.
Definition resolution.hpp:16
constexpr value_type lat() const noexcept
Gets latitude resolution.
Definition resolution.hpp:35
constexpr value_type lon() const noexcept
Gets longitude resolution.
Definition resolution.hpp:41
Geospatial data formatting and processing.
Definition geography.hpp:17
std::ostream & operator<<(std::ostream &stream, const C &c)
GridCoordinates are streamable.
Definition coordinates.hpp:51
Grid coordinates.
Definition coordinates.hpp:82
value_type col
Column descriptor.
Definition coordinates.hpp:87
value_type row
Row descriptor.
Definition coordinates.hpp:90
Geographic coordinates.
Definition coordinates.hpp:179
Geographic coordinates.
Definition coordinates.hpp:218
Grid coordinates.
Definition coordinates.hpp:61