Skip to content

Commit 39ae0da

Browse files
committed
feat: Implement GridDisksUnsafe, GridDiskDistancesUnsafe, GridDiskDistancesSafe
Implements remaining bindings for C library functions. This includes: - GridDisksUnsafe - GridDiskDistancesUnsafe - GridDiskDistancesSafe which are listed in the API Reference[0]. - 0: https://h3geo.org/docs/api/traversal
1 parent 5c18e4a commit 39ae0da

File tree

2 files changed

+214
-2
lines changed

2 files changed

+214
-2
lines changed

h3.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,34 @@ func (c Cell) GridDisk(k int) ([]Cell, error) {
230230
return GridDisk(c, k)
231231
}
232232

233+
// GridDisksUnsafe produces cells within grid distance k of all provided origin
234+
// cells.
235+
//
236+
// k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
237+
// all neighboring cells, and so on.
238+
//
239+
// Outer slice is ordered in the same order origins were passed in. Inner slices
240+
// are in no particular order.
241+
//
242+
// This does not call through to the underlying C.gridDisksUnsafe implementation
243+
// as it is slightly easier to do so to avoid unnecessary type conversions.
244+
func GridDisksUnsafe(origins []Cell, k int) ([][]Cell, error) {
245+
out := make([][]Cell, len(origins))
246+
for i := range origins {
247+
inner := make([]C.H3Index, maxGridDiskSize(k))
248+
errC := C.gridDiskUnsafe(C.H3Index(origins[i]), C.int(k), &inner[0])
249+
if err := toErr(errC); err != nil {
250+
return out, err
251+
}
252+
out[i] = cellsFromC(inner, true, false)
253+
}
254+
return out, nil
255+
}
256+
233257
// GridDiskDistances produces cells within grid distance k of the origin cell.
258+
// This method optimistically tries the faster GridDiskDistancesUnsafe first.
259+
// If a cell was a pentagon or was in the pentagon distortion area, it falls
260+
// back to GridDiskDistancesSafe.
234261
//
235262
// k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
236263
// all neighboring cells, and so on.
@@ -260,6 +287,9 @@ func GridDiskDistances(origin Cell, k int) ([][]Cell, error) {
260287
}
261288

262289
// GridDiskDistances produces cells within grid distance k of the origin cell.
290+
// This method optimistically tries the faster GridDiskDistancesUnsafe first.
291+
// If a cell was a pentagon or was in the pentagon distortion area, it falls
292+
// back to GridDiskDistancesSafe.
263293
//
264294
// k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
265295
// all neighboring cells, and so on.
@@ -271,6 +301,94 @@ func (c Cell) GridDiskDistances(k int) ([][]Cell, error) {
271301
return GridDiskDistances(c, k)
272302
}
273303

