Skip to content

Commit 74f9ef1

Browse files
committed
add NeighborsFromLatLon method with test
1 parent ddc8591 commit 74f9ef1

File tree

2 files changed

+94
-8
lines changed

2 files changed

+94
-8
lines changed

dist.go

+64-8
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ import (
1111
"github.com/uber/h3-go/v3"
1212
)
1313

14-
// ErrNoSlots means that the number of virtual nodes is distributed by 100%.
15-
// It is necessary to change the configuration of virtual nodes.
16-
var ErrNoSlots = errors.New("h3geodist: no distribute slots")
14+
var (
15+
// ErrNoSlots means that the number of virtual nodes is distributed by 100%.
16+
// It is necessary to change the configuration of virtual nodes.
17+
ErrNoSlots = errors.New("h3geodist: no distribute slots")
18+
19+
// ErrVNodes returns when there are no virtual nodes.
20+
ErrVNodes = errors.New("h3geodist: vnodes not found")
21+
)
1722

1823
// Distributed holds information about nodes,
1924
// and scheduler of virtual nodes with replicas.
@@ -32,12 +37,20 @@ type Distributed struct {
3237
}
3338

3439
// Cell is a type to represent a distributed cell
35-
// with specifying the hostname and H3 index.
40+
// with specifying the hostname and H3 Index.
3641
type Cell struct {
3742
H3ID h3.H3Index
3843
Host string
3944
}
4045

46+
func (c Cell) String() string {
47+
return fmt.Sprintf("Cell{Host: %s, ID: %s}", c.Host, h3.ToString(c.H3ID))
48+
}
49+
50+
func (c Cell) HexID() string {
51+
return h3.ToString(c.H3ID)
52+
}
53+
4154
// NodeInfo is a type to represent a node load statistic.
4255
type NodeInfo struct {
4356
Host string
@@ -156,7 +169,7 @@ func (d *Distributed) WhereIsMyParent(child h3.H3Index) (c Cell, err error) {
156169
cell := h3.ToParent(child, d.level)
157170
addr, ok := d.lookup(cell)
158171
if !ok {
159-
return c, fmt.Errorf("h3geodist: distributed cell %v not found", cell)
172+
return c, ErrVNodes
160173
}
161174
c.H3ID = cell
162175
c.Host = addr
@@ -170,11 +183,54 @@ func (d *Distributed) LookupFromLatLon(lat float64, lon float64) (c Cell, err er
170183
cell := h3.FromGeo(h3.GeoCoord{Latitude: lat, Longitude: lon}, d.level)
171184
addr, ok := d.lookup(cell)
172185
if !ok {
173-
return c, fmt.Errorf("h3geodist: distributed cell %v not found", cell)
186+
return c, ErrVNodes
174187
}
175188
return Cell{H3ID: cell, Host: addr}, nil
176189
}
177190

191+
// Neighbor is a type for represent a neighbor distributed cell,
192+
// with the distance from a target point to the center of each neighbor.
193+
type Neighbor struct {
194+
Cell Cell
195+
DistanceM float64
196+
}
197+
198+
// NeighborsFromLatLon returns the current distributed cell
199+
// for a geographic coordinate and neighbors sorted by distance in descending order.
200+
// Distance is measured from geographic coordinates to the center of each neighbor.
201+
func (d *Distributed) NeighborsFromLatLon(lat float64, lon float64) (target Cell, neighbors []Neighbor, err error) {
202+
d.mu.RLock()
203+
defer d.mu.RUnlock()
204+
src := h3.GeoCoord{Latitude: lat, Longitude: lon}
205+
cell := h3.FromGeo(src, d.level)
206+
addr, ok := d.lookup(cell)
207+
if !ok {
208+
return target, nil, ErrVNodes
209+
}
210+
target.Host = addr
211+
target.H3ID = cell
212+
ring := h3.KRing(cell, 1)
213+
neighbors = make([]Neighbor, 0, len(ring))
214+
for i := 0; i < len(ring); i++ {
215+
if !h3.AreNeighbors(cell, ring[i]) {
216+
continue
217+
}
218+
addr, ok := d.lookup(ring[i])
219+
if !ok {
220+
continue
221+
}
222+
dest := h3.ToGeo(ring[i])
223+
neighbors = append(neighbors, Neighbor{
224+
Cell: Cell{Host: addr, H3ID: ring[i]},
225+
DistanceM: h3.PointDistM(src, dest),
226+
})
227+
}
228+
sort.Slice(neighbors, func(i, j int) bool {
229+
return neighbors[i].DistanceM < neighbors[j].DistanceM
230+
})
231+
return
232+
}
233+
178234
// ReplicaFor returns a list of hosts for replication.
179235
func (d *Distributed) ReplicaFor(cell h3.H3Index, n int) ([]string, error) {
180236
d.mu.RLock()
@@ -189,7 +245,7 @@ func (d *Distributed) ReplicaFor(cell h3.H3Index, n int) ([]string, error) {
189245
var next int
190246
myaddr, ok := d.lookup(cell)
191247
if !ok {
192-
return nil, fmt.Errorf("h3geodist: vnode for cell %v not found", cell)
248+
return nil, ErrVNodes
193249
}
194250
keys := make([]uint64, 0, 4)
195251
hosts := make(map[uint64]*node)
@@ -241,7 +297,7 @@ func (d *Distributed) LookupMany(cell []h3.H3Index, iter func(c Cell) bool) bool
241297
return true
242298
}
243299

244-
// VNodeIndex returns the index of the virtual node by H3Index.
300+
// VNodeIndex returns the Index of the virtual node by H3Index.
245301
func (d *Distributed) VNodeIndex(cell h3.H3Index) int {
246302
hashKey := uint2hash(uint64(cell))
247303
return int(hashKey % d.vnodes)

dist_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,36 @@ func TestDistributed_LookupFromLatLon(t *testing.T) {
258258
}
259259
}
260260

261+
func TestDistributed_NeighborsFromLatLon(t *testing.T) {
262+
h3dist, err := New(Level6, WithVNodes(1024))
263+
if err != nil {
264+
t.Fatal(err)
265+
}
266+
267+
_ = h3dist.Add("127.0.0.1")
268+
_ = h3dist.Add("127.0.0.2")
269+
_ = h3dist.Add("127.0.0.3")
270+
_ = h3dist.Add("127.0.0.5")
271+
_ = h3dist.Add("127.0.0.6")
272+
273+
target, neighbors, err := h3dist.NeighborsFromLatLon(42.9284783, -72.2776111)
274+
if err != nil {
275+
t.Fatal(err)
276+
}
277+
278+
if have, want := target.Host, "127.0.0.6"; have != want {
279+
t.Fatalf("have %s, want %s", have, want)
280+
}
281+
if len(neighbors) != 6 {
282+
t.Fatalf("have %d, want 6 neighbors", len(neighbors))
283+
}
284+
winner0 := neighbors[0]
285+
winner1 := neighbors[1]
286+
if winner0.DistanceM > winner1.DistanceM {
287+
t.Fatalf("winner0.DistanceM > winner1.DistanceM")
288+
}
289+
}
290+
261291
func TestDistributed_WhereIsMyParent(t *testing.T) {
262292
h3dist, err := New(Level3)
263293
if err != nil {

0 commit comments

Comments
 (0)