From 68277f852f097532817d1a3e2f9268d39dc8507a Mon Sep 17 00:00:00 2001 From: Charles Nguyen Date: Thu, 8 Feb 2024 03:29:44 +0000 Subject: [PATCH] Introduce Error Codes --- README.md | 8 +- bench_test.go | 12 +- example_test.go | 10 +- h3.go | 403 +++++++++++++++++++++++++++++-------------- h3_test.go | 449 ++++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 672 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index 4aa7267..6c8bea1 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,13 @@ func ExampleLatLngToCell() { latLng := h3.NewLatLng(37.775938728915946, -122.41795063018799) resolution := 9 // between 0 (biggest cell) and 15 (smallest cell) - cell := h3.LatLngToCell(latLng, resolution) + cell, err := h3.LatLngToCell(latLng, resolution) + if err != nil { + fmt.Printf("%s", err.Error()) + } else { + fmt.Printf("%s", cell) + } - fmt.Printf("%s", cell) // Output: // 8928308280fffff } diff --git a/bench_test.go b/bench_test.go index cb6b0d3..bfb1da2 100644 --- a/bench_test.go +++ b/bench_test.go @@ -10,7 +10,7 @@ var ( Lat: 37, Lng: -122, } - cell = LatLngToCell(geo, 15) + cell, _ = LatLngToCell(geo, 15) addr = cell.String() geoBndry CellBoundary cells []Cell @@ -30,30 +30,30 @@ func BenchmarkFromString(b *testing.B) { func BenchmarkToGeoRes15(b *testing.B) { for n := 0; n < b.N; n++ { - geo = CellToLatLng(cell) + geo, _ = CellToLatLng(cell) } } func BenchmarkFromGeoRes15(b *testing.B) { for n := 0; n < b.N; n++ { - cell = LatLngToCell(geo, 15) + cell, _ = LatLngToCell(geo, 15) } } func BenchmarkToGeoBndryRes15(b *testing.B) { for n := 0; n < b.N; n++ { - geoBndry = CellToBoundary(cell) + geoBndry, _ = CellToBoundary(cell) } } func BenchmarkHexRange(b *testing.B) { for n := 0; n < b.N; n++ { - cells = cell.GridDisk(10) + cells, _ = cell.GridDisk(10) } } func BenchmarkPolyfill(b *testing.B) { for n := 0; n < b.N; n++ { - cells = PolygonToCells(validGeoPolygonHoles, 15) + cells, _ = PolygonToCells(validGeoPolygonHoles, 15) } } diff --git a/example_test.go b/example_test.go index 5450d3c..1b6d777 100644 --- a/example_test.go +++ b/example_test.go @@ -9,8 +9,14 @@ import ( func ExampleLatLngToCell() { latLng := h3.NewLatLng(37.775938728915946, -122.41795063018799) resolution := 9 - c := h3.LatLngToCell(latLng, resolution) - fmt.Printf("%s", c) + + cell, err := h3.LatLngToCell(latLng, resolution) + if err != nil { + fmt.Printf("%s", err.Error()) + } else { + fmt.Printf("%s", cell) + } + // Output: // 8928308280fffff } diff --git a/h3.go b/h3.go index c0c871a..38013cc 100644 --- a/h3.go +++ b/h3.go @@ -67,6 +67,61 @@ const ( RadsToDegs = 180.0 / math.Pi ) +var ( + ErrH3Failed = errors.New("unknown error") + ErrH3Domain = errors.New("argument out of range error") + ErrH3LatLngDomain = errors.New("latitude or longitude out of range error") + ErrH3ResDomain = errors.New("resolution out of range error") + ErrH3InvalidIndex = errors.New("h3index invalid error") + ErrH3InvalidDirEdge = errors.New("directed edge invalid error") + ErrH3InvalidUnDirEdge = errors.New("undirected edge invalid error") + ErrH3InvalidVertex = errors.New("vertex invalid error") + ErrH3Pentagon = errors.New("pentagon distortion encountered error") + ErrH3DupInput = errors.New("duplicate input error") + ErrH3Neighbors = errors.New("cells are not neighbors error") + ErrH3ResMismatch = errors.New("resolution mismatch error") + ErrH3MemAlloc = errors.New("memory allocation error") + ErrH3MemBound = errors.New("memory bounds error") + ErrH3InvalidOption = errors.New("invalid mode or flag error") +) + +func errFromCode(errc int) error { + switch errc { + case C.E_FAILED: + return ErrH3Failed + case C.E_DOMAIN: + return ErrH3Domain + case C.E_LATLNG_DOMAIN: + return ErrH3LatLngDomain + case C.E_RES_DOMAIN: + return ErrH3ResDomain + case C.E_CELL_INVALID: + return ErrH3InvalidIndex + case C.E_DIR_EDGE_INVALID: + return ErrH3InvalidDirEdge + case C.E_UNDIR_EDGE_INVALID: + return ErrH3InvalidUnDirEdge + case C.E_VERTEX_INVALID: + return ErrH3InvalidVertex + case C.E_PENTAGON: + return ErrH3Pentagon + case C.E_DUPLICATE_INPUT: + return ErrH3DupInput + case C.E_NOT_NEIGHBORS: + return ErrH3Neighbors + case C.E_RES_MISMATCH: + return ErrH3ResMismatch + case C.E_MEMORY_ALLOC: + return ErrH3MemAlloc + case C.E_MEMORY_BOUNDS: + return ErrH3MemBound + case C.E_OPTION_INVALID: + return ErrH3InvalidOption + default: + return ErrH3Failed + } +} + type ( // Cell is an Index that identifies a single hexagon cell at a resolution. @@ -107,44 +162,50 @@ func NewLatLng(lat, lng float64) LatLng { } // LatLngToCell returns the Cell at resolution for a geographic coordinate. -func LatLngToCell(latLng LatLng, resolution int) Cell { +func LatLngToCell(latLng LatLng, resolution int) (Cell, error) { var i C.H3Index - C.latLngToCell(latLng.toCPtr(), C.int(resolution), &i) + if rc := C.latLngToCell(latLng.toCPtr(), C.int(resolution), &i); rc != C.E_SUCCESS { + return Cell(i), errFromCode(int(rc)) + } - return Cell(i) + return Cell(i), nil } // Cell returns the Cell at resolution for a geographic coordinate. -func (g LatLng) Cell(resolution int) Cell { +func (g LatLng) Cell(resolution int) (Cell, error) { return LatLngToCell(g, resolution) } // CellToLatLng returns the geographic centerpoint of a Cell. -func CellToLatLng(c Cell) LatLng { +func CellToLatLng(c Cell) (LatLng, error) { var g C.LatLng - C.cellToLatLng(C.H3Index(c), &g) + if rc := C.cellToLatLng(C.H3Index(c), &g); rc != C.E_SUCCESS { + return LatLng{}, errFromCode(int(rc)) + } - return latLngFromC(g) + return latLngFromC(g), nil } // LatLng returns the Cell at resolution for a geographic coordinate. -func (c Cell) LatLng() LatLng { +func (c Cell) LatLng() (LatLng, error) { return CellToLatLng(c) } // CellToBoundary returns a CellBoundary of the Cell. -func CellToBoundary(c Cell) CellBoundary { +func CellToBoundary(c Cell) (CellBoundary, error) { var cb C.CellBoundary - C.cellToBoundary(C.H3Index(c), &cb) + if rc := C.cellToBoundary(C.H3Index(c), &cb); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellBndryFromC(&cb) + return cellBndryFromC(&cb), nil } // Boundary returns a CellBoundary of the Cell. -func (c Cell) Boundary() CellBoundary { +func (c Cell) Boundary() (CellBoundary, error) { return CellToBoundary(c) } @@ -155,11 +216,14 @@ func (c Cell) Boundary() CellBoundary { // // Output is placed in an array in no particular order. Elements of the output // array may be left zero, as can happen when crossing a pentagon. -func GridDisk(origin Cell, k int) []Cell { +func GridDisk(origin Cell, k int) ([]Cell, error) { out := make([]C.H3Index, maxGridDiskSize(k)) - C.gridDisk(C.H3Index(origin), C.int(k), &out[0]) + + if rc := C.gridDisk(C.H3Index(origin), C.int(k), &out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } // QUESTION: should we prune zeroes from the output? - return cellsFromC(out, true, false) + return cellsFromC(out, true, false), nil } // GridDisk produces cells within grid distance k of the origin cell. @@ -169,7 +233,7 @@ func GridDisk(origin Cell, k int) []Cell { // // Output is placed in an array in no particular order. Elements of the output // array may be left zero, as can happen when crossing a pentagon. -func (c Cell) GridDisk(k int) []Cell { +func (c Cell) GridDisk(k int) ([]Cell, error) { return GridDisk(c, k) } @@ -181,11 +245,14 @@ func (c Cell) GridDisk(k int) []Cell { // Outer slice is ordered from origin outwards. Inner slices are in no // particular order. Elements of the output array may be left zero, as can // happen when crossing a pentagon. -func GridDiskDistances(origin Cell, k int) [][]Cell { +func GridDiskDistances(origin Cell, k int) ([][]Cell, error) { rsz := maxGridDiskSize(k) outHexes := make([]C.H3Index, rsz) outDists := make([]C.int, rsz) - C.gridDiskDistances(C.H3Index(origin), C.int(k), &outHexes[0], &outDists[0]) + + if rc := C.gridDiskDistances(C.H3Index(origin), C.int(k), &outHexes[0], &outDists[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } ret := make([][]Cell, k+1) for i := 0; i <= k; i++ { @@ -196,7 +263,7 @@ func GridDiskDistances(origin Cell, k int) [][]Cell { ret[d] = append(ret[d], Cell(outHexes[i])) } - return ret + return ret, nil } // GridDiskDistances produces cells within grid distance k of the origin cell. @@ -207,7 +274,7 @@ func GridDiskDistances(origin Cell, k int) [][]Cell { // Outer slice is ordered from origin outwards. Inner slices are in no // particular order. Elements of the output array may be left zero, as can // happen when crossing a pentagon. -func (c Cell) GridDiskDistances(k int) [][]Cell { +func (c Cell) GridDiskDistances(k int) ([][]Cell, error) { return GridDiskDistances(c, k) } @@ -218,21 +285,27 @@ func (c Cell) GridDiskDistances(k int) [][]Cell { // hexagons, tests them and their neighbors to be contained by the geoloop(s), // and then any newly found hexagons are used to test again until no new // hexagons are found. -func PolygonToCells(polygon GeoPolygon, resolution int) []Cell { +func PolygonToCells(polygon GeoPolygon, resolution int) ([]Cell, error) { if len(polygon.GeoLoop) == 0 { - return nil + return nil, nil } cpoly := allocCGeoPolygon(polygon) defer freeCGeoPolygon(&cpoly) maxLen := new(C.int64_t) - C.maxPolygonToCellsSize(&cpoly, C.int(resolution), 0, maxLen) + + if rc := C.maxPolygonToCellsSize(&cpoly, C.int(resolution), 0, maxLen); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } out := make([]C.H3Index, *maxLen) - C.polygonToCells(&cpoly, C.int(resolution), 0, &out[0]) - return cellsFromC(out, true, false) + if rc := C.polygonToCells(&cpoly, C.int(resolution), 0, &out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } + + return cellsFromC(out, true, false), nil } // PolygonToCells takes a given GeoJSON-like data structure fills it with the @@ -242,7 +315,7 @@ func PolygonToCells(polygon GeoPolygon, resolution int) []Cell { // hexagons, tests them and their neighbors to be contained by the geoloop(s), // and then any newly found hexagons are used to test again until no new // hexagons are found. -func (p GeoPolygon) Cells(resolution int) []Cell { +func (p GeoPolygon) Cells(resolution int) ([]Cell, error) { return PolygonToCells(p, resolution) } @@ -270,99 +343,119 @@ func GreatCircleDistanceM(a, b LatLng) float64 { // HexAreaKm2 returns the average hexagon area in square kilometers at the given // resolution. -func HexagonAreaAvgKm2(resolution int) float64 { +func HexagonAreaAvgKm2(resolution int) (float64, error) { var out C.double - C.getHexagonAreaAvgKm2(C.int(resolution), &out) + if rc := C.getHexagonAreaAvgKm2(C.int(resolution), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // HexAreaM2 returns the average hexagon area in square meters at the given // resolution. -func HexagonAreaAvgM2(resolution int) float64 { +func HexagonAreaAvgM2(resolution int) (float64, error) { var out C.double - C.getHexagonAreaAvgM2(C.int(resolution), &out) + if rc := C.getHexagonAreaAvgM2(C.int(resolution), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // CellAreaRads2 returns the exact area of specific cell in square radians. -func CellAreaRads2(c Cell) float64 { +func CellAreaRads2(c Cell) (float64, error) { var out C.double - C.cellAreaRads2(C.H3Index(c), &out) + if rc := C.cellAreaRads2(C.H3Index(c), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // CellAreaKm2 returns the exact area of specific cell in square kilometers. -func CellAreaKm2(c Cell) float64 { +func CellAreaKm2(c Cell) (float64, error) { var out C.double - C.cellAreaKm2(C.H3Index(c), &out) + if rc := C.cellAreaKm2(C.H3Index(c), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // CellAreaM2 returns the exact area of specific cell in square meters. -func CellAreaM2(c Cell) float64 { +func CellAreaM2(c Cell) (float64, error) { var out C.double - C.cellAreaM2(C.H3Index(c), &out) + if rc := C.cellAreaM2(C.H3Index(c), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // HexagonEdgeLengthAvgKm returns the average hexagon edge length in kilometers // at the given resolution. -func HexagonEdgeLengthAvgKm(resolution int) float64 { +func HexagonEdgeLengthAvgKm(resolution int) (float64, error) { var out C.double - C.getHexagonEdgeLengthAvgKm(C.int(resolution), &out) + if rc := C.getHexagonEdgeLengthAvgKm(C.int(resolution), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // HexagonEdgeLengthAvgM returns the average hexagon edge length in meters at // the given resolution. -func HexagonEdgeLengthAvgM(resolution int) float64 { +func HexagonEdgeLengthAvgM(resolution int) (float64, error) { var out C.double - C.getHexagonEdgeLengthAvgM(C.int(resolution), &out) + if rc := C.getHexagonEdgeLengthAvgM(C.int(resolution), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // EdgeLengthRads returns the exact edge length of specific unidirectional edge // in radians. -func EdgeLengthRads(e DirectedEdge) float64 { +func EdgeLengthRads(e DirectedEdge) (float64, error) { var out C.double - C.edgeLengthRads(C.H3Index(e), &out) + if rc := C.edgeLengthRads(C.H3Index(e), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // EdgeLengthKm returns the exact edge length of specific unidirectional // edge in kilometers. -func EdgeLengthKm(e DirectedEdge) float64 { +func EdgeLengthKm(e DirectedEdge) (float64, error) { var out C.double - C.edgeLengthKm(C.H3Index(e), &out) + if rc := C.edgeLengthKm(C.H3Index(e), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // EdgeLengthM returns the exact edge length of specific unidirectional // edge in meters. -func EdgeLengthM(e DirectedEdge) float64 { +func EdgeLengthM(e DirectedEdge) (float64, error) { var out C.double - C.edgeLengthM(C.H3Index(e), &out) + if rc := C.edgeLengthM(C.H3Index(e), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return float64(out) + return float64(out), nil } // NumCells returns the number of cells at the given resolution. @@ -373,19 +466,23 @@ func NumCells(resolution int) int { } // Res0Cells returns all the cells at resolution 0. -func Res0Cells() []Cell { +func Res0Cells() ([]Cell, error) { out := make([]C.H3Index, C.res0CellCount()) - C.getRes0Cells(&out[0]) + if rc := C.getRes0Cells(&out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellsFromC(out, false, false) + return cellsFromC(out, false, false), nil } // Pentagons returns all the pentagons at resolution. -func Pentagons(resolution int) []Cell { +func Pentagons(resolution int) ([]Cell, error) { out := make([]C.H3Index, NumPentagons) - C.getPentagons(C.int(resolution), &out[0]) + if rc := C.getPentagons(C.int(resolution), &out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellsFromC(out, false, false) + return cellsFromC(out, false, false), nil } func (c Cell) Resolution() int { @@ -451,43 +548,51 @@ func (c Cell) IsValid() bool { } // Parent returns the parent or grandparent Cell of this Cell. -func (c Cell) Parent(resolution int) Cell { +func (c Cell) Parent(resolution int) (Cell, error) { var out C.H3Index - C.cellToParent(C.H3Index(c), C.int(resolution), &out) + if rc := C.cellToParent(C.H3Index(c), C.int(resolution), &out); rc != C.E_SUCCESS { + return Cell(-1), errFromCode(int(rc)) + } - return Cell(out) + return Cell(out), nil } // Parent returns the parent or grandparent Cell of this Cell. -func (c Cell) ImmediateParent() Cell { +func (c Cell) ImmediateParent() (Cell, error) { return c.Parent(c.Resolution() - 1) } // Children returns the children or grandchildren cells of this Cell. -func (c Cell) Children(resolution int) []Cell { +func (c Cell) Children(resolution int) ([]Cell, error) { var outsz C.int64_t - C.cellToChildrenSize(C.H3Index(c), C.int(resolution), &outsz) + if rc := C.cellToChildrenSize(C.H3Index(c), C.int(resolution), &outsz); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } out := make([]C.H3Index, outsz) - C.cellToChildren(C.H3Index(c), C.int(resolution), &out[0]) + if rc := C.cellToChildren(C.H3Index(c), C.int(resolution), &out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellsFromC(out, false, false) + return cellsFromC(out, false, false), nil } // ImmediateChildren returns the children or grandchildren cells of this Cell. -func (c Cell) ImmediateChildren() []Cell { +func (c Cell) ImmediateChildren() ([]Cell, error) { return c.Children(c.Resolution() + 1) } // CenterChild returns the center child Cell of this Cell. -func (c Cell) CenterChild(resolution int) Cell { +func (c Cell) CenterChild(resolution int) (Cell, error) { var out C.H3Index - C.cellToCenterChild(C.H3Index(c), C.int(resolution), &out) + if rc := C.cellToCenterChild(C.H3Index(c), C.int(resolution), &out); rc != C.E_SUCCESS { + return Cell(-1), errFromCode(int(rc)) + } - return Cell(out) + return Cell(out), nil } // IsResClassIII returns true if this is a class III index. If false, this is a @@ -502,39 +607,49 @@ func (c Cell) IsPentagon() bool { } // IcosahedronFaces finds all icosahedron faces (0-19) intersected by this Cell. -func (c Cell) IcosahedronFaces() []int { +func (c Cell) IcosahedronFaces() ([]int, error) { var outsz C.int - C.maxFaceCount(C.H3Index(c), &outsz) + if rc := C.maxFaceCount(C.H3Index(c), &outsz); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } out := make([]C.int, outsz) - C.getIcosahedronFaces(C.H3Index(c), &out[0]) + if rc := C.getIcosahedronFaces(C.H3Index(c), &out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return intsFromC(out) + return intsFromC(out), nil } // IsNeighbor returns true if this Cell is a neighbor of the other Cell. -func (c Cell) IsNeighbor(other Cell) bool { +func (c Cell) IsNeighbor(other Cell) (bool, error) { var out C.int - C.areNeighborCells(C.H3Index(c), C.H3Index(other), &out) + if rc := C.areNeighborCells(C.H3Index(c), C.H3Index(other), &out); rc != C.E_SUCCESS { + return false, errFromCode(int(rc)) + } - return out == 1 + return out == 1, nil } // DirectedEdge returns a DirectedEdge from this Cell to other. -func (c Cell) DirectedEdge(other Cell) DirectedEdge { +func (c Cell) DirectedEdge(other Cell) (DirectedEdge, error) { var out C.H3Index - C.cellsToDirectedEdge(C.H3Index(c), C.H3Index(other), &out) + if rc := C.cellsToDirectedEdge(C.H3Index(c), C.H3Index(other), &out); rc != C.E_SUCCESS { + return DirectedEdge(-1), errFromCode(int(rc)) + } - return DirectedEdge(out) + return DirectedEdge(out), nil } // DirectedEdges returns 6 directed edges with h as the origin. -func (c Cell) DirectedEdges() []DirectedEdge { +func (c Cell) DirectedEdges() ([]DirectedEdge, error) { out := make([]C.H3Index, numCellEdges) // always 6 directed edges - C.originToDirectedEdges(C.H3Index(c), &out[0]) + if rc := C.originToDirectedEdges(C.H3Index(c), &out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return edgesFromC(out) + return edgesFromC(out), nil } func (e DirectedEdge) IsValid() bool { @@ -542,138 +657,166 @@ func (e DirectedEdge) IsValid() bool { } // Origin returns the origin cell of this directed edge. -func (e DirectedEdge) Origin() Cell { +func (e DirectedEdge) Origin() (Cell, error) { var out C.H3Index - C.getDirectedEdgeOrigin(C.H3Index(e), &out) + if rc := C.getDirectedEdgeOrigin(C.H3Index(e), &out); rc != C.E_SUCCESS { + return Cell(-1), errFromCode(int(rc)) + } - return Cell(out) + return Cell(out), nil } // Destination returns the destination cell of this directed edge. -func (e DirectedEdge) Destination() Cell { +func (e DirectedEdge) Destination() (Cell, error) { var out C.H3Index - C.getDirectedEdgeDestination(C.H3Index(e), &out) + if rc := C.getDirectedEdgeDestination(C.H3Index(e), &out); rc != C.E_SUCCESS { + return Cell(-1), errFromCode(int(rc)) + } - return Cell(out) + return Cell(out), nil } // Cells returns the origin and destination cells in that order. -func (e DirectedEdge) Cells() []Cell { +func (e DirectedEdge) Cells() ([]Cell, error) { out := make([]C.H3Index, numEdgeCells) - C.directedEdgeToCells(C.H3Index(e), &out[0]) + if rc := C.directedEdgeToCells(C.H3Index(e), &out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellsFromC(out, false, false) + return cellsFromC(out, false, false), nil } // Boundary provides the coordinates of the boundary of the directed edge. Note, // the type returned is CellBoundary, but the coordinates will be from the // center of the origin to the center of the destination. There may be more than // 2 coordinates to account for crossing faces. -func (e DirectedEdge) Boundary() CellBoundary { +func (e DirectedEdge) Boundary() (CellBoundary, error) { var out C.CellBoundary - C.directedEdgeToBoundary(C.H3Index(e), &out) + if rc := C.directedEdgeToBoundary(C.H3Index(e), &out); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellBndryFromC(&out) + return cellBndryFromC(&out), nil } // CompactCells merges full sets of children into their parent H3Index // recursively, until no more merges are possible. -func CompactCells(in []Cell) []Cell { +func CompactCells(in []Cell) ([]Cell, error) { cin := cellsToC(in) csz := C.int64_t(len(in)) // worst case no compaction so we need a set **at least** as large as the // input cout := make([]C.H3Index, csz) - C.compactCells(&cin[0], &cout[0], csz) + if rc := C.compactCells(&cin[0], &cout[0], csz); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellsFromC(cout, false, true) + return cellsFromC(cout, false, true), nil } // UncompactCells splits every H3Index in in if its resolution is greater // than resolution recursively. Returns all the H3Indexes at resolution resolution. -func UncompactCells(in []Cell, resolution int) []Cell { +func UncompactCells(in []Cell, resolution int) ([]Cell, error) { cin := cellsToC(in) var csz C.int64_t - C.uncompactCellsSize(&cin[0], C.int64_t(len(cin)), C.int(resolution), &csz) + if rc := C.uncompactCellsSize(&cin[0], C.int64_t(len(cin)), C.int(resolution), &csz); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } cout := make([]C.H3Index, csz) - C.uncompactCells( + if rc := C.uncompactCells( &cin[0], C.int64_t(len(in)), &cout[0], csz, - C.int(resolution)) + C.int(resolution)); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellsFromC(cout, false, true) + return cellsFromC(cout, false, true), nil } // ChildPosToCell returns the child of cell a at a given position within an ordered list of all // children at the specified resolution. -func ChildPosToCell(position int, a Cell, resolution int) Cell { +func ChildPosToCell(position int, a Cell, resolution int) (Cell, error) { var out C.H3Index - C.childPosToCell(C.int64_t(position), C.H3Index(a), C.int(resolution), &out) + if rc := C.childPosToCell(C.int64_t(position), C.H3Index(a), C.int(resolution), &out); rc != C.E_SUCCESS { + return Cell(-1), errFromCode(int(rc)) + } - return Cell(out) + return Cell(out), nil } // ChildPosToCell returns the child cell at a given position within an ordered list of all // children at the specified resolution. -func (c Cell) ChildPosToCell(position int, resolution int) Cell { +func (c Cell) ChildPosToCell(position int, resolution int) (Cell, error) { return ChildPosToCell(position, c, resolution) } // CellToChildPos returns the position of the cell a within an ordered list of all children of the cell's parent // at the specified resolution. -func CellToChildPos(a Cell, resolution int) int { +func CellToChildPos(a Cell, resolution int) (int, error) { var out C.int64_t - C.cellToChildPos(C.H3Index(a), C.int(resolution), &out) + if rc := C.cellToChildPos(C.H3Index(a), C.int(resolution), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return int(out) + return int(out), nil } // ChildPos returns the position of the cell within an ordered list of all children of the cell's parent // at the specified resolution. -func (c Cell) ChildPos(resolution int) int { +func (c Cell) ChildPos(resolution int) (int, error) { return CellToChildPos(c, resolution) } -func GridDistance(a, b Cell) int { +func GridDistance(a, b Cell) (int, error) { var out C.int64_t - C.gridDistance(C.H3Index(a), C.H3Index(b), &out) + if rc := C.gridDistance(C.H3Index(a), C.H3Index(b), &out); rc != C.E_SUCCESS { + return -1, errFromCode(int(rc)) + } - return int(out) + return int(out), nil } -func (c Cell) GridDistance(other Cell) int { +func (c Cell) GridDistance(other Cell) (int, error) { return GridDistance(c, other) } -func GridPath(a, b Cell) []Cell { +func GridPath(a, b Cell) ([]Cell, error) { var outsz C.int64_t - C.gridPathCellsSize(C.H3Index(a), C.H3Index(b), &outsz) + if rc := C.gridPathCellsSize(C.H3Index(a), C.H3Index(b), &outsz); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } out := make([]C.H3Index, outsz) - C.gridPathCells(C.H3Index(a), C.H3Index(b), &out[0]) + if rc := C.gridPathCells(C.H3Index(a), C.H3Index(b), &out[0]); rc != C.E_SUCCESS { + return nil, errFromCode(int(rc)) + } - return cellsFromC(out, false, false) + return cellsFromC(out, false, false), nil } -func (c Cell) GridPath(other Cell) []Cell { +func (c Cell) GridPath(other Cell) ([]Cell, error) { return GridPath(c, other) } -func CellToLocalIJ(origin, cell Cell) CoordIJ { +func CellToLocalIJ(origin, cell Cell) (CoordIJ, error) { var out C.CoordIJ - C.cellToLocalIj(C.H3Index(origin), C.H3Index(cell), 0, &out) + if rc := C.cellToLocalIj(C.H3Index(origin), C.H3Index(cell), 0, &out); rc != C.E_SUCCESS { + return CoordIJ{}, errFromCode(int(rc)) + } - return CoordIJ{int(out.i), int(out.j)} + return CoordIJ{int(out.i), int(out.j)}, nil } -func LocalIJToCell(origin Cell, ij CoordIJ) Cell { +func LocalIJToCell(origin Cell, ij CoordIJ) (Cell, error) { var out C.H3Index - C.localIjToCell(C.H3Index(origin), ij.toCPtr(), 0, &out) + if rc := C.localIjToCell(C.H3Index(origin), ij.toCPtr(), 0, &out); rc != C.E_SUCCESS { + return Cell(-1), errFromCode(int(rc)) + } - return Cell(out) + return Cell(out), nil } func maxGridDiskSize(k int) int { diff --git a/h3_test.go b/h3_test.go index e3b4c58..c5a25ed 100644 --- a/h3_test.go +++ b/h3_test.go @@ -16,6 +16,7 @@ package h3 import ( + "errors" "fmt" "math" "sort" @@ -103,60 +104,107 @@ var ( }, } - validEdge = DirectedEdge(0x1250dab73fffffff) + validEdge = DirectedEdge(0x1250dab73fffffff) + invalidEdge = DirectedEdge(0x175283773fffffff) ) func TestLatLngToCell(t *testing.T) { t.Parallel() - c := LatLngToCell(validLatLng1, 5) + c, err := LatLngToCell(validLatLng1, 5) + assertNoErr(t, err) assertEqual(t, validCell, c) } +func TestLatLngToCellError(t *testing.T) { + t.Parallel() + _, err := LatLngToCell(validLatLng1, -1) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) + + _, err = LatLngToCell(NewLatLng(math.Inf(1), math.Inf(1)), 5) + assertTrue(t, errors.Is(err, ErrH3LatLngDomain)) + + _, err = LatLngToCell(NewLatLng(math.Inf(-1), math.Inf(-1)), 5) + assertTrue(t, errors.Is(err, ErrH3LatLngDomain)) +} func TestCellToLatLng(t *testing.T) { t.Parallel() - g := CellToLatLng(validCell) + g, err := CellToLatLng(validCell) + assertNoErr(t, err) assertEqualLatLng(t, validLatLng1, g) } +func TestCellToLatLngError(t *testing.T) { + t.Parallel() + _, err := CellToLatLng(Cell(0xfffffffffffffff)) + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) +} func TestToCellBoundary(t *testing.T) { t.Parallel() - boundary := validCell.Boundary() + boundary, err := validCell.Boundary() + assertNoErr(t, err) assertEqualLatLngs(t, validGeoLoop[:], boundary[:]) } +func TestToCellBoundaryError(t *testing.T) { + t.Parallel() + _, err := Cell(0xfffffffffffffff).Boundary() + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) +} func TestGridDisk(t *testing.T) { t.Parallel() t.Run("no pentagon", func(t *testing.T) { t.Parallel() + disks, err := validCell.GridDisk(len(validDiskDist3_1) - 1) + assertNoErr(t, err) assertEqualDisks(t, flattenDisks(validDiskDist3_1), - validCell.GridDisk(len(validDiskDist3_1)-1)) + disks, + ) }) t.Run("pentagon ok", func(t *testing.T) { t.Parallel() assertNoPanic(t, func() { - disk := GridDisk(pentagonCell, 1) + disk, err := GridDisk(pentagonCell, 1) + assertNoErr(t, err) assertEqual(t, 6, len(disk), "expected pentagon disk to have 6 cells") }) }) } +func TestGridDiskError(t *testing.T) { + t.Parallel() + _, err := GridDisk(Cell(0xfffffffffffffff), 1) + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) + + _, err = GridDisk(validCell, -1) + assertTrue(t, errors.Is(err, ErrH3Domain)) +} func TestGridDiskDistances(t *testing.T) { t.Parallel() t.Run("no pentagon", func(t *testing.T) { t.Parallel() - rings := validCell.GridDiskDistances(len(validDiskDist3_1) - 1) + rings, err := validCell.GridDiskDistances(len(validDiskDist3_1) - 1) + assertNoErr(t, err) assertEqualDiskDistances(t, validDiskDist3_1, rings) }) t.Run("pentagon centered", func(t *testing.T) { t.Parallel() assertNoPanic(t, func() { - rings := GridDiskDistances(pentagonCell, 1) + rings, err := GridDiskDistances(pentagonCell, 1) + assertNoErr(t, err) assertEqual(t, 2, len(rings), "expected 2 rings") assertEqual(t, 5, len(rings[1]), "expected 5 cells in second ring") }) }) } +func TestGridDiskDistancesError(t *testing.T) { + t.Parallel() + _, err := GridDiskDistances(Cell(0xfffffffffffffff), 1) + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) + + _, err = GridDiskDistances(validCell, -1) + assertTrue(t, errors.Is(err, ErrH3Domain)) +} func TestIsValid(t *testing.T) { t.Parallel() @@ -169,15 +217,17 @@ func TestRoundtrip(t *testing.T) { t.Run("latlng", func(t *testing.T) { t.Parallel() expectedGeo := LatLng{Lat: 1, Lng: 2} - c := LatLngToCell(expectedGeo, MaxResolution) - actualGeo := CellToLatLng(c) + c, _ := LatLngToCell(expectedGeo, MaxResolution) + actualGeo, _ := CellToLatLng(c) assertEqualLatLng(t, expectedGeo, actualGeo) - assertEqualLatLng(t, expectedGeo, expectedGeo.Cell(MaxResolution).LatLng()) + c, _ = expectedGeo.Cell(MaxResolution) + latlng, _ := c.LatLng() + assertEqualLatLng(t, expectedGeo, latlng) }) t.Run("cell", func(t *testing.T) { t.Parallel() - geo := CellToLatLng(validCell) - actualCell := LatLngToCell(geo, validCell.Resolution()) + geo, _ := CellToLatLng(validCell) + actualCell, _ := LatLngToCell(geo, validCell.Resolution()) assertEqual(t, validCell, actualCell) }) } @@ -186,11 +236,15 @@ func TestResolution(t *testing.T) { t.Parallel() for i := 1; i <= MaxResolution; i++ { - c := LatLngToCell(validLatLng1, i) + c, err := LatLngToCell(validLatLng1, i) + assertNoErr(t, err) assertEqual(t, i, c.Resolution()) } - for _, e := range validCell.DirectedEdges() { + edges, err := validCell.DirectedEdges() + assertNoErr(t, err) + + for _, e := range edges { assertEqual(t, validCell.Resolution(), e.Resolution()) } } @@ -204,56 +258,114 @@ func TestBaseCellNumber(t *testing.T) { func TestParent(t *testing.T) { t.Parallel() // get the index's parent by requesting that index's resolution+1 - parent := validCell.ImmediateParent() + parent, errParent := validCell.ImmediateParent() // get the children at the resolution of the original index - children := parent.ImmediateChildren() + children, errChildren := parent.ImmediateChildren() + assertNoErr(t, errParent) + assertNoErr(t, errChildren) assertCellIn(t, validCell, children) } +func TestParentError(t *testing.T) { + t.Parallel() + _, err := validCell.Parent(6) + assertTrue(t, errors.Is(err, ErrH3ResMismatch)) + + _, err = validCell.Parent(-1) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) +} func TestCompactCells(t *testing.T) { t.Parallel() in := flattenDisks(validDiskDist3_1[:2]) t.Logf("in: %v", in) - out := CompactCells(in) - t.Logf("out: %v", in) + + out, err := CompactCells(in) + t.Logf("out: %v", out) + assertNoErr(t, err) assertEqual(t, 1, len(out)) - assertEqual(t, validDiskDist3_1[0][0].ImmediateParent(), out[0]) + + parentCell, err := validDiskDist3_1[0][0].ImmediateParent() + assertNoErr(t, err) + assertEqual(t, parentCell, out[0]) +} +func TestCompactCellsError(t *testing.T) { + t.Parallel() + + cell := Cell(0x863e35407ffffff) + in := []Cell{} + + for i := 0; i < 8; i++ { + in = append(in, cell) + } + + _, err := CompactCells(in) + assertTrue(t, errors.Is(err, ErrH3DupInput)) } func TestUncompactCells(t *testing.T) { t.Parallel() // get the index's parent by requesting that index's resolution+1 - parent := validCell.ImmediateParent() - out := UncompactCells([]Cell{parent}, parent.Resolution()+1) + parent, err := validCell.ImmediateParent() + assertNoErr(t, err) + out, err := UncompactCells([]Cell{parent}, parent.Resolution()+1) + assertNoErr(t, err) assertCellIn(t, validCell, out) } +func TestUncompactCellsError(t *testing.T) { + t.Parallel() + + in := validDiskDist3_1[1] + + _, err := UncompactCells(in, 3) + assertTrue(t, errors.Is(err, ErrH3ResMismatch)) + + _, err = UncompactCells(in, -1) + assertTrue(t, errors.Is(err, ErrH3ResMismatch)) + + _, err = UncompactCells(in, 16) + assertTrue(t, errors.Is(err, ErrH3ResMismatch)) +} func TestChildPosToCell(t *testing.T) { t.Parallel() - children := validCell.Children(6) + children, err := validCell.Children(6) + assertNoErr(t, err) - assertEqual(t, children[0], validCell.ChildPosToCell(0, 6)) - assertEqual(t, children[0], ChildPosToCell(0, validCell, 6)) + child, err := validCell.ChildPosToCell(0, 6) + assertNoErr(t, err) + assertEqual(t, children[0], child) + + child, err = ChildPosToCell(0, validCell, 6) + assertNoErr(t, err) + assertEqual(t, children[0], child) } func TestChildPos(t *testing.T) { t.Parallel() - children := validCell.Children(7) + children, err := validCell.Children(7) + assertNoErr(t, err) + + child, err := children[32].ChildPos(validCell.Resolution()) + assertNoErr(t, err) + assertEqual(t, 32, child) - assertEqual(t, 32, children[32].ChildPos(validCell.Resolution())) - assertEqual(t, 32, CellToChildPos(children[32], validCell.Resolution())) + child, err = CellToChildPos(children[32], validCell.Resolution()) + assertNoErr(t, err) + assertEqual(t, 32, child) } func TestIsResClassIII(t *testing.T) { t.Parallel() + parentCell, err := validCell.ImmediateParent() + assertNoErr(t, err) assertTrue(t, validCell.IsResClassIII()) - assertFalse(t, validCell.ImmediateParent().IsResClassIII()) + assertFalse(t, parentCell.IsResClassIII()) } func TestIsPentagon(t *testing.T) { @@ -264,16 +376,31 @@ func TestIsPentagon(t *testing.T) { func TestIsNeighbor(t *testing.T) { t.Parallel() - assertFalse(t, validCell.IsNeighbor(pentagonCell)) - assertTrue(t, validCell.DirectedEdges()[0].Destination().IsNeighbor(validCell)) + + res, err := validCell.IsNeighbor(validDiskDist3_1[2][0]) + assertNoErr(t, err) + assertFalse(t, res) + + edges, _ := validCell.DirectedEdges() + dest, _ := edges[0].Destination() + + res, err = dest.IsNeighbor(validCell) + assertNoErr(t, err) + assertTrue(t, res) } func TestDirectedEdge(t *testing.T) { t.Parallel() origin := validDiskDist3_1[1][0] - destination := origin.DirectedEdges()[0].Destination() - edge := origin.DirectedEdge(destination) + edges, err := origin.DirectedEdges() + assertNoErr(t, err) + + destination, err := edges[0].Destination() + assertNoErr(t, err) + + edge, err := origin.DirectedEdge(destination) + assertNoErr(t, err) t.Run("is valid", func(t *testing.T) { t.Parallel() @@ -283,34 +410,71 @@ func TestDirectedEdge(t *testing.T) { t.Run("get origin/destination from edge", func(t *testing.T) { t.Parallel() - assertEqual(t, origin, edge.Origin()) - assertEqual(t, destination, edge.Destination()) + originExpected, err := edge.Origin() + assertNoErr(t, err) + assertEqual(t, origin, originExpected) + + destinationExpected, err := edge.Destination() + assertNoErr(t, err) + assertEqual(t, destination, destinationExpected) // shadow origin/destination - cells := edge.Cells() + cells, err := edge.Cells() + assertNoErr(t, err) + origin, destination := cells[0], cells[1] - assertEqual(t, origin, edge.Origin()) - assertEqual(t, destination, edge.Destination()) + originExpected, err = edge.Origin() + assertNoErr(t, err) + assertEqual(t, origin, originExpected) + + destinationExpected, err = edge.Destination() + assertNoErr(t, err) + assertEqual(t, destination, destinationExpected) }) t.Run("get edges from hexagon", func(t *testing.T) { t.Parallel() - edges := validCell.DirectedEdges() + edges, err := validCell.DirectedEdges() + assertNoErr(t, err) assertEqual(t, 6, len(edges), "hexagon has 6 edges") }) t.Run("get edges from pentagon", func(t *testing.T) { t.Parallel() - edges := pentagonCell.DirectedEdges() + edges, err := pentagonCell.DirectedEdges() + assertNoErr(t, err) assertEqual(t, 5, len(edges), "pentagon has 5 edges") }) t.Run("get boundary from edge", func(t *testing.T) { t.Parallel() - gb := edge.Boundary() + gb, err := edge.Boundary() + assertNoErr(t, err) assertEqual(t, 2, len(gb), "edge has 2 boundary cells") }) } +func TestDirectedEdgeError(t *testing.T) { + t.Parallel() + + _, err := validCell.DirectedEdge(validDiskDist3_1[2][0]) + assertTrue(t, errors.Is(err, ErrH3Neighbors)) + + _, err = DirectedEdge(0).Origin() + assertTrue(t, errors.Is(err, ErrH3InvalidDirEdge)) + + _, err = DirectedEdge(0).Destination() + assertTrue(t, errors.Is(err, ErrH3InvalidDirEdge)) + _, err = invalidEdge.Destination() + assertTrue(t, errors.Is(err, ErrH3Failed)) + + _, err = DirectedEdge(0).Cells() + assertTrue(t, errors.Is(err, ErrH3InvalidDirEdge)) + _, err = invalidEdge.Cells() + assertTrue(t, errors.Is(err, ErrH3Failed)) + + _, err = invalidEdge.Boundary() + assertTrue(t, errors.Is(err, ErrH3InvalidDirEdge)) +} func TestStrings(t *testing.T) { t.Parallel() @@ -353,13 +517,14 @@ func TestPolygonToCells(t *testing.T) { t.Run("empty", func(t *testing.T) { t.Parallel() - cells := PolygonToCells(GeoPolygon{}, 6) + cells, err := PolygonToCells(GeoPolygon{}, 6) + assertNoErr(t, err) assertEqual(t, 0, len(cells)) }) t.Run("without holes", func(t *testing.T) { t.Parallel() - cells := validGeoPolygonNoHoles.Cells(6) + cells, err := validGeoPolygonNoHoles.Cells(6) expectedIndexes := []Cell{ 0x860dab607ffffff, 0x860dab60fffffff, @@ -369,12 +534,13 @@ func TestPolygonToCells(t *testing.T) { 0x860dab62fffffff, 0x860dab637ffffff, } + assertNoErr(t, err) assertEqualCells(t, expectedIndexes, cells) }) t.Run("with hole", func(t *testing.T) { t.Parallel() - cells := validGeoPolygonHoles.Cells(6) + cells, err := validGeoPolygonHoles.Cells(6) expectedIndexes := []Cell{ 0x860dab60fffffff, 0x860dab617ffffff, @@ -383,19 +549,36 @@ func TestPolygonToCells(t *testing.T) { 0x860dab62fffffff, 0x860dab637ffffff, } + assertNoErr(t, err) assertEqualCells(t, expectedIndexes, cells) }) } +func TestPolygonToCellsError(t *testing.T) { + t.Parallel() + _, err := PolygonToCells(validGeoPolygonHoles, -1) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) + + invalidGeoPolygon := GeoPolygon{ + GeoLoop: GeoLoop{ + {Lat: math.Inf(1), Lng: math.Inf(1)}, + {Lat: math.Inf(-1), Lng: math.Inf(-1)}, + }, + Holes: []GeoLoop{}, + } + _, err = PolygonToCells(invalidGeoPolygon, 5) + assertTrue(t, errors.Is(err, ErrH3Failed)) +} func TestGridPath(t *testing.T) { t.Parallel() - path := lineStartCell.GridPath(lineEndCell) - + path, err := lineStartCell.GridPath(lineEndCell) + assertNoErr(t, err) assertEqual(t, lineStartCell, path[0]) assertEqual(t, lineEndCell, path[len(path)-1]) for i := 0; i < len(path)-1; i++ { - assertTrue(t, path[i].IsNeighbor(path[i+1])) + res, _ := path[i].IsNeighbor(path[i+1]) + assertTrue(t, res) } } @@ -403,33 +586,61 @@ func TestHexAreaKm2(t *testing.T) { t.Parallel() t.Run("min resolution", func(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(4357449.4161), HexagonAreaAvgKm2(0)) + area, err := HexagonAreaAvgKm2(0) + assertNoErr(t, err) + assertEqualEps(t, float64(4357449.4161), area) }) t.Run("max resolution", func(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(0.0000009), HexagonAreaAvgKm2(15)) + area, err := HexagonAreaAvgKm2(15) + assertNoErr(t, err) + assertEqualEps(t, float64(0.0000009), area) }) t.Run("mid resolution", func(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(0.7373276), HexagonAreaAvgKm2(8)) + area, err := HexagonAreaAvgKm2(8) + assertNoErr(t, err) + assertEqualEps(t, float64(0.7373276), area) }) } +func TestHexAreaKm2Error(t *testing.T) { + t.Parallel() + _, err := HexagonAreaAvgKm2(-1) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) + + _, err = HexagonAreaAvgKm2(16) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) +} func TestHexAreaM2(t *testing.T) { t.Parallel() t.Run("min resolution", func(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(4357449416078.3901), HexagonAreaAvgM2(0)) + area, err := HexagonAreaAvgM2(0) + assertNoErr(t, err) + assertEqualEps(t, float64(4357449416078.3901), area) }) t.Run("max resolution", func(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(0.8953), HexagonAreaAvgM2(15)) + area, err := HexagonAreaAvgM2(15) + assertNoErr(t, err) + assertEqualEps(t, float64(0.8953), area) }) t.Run("mid resolution", func(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(737327.5976), HexagonAreaAvgM2(8)) + area, err := HexagonAreaAvgM2(8) + assertNoErr(t, err) + assertEqualEps(t, float64(737327.5976), area) }) } +func TestHexAreaM2Error(t *testing.T) { + t.Parallel() + _, err := HexagonAreaAvgM2(-1) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) + + _, err = HexagonAreaAvgM2(16) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) +} func TestPointDistRads(t *testing.T) { t.Parallel() @@ -451,72 +662,138 @@ func TestPointDistM(t *testing.T) { func TestCellAreaRads2(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(0.000006643967854567278), CellAreaRads2(validCell)) + area, err := CellAreaRads2(validCell) + assertNoErr(t, err) + assertEqualEps(t, float64(0.000006643967854567278), area) +} +func TestCellAreaRads2Error(t *testing.T) { + t.Parallel() + _, err := CellAreaRads2(Cell(0xfffffffffffffff)) + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) } func TestCellAreaKm2(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(269.6768779509321), CellAreaKm2(validCell)) + area, err := CellAreaKm2(validCell) + assertNoErr(t, err) + assertEqualEps(t, float64(269.6768779509321), area) +} +func TestCellAreaKm2Error(t *testing.T) { + t.Parallel() + _, err := CellAreaKm2(Cell(0xfffffffffffffff)) + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) } func TestCellAreaM2(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(269676877.95093215), CellAreaM2(validCell)) + area, err := CellAreaM2(validCell) + assertNoErr(t, err) + assertEqualEps(t, float64(269676877.95093215), area) +} +func TestCellAreaM2Error(t *testing.T) { + t.Parallel() + _, err := CellAreaM2(Cell(0xfffffffffffffff)) + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) } func TestHexagonEdgeLengthKm(t *testing.T) { t.Parallel() t.Run("min resolution", func(t *testing.T) { t.Parallel() - assertEqual(t, float64(1107.712591), HexagonEdgeLengthAvgKm(0)) + area, err := HexagonEdgeLengthAvgKm(0) + assertNoErr(t, err) + assertEqual(t, float64(1107.712591), area) }) t.Run("max resolution", func(t *testing.T) { t.Parallel() - assertEqual(t, float64(0.000509713), HexagonEdgeLengthAvgKm(15)) + area, err := HexagonEdgeLengthAvgKm(15) + assertNoErr(t, err) + assertEqual(t, float64(0.000509713), area) }) t.Run("mid resolution", func(t *testing.T) { t.Parallel() - assertEqual(t, float64(0.461354684), HexagonEdgeLengthAvgKm(8)) + area, err := HexagonEdgeLengthAvgKm(8) + assertNoErr(t, err) + assertEqual(t, float64(0.461354684), area) }) } +func TestHexagonEdgeLengthKmError(t *testing.T) { + t.Parallel() + _, err := HexagonEdgeLengthAvgKm(-1) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) + + _, err = HexagonEdgeLengthAvgKm(16) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) +} func TestHexagonEdgeLengthM(t *testing.T) { t.Parallel() t.Run("min resolution", func(t *testing.T) { t.Parallel() - area := HexagonEdgeLengthAvgM(0) + area, err := HexagonEdgeLengthAvgM(0) + assertNoErr(t, err) assertEqual(t, float64(1107712.591), area) }) t.Run("max resolution", func(t *testing.T) { t.Parallel() - area := HexagonEdgeLengthAvgM(15) + area, err := HexagonEdgeLengthAvgM(15) + assertNoErr(t, err) assertEqual(t, float64(0.509713273), area) }) t.Run("mid resolution", func(t *testing.T) { t.Parallel() - area := HexagonEdgeLengthAvgM(8) + area, err := HexagonEdgeLengthAvgM(8) + assertNoErr(t, err) assertEqual(t, float64(461.3546837), area) }) } +func TestHexagonEdgeLengthMError(t *testing.T) { + t.Parallel() + _, err := HexagonEdgeLengthAvgM(-1) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) + + _, err = HexagonEdgeLengthAvgM(16) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) +} func TestEdgeLengthRads(t *testing.T) { t.Parallel() - assertEqualEps(t, float64(0.001569665746947077), EdgeLengthRads(validEdge)) + + distance, err := EdgeLengthRads(validEdge) + assertNoErr(t, err) + assertEqualEps(t, float64(0.001569665746947077), distance) +} +func TestEdgeLengthRadsError(t *testing.T) { + t.Parallel() + _, err := EdgeLengthRads(DirectedEdge(0)) + assertTrue(t, errors.Is(err, ErrH3InvalidDirEdge)) } func TestEdgeLengthKm(t *testing.T) { t.Parallel() - distance := EdgeLengthKm(validEdge) + distance, err := EdgeLengthKm(validEdge) + assertNoErr(t, err) assertEqualEps(t, float64(10.00035174544159), distance) } +func TestEdgeLengthKmError(t *testing.T) { + t.Parallel() + _, err := EdgeLengthRads(DirectedEdge(0)) + assertTrue(t, errors.Is(err, ErrH3InvalidDirEdge)) +} func TestEdgeLengthM(t *testing.T) { t.Parallel() - distance := EdgeLengthM(validEdge) + distance, err := EdgeLengthM(validEdge) + assertNoErr(t, err) assertEqualEps(t, float64(10000.351745441589), distance) } +func TestEdgeLengthMError(t *testing.T) { + t.Parallel() + _, err := EdgeLengthRads(DirectedEdge(0)) + assertTrue(t, errors.Is(err, ErrH3InvalidDirEdge)) +} func TestNumCells(t *testing.T) { t.Parallel() @@ -536,8 +813,9 @@ func TestNumCells(t *testing.T) { func TestRes0Cells(t *testing.T) { t.Parallel() - cells := Res0Cells() + cells, err := Res0Cells() + assertNoErr(t, err) assertEqual(t, 122, len(cells)) assertEqual(t, Cell(0x8001fffffffffff), cells[0]) assertEqual(t, Cell(0x80f3fffffffffff), cells[121]) @@ -545,24 +823,49 @@ func TestRes0Cells(t *testing.T) { func TestGridDistance(t *testing.T) { t.Parallel() - assertEqual(t, 1823, lineStartCell.GridDistance(lineEndCell)) + dist, err := lineStartCell.GridDistance(lineEndCell) + assertNoErr(t, err) + assertEqual(t, 1823, dist) +} +func TestGridDistanceError(t *testing.T) { + t.Parallel() + _, err := GridDistance(Cell(0x821c37fffffffff), Cell(0x822837fffffffff)) + assertTrue(t, errors.Is(err, ErrH3Failed)) + + _, err = GridDistance(Cell(0x81283ffffffffff), Cell(0x8029fffffffffff)) + assertTrue(t, errors.Is(err, ErrH3ResMismatch)) + + _, err = GridDistance(Cell(0xfffffffffffffff), Cell(0xfffffffffffffff)) + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) } func TestCenterChild(t *testing.T) { t.Parallel() - child := validCell.CenterChild(15) + child, err := validCell.CenterChild(15) + assertNoErr(t, err) assertEqual(t, Cell(0x8f0dab600000000), child) } +func TestCenterChildError(t *testing.T) { + t.Parallel() + _, err := validCell.CenterChild(4) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) +} func TestIcosahedronFaces(t *testing.T) { t.Parallel() - faces := validDiskDist3_1[1][1].IcosahedronFaces() + faces, err := validDiskDist3_1[1][1].IcosahedronFaces() + assertNoErr(t, err) assertEqual(t, 1, len(faces)) assertEqual(t, 1, faces[0]) } +func TestIcosahedronFacesError(t *testing.T) { + t.Parallel() + _, err := Cell(0xfffffffffffffff).IcosahedronFaces() + assertTrue(t, errors.Is(err, ErrH3InvalidIndex)) +} func TestPentagons(t *testing.T) { t.Parallel() @@ -570,7 +873,8 @@ func TestPentagons(t *testing.T) { for _, res := range []int{0, 8, 15} { t.Run(fmt.Sprintf("res=%d", res), func(t *testing.T) { t.Parallel() - pentagons := Pentagons(res) + pentagons, err := Pentagons(res) + assertNoErr(t, err) assertEqual(t, 12, len(pentagons)) for _, pentagon := range pentagons { assertTrue(t, pentagon.IsPentagon()) @@ -579,6 +883,11 @@ func TestPentagons(t *testing.T) { }) } } +func TestPentagonsError(t *testing.T) { + t.Parallel() + _, err := Pentagons(-1) + assertTrue(t, errors.Is(err, ErrH3ResDomain)) +} func equalEps(expected, actual float64) bool { return math.Abs(expected-actual) < eps