Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ VERSION := $(shell git describe --tags || echo "v0.0.0")
VER_CUT := $(shell echo $(VERSION) | cut -c2-)

build:
@CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(ARCH) \
@CGO_ENABLED=1 GOOS=$(GOOS) GOARCH=$(ARCH) \
go build -o bin/$(BIN_NAME) ./

run: build
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pressly/goose v2.7.0+incompatible
github.com/stretchr/testify v1.9.0
github.com/uber/h3-go/v4 v4.1.0
google.golang.org/grpc v1.63.2
)

Expand Down
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,6 @@ github.com/ClickHouse/clickhouse-go/v2 v2.23.1 h1:h+wOAjtycWeR8gNh0pKip+P4/Lyp9x
github.com/ClickHouse/clickhouse-go/v2 v2.23.1/go.mod h1:aNap51J1OM3yxQJRgM+AlP/MPkGBCL8A74uQThoQhR0=
github.com/DIMO-Network/devices-api v1.25.16 h1:lGOTMgWlrmTcXPhatWewm7wBwuuaDfFNcH9T2AvQjsM=
github.com/DIMO-Network/devices-api v1.25.16/go.mod h1:SYEGPaxWzNQ2wByAnYjqGhu+IZyML0xofkqpzog6lWU=
github.com/DIMO-Network/model-garage v0.1.1 h1:da2VSuzsqneizZrlaScruBAD4OeRvIL/aOLJTs244nw=
github.com/DIMO-Network/model-garage v0.1.1/go.mod h1:5bHic+vrkizMOLUfFsMs0SkgOnKgbUzwTNf4i2vPmK0=
github.com/DIMO-Network/model-garage v0.1.2-0.20240425165107-2cb5c64d213f h1:7mA0E/4oZn/p3L0O4O83SDPKQHSTVioJXvP/UIw19mk=
github.com/DIMO-Network/model-garage v0.1.2-0.20240425165107-2cb5c64d213f/go.mod h1:5bHic+vrkizMOLUfFsMs0SkgOnKgbUzwTNf4i2vPmK0=
github.com/DIMO-Network/model-garage v0.1.2 h1:zcpwbC52ABG1Jv/BMo+immVAbCWufkZf7HNbQoUCZJo=
github.com/DIMO-Network/model-garage v0.1.2/go.mod h1:5bHic+vrkizMOLUfFsMs0SkgOnKgbUzwTNf4i2vPmK0=
github.com/DIMO-Network/shared v0.10.10 h1:h9b0oj0hSvR2CRp6PzqPhsSjf03XUyTGFsAlPv1g1XM=
Expand Down Expand Up @@ -1063,6 +1059,8 @@ github.com/twmb/franz-go v1.16.1 h1:rpWc7fB9jd7TgmCyfxzenBI+QbgS8ZfJOUQE+tzPtbE=
github.com/twmb/franz-go v1.16.1/go.mod h1:/pER254UPPGp/4WfGqRi+SIRGE50RSQzVubQp6+N4FA=
github.com/twmb/franz-go/pkg/kmsg v1.7.0 h1:a457IbvezYfA5UkiBvyV3zj0Is3y1i8EJgqjJYoij2E=
github.com/twmb/franz-go/pkg/kmsg v1.7.0/go.mod h1:se9Mjdt0Nwzc9lnjJ0HyDtLyBnaBDAd7pCje47OhSyw=
github.com/uber/h3-go/v4 v4.1.0 h1:HWmEFiTxS3m4WgwDZjt4N73klOhrUZ/aFoY+RC6VFZk=
github.com/uber/h3-go/v4 v4.1.0/go.mod h1:VDpXVn4NLetBoISLEbiTVNstwW00bhHolV8I+jx9G+4=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
Expand Down
179 changes: 179 additions & 0 deletions internal/h3/h3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package h3

import (
"fmt"

"github.com/benthosdev/benthos/v4/public/bloblang"
"github.com/uber/h3-go/v4"
)

