Skip to content

Commit

Permalink
Adding CellsToMultiPolygon (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
zachcoleman authored Aug 12, 2024
1 parent a6e5063 commit 0a46771
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 7 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func ExampleLatLngToCell() {
| `gridDiskDistances` | `GridDiskDistances`, `Cell#GridDiskDistances` |
| `gridRingUnsafe` | N/A |
| `polygonToCells` | `PolygonToCells`, `GeoPolygon#Cells` |
| `cellsToMultiPolygon` | TODO |
| `cellsToMultiPolygon` | `CellsToMultiPolygon`
| `degsToRads` | `DegsToRads` |
| `radsToDegs` | `RadsToDegs` |
| `greatCircleDistance` | `GreatCircleDistance* (3/3)` |
Expand Down
49 changes: 43 additions & 6 deletions h3.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,6 @@ type (
GeoLoop GeoLoop
Holes []GeoLoop
}

// LinkedGeoPolygon is a linked-list of GeoPolygons.
// TODO: not implemented.
LinkedGeoPolygon struct{}
)

func NewLatLng(lat, lng float64) LatLng {
Expand Down Expand Up @@ -246,8 +242,49 @@ func (p GeoPolygon) Cells(resolution int) []Cell {
return PolygonToCells(p, resolution)
}

func CellsToMultiPolygon(cells []Cell) *LinkedGeoPolygon {
panic("not implemented")
// CellsToMultiPolygon takes a set of cells and creates GeoPolygon(s)
// describing the outline(s) of a set of hexagons. Polygon outlines will follow
// GeoJSON MultiPolygon order: Each polygon will have one outer loop, which is first in
// the list, followed by any holes.
//
// It is expected that all hexagons in the set have the same resolution and that the set
// contains no duplicates. Behavior is undefined if duplicates or multiple resolutions are
// present, and the algorithm may produce unexpected or invalid output.
func CellsToMultiPolygon(cells []Cell) []GeoPolygon {
if len(cells) == 0 {
return nil
}
h3Indexes := cellsToC(cells)
cLinkedGeoPolygon := new(C.LinkedGeoPolygon)
C.cellsToLinkedMultiPolygon(&h3Indexes[0], C.int(len(h3Indexes)), cLinkedGeoPolygon)
ret := []GeoPolygon{}

// traverse polygons for linked list of polygons
currPoly := cLinkedGeoPolygon
for currPoly != nil {
loops := []GeoLoop{}

// traverse loops for a polygon
currLoop := currPoly.first
for currLoop != nil {
loop := []LatLng{}

// traverse points for a loop
currPt := currLoop.first
for currPt != nil {
loop = append(loop, latLngFromC(currPt.vertex))
currPt = currPt.next
}

loops = append(loops, loop)
currLoop = currLoop.next
}

ret = append(ret, GeoPolygon{GeoLoop: loops[0], Holes: loops[1:]})
currPoly = currPoly.next
}

return ret
}

// PointDistRads returns the "great circle" or "haversine" distance between
Expand Down
41 changes: 41 additions & 0 deletions h3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,47 @@ func TestPolygonToCells(t *testing.T) {
})
}

func TestCellsToMultiPolygon(t *testing.T) {
t.Parallel()

// 7 cells in disk -> 1 polygon, 18-point loop, and no holes
cells := GridDisk(LatLngToCell(NewLatLng(0, 0), 10), 1)
res := CellsToMultiPolygon(cells)
assertEqual(t, len(res), 1)
assertEqual(t, len(res[0].GeoLoop), 18)
assertEqual(t, len(res[0].Holes), 0)

// 6 cells in ring -> 1 polygon, 18-point loop, and 1 6-point hole
cells = GridDisk(LatLngToCell(NewLatLng(0, 0), 10), 1)[1:]
res = CellsToMultiPolygon(cells)
assertEqual(t, len(res), 1)
assertEqual(t, len(res[0].GeoLoop), 18)
assertEqual(t, len(res[0].Holes), 1)
assertEqual(t, len(res[0].Holes[0]), 6)

// 2 hexagons connected -> 1 polygon, 10-point loop (2 shared points) and no holes
cells = GridDisk(LatLngToCell(NewLatLng(0, 0), 10), 1)[:2]
res = CellsToMultiPolygon(cells)
assertEqual(t, len(res), 1)
assertEqual(t, len(res[0].GeoLoop), 10)
assertEqual(t, len(res[0].Holes), 0)

// 2 distinct disks -> 2 polygons, 2 18-point loops, and no holes
cells1 := GridDisk(LatLngToCell(NewLatLng(0, 0), 10), 1)
cells2 := GridDisk(LatLngToCell(NewLatLng(10, 10), 10), 1)
cells = append(cells1, cells2...)
res = CellsToMultiPolygon(cells)
assertEqual(t, len(res), 2)
assertEqual(t, len(res[0].GeoLoop), 18)
assertEqual(t, len(res[0].Holes), 0)
assertEqual(t, len(res[1].GeoLoop), 18)
assertEqual(t, len(res[1].Holes), 0)

// empty
res = CellsToMultiPolygon([]Cell{})
assertEqual(t, len(res), 0)
}

func TestGridPath(t *testing.T) {
t.Parallel()
path := lineStartCell.GridPath(lineEndCell)
Expand Down

0 comments on commit 0a46771

Please sign in to comment.