From 682b33102a91d758f6b9e33a358d99dba01f8021 Mon Sep 17 00:00:00 2001 From: Jakub Jarosz Date: Mon, 11 Dec 2023 20:18:19 +0000 Subject: [PATCH] Add upport for elevation checking --- elevation.go | 117 ++++++++++++++++++++++++++++++++++++++--- elevation_test.go | 131 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 231 insertions(+), 17 deletions(-) diff --git a/elevation.go b/elevation.go index a64c769..408b451 100644 --- a/elevation.go +++ b/elevation.go @@ -1,18 +1,123 @@ package geonames -type ElevationSRTM1 struct { +import ( + "context" + "fmt" +) + +type srtm1Resp struct { Srtm1 int `json:"srtm1"` Lng float64 `json:"lng"` Lat float64 `json:"lat"` } +type srtm3Resp struct { + Srtm3 int `json:"srtm3"` + Lng float64 `json:"lng"` + Lat float64 `json:"lat"` +} + +type asterResp struct { + Astergdem int `json:"astergdem"` + Lng float64 `json:"lng"` + Lat float64 `json:"lat"` +} + +type gtopoResp struct { + Gtopo30 int `json:"gtopo30"` + Lng float64 `json:"lng"` + Lat float64 `json:"lat"` +} + +// Elevation holds elevation data expressed in meters npm. type Elevation struct { - Type string - Lat float64 - Lng float64 + Type string + Lat float64 + Lng float64 + Value int +} + +// GetElevationSRTM1 takes two float numbers representing latitude and longitude +// and returns elevation in meters according to SRMT1. The sample area is ca 30m x 30m. +// Ocean areas returns "no data", and have assigned a value of -32768. +func (c *Client) GetElevationSRTM1(ctx context.Context, lat, lng float64) (Elevation, error) { + path := fmt.Sprintf("/srtm1JSON?lat=%.3f&lng=%.3f&username=%s", lat, lng, c.UserName) + var er srtm1Resp + err := c.get(ctx, c.BaseURL+path, &er) + if err != nil { + return Elevation{}, err + } + e := Elevation{ + Type: "srtm1", + Lat: er.Lat, + Lng: er.Lng, + Value: er.Srtm1, + } + return e, nil } -func GetElevationSRTM1() Elevation { +// GetElevationSRTM3 takes two float numbers representing latitude and longitude +// and returns elevation in meters according to SRMT3. +// +// SRTM data consisted of a specially modified radar system that flew +// onboard the Space Shuttle Endeavour during an 11-day mission in February of 2000. +// The dataset covers land areas between 60 degrees north and 56 degrees south. +// SRTM3 data are data points located every 3-arc-second (approximately 90 meters) on a latitude/longitude grid. +func (c *Client) GetElevationSRTM3(ctx context.Context, lat, lng float64) (Elevation, error) { + path := fmt.Sprintf("/srtm3JSON?lat=%.3f&lng=%.3f&username=%s", lat, lng, c.UserName) + var er srtm3Resp + err := c.get(ctx, c.BaseURL+path, &er) + if err != nil { + return Elevation{}, err + } + e := Elevation{ + Type: "srtm3", + Lat: er.Lat, + Lng: er.Lng, + Value: er.Srtm3, + } + return e, nil +} + +// GetElevationAstergdem returns elevation in meters according to aster gdem. +// +// Sample are: ca 30m x 30m, between 83N and 65S latitude. Ocean areas have been assigned a value of -32768 +func (c *Client) GetElevationAstergdem(ctx context.Context, lat, lng float64) (Elevation, error) { + path := fmt.Sprintf("/astergdemJSON?lat=%.3f&lng=%.3f&username=%s", lat, lng, c.UserName) + var er asterResp + err := c.get(ctx, c.BaseURL+path, &er) + if err != nil { + return Elevation{}, err + } + e := Elevation{ + Type: "astergdem", + Lat: er.Lat, + Lng: er.Lng, + Value: er.Astergdem, + } + return e, nil +} - return Elevation{} +// GetElevationGTOPO30 returns elevation data sampled for the area of 1km x 1km. +// +// Ocean areas have assigned value of -9999 indicating no data for the requested lat and lng. +// GTOPO30 is a global digital elevation model (DEM) with a horizontal grid spacing +// of 30 arc seconds (approximately 1 kilometer). +// GTOPO30 is derived from several raster and vector sources of topographic information. +// +// Documentation: http://eros.usgs.gov/#/Find_Data/Products_and_Data_Available/gtopo30_info +func (c *Client) GetElevationGTOPO30(ctx context.Context, lat, lng float64) (Elevation, error) { + path := fmt.Sprintf("/gtopo30JSON?lat=%.3f&lng=%.3f&username=%s", lat, lng, c.UserName) + var er gtopoResp + err := c.get(ctx, c.BaseURL+path, &er) + if err != nil { + return Elevation{}, err + } + e := Elevation{ + Type: "gtopo30", + Lat: er.Lat, + Lng: er.Lng, + Value: er.Gtopo30, + } + return e, nil } diff --git a/elevation_test.go b/elevation_test.go index 872a972..8818dfd 100644 --- a/elevation_test.go +++ b/elevation_test.go @@ -2,20 +2,23 @@ package geonames_test import ( "bytes" + "context" + "fmt" "io" "net/http" "net/http/httptest" "testing" + "github.com/google/go-cmp/cmp" "github.com/qba73/geonames" ) -// ts is a helper func that creates a test server with embedded URI validation. -var testServer = func(reader io.Reader, wantURI string, t *testing.T) *httptest.Server { +// newElevationtestServer creates a test server with embedded URI validation. +func newElevationTestServer(data []byte, wantURI string, t *testing.T) *httptest.Server { ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { gotReqURI := r.RequestURI verifyURIs(wantURI, gotReqURI, t) - _, err := io.Copy(rw, reader) + _, err := io.Copy(rw, bytes.NewBuffer(data)) if err != nil { t.Fatal(err) } @@ -23,21 +26,44 @@ var testServer = func(reader io.Reader, wantURI string, t *testing.T) *httptest. return ts } -func TestGetElevationReturnsDataOnValidInput(t *testing.T) { +func TestGetElevationSRTM1ReturnsDataOnValidInput(t *testing.T) { t.Parallel() - resp := new(bytes.Buffer) - resp.Read([]byte(`23`)) + lat, lng := 50.0, 50.0 + wantReqURI := fmt.Sprintf("/srtm1JSON?lat=%.3f&lng=%.3f&username=DummyUser", lat, lng) - ts := testServer(&resp) + ts := newElevationTestServer(srtm1, wantReqURI, t) + defer ts.Close() + + client, err := geonames.NewClient("DummyUser", geonames.WithBaseURL(ts.URL)) + if err != nil { + t.Fatal(err) + } + + want := geonames.Elevation{ + Type: "srtm1", + Lat: 54.166, + Lng: -6.083, + Value: 375, + } + got, err := client.GetElevationSRTM1(context.Background(), lat, lng) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } } -func TestGetElevationValidInput(t *testing.T) { +func TestGetElevationSRTM3ReturnsDataOnValidInput(t *testing.T) { t.Parallel() - wantReqURI := "/srtm1JSON?lat=50&lng=50&username=DummyUser" - ts := newTestServer(testFile, wantReqURI, t) + lat, lng := 55.166, -6.088 + wantReqURI := fmt.Sprintf("/srtm3JSON?lat=%.3f&lng=%.3f&username=DummyUser", lat, lng) + + ts := newElevationTestServer(srtm3, wantReqURI, t) defer ts.Close() client, err := geonames.NewClient("DummyUser", geonames.WithBaseURL(ts.URL)) @@ -45,5 +71,88 @@ func TestGetElevationValidInput(t *testing.T) { t.Fatal(err) } - client.GetElevation() + want := geonames.Elevation{ + Type: "srtm3", + Lat: 55.166, + Lng: -6.088, + Value: 263, + } + + got, err := client.GetElevationSRTM3(context.Background(), lat, lng) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } } + +func TestGetElevationAstergdemReturnsDataOnValidInput(t *testing.T) { + t.Parallel() + + lat, lng := 50.01, 10.20 + wantReqURI := fmt.Sprintf("/astergdemJSON?lat=%.3f&lng=%.3f&username=DummyUser", lat, lng) + + ts := newElevationTestServer(astergdem, wantReqURI, t) + defer ts.Close() + + client, err := geonames.NewClient("DummyUser", geonames.WithBaseURL(ts.URL)) + if err != nil { + t.Fatal(err) + } + + want := geonames.Elevation{ + Type: "astergdem", + Lat: 50.010, + Lng: 10.200, + Value: 206, + } + + got, err := client.GetElevationAstergdem(context.Background(), lat, lng) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + +func TestGetElevationGTOPO30ReturnsDataOnValidInput(t *testing.T) { + t.Parallel() + + lat, lng := 47.01, 10.2 + wantReqURI := fmt.Sprintf("/gtopo30JSON?lat=%.3f&lng=%.3f&username=DummyUser", lat, lng) + + ts := newElevationTestServer(gtopo30, wantReqURI, t) + defer ts.Close() + + client, err := geonames.NewClient("DummyUser", geonames.WithBaseURL(ts.URL)) + if err != nil { + t.Fatal(err) + } + + want := geonames.Elevation{ + Type: "gtopo30", + Lat: 47.01, + Lng: 10.20, + Value: 2632, + } + + got, err := client.GetElevationGTOPO30(context.Background(), lat, lng) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + +var ( + srtm1 = []byte(`{"srtm1":375,"lng":-6.083,"lat":54.166}`) + srtm3 = []byte(`{"srtm3":263,"lng":-6.088,"lat":55.166}`) + astergdem = []byte(`{"lng":10.2,"astergdem":206,"lat":50.01}`) + gtopo30 = []byte(`{"lng":10.2,"gtopo30":2632,"lat":47.01}`) +)