func init() {
latLngToHexSpec := bloblang.NewPluginSpec().
Description("Returns hex id for given latitude and longitude at desired resolution.").
Param(bloblang.NewFloat64Param("lat")).
Param(bloblang.NewFloat64Param("lng")).
Param(bloblang.NewInt64Param("resolution"))

err := bloblang.RegisterFunctionV2("h3_lat_lng_to_hex", latLngToHexSpec, func(args *bloblang.ParsedParams) (bloblang.Function, error) {
lat, err := args.GetFloat64("lat")
if err != nil {
return nil, err
}

lng, err := args.GetFloat64("lng")
if err != nil {
return nil, err
}

resolution, err := args.GetInt64("resolution")
if err != nil {
return nil, err
}

return func() (interface{}, error) {
if resolution < 0 && resolution < 15 {
return nil, fmt.Errorf("resolution should be between 0 and 15")
}
latLng := h3.NewLatLng(lat, lng)
cell := h3.LatLngToCell(latLng, int(resolution))
return cell.String(), err
}, nil
})
if err != nil {
panic(err)
}

hexLatLonSpec := bloblang.NewPluginSpec().
Description("Returns lat,lon for given hex.").
Param(bloblang.NewStringParam("hex_id"))

err = bloblang.RegisterFunctionV2("h3_hex_to_geo", hexLatLonSpec, func(args *bloblang.ParsedParams) (bloblang.Function, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool staff overall!!! Nit - extracting those anonymous functions to separate ones for readability might be good.

hex_id, err := args.GetString("hex_id")
if err != nil {
return nil, err
}
return func() (interface{}, error) {
index := h3.IndexFromString(hex_id)
cell := h3.Cell(index)
if !cell.IsValid() {
return nil, fmt.Errorf("failed to parse hex id")
}
return map[string]float64{"latitude": cell.LatLng().Lat, "longitude": cell.LatLng().Lng}, nil
}, nil
})

if err != nil {
panic(err)
}

validHexSpec := bloblang.NewPluginSpec().
Description("Returns true if the given hex id is valid.").
Param(bloblang.NewStringParam("hex_id"))

err = bloblang.RegisterFunctionV2("h3_valid_hex", validHexSpec, func(args *bloblang.ParsedParams) (bloblang.Function, error) {
hex_id, err := args.GetString("hex_id")
if err != nil {
return nil, err
}
return func() (interface{}, error) {
index := h3.IndexFromString(hex_id)
cell := h3.Cell(index)
return cell.IsValid(), nil
}, nil
})

if err != nil {
panic(err)
}

getH3ResolutionSpec := bloblang.NewPluginSpec().
Description("Returns the resolution of the given hex id.").
Param(bloblang.NewStringParam("hex_id"))

err = bloblang.RegisterFunctionV2("h3_get_resolution", getH3ResolutionSpec, func(args *bloblang.ParsedParams) (bloblang.Function, error) {
hex_id, err := args.GetString("hex_id")
if err != nil {
return nil, err
}
return func() (interface{}, error) {
index := h3.IndexFromString(hex_id)
cell := h3.Cell(index)
if !cell.IsValid() {
return nil, fmt.Errorf("failed to parse hex id")
}
return cell.Resolution(), nil
}, nil
})

if err != nil {
panic(err)
}

getH3ParentIdSpec := bloblang.NewPluginSpec().
Description("Returns the parent hex id of the given hex id at the given resolution.").
Param(bloblang.NewStringParam("hex_id")).
Param(bloblang.NewInt64Param("resolution"))

err = bloblang.RegisterFunctionV2("h3_hex_parent_id", getH3ParentIdSpec, func(args *bloblang.ParsedParams) (bloblang.Function, error) {
hex_id, err := args.GetString("hex_id")
if err != nil {
return nil, err
}

resolution, err := args.GetInt64("resolution")
if err != nil {
return nil, err
}

return func() (interface{}, error) {
if resolution < 0 && resolution < 15 {
return nil, fmt.Errorf("resolution should be between 0 and 15")
}
index := h3.IndexFromString(hex_id)
cell := h3.Cell(index)
if !cell.IsValid() {
return nil, fmt.Errorf("failed to parse hex id")
}
return cell.Parent(int(resolution)).String(), nil
}, nil
})

if err != nil {
panic(err)
}

getH3ParentLatLonSpec := bloblang.NewPluginSpec().
Description("Returns the parent hex lat,lon of the given hex id at the given resolution.").
Param(bloblang.NewStringParam("hex_id")).
Param(bloblang.NewInt64Param("resolution"))

err = bloblang.RegisterFunctionV2("h3_hex_parent_to_geo", getH3ParentLatLonSpec, func(args *bloblang.ParsedParams) (bloblang.Function, error) {
hex_id, err := args.GetString("hex_id")
if err != nil {
return nil, err
}

resolution, err := args.GetInt64("resolution")
if err != nil {
return nil, err
}

return func() (interface{}, error) {
if resolution < 0 && resolution < 15 {
return nil, fmt.Errorf("resolution should be between 0 and 15")
}
index := h3.IndexFromString(hex_id)
cell := h3.Cell(index)

if !cell.IsValid() {
return nil, fmt.Errorf("failed to parse hex id")
}
parent := cell.Parent(int(resolution))
return map[string]float64{"latitude": parent.LatLng().Lat, "longitude": parent.LatLng().Lng}, nil
}, nil
})

if err != nil {
panic(err)
}
}
126 changes: 126 additions & 0 deletions internal/h3/h3_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package h3

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/benthosdev/benthos/v4/public/bloblang"
)

func TestGetH3HexSuccess(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_lat_lng_to_hex(lat: this.latitude, lng: this.longitude, resolution: 5)")
require.NoError(t, err)

hexId, err := exec.Query(map[string]any{
"latitude": 40.776676, "longitude": -73.971321,
})
require.NoError(t, err)
require.Equal(t, "852a100bfffffff", hexId)
}

func TestGetH3LatLon(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_hex_to_geo(hex_id: this.hex)")
require.NoError(t, err)

latLng, err := exec.Query(map[string]any{
"hex": "852a100bfffffff",
})
require.NoError(t, err)
require.Equal(t, map[string]float64{"latitude": 40.85293293570688, "longitude": -73.99191613398101}, latLng)
}

func TestValidH3Hex(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_valid_hex(hex_id: this.hex)")
require.NoError(t, err)

isValid, err := exec.Query(map[string]any{
"hex": "852a100bfffffff",
})
require.NoError(t, err)
require.Equal(t, true, isValid)
}

func TestInValidH3Hex(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_valid_hex(hex_id: this.hex)")
require.NoError(t, err)

isValid, err := exec.Query(map[string]any{
"hex": "0",
})
require.NoError(t, err)
require.Equal(t, false, isValid)
}

func TestGetResolutionSuccess(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_get_resolution(hex_id: this.hex)")
require.NoError(t, err)

resolution, err := exec.Query(map[string]any{
"hex": "852a100bfffffff",
})
require.NoError(t, err)
require.Equal(t, 5, resolution)
}

func TestGetResolutionFail(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_get_resolution(hex_id: this.hex)")
require.NoError(t, err)

_, err = exec.Query(map[string]any{
"hex": "852a100b",
})
require.Error(t, err)
}

func TestGetParentAtResolutionSuccess(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_hex_parent_id(hex_id: this.hex, resolution: 4)")
require.NoError(t, err)

resolution, err := exec.Query(map[string]any{
"hex": "852a100bfffffff",
})
require.NoError(t, err)
require.Equal(t, "842a101ffffffff", resolution)
}

func TestGetParentAtInvalidHexResolutionFail(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_hex_parent_id(hex_id: this.hex, resolution: 4)")
require.NoError(t, err)

_, err = exec.Query(map[string]any{
"hex": "852a100",
})
require.Error(t, err)
}

func TestGetParentAtInvalidResolutionFail(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_hex_parent_id(hex_id: this.hex, resolution: 200)")
require.NoError(t, err)

_, err = exec.Query(map[string]any{
"hex": "852a100",
})
require.Error(t, err)
}

func TestGetParentGeoAtResolutionSuccess(t *testing.T) {
// It's safe to pass nil in place of a logger for testing purposes
exec, err := bloblang.Parse("root = h3_hex_parent_to_geo(hex_id: this.hex, resolution: 3)")
require.NoError(t, err)

resolution, err := exec.Query(map[string]any{
"hex": "852a100bfffffff",
})
require.NoError(t, err)
require.Equal(t, map[string]float64{"latitude": 40.85841614742274, "longitude": -73.7819279919521}, resolution)
}