hgtchop and terrafit

Last updated · how this page is sourced

A one-degree SRTM tile holds 12,967,201 elevation samples. The terrain that comes out of it has 15,828 nodes. Almost everything is thrown away, and which parts survive is the whole point of this stage.

Where the 818 to 1 goes

SRTM gives you a regular grid: a height every arc-second, whether the ground under it is a cliff face or the middle of the Pacific. A renderer does not want a regular grid, it wants a mesh with detail where the ground has detail.

terrafit fits a surface to the grid, adding nodes until the worst remaining error falls below a tolerance. Flat water needs almost none. A ridgeline needs many. For N37W123 the result looks like this:

The surviving mesh nodes over one degree square: the ocean half is
       almost empty, nodes crowd along the coast and the ridges.
Every node that survived, coloured by elevation. The coastline is legible purely from where the algorithm decided detail was worth keeping; nobody drew it.

The practical consequence: a mountainous square costs more time and produces larger output than a flat one of the same size. Area is not the only thing that decides what a build costs. If you are estimating, look at the terrain, not just the bounding box.

The two commands

hgtchop 1 N37W123.hgt $WORK/SRTMGL1
terrafit -j 10 $WORK/SRTMGL1

Two seconds and one second respectively, at 29 MB and 46 MB peak memory. This is the cheap end of a TerraGear build by a wide margin; see where the time actually goes.

hgtchop: grid in, tiles out

Three positional arguments, no flags:

hgtchop <resolution> <hgt_file> <work_dir>

Resolution is 1 or 3, for 1-arc-second or 3-arc-second data, and has to match the file. Anything else exits with the usage message.

Two things catch people. It takes a raw .hgt, not the zip the repository README describes: unzip first. And the directory you name has to be one genapts recognises later: SRTM-1, SRTM-3, SRTM-30, SRTMGL1 or SRTMGL3. Call it anything else and tg-construct will find it while genapts will not.

One degree at latitude 37 comes out as 32 tiles: four columns by eight rows, each 0.25° of longitude by 0.125° of latitude, all inside the bucket w130n30/w123n37. Tiles are wider than they are tall because longitude width grows toward the poles to keep ground area roughly even.

terrafit: the knobs that matter

terrafit [-m minnodes] [-x maxnodes] [-e maxerror] [-j threads] [-f] <path>

Point it at a directory and it walks every .arr.gz beneath it. Three settings decide the trade:

FlagDefaultWhat raising it does
--maxerror40 mLoosens the tolerance. Fewer nodes, faster, coarser ground.
--maxnodes1000Raises the ceiling per tile. More detail where the terrain earns it.
--minnodes50Floor per tile. Mostly matters over water, where the fit would otherwise stop early.

--force reprocesses tiles whose output is newer than the input. Without it, re-running is cheap and skips finished work; useful, because the usual reason to re-run this stage is to try a different --maxerror.

Reading the output yourself

A .fit.gz is gzipped plain text: a node count, then one longitude latitude elevation per line.

$ zcat 942050.fit.gz | head -3
1000
-122.50000000 +37.50000000 0.00
-122.50000000 +37.55250000 96.00

Which makes it the easiest place in the whole pipeline to check that your elevation data landed where you think it did. If a tile’s nodes are all at zero, the problem is upstream of here and you can see it without loading anything. The rest of the chain is on the files a build writes.

The other choppers

Four more preprocessing tools install alongside these, none of which this build used: srtmchop, dtedchop and gdalchop read other input formats, and fillvoids patches SRTM’s data holes from a second source. SRTM version 3 is void-filled already, so fillvoids matters mainly if you are working with older data.