304+
// GridDiskDistancesUnsafe produces cells within grid distance k of the origin cell.
305+
// Output behavior is undefined when one of the cells returned by this
306+
// function is a pentagon or is in the pentagon distortion area.
307+
//
308+
// k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
309+
// all neighboring cells, and so on.
310+
//
311+
// Outer slice is ordered from origin outwards. Inner slices are in no
312+
// particular order. Elements of the output array may be left zero, as can
313+
// happen when crossing a pentagon.
314+
func GridDiskDistancesUnsafe(origin Cell, k int) ([][]Cell, error) {
315+
rsz := maxGridDiskSize(k)
316+
outHexes := make([]C.H3Index, rsz)
317+
outDists := make([]C.int, rsz)
318+
319+
if err := toErr(C.gridDiskDistancesUnsafe(C.H3Index(origin), C.int(k), &outHexes[0], &outDists[0])); err != nil {
320+
return nil, err
321+
}
322+
323+
ret := make([][]Cell, k+1)
324+
for i := 0; i <= k; i++ {
325+
ret[i] = make([]Cell, 0, ringSize(i))
326+
}
327+
328+
for i, d := range outDists {
329+
ret[d] = append(ret[d], Cell(outHexes[i]))
330+
}
331+
332+
return ret, nil
333+
}
334+
335+
// GridDiskDistancesUnsafe produces cells within grid distance k of the origin cell.
336+
// Output behavior is undefined when one of the cells returned by this
337+
// function is a pentagon or is in the pentagon distortion area.
338+
//
339+
// k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
340+
// all neighboring cells, and so on.
341+
//
342+
// Outer slice is ordered from origin outwards. Inner slices are in no
343+
// particular order. Elements of the output array may be left zero, as can
344+
// happen when crossing a pentagon.
345+
func (c Cell) GridDiskDistancesUnsafe(k int) ([][]Cell, error) {
346+
return GridDiskDistancesUnsafe(c, k)
347+
}
348+
349+
// GridDiskDistancesSafe produces cells within grid distance k of the origin cell.
350+
// This is the safe, but slow version of GridDiskDistances.
351+
//
352+
// k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
353+
// all neighboring cells, and so on.
354+
//
355+
// Outer slice is ordered from origin outwards. Inner slices are in no
356+
// particular order. Elements of the output array may be left zero, as can
357+
// happen when crossing a pentagon.
358+
func GridDiskDistancesSafe(origin Cell, k int) ([][]Cell, error) {
359+
rsz := maxGridDiskSize(k)
360+
outHexes := make([]C.H3Index, rsz)
361+
outDists := make([]C.int, rsz)
362+
363+
if err := toErr(C.gridDiskDistancesSafe(C.H3Index(origin), C.int(k), &outHexes[0], &outDists[0])); err != nil {
364+
return nil, err
365+
}
366+
367+
ret := make([][]Cell, k+1)
368+
for i := 0; i <= k; i++ {
369+
ret[i] = make([]Cell, 0, ringSize(i))
370+
}
371+
372+
for i, d := range outDists {
373+
ret[d] = append(ret[d], Cell(outHexes[i]))
374+
}
375+
376+
return ret, nil
377+
}
378+
379+
// GridDiskDistancesSafe produces cells within grid distance k of the origin cell.
380+
// This is the safe, but slow version of GridDiskDistances.
381+
//
382+
// k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
383+
// all neighboring cells, and so on.
384+
//
385+
// Outer slice is ordered from origin outwards. Inner slices are in no
386+
// particular order. Elements of the output array may be left zero, as can
387+
// happen when crossing a pentagon.
388+
func (c Cell) GridDiskDistancesSafe(k int) ([][]Cell, error) {
389+
return GridDiskDistancesSafe(c, k)
390+
}
391+
274392
// GridRing produces the "hollow" ring of cells at exactly grid distance k from the origin cell.
275393
//
276394
// k-ring 0 returns just the origin hexagon.

h3_test.go

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,53 @@ func TestGridDisk(t *testing.T) {
203203
})
204204
}
205205

206+
func TestGridDisksUnsafe(t *testing.T) {
207+
t.Parallel()
208+
209+
t.Run("two cells", func(t *testing.T) {
210+
t.Parallel()
211+
212+
gds, err := GridDisksUnsafe([]Cell{validCell, validCell}, len(validDiskDist3_1)-1)
213+
assertNoErr(t, err)
214+
assertEqual(t, 2, len(gds), "expected grid disks to have two arrays returned")
215+
assertEqualDisks(t,
216+
flattenDisks(validDiskDist3_1),
217+
gds[0],
218+
"expected grid disks[0] to be the same",
219+
)
220+
assertEqualDisks(t,
221+
flattenDisks(validDiskDist3_1),
222+
gds[1],
223+
"expected grid disks[1] to be the same",
224+
)
225+
})
226+
227+
t.Run("pentagon", func(t *testing.T) {
228+
t.Parallel()
229+
230+
_, err := GridDisksUnsafe([]Cell{pentagonCell}, 1)
231+
assertErr(t, err)
232+
assertErrIs(t, err, ErrPentagon)
233+
})
234+
235+
t.Run("invalid cell", func(t *testing.T) {
236+
t.Parallel()
237+
238+
c := Cell(-1)
239+
_, err := GridDisksUnsafe([]Cell{c}, 1)
240+
assertErr(t, err)
241+
assertErrIs(t, err, ErrCellInvalid)
242+
})
243+
244+
t.Run("invalid k", func(t *testing.T) {
245+
t.Parallel()
246+
247+
_, err := GridDisksUnsafe([]Cell{validCell}, -1)
248+
assertErr(t, err)
249+
assertErrIs(t, err, ErrDomain)
250+
})
251+
}
252+
206253
func TestGridDiskDistances(t *testing.T) {
207254
t.Parallel()
208255

@@ -211,6 +258,10 @@ func TestGridDiskDistances(t *testing.T) {
211258
rings, err := validCell.GridDiskDistances(len(validDiskDist3_1) - 1)
212259
assertNoErr(t, err)
213260
assertEqualDiskDistances(t, validDiskDist3_1, rings)
261+
262+
rings, err = validCell.GridDiskDistancesSafe(len(validDiskDist3_1) - 1)
263+
assertNoErr(t, err)
264+
assertEqualDiskDistances(t, validDiskDist3_1, rings)
214265
})
215266

216267
t.Run("pentagon centered", func(t *testing.T) {
@@ -220,6 +271,11 @@ func TestGridDiskDistances(t *testing.T) {
220271
assertNoErr(t, err)
221272
assertEqual(t, 2, len(rings), "expected 2 rings")
222273
assertEqual(t, 5, len(rings[1]), "expected 5 cells in second ring")
274+
275+
rings, err = GridDiskDistancesSafe(pentagonCell, 1)
276+
assertNoErr(t, err)
277+
assertEqual(t, 2, len(rings), "expected 2 rings")
278+
assertEqual(t, 5, len(rings[1]), "expected 5 cells in second ring")
223279
})
224280
})
225281

@@ -228,6 +284,38 @@ func TestGridDiskDistances(t *testing.T) {
228284
assertErr(t, err)
229285
assertErrIs(t, err, ErrDomain)
230286
assertNil(t, rings)
287+
288+
rings, err = GridDiskDistancesSafe(pentagonCell, -1)
289+
assertErr(t, err)
290+
assertErrIs(t, err, ErrDomain)
291+
assertNil(t, rings)
292+
})
293+
}
294+
295+
func TestGridDiskDistancesUnsafe(t *testing.T) {
296+
t.Parallel()
297+
298+
t.Run("no pentagon", func(t *testing.T) {
299+
t.Parallel()
300+
rings, err := validCell.GridDiskDistancesUnsafe(len(validDiskDist3_1) - 1)
301+
assertNoErr(t, err)
302+
assertEqualDiskDistances(t, validDiskDist3_1, rings)
303+
})
304+
305+
t.Run("pentagon centered", func(t *testing.T) {
306+
t.Parallel()
307+
assertNoPanic(t, func() {
308+
_, err := GridDiskDistancesUnsafe(pentagonCell, 1)
309+
assertErr(t, err)
310+
assertErrIs(t, err, ErrPentagon)
311+
})
312+
})
313+
314+
t.Run("invalid k-ring", func(t *testing.T) {
315+
rings, err := GridDiskDistancesUnsafe(pentagonCell, -1)
316+
assertErr(t, err)
317+
assertErrIs(t, err, ErrDomain)
318+
assertNil(t, rings)
231319
})
232320
}
233321

@@ -1354,11 +1442,13 @@ func assertEqualDiskDistances(t *testing.T, expected, actual [][]Cell) {
13541442
}
13551443
}
13561444

1357-
func assertEqualDisks(t *testing.T, expected, actual []Cell) {
1445+
func assertEqualDisks(t *testing.T, expected, actual []Cell, msgAndArgs ...any) {
13581446
t.Helper()
13591447

13601448
if len(expected) != len(actual) {
13611449
t.Errorf("disk size mismatch: %v != %v", len(expected), len(actual))
1450+
logMsgAndArgs(t, msgAndArgs...)
1451+
13621452
return
13631453
}
13641454

@@ -1370,9 +1460,9 @@ func assertEqualDisks(t *testing.T, expected, actual []Cell) {
13701460
for i, cell := range expected {
13711461
if cell != actual[i] {
13721462
t.Errorf("cell[%d]: %v != %v", i, cell, actual[i])
1463+
logMsgAndArgs(t, msgAndArgs...)
13731464

13741465
count++
1375-
13761466
if count > 5 {
13771467
t.Logf("... and more")
13781468
break
@@ -1516,3 +1606,7 @@ func TestToErr(t *testing.T) {
15161606
assertErrIs(t, toErr(999), ErrUnknown)
15171607
})
15181608
}
1609+
1610+
func TestLatLngsToC_Nil(t *testing.T) {
1611+
assertEqual(t, nil, latLngsToC(nil))
1612+
}

0 commit comments

Comments
 (0)