forked from gsn/predictor
feat: refactor
This commit is contained in:
parent
82ef1cb3b8
commit
51bbf3c579
44 changed files with 8589 additions and 0 deletions
113
internal/elevation/elevation.go
Normal file
113
internal/elevation/elevation.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
package elevation
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
|
||||
mmap "github.com/edsrzf/mmap-go"
|
||||
)
|
||||
|
||||
// Dataset provides global elevation lookup, compatible with ruaumoko.
|
||||
// Binary format: int16 little-endian elevation values in metres, row-major (lat, lon).
|
||||
// Latitude axis: -90 to +90 (south to north), Longitude axis: 0 to 360 (wraps).
|
||||
// Resolution: 120 cells per degree (30 arc-seconds).
|
||||
const (
|
||||
CellsPerDegree = 120
|
||||
NumLats = 180*CellsPerDegree + 1 // 21601
|
||||
NumLons = 360 * CellsPerDegree // 43200
|
||||
DataSize = NumLats * NumLons * 2 // 1,866,326,400 bytes (~1.74 GiB)
|
||||
)
|
||||
|
||||
// Dataset is a memory-mapped global elevation grid.
|
||||
type Dataset struct {
|
||||
mm mmap.MMap
|
||||
file *os.File
|
||||
}
|
||||
|
||||
// Open opens an existing elevation dataset file.
|
||||
func Open(path string) (*Dataset, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open elevation: %w", err)
|
||||
}
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("stat elevation: %w", err)
|
||||
}
|
||||
if info.Size() != DataSize {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("elevation dataset should be %d bytes (was %d)", DataSize, info.Size())
|
||||
}
|
||||
|
||||
mm, err := mmap.Map(f, mmap.RDONLY, 0)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("mmap elevation: %w", err)
|
||||
}
|
||||
|
||||
return &Dataset{mm: mm, file: f}, nil
|
||||
}
|
||||
|
||||
// getCell reads the int16 elevation at grid indices (latIdx, lngIdx).
|
||||
func (d *Dataset) getCell(latIdx, lngIdx int) int16 {
|
||||
// Clamp latitude
|
||||
if latIdx < 0 {
|
||||
latIdx = 0
|
||||
}
|
||||
if latIdx >= NumLats {
|
||||
latIdx = NumLats - 1
|
||||
}
|
||||
// Wrap longitude
|
||||
lngIdx = lngIdx % NumLons
|
||||
if lngIdx < 0 {
|
||||
lngIdx += NumLons
|
||||
}
|
||||
|
||||
off := (latIdx*NumLons + lngIdx) * 2
|
||||
return int16(binary.LittleEndian.Uint16(d.mm[off : off+2]))
|
||||
}
|
||||
|
||||
// Get returns the interpolated elevation in metres at the given coordinates.
|
||||
// lat: -90 to +90, lng: 0 to 360 (or -180 to 180, will be normalised).
|
||||
func (d *Dataset) Get(lat, lng float64) float64 {
|
||||
// Normalise longitude to [0, 360)
|
||||
if lng < 0 {
|
||||
lng += 360
|
||||
}
|
||||
|
||||
// Convert to cell coordinates
|
||||
latCell := (lat + 90.0) * CellsPerDegree
|
||||
lngCell := lng * CellsPerDegree
|
||||
|
||||
lat0 := int(math.Floor(latCell))
|
||||
lng0 := int(math.Floor(lngCell))
|
||||
latFrac := latCell - float64(lat0)
|
||||
lngFrac := lngCell - float64(lng0)
|
||||
|
||||
// Bilinear interpolation
|
||||
v00 := float64(d.getCell(lat0, lng0))
|
||||
v10 := float64(d.getCell(lat0+1, lng0))
|
||||
v01 := float64(d.getCell(lat0, lng0+1))
|
||||
v11 := float64(d.getCell(lat0+1, lng0+1))
|
||||
|
||||
return (1-latFrac)*((1-lngFrac)*v00+lngFrac*v01) +
|
||||
latFrac*((1-lngFrac)*v10+lngFrac*v11)
|
||||
}
|
||||
|
||||
// Close unmaps and closes the dataset.
|
||||
func (d *Dataset) Close() error {
|
||||
if d.mm != nil {
|
||||
d.mm.Unmap()
|
||||
d.mm = nil
|
||||
}
|
||||
if d.file != nil {
|
||||
err := d.file.Close()
|
||||
d.file = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue