Go module to read elevation data from SRTM HGT files.
- 30m (1 arc second)
- 90m (3 arc seconds)
Lookups take the nearest sample on the tile's grid, which spans the full
degree: 3601 samples 1/3600° apart, or 1201 samples 1/1200° apart. A sample
with no data returns ErrVoidData.
t, err := hgt.OpenTile("data/N48E021.hgt")
if err != nil {
log.Fatal(err)
}
defer t.Close()
e, err := t.At(hgt.Coord{Lon: 21.2613, Lat: 48.7162})
if err != nil {
log.Println(err)
}
log.Printf("elevation=%d resolution=%s", e, t.Resolution)Store resolves whichever tile a coordinate falls in, opening it on demand
and keeping recently used tiles in a bounded cache. It is safe for
concurrent use.
s, err := hgt.OpenStore("data")
if err != nil {
log.Fatal(err)
}
defer s.Close()
e, res, err := s.At(context.Background(), hgt.Coord{Lon: 21.2613, Lat: 48.7162})
if err != nil {
log.Println(err)
}
log.Printf("elevation=%d resolution=%s", e, res)AtBatch keeps one tile in hand across consecutive coordinates that fall
in it — the shape of a GPS track. Results come back in input order, with
per-point failures in Result.Err rather than aborting the batch. Tile
has it too, without the context.
for i, r := range s.AtBatch(context.Background(), track) {
if r.Err != nil {
log.Printf("point %d: %s", i, r.Err)
continue
}
log.Printf("point %d: elevation=%d resolution=%s", i, r.Elevation, r.Resolution)
}AtBatchScattered takes the same arguments and returns results in the same
input order, but resolves them tile by tile. It pays off only when a batch
touches more tiles than the cache holds; on a track, or with a cache that
fits the working set, it is slower than AtBatch.
At and AtBatch check the context before opening a tile (in a batch, at
each tile change). They cannot interrupt a read already in flight. A
cancelled batch fills the results it has not reached with the context error.
WithCache(c)— the tile cache.DefaultCache(n)is the built-in one, holdingntiles; a store with noWithCachegets 128.WithMmapTiles()— memory-map each tile instead of reading it withReadAt, avoiding a syscall per sample once the pages are resident.WithMmapdoes the same for a singleOpenTile. Mapping is unix-only; on other platforms, and for any tile that cannot be mapped, it silently falls back toReadAt.WithPreloadTiles()— read each tile fully into the heap on open. Takes precedence over mmap, and makes the cache capacity a memory budget: a 1-arc-second tile is ~25 MB.WithBounds(fn)— reject coordinates outside your dataset. Defaults to SRTM coverage, roughly 56°S to 60°N.WithExtension(ext)— extension to resolve tile names with, instead of.hgt. The leading dot is optional;""means no extension. A path separator in it is rejected.
s, err := hgt.OpenStore("data", hgt.WithExtension(".HGT"))Misses are remembered, so uncovered coordinates cost nothing after the
first. Call Forget if tiles arrive while the store is live:
s.Forget()A wrong-sized tile is never remembered — that is what a tile looks like
mid-download — so it is retried until the write completes. Forget leaves
cached tiles alone; an open tile keeps serving what it was opened on.
Implement hgt.Cache (Get, Add and Clear over hgt.TileKey and
*hgt.Tile) for a residency policy of your own — an unbounded map, a
budget in bytes, metrics, or nothing cached at all. A TileKey is the tile
id (N48E021), extension excluded.
The cache owns the tiles it is given: it must call (*Tile).Close on every
tile it drops, or that tile's descriptor leaks. Close is idempotent and
never blocks on in-flight reads. All three methods must be safe for
concurrent use, and a cache belongs to one Store.
s, err := hgt.OpenStore("data", hgt.WithCache(myCache))The CLOCK cache behind DefaultCache lives in the hgt/cache subpackage,
generic over key and value, if you would rather delegate to it than
reimplement eviction.
The tests generate their own tiles, so no dataset is needed:
go test ./...
Fixtures go to a versioned temp directory (~80 MB) and are reused.
Licensed under MIT license.