Skip to content

Commit d8cb174

Browse files
authored
Fix highest peaks being filtered from bathymetry (#44)
* Fix highest peaks being filtered from bathymetry. We were assuming that prominence=elevation for the highest peak in a tree. When the peak's elevation is negative, as in bathymetry, this causes the prominence of the highest peak to be negative, which means the highest peak will always be filtered out. * Update README.md
1 parent 21fbbe0 commit d8cb174

9 files changed

+78
-15
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,16 @@ The input divide tree must be free of runoffs (see the options to ```merge_divid
355355
parent, and its line parent on each line. Landmass high points (where the prominence is equal to the elevation) are not included.
356356
Their key saddles are the ocean, and there isn't a well-defined way to connect such peaks to other land masses through the divide tree.
357357

358+
## Bathymetry
359+
360+
For "regular" terrain on the Earth, we set the prominence of the highest peak equal to its elevation by convention. This obviously
361+
doesn't work for bathymetry (terrain below sea level). It's not clear what the prominence of such underwater peaks should be,
362+
but clearly we don't want to filter them away by assigning them negative prominence values.
363+
364+
If the ```-b``` flag is set on the ```prominence``` and ```merge_divide_trees```, we compute the prominence of the highest peak to
365+
be its elevation, minus the elevation of the lowest saddle in the divide tree. This guarantees that the highest peak will have
366+
the highest prominence. Setting the ```--bathymetry``` flag on ```run_prominence.py``` will also turn on this behavior.
367+
358368
## Anti-prominence
359369

360370
The "anti-prominence" of low points can be computed by the same algorithm, simply by changing

code/compute_parents.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ int main(int argc, char **argv) {
112112

113113
// Build island tree to get prominence values
114114
IslandTree *islandTree = new IslandTree(*divideTree);
115-
islandTree->build();
115+
islandTree->build(false); // TODO: Flag for bathymetry?
116116

117117
// Build line parent tree
118118
LineTree *lineTree = new LineTree(*divideTree);

code/island_tree.cpp

+27-5
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ IslandTree::IslandTree(const DivideTree &divideTree) :
3636
mDivideTree(divideTree) {
3737
}
3838

39-
void IslandTree::build() {
39+
void IslandTree::build(bool isBathymetry) {
4040
// Initialize topology
4141
mNodes.resize(mDivideTree.nodes().size());
4242
int index = 1; // Peaks are 1-indexed; put in a dummy node 0
@@ -54,7 +54,7 @@ void IslandTree::build() {
5454
uninvertPeaks();
5555
uninvertSaddles();
5656

57-
computeProminences();
57+
computeProminences(isBathymetry);
5858
}
5959

6060
// Sort peaks so that parent is always higher elevation.
@@ -152,7 +152,7 @@ void IslandTree::uninvertSaddle(int nodeId) {
152152
}
153153
}
154154

155-
void IslandTree::computeProminences() {
155+
void IslandTree::computeProminences(bool isBathymetry) {
156156
for (int i = 1; i < (int) mNodes.size(); ++i) {
157157
Elevation elev = getPeak(i).elevation;
158158
int childNodeId = i;
@@ -177,8 +177,17 @@ void IslandTree::computeProminences() {
177177
}
178178

179179
if (parentNodeId == Node::Null) {
180-
// This is the highest point in the tree
181-
mNodes[i].prominence = elev;
180+
// This is the highest point in the tree. In "normal" circumstances where
181+
// all the elevations are nonnegative, we can safely define the prominence as equal
182+
// to the elevation. But for bathymetry the prominence is not so clear. We
183+
// use the elevation of the minimum saddle as "sea level". This should agree
184+
// with the traditional definition (prominence = elevation) for regular terrain,
185+
// and for bathymetry, it will guarantee that the highest point has the
186+
// highest prominence, which matches intuition.
187+
//
188+
// Other definitions of the prominence of the highest point are possible,
189+
// for example, height above the minimum sample value.
190+
mNodes[i].prominence = elev - getSeaLevelValue(isBathymetry);
182191
} else {
183192
// Prominence = peak elevation - key col elevation
184193
int saddlePeakId = mNodes[childNodeId].saddlePeakId;
@@ -189,6 +198,19 @@ void IslandTree::computeProminences() {
189198
}
190199
}
191200

201+
Elevation IslandTree::getSeaLevelValue(bool isBathymetry) {
202+
// If this is bathymetry, find the minimum saddle elevation in the tree.
203+
if (isBathymetry && !mNodes.empty()) {
204+
Elevation elevation = std::numeric_limits<Elevation>::max();
205+
for (auto const &saddle : mDivideTree.saddles()) {
206+
elevation = std::min(elevation, saddle.elevation);
207+
}
208+
return elevation;
209+
}
210+
211+
return 0;
212+
}
213+
192214
const Peak &IslandTree::getPeak(int peakId) const {
193215
return mDivideTree.peaks()[peakId - 1]; // 1-indexed
194216
}

code/island_tree.h

+7-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ class IslandTree {
4949

5050
explicit IslandTree(const DivideTree &divideTree);
5151

52-
void build();
52+
// If isBathymetry is true, don't assume sea level = 0.
53+
void build(bool isBathymetry);
5354

5455
bool writeToFile(const std::string &filename) const;
5556

@@ -66,11 +67,15 @@ class IslandTree {
6667
// Sort peaks by increasing divide tree saddle elevation
6768
void uninvertSaddles();
6869

69-
void computeProminences();
70+
void computeProminences(bool isBathymetry);
7071

7172
void uninvertPeak(int nodeId);
7273
void uninvertSaddle(int nodeId);
7374

75+
// Return the elevation of "sea level", i.e. the elevation used as the
76+
// base of the highest peak in the tree.
77+
Elevation getSeaLevelValue(bool isBathymetry);
78+
7479
// Indices start at 1; use these helper functions to deal with offset.
7580
const Peak &getPeak(int peakId) const;
7681
const Saddle &getSaddle(int saddleId) const;

code/merge_divide_trees.cpp

+10-4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ static void usage() {
4848
printf(" in same units as divide tree, default = 100\n");
4949
printf(" -s scale_factor Scale output elevations by this (e.g. meters to feet), default = 1\n");
5050
printf(" -t num_threads Number of threads to use, default = 1\n");
51+
printf(" -b Input data is bathymetric (do not use sea level)\n");
5152
exit(1);
5253
}
5354

@@ -83,6 +84,7 @@ int main(int argc, char **argv) {
8384
Elevation minProminence = 100;
8485
bool finalize = false;
8586
bool flipElevations = false;
87+
bool bathymetry = false;
8688
int numThreads = 1;
8789
float elevationScale = 1.0f;
8890

@@ -96,12 +98,16 @@ int main(int argc, char **argv) {
9698
{"v", required_argument, nullptr, 0},
9799
{nullptr, 0, 0, 0},
98100
};
99-
while ((ch = getopt_long(argc, argv, "afm:s:t:", long_options, nullptr)) != -1) {
101+
while ((ch = getopt_long(argc, argv, "abfm:s:t:", long_options, nullptr)) != -1) {
100102
switch (ch) {
101103
case 'a':
102104
flipElevations = true;
103105
break;
104-
106+
107+
case 'b':
108+
bathymetry = true;
109+
break;
110+
105111
case 'f':
106112
finalize = true;
107113
break;
@@ -183,7 +189,7 @@ int main(int argc, char **argv) {
183189
VLOG(1) << "Building prominence island tree";
184190

185191
IslandTree *unprunedIslandTree = new IslandTree(*divideTree);
186-
unprunedIslandTree->build();
192+
unprunedIslandTree->build(bathymetry);
187193

188194
if (finalize) {
189195
divideTree->deleteRunoffs(); // Does not affect island tree
@@ -211,7 +217,7 @@ int main(int argc, char **argv) {
211217

212218
// Build new island tree on pruned divide tree to get final prominence values
213219
IslandTree *prunedIslandTree = new IslandTree(*divideTree);
214-
prunedIslandTree->build();
220+
prunedIslandTree->build(bathymetry);
215221

216222
// Write final prominence value table
217223
string filename = outputFilename + ".txt";

code/prominence.cpp

+9-2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ static void usage() {
6262
printf(" -t num_threads Number of threads, default = 1\n");
6363
printf(" -z UTM zone (if input data is in UTM)\n");
6464
printf(" -a Compute anti-prominence instead of prominence\n");
65+
printf(" -b Input DEM is bathymetric (do not use sea level)\n");
6566
printf(" --kml Generate KML output of divide tree\n");
6667
exit(1);
6768
}
@@ -80,6 +81,7 @@ int main(int argc, char **argv) {
8081
int ch;
8182
string str;
8283
bool antiprominence = false;
84+
bool bathymetry = false;
8385
int writeKml = false;
8486
int utmZone = NO_UTM_ZONE;
8587

@@ -91,12 +93,16 @@ int main(int argc, char **argv) {
9193
{"kml", no_argument, &writeKml, 1},
9294
{nullptr, 0, 0, 0},
9395
};
94-
while ((ch = getopt_long(argc, argv, "af:i:k:m:o:t:z:", long_options, nullptr)) != -1) {
96+
while ((ch = getopt_long(argc, argv, "abf:i:k:m:o:t:z:", long_options, nullptr)) != -1) {
9597
switch (ch) {
9698
case 'a':
9799
antiprominence = true;
98100
break;
99-
101+
102+
case 'b':
103+
bathymetry = true;
104+
break;
105+
100106
case 'f': {
101107
auto format = FileFormat::fromName(optarg);
102108
if (format == nullptr) {
@@ -183,6 +189,7 @@ int main(int argc, char **argv) {
183189

184190
// Don't write out unpruned divide tree--it's too large and slow
185191
ProminenceOptions options = {output_directory, minProminence, false, antiprominence,
192+
bathymetry,
186193
static_cast<bool>(writeKml)};
187194

188195
// Caching doesn't do anything for our calculation and the tiles are huge

code/prominence_task.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ bool ProminenceTask::run(double lat, double lng, const CoordinateSystem &coordin
8686
VLOG(1) << "Pruning divide tree to " << mOptions.minProminence << " prominence";
8787

8888
IslandTree islandTree(*divideTree);
89-
islandTree.build();
89+
islandTree.build(mOptions.bathymetry);
9090

9191
divideTree->prune(mOptions.minProminence, islandTree);
9292

code/prominence_task.h

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ struct ProminenceOptions {
4343
// or anti-prominence, which is the "prominence" of low points.
4444
bool antiprominence;
4545

46+
// Data is bathymetry; do not assume sea level = 0 for prom calculations.
47+
bool bathymetry;
48+
4649
// Write KML of pruned divide tree?
4750
bool writeKml;
4851
};

scripts/run_prominence.py

+10
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ def main():
219219
help='Size of reprojected tiles to generate')
220220
parser.add_argument('--samples_per_tile', type=int, default=10000,
221221
help='Number of samples per edge of a reprojected tile')
222+
parser.add_argument('--bathymetry', action=argparse.BooleanOptionalAction,
223+
help='Is the input DEM bathymetry?')
222224
args = parser.parse_args()
223225

224226
gdal.UseExceptions()
@@ -304,9 +306,13 @@ def main():
304306
# Run prominence and merge_divide_trees
305307
print("Running prominence")
306308
prominence_binary = os.path.join(args.binary_dir, "prominence")
309+
extra_args = ""
310+
if args.bathymetry:
311+
extra_args += " -b"
307312
prom_command = f"{prominence_binary} --v=1 -f CUSTOM-{args.degrees_per_tile}-{args.samples_per_tile}" + \
308313
f" -i {args.tile_dir} -o {args.output_dir}" + \
309314
f" -t {args.threads} -m {args.min_prominence}" + \
315+
extra_args + \
310316
f" -- {ymin} {ymax} {xmin} {xmax}"
311317
run_command(prom_command)
312318

@@ -317,8 +323,12 @@ def main():
317323
units_multiplier = 1
318324
if args.output_units == "feet":
319325
units_multiplier = 3.28084
326+
extra_args = ""
327+
if args.bathymetry:
328+
extra_args += " -b"
320329
merge_command = f"{merge_binary} --v=1 -f" + \
321330
f" -t {args.threads} -m {args.min_prominence} -s {units_multiplier}" + \
331+
extra_args + \
322332
f" {merge_output} {merge_inputs}"
323333
run_command(merge_command)
324334

0 commit comments

Comments
 (0)