-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathheightfield-fill.cpp
More file actions
218 lines (177 loc) · 6.94 KB
/
Copy pathheightfield-fill.cpp
File metadata and controls
218 lines (177 loc) · 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <vector>
#include <queue>
#include <tuple>
#include <functional>
#include "heightfield.h"
// Neighbour Directions
const int DIRECTIONS = 8;
const int dx[DIRECTIONS] = { -1, -1, 0, 1, 1, 1, 0, -1 };
const int dy[DIRECTIONS] = { 0, -1, -1, -1, 0, 1, 1, 1 };
// State of a vertex during the execution of the Priority-Flood algorithm.
enum State
{
CLOSED,
OPEN
};
// Element in the priority queue during the execution of the Priority-Flood algorithm.
struct Element
{
// Priority
double elevation;
// Coordinates
int x;
int y;
Element() : elevation(-1.0), x(-1), y(-1) {}
Element(int x, int y, double elevation) : elevation(elevation), x(x), y(y) {}
// Compare elevation, then x, then y
bool operator>(const Element& rhs) const
{
return std::tie(elevation, x, y) > std::tie(rhs.elevation, rhs.x, rhs.y);
}
typedef std::queue<Element> Queue;
typedef std::priority_queue<Element, std::vector<Element>, std::greater<Element> > PriorityQueue;
};
// Init the state of all cells on the edges of the HeightField
void InitBorders(const HeightField* height_field, Element::PriorityQueue& open, std::vector<std::vector<State> >& state)
{
const int size_x = height_field->getSizeX();
const int size_y = height_field->getSizeY();
for (int x = 0; x < size_x; x++)
{
open.emplace(x, 0, height_field->at(x, 0));
state[x][0] = CLOSED;
open.emplace(x, size_y - 1, height_field->at(x, size_y - 1));
state[x][size_y - 1] = CLOSED;
}
for (int y = 1; y < size_y - 1; y++)
{
open.emplace(0, y, height_field->at(0, y));
state[0][y] = CLOSED;
open.emplace(size_x - 1, y, height_field->at(size_x - 1, y));
state[size_x - 1][y] = CLOSED;
}
}
/*!
\brief Modifies the HeightField to guarantee drainage.
\sa FillDepressions(const double&, ScalarField2&) const
\author Mathieu Gaillard
\param eps The minimal elevation difference between two cells.
\return The number of cells where a significant alteration of the DEM has occurred.
*/
int HeightField::fillDepressions(const double& eps)
{
// Store the depressions in a separate ScalarField2
ScalarField2 depression_field(getDomain(), nx, ny, 0.0);
int nb_false_pit_cell = fillDepressions(eps, depression_field);
// Add the depressions to this HeightField
operator+=(depression_field);
return nb_false_pit_cell;
}
/*!
\brief Modifies the HeightField to guarantee drainage.
Algorithm:
Priority-flood: An optimal depression-filling and watershed-labeling
algorithm for digital elevation models.
Barnes, R., Lehman, C., Mulla, D., 2014.
This version of Priority-Flood starts on the edges of the DEM and then
works its way inwards using a priority queue to determine the lowest cell
which has a path to the edge. The neighbours of this cell are added to the
priority queue if they are higher. If they are lower, then their elevation
is increased by a small amount to ensure that they have a drainage path and
they are added to a "pit" queue which is used to flood pits. Cells which
are higher than a pit being filled are added to the priority queue. In this
way, pits are filled without incurring the expense of the priority queue.
The values to fill the depressions are added to the depression field.
The depression field should have the exact same size as the HeightField.
If the given depression field does not have the exact same size as the height field,
returns the total number of vertices in the heightfield.
To be sure that the height field is never flat, elevations are always increased
by std::nextafter(elevation) + eps. Thus, even if eps == 0, the terrain is never flat.
Warning, because the method is const it is not recommended to use it like that
\code
// Not recommended, although it works.
hf.FillDepressions(0.0, hf);
\endcode
\author Mathieu Gaillard
\param eps The minimal elevation difference between two cells.
\param depression_field A scalar field in which the values to fill depressions are added
\return The number of cells where a significant alteration of the DEM has occurred.
*/
int HeightField::fillDepressions(const double& eps, ScalarField2& depression_field) const
{
// Heightfield properties
const int size_x = getSizeX();
const int size_y = getSizeY();
// Check ScalarField2 size
if (depression_field.getSizeX() != size_x || depression_field.getSizeY() != size_y)
{
return getNumElements();
}
// State of vertices
std::vector<std::vector<State> > state(size_x, std::vector<State>(size_y, OPEN));
// A min priority queue of vertices to visit, ordered by elevation
Element::PriorityQueue open;
// A queue of vertices to visit in priority, because they have the same elevation as the current element
Element::Queue pit;
// True if currently filling a pit
bool currently_in_pit = false;
// Elevation of the first vertex of the pit
double pit_top = 0;
// The number of cells where a significant alteration of the DEM has occurred
int nb_false_pit_cell = 0;
// Init the state of all cells on the edges of the HeightField
InitBorders(this, open, state);
// Perform algorithm
while (!open.empty() || !pit.empty())
{
Element curr_vertex;
// Maintains the total order by ensuring that all cells of equal elevation
// which may border a depression are treated equally. Otherwise, in priority,
// take the next element from the pit queue.
if ((!open.empty() && !pit.empty() && open.top().elevation == pit.front().elevation) || pit.empty())
{
curr_vertex = open.top();
open.pop();
currently_in_pit = false;
}
else if (!pit.empty())
{
curr_vertex = pit.front();
pit.pop();
if (currently_in_pit == false)
{
currently_in_pit = true;
pit_top = curr_vertex.elevation;
}
}
// For all neighbors of the current element
for (int d = 0; d < DIRECTIONS; d++)
{
int neighbor_x = curr_vertex.x + dx[d];
int neighbor_y = curr_vertex.y + dy[d];
// If the neighbor exists and is not closed
if (neighbor_x >= 0 && neighbor_x < size_x && neighbor_y >= 0 && neighbor_y < size_y && state[neighbor_x][neighbor_y] == OPEN)
{
state[neighbor_x][neighbor_y] = CLOSED;
// Current elevation plus an epsilon
double curr_elevation_plus_eps = nextafter(curr_vertex.elevation, std::numeric_limits<double>::infinity()) + eps;
if (at(neighbor_x, neighbor_y) <= curr_elevation_plus_eps)
{
if (currently_in_pit == true && pit_top < at(neighbor_x, neighbor_y))
{
// A significant alteration of the DEM has occurred
// The inside of the pit is now higher than the terrain surrounding it
nb_false_pit_cell++;
}
depression_field(neighbor_x, neighbor_y) += curr_elevation_plus_eps - at(neighbor_x, neighbor_y);
pit.emplace(neighbor_x, neighbor_y, curr_elevation_plus_eps);
}
else
{
open.emplace(neighbor_x, neighbor_y, at(neighbor_x, neighbor_y));
}
}
}
}
return nb_false_pit_cell;
}