raster/grid.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "terraingen/raster/grid.h" | ||
| 2 | |||
| 3 | #include "terraingen/util/checks.h" | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <cmath> | ||
| 7 | #include <limits> | ||
| 8 | #include <stdexcept> | ||
| 9 | |||
| 10 | namespace terraingen { | ||
| 11 | |||
| 12 | 883 | Bounds2d RasterGrid::Bounds() const { | |
| 13 | 883 | constexpr double kInf = std::numeric_limits<double>::infinity(); | |
| 14 | 883 | Bounds2d bounds{kInf, kInf, -kInf, -kInf}; | |
| 15 | 2649 | for (const double col : {0.0, static_cast<double>(width)}) { | |
| 16 | 5298 | for (const double row : {0.0, static_cast<double>(height)}) { | |
| 17 | 3532 | const Vec2d corner = transform.PixelToWorld(col, row); | |
| 18 | 3532 | bounds.minX = std::fmin(bounds.minX, corner.x); | |
| 19 | 3532 | bounds.minY = std::fmin(bounds.minY, corner.y); | |
| 20 | 3532 | bounds.maxX = std::fmax(bounds.maxX, corner.x); | |
| 21 | 3532 | bounds.maxY = std::fmax(bounds.maxY, corner.y); | |
| 22 | } | ||
| 23 | } | ||
| 24 | 883 | return bounds; | |
| 25 | } | ||
| 26 | |||
| 27 | 410 | double RasterGrid::Area() const { | |
| 28 | 410 | return static_cast<double>(width) * static_cast<double>(height) * PixelSize() * PixelSize(); | |
| 29 | } | ||
| 30 | |||
| 31 | 4703 | double RasterGrid::PixelSize() const { | |
| 32 | 4703 | return std::sqrt(std::fabs(transform.pixelWidth * transform.pixelHeight - | |
| 33 | 4703 | transform.rowRotation * transform.colRotation)); | |
| 34 | } | ||
| 35 | |||
| 36 | 1261 | Raster<float> RasterGrid::MakeRaster(float fill) const { | |
| 37 | 1261 | Raster<float> raster(width, height, fill); | |
| 38 | 1261 | raster.SetTransform(transform); | |
| 39 | 1261 | return raster; | |
| 40 | } | ||
| 41 | |||
| 42 | 3906 | double RasterGrid::Spacing(const Surface& surface) const { | |
| 43 | 3906 | if (!surface.IsBody()) { | |
| 44 | 3426 | return PixelSize(); | |
| 45 | } | ||
| 46 | 480 | return std::hypot(transform.rowRotation, transform.pixelHeight) * surface.UnitLength(); | |
| 47 | } | ||
| 48 | |||
| 49 | 40 | RasterGrid ResampledGrid(const RasterGrid& grid, double pixelSize) { | |
| 50 | 40 | RequirePositive(pixelSize, "Pixel size"); | |
| 51 | 36 | const GeoTransform& source = grid.transform; | |
| 52 | 36 | if (source.rowRotation != 0.0 || source.colRotation != 0.0) { | |
| 53 | 3 | throw std::invalid_argument("Cannot resample a rotated raster"); | |
| 54 | } | ||
| 55 | 33 | const double scaleX = std::fabs(source.pixelWidth) / pixelSize; | |
| 56 | 33 | const double scaleY = std::fabs(source.pixelHeight) / pixelSize; | |
| 57 | 33 | const double width = std::max(1.0, std::round(static_cast<double>(grid.width) * scaleX)); | |
| 58 | 33 | const double height = std::max(1.0, std::round(static_cast<double>(grid.height) * scaleY)); | |
| 59 | 33 | if (width > 2147483647.0 || height > 2147483647.0) { | |
| 60 | 2 | throw std::invalid_argument("Resampled raster would be too large"); | |
| 61 | } | ||
| 62 | 31 | RasterGrid resampled{static_cast<std::size_t>(width), static_cast<std::size_t>(height), source}; | |
| 63 | 31 | resampled.transform.pixelWidth = std::copysign(pixelSize, source.pixelWidth); | |
| 64 | 31 | resampled.transform.pixelHeight = std::copysign(pixelSize, source.pixelHeight); | |
| 65 | 31 | return resampled; | |
| 66 | } | ||
| 67 | |||
| 68 | 450 | double ResolvedSpacing(const Raster<float>& raster, double spacing, const Surface& surface) { | |
| 69 | 450 | const RasterGrid grid = RasterGrid::FromRaster(raster); | |
| 70 | 450 | if (!(grid.PixelSize() > 0.0)) { | |
| 71 | 2 | throw std::domain_error("The raster's transform is not invertible"); | |
| 72 | } | ||
| 73 | 448 | return std::max(spacing, grid.Spacing(surface)); | |
| 74 | } | ||
| 75 | |||
| 76 | 453 | double RasterGrid::SurfaceArea(const Surface& surface) const { | |
| 77 | 453 | if (!surface.IsBody()) { | |
| 78 | 406 | return Area(); | |
| 79 | } | ||
| 80 | 47 | const Bounds2d box = Bounds(); | |
| 81 | 47 | const double turn = std::fmin(box.maxX - box.minX, 360.0) * kDegree; | |
| 82 | 47 | const double south = std::fmax(box.minY, -90.0) * kDegree; | |
| 83 | 47 | const double north = std::fmin(box.maxY, 90.0) * kDegree; | |
| 84 | 47 | if (surface.IsSphere()) { | |
| 85 | 44 | const double radius = surface.Radius(); | |
| 86 | 44 | return radius * radius * turn * std::fmax(0.0, std::sin(north) - std::sin(south)); | |
| 87 | } | ||
| 88 | 3 | if (!(north > south)) { | |
| 89 | 1 | return 0.0; | |
| 90 | } | ||
| 91 | 2 | constexpr int kSteps = 64; | |
| 92 | 2 | const double west = box.minX * kDegree; | |
| 93 | 2 | double area = 0.0; | |
| 94 | 130 | for (int j = 0; j < kSteps; ++j) { | |
| 95 | 128 | const double lat = south + (north - south) * (j + 0.5) / kSteps; | |
| 96 | 8320 | for (int i = 0; i < kSteps; ++i) { | |
| 97 | 8192 | const double lon = west + turn * (i + 0.5) / kSteps; | |
| 98 | 8192 | area += surface.AreaPerSteradian({lon / kDegree, lat / kDegree}) * std::cos(lat); | |
| 99 | } | ||
| 100 | } | ||
| 101 | 2 | return area * turn * (north - south) / (kSteps * kSteps); | |
| 102 | } | ||
| 103 | |||
| 104 | } // namespace terraingen | ||
| 105 |