h3.go (629 lines of code) (raw):

// Package h3 is the go binding for Uber's H3 Geo Index system. // It uses cgo to link with a statically compiled h3 library package h3 /* * Copyright 2018 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* #cgo CFLAGS: -std=c99 #cgo CFLAGS: -DH3_HAVE_VLA=1 #cgo LDFLAGS: -lm #include <stdlib.h> #include <h3_h3api.h> #include <h3_h3Index.h> #include <h3_polygon.h> #include <h3_polyfill.h> */ import "C" import ( "errors" "fmt" "math" "strconv" "strings" "unsafe" ) const ( // MaxCellBndryVerts is the maximum number of vertices that can be used // to represent the shape of a cell. MaxCellBndryVerts = C.MAX_CELL_BNDRY_VERTS // MaxResolution is the maximum H3 resolution a LatLng can be indexed to. MaxResolution = C.MAX_H3_RES // The number of faces on an icosahedron NumIcosaFaces = C.NUM_ICOSA_FACES // The number of H3 base cells NumBaseCells = C.NUM_BASE_CELLS // The number of H3 pentagon cells (same at every resolution) NumPentagons = C.NUM_PENTAGONS // InvalidH3Index is a sentinel value for an invalid H3 index. InvalidH3Index = C.H3_NULL base16 = 16 bitSize = 64 numCellEdges = 6 numEdgeCells = 2 numCellVertexes = 6 DegsToRads = math.Pi / 180.0 RadsToDegs = 180.0 / math.Pi // PolygonToCells containment modes ContainmentCenter ContainmentMode = C.CONTAINMENT_CENTER // Cell center is contained in the shape ContainmentFull ContainmentMode = C.CONTAINMENT_FULL // Cell is fully contained in the shape ContainmentOverlapping ContainmentMode = C.CONTAINMENT_OVERLAPPING // Cell overlaps the shape at any point ContainmentOverlappingBbox ContainmentMode = C.CONTAINMENT_OVERLAPPING_BBOX // Cell bounding box overlaps shape ContainmentInvalid ContainmentMode = C.CONTAINMENT_INVALID // This mode is invalid and should not be used ) // Error codes. var ( ErrFailed = errors.New("the operation failed") ErrDomain = errors.New("argument was outside of acceptable range") ErrLatLngDomain = errors.New("latitude or longitude arguments were outside of acceptable range") ErrResolutionDomain = errors.New("resolution argument was outside of acceptable range") ErrCellInvalid = errors.New("H3Index cell argument was not valid") ErrDirectedEdgeInvalid = errors.New("H3Index directed edge argument was not valid") ErrUndirectedEdgeInvalid = errors.New("H3Index undirected edge argument was not valid") ErrVertexInvalid = errors.New("H3Index vertex argument was not valid") ErrPentagon = errors.New("pentagon distortion was encountered") ErrDuplicateInput = errors.New("duplicate input was encountered in the arguments") ErrNotNeighbors = errors.New("H3Index cell arguments were not neighbors") ErrRsolutionMismatch = errors.New("H3Index cell arguments had incompatible resolutions") ErrMemoryAlloc = errors.New("necessary memory allocation failed") ErrMemoryBounds = errors.New("bounds of provided memory were not large enough") ErrOptionInvalid = errors.New("mode or flags argument was not valid") ErrUnknown = errors.New("unknown error code returned by H3") errMap = map[C.uint32_t]error{ 0: nil, // Success error code. 1: ErrFailed, 2: ErrDomain, 3: ErrLatLngDomain, 4: ErrResolutionDomain, 5: ErrCellInvalid, 6: ErrDirectedEdgeInvalid, 7: ErrUndirectedEdgeInvalid, 8: ErrVertexInvalid, 9: ErrPentagon, 10: ErrDuplicateInput, 11: ErrNotNeighbors, 12: ErrRsolutionMismatch, 13: ErrMemoryAlloc, 14: ErrMemoryBounds, 15: ErrOptionInvalid, } ) type ( // Cell is an Index that identifies a single hexagon cell at a resolution. Cell int64 // DirectedEdge is an Index that identifies a directed edge between two cells. DirectedEdge int64 CoordIJ struct { I, J int } // CellBoundary is a slice of LatLng. Note, len(CellBoundary) will never // exceed MaxCellBndryVerts. CellBoundary []LatLng // GeoLoop is a slice of LatLng points that make up a loop. GeoLoop []LatLng // LatLng is a struct for geographic coordinates in degrees. LatLng struct { Lat, Lng float64 } // GeoPolygon is a GeoLoop with 0 or more GeoLoop holes. GeoPolygon struct { GeoLoop GeoLoop Holes []GeoLoop } // ContainmentMode is an int for specifying PolygonToCell containment behavior. ContainmentMode C.uint32_t ) func NewLatLng(lat, lng float64) LatLng { return LatLng{lat, lng} } // LatLngToCell returns the Cell at resolution for a geographic coordinate. func LatLngToCell(latLng LatLng, resolution int) (Cell, error) { var i C.H3Index errC := C.latLngToCell(latLng.toCPtr(), C.int(resolution), &i) return Cell(i), toErr(errC) } // Cell returns the Cell at resolution for a geographic coordinate. 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, error) { var g C.LatLng errC := C.cellToLatLng(C.H3Index(c), &g) return latLngFromC(g), toErr(errC) } // LatLng returns the Cell at resolution for a geographic coordinate. func (c Cell) LatLng() (LatLng, error) { return CellToLatLng(c) } // CellToBoundary returns a CellBoundary of the Cell. func CellToBoundary(c Cell) (CellBoundary, error) { var cb C.CellBoundary errC := C.cellToBoundary(C.H3Index(c), &cb) return cellBndryFromC(&cb), toErr(errC) } // Boundary returns a CellBoundary of the Cell. func (c Cell) Boundary() (CellBoundary, error) { return CellToBoundary(c) } // GridDisk produces cells within grid distance k of the origin cell. // // k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and // all neighboring cells, and so on. // // 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, error) { out := make([]C.H3Index, maxGridDiskSize(k)) errC := C.gridDisk(C.H3Index(origin), C.int(k), &out[0]) // QUESTION: should we prune zeroes from the output? return cellsFromC(out, true, false), toErr(errC) } // GridDisk produces cells within grid distance k of the origin cell. // // k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and // all neighboring cells, and so on. // // 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, error) { return GridDisk(c, k) } // GridDiskDistances produces cells within grid distance k of the origin cell. // // k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and // all neighboring cells, and so on. // // 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, error) { rsz := maxGridDiskSize(k) outHexes := make([]C.H3Index, rsz) outDists := make([]C.int, rsz) if err := toErr(C.gridDiskDistances(C.H3Index(origin), C.int(k), &outHexes[0], &outDists[0])); err != nil { return nil, err } ret := make([][]Cell, k+1) for i := 0; i <= k; i++ { ret[i] = make([]Cell, 0, ringSize(i)) } for i, d := range outDists { ret[d] = append(ret[d], Cell(outHexes[i])) } return ret, nil } // GridDiskDistances produces cells within grid distance k of the origin cell. // // k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and // all neighboring cells, and so on. // // 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, error) { return GridDiskDistances(c, k) } // PolygonToCells takes a given GeoJSON-like data structure fills it with the // hexagon cells that are contained by the GeoJSON-like data structure. // // This implementation traces the GeoJSON geoloop(s) in cartesian space with // 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, error) { if len(polygon.GeoLoop) == 0 { return nil, nil } cpoly := allocCGeoPolygon(polygon) defer freeCGeoPolygon(&cpoly) maxLen := new(C.int64_t) if err := toErr(C.maxPolygonToCellsSize(&cpoly, C.int(resolution), 0, maxLen)); err != nil { return nil, err } out := make([]C.H3Index, *maxLen) errC := C.polygonToCells(&cpoly, C.int(resolution), 0, &out[0]) return cellsFromC(out, true, false), toErr(errC) } // PolygonToCells takes a given GeoJSON-like data structure fills it with the // hexagon cells that are contained by the GeoJSON-like data structure. // // This implementation traces the GeoJSON geoloop(s) in cartesian space with // 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 PolygonToCellsExperimental(polygon GeoPolygon, resolution int, mode ContainmentMode, maxNumCellsReturn ...int64) ([]Cell, error) { var maxNumCells int64 = math.MaxInt64 if len(maxNumCellsReturn) > 0 { maxNumCells = maxNumCellsReturn[0] } if len(polygon.GeoLoop) == 0 { return nil, nil } cpoly := allocCGeoPolygon(polygon) defer freeCGeoPolygon(&cpoly) maxLen := new(C.int64_t) if err := toErr(C.maxPolygonToCellsSizeExperimental(&cpoly, C.int(resolution), C.uint32_t(mode), maxLen)); err != nil { return nil, err } out := make([]C.H3Index, *maxLen) errC := C.polygonToCellsExperimental(&cpoly, C.int(resolution), C.uint32_t(mode), C.int64_t(maxNumCells), &out[0]) return cellsFromC(out, true, false), toErr(errC) } // PolygonToCells takes a given GeoJSON-like data structure fills it with the // hexagon cells that are contained by the GeoJSON-like data structure. // // This implementation traces the GeoJSON geoloop(s) in cartesian space with // 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, error) { return PolygonToCells(p, resolution) } // 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, error) { if len(cells) == 0 { return nil, nil } h3Indexes := cellsToC(cells) cLinkedGeoPolygon := new(C.LinkedGeoPolygon) if err := toErr(C.cellsToLinkedMultiPolygon(&h3Indexes[0], C.int(len(h3Indexes)), cLinkedGeoPolygon)); err != nil { return nil, err } 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 } C.destroyLinkedMultiPolygon(cLinkedGeoPolygon) return ret, nil } // PointDistRads returns the "great circle" or "haversine" distance between // pairs of LatLng points (lat/lng pairs) in radians. func GreatCircleDistanceRads(a, b LatLng) float64 { return float64(C.greatCircleDistanceRads(a.toCPtr(), b.toCPtr())) } // PointDistKm returns the "great circle" or "haversine" distance between pairs // of LatLng points (lat/lng pairs) in kilometers. func GreatCircleDistanceKm(a, b LatLng) float64 { return float64(C.greatCircleDistanceKm(a.toCPtr(), b.toCPtr())) } // PointDistM returns the "great circle" or "haversine" distance between pairs // of LatLng points (lat/lng pairs) in meters. func GreatCircleDistanceM(a, b LatLng) float64 { return float64(C.greatCircleDistanceM(a.toCPtr(), b.toCPtr())) } // HexAreaKm2 returns the average hexagon area in square kilometers at the given // resolution. func HexagonAreaAvgKm2(resolution int) (float64, error) { var out C.double errC := C.getHexagonAreaAvgKm2(C.int(resolution), &out) return float64(out), toErr(errC) } // HexAreaM2 returns the average hexagon area in square meters at the given // resolution. func HexagonAreaAvgM2(resolution int) (float64, error) { var out C.double errC := C.getHexagonAreaAvgM2(C.int(resolution), &out) return float64(out), toErr(errC) } // CellAreaRads2 returns the exact area of specific cell in square radians. func CellAreaRads2(c Cell) (float64, error) { var out C.double errC := C.cellAreaRads2(C.H3Index(c), &out) return float64(out), toErr(errC) } // CellAreaKm2 returns the exact area of specific cell in square kilometers. func CellAreaKm2(c Cell) (float64, error) { var out C.double errC := C.cellAreaKm2(C.H3Index(c), &out) return float64(out), toErr(errC) } // CellAreaM2 returns the exact area of specific cell in square meters. func CellAreaM2(c Cell) (float64, error) { var out C.double errC := C.cellAreaM2(C.H3Index(c), &out) return float64(out), toErr(errC) } // HexagonEdgeLengthAvgKm returns the average hexagon edge length in kilometers // at the given resolution. func HexagonEdgeLengthAvgKm(resolution int) (float64, error) { var out C.double errC := C.getHexagonEdgeLengthAvgKm(C.int(resolution), &out) return float64(out), toErr(errC) } // HexagonEdgeLengthAvgM returns the average hexagon edge length in meters at // the given resolution. func HexagonEdgeLengthAvgM(resolution int) (float64, error) { var out C.double errC := C.getHexagonEdgeLengthAvgM(C.int(resolution), &out) return float64(out), toErr(errC) } // EdgeLengthRads returns the exact edge length of specific unidirectional edge // in radians. func EdgeLengthRads(e DirectedEdge) (float64, error) { var out C.double errC := C.edgeLengthRads(C.H3Index(e), &out) return float64(out), toErr(errC) } // EdgeLengthKm returns the exact edge length of specific unidirectional // edge in kilometers. func EdgeLengthKm(e DirectedEdge) (float64, error) { var out C.double errC := C.edgeLengthKm(C.H3Index(e), &out) return float64(out), toErr(errC) } // EdgeLengthM returns the exact edge length of specific unidirectional // edge in meters. func EdgeLengthM(e DirectedEdge) (float64, error) { var out C.double errC := C.edgeLengthM(C.H3Index(e), &out) return float64(out), toErr(errC) } // NumCells returns the number of cells at the given resolution. func NumCells(resolution int) int { // NOTE: this is a mathematical operation, no need to call into H3 library. // See h3api.h for formula derivation. return 2 + 120*intPow(7, (resolution)) //nolint:mnd // math formula } // Res0Cells returns all the cells at resolution 0. func Res0Cells() ([]Cell, error) { out := make([]C.H3Index, C.res0CellCount()) errC := C.getRes0Cells(&out[0]) return cellsFromC(out, false, false), toErr(errC) } // Pentagons returns all the pentagons at resolution. func Pentagons(resolution int) ([]Cell, error) { out := make([]C.H3Index, NumPentagons) errC := C.getPentagons(C.int(resolution), &out[0]) return cellsFromC(out, false, false), toErr(errC) } func (c Cell) Resolution() int { return int(C.getResolution(C.H3Index(c))) } func (e DirectedEdge) Resolution() int { return int(C.getResolution(C.H3Index(e))) } // BaseCellNumber returns the integer ID (0-121) of the base cell the H3Index h // belongs to. func BaseCellNumber(h Cell) int { return int(C.getBaseCellNumber(C.H3Index(h))) } // BaseCellNumber returns the integer ID (0-121) of the base cell the H3Index h // belongs to. func (c Cell) BaseCellNumber() int { return BaseCellNumber(c) } // IndexFromString returns a Cell from a string. Should call c.IsValid() to check // if the Cell is valid before using it. func IndexFromString(s string) uint64 { if len(s) > 2 && strings.ToLower(s[:2]) == "0x" { s = s[2:] } c, _ := strconv.ParseUint(s, base16, bitSize) return c } // IndexToString returns a Cell from a string. Should call c.IsValid() to check // if the Cell is valid before using it. func IndexToString(i uint64) string { return strconv.FormatUint(i, base16) } // String returns the string representation of the H3Index h. func (c Cell) String() string { return IndexToString(uint64(c)) } // MarshalText implements the encoding.TextMarshaler interface. func (c Cell) MarshalText() ([]byte, error) { return []byte(c.String()), nil } // UnmarshalText implements the encoding.TextUnmarshaler interface. func (c *Cell) UnmarshalText(text []byte) error { *c = Cell(IndexFromString(string(text))) if !c.IsValid() { return errors.New("invalid cell index") } return nil } // IsValid returns if a Cell is a valid cell (hexagon or pentagon). func (c Cell) IsValid() bool { return c != 0 && C.isValidCell(C.H3Index(c)) == 1 } // Parent returns the parent or grandparent Cell of this Cell. func (c Cell) Parent(resolution int) (Cell, error) { var out C.H3Index errC := C.cellToParent(C.H3Index(c), C.int(resolution), &out) return Cell(out), toErr(errC) } // Parent returns the parent or grandparent Cell of this 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, error) { var outsz C.int64_t if err := toErr(C.cellToChildrenSize(C.H3Index(c), C.int(resolution), &outsz)); err != nil { return nil, err } out := make([]C.H3Index, outsz) // Seems like this function always returns E_SUCCESS. errC := C.cellToChildren(C.H3Index(c), C.int(resolution), &out[0]) return cellsFromC(out, false, false), toErr(errC) } // ImmediateChildren returns the children or grandchildren cells of this 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, error) { var out C.H3Index errC := C.cellToCenterChild(C.H3Index(c), C.int(resolution), &out) return Cell(out), toErr(errC) } // IsResClassIII returns true if this is a class III index. If false, this is a // class II index. func (c Cell) IsResClassIII() bool { return C.isResClassIII(C.H3Index(c)) == 1 } // IsPentagon returns true if this is a pentagon. func (c Cell) IsPentagon() bool { return C.isPentagon(C.H3Index(c)) == 1 } // IcosahedronFaces finds all icosahedron faces (0-19) intersected by this Cell. func (c Cell) IcosahedronFaces() ([]int, error) { var outsz C.int // Seems like this function always returns E_SUCCESS. C.maxFaceCount(C.H3Index(c), &outsz) out := make([]C.int, outsz) errC := C.getIcosahedronFaces(C.H3Index(c), &out[0]) return intsFromC(out), toErr(errC) } // IsNeighbor returns true if this Cell is a neighbor of the other Cell. func (c Cell) IsNeighbor(other Cell) (bool, error) { var out C.int errC := C.areNeighborCells(C.H3Index(c), C.H3Index(other), &out) return out == 1, toErr(errC) } // DirectedEdge returns a DirectedEdge from this Cell to other. func (c Cell) DirectedEdge(other Cell) (DirectedEdge, error) { var out C.H3Index errC := C.cellsToDirectedEdge(C.H3Index(c), C.H3Index(other), &out) return DirectedEdge(out), toErr(errC) } // DirectedEdges returns 6 directed edges with h as the origin. func (c Cell) DirectedEdges() ([]DirectedEdge, error) { out := make([]C.H3Index, numCellEdges) // always 6 directed edges // Seems like this function always returns E_SUCCESS. errC := C.originToDirectedEdges(C.H3Index(c), &out[0]) return edgesFromC(out), toErr(errC) } func (e DirectedEdge) IsValid() bool { return C.isValidDirectedEdge(C.H3Index(e)) == 1 } // Origin returns the origin cell of this directed edge. func (e DirectedEdge) Origin() (Cell, error) { var out C.H3Index errC := C.getDirectedEdgeOrigin(C.H3Index(e), &out) return Cell(out), toErr(errC) } // Destination returns the destination cell of this directed edge. func (e DirectedEdge) Destination() (Cell, error) { var out C.H3Index errC := C.getDirectedEdgeDestination(C.H3Index(e), &out) return Cell(out), toErr(errC) } // Cells returns the origin and destination cells in that order. func (e DirectedEdge) Cells() ([]Cell, error) { out := make([]C.H3Index, numEdgeCells) if err := toErr(C.directedEdgeToCells(C.H3Index(e), &out[0])); err != nil { return nil, err } 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, error) { var out C.CellBoundary if err := toErr(C.directedEdgeToBoundary(C.H3Index(e), &out)); err != nil { return nil, err } 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, 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) errC := C.compactCells(&cin[0], &cout[0], csz) return cellsFromC(cout, false, true), toErr(errC) } // 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, error) { cin := cellsToC(in) var csz C.int64_t if err := toErr(C.uncompactCellsSize(&cin[0], C.int64_t(len(cin)), C.int(resolution), &csz)); err != nil { return nil, err } cout := make([]C.H3Index, csz) errC := C.uncompactCells( &cin[0], C.int64_t(len(in)), &cout[0], csz, C.int(resolution)) return cellsFromC(cout, false, true), toErr(errC) } // 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, error) { var out C.H3Index errC := C.childPosToCell(C.int64_t(position), C.H3Index(a), C.int(resolution), &out) return Cell(out), toErr(errC) } // 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, 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, error) { var out C.int64_t errC := C.cellToChildPos(C.H3Index(a), C.int(resolution), &out) return int(out), toErr(errC) } // 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, error) { return CellToChildPos(c, resolution) } func GridDistance(a, b Cell) (int, error) { var out C.int64_t errC := C.gridDistance(C.H3Index(a), C.H3Index(b), &out) return int(out), toErr(errC) } func (c Cell) GridDistance(other Cell) (int, error) { return GridDistance(c, other) } func GridPath(a, b Cell) ([]Cell, error) { var outsz C.int64_t if err := toErr(C.gridPathCellsSize(C.H3Index(a), C.H3Index(b), &outsz)); err != nil { return nil, err } out := make([]C.H3Index, outsz) if err := toErr(C.gridPathCells(C.H3Index(a), C.H3Index(b), &out[0])); err != nil { return nil, err } return cellsFromC(out, false, false), nil } func (c Cell) GridPath(other Cell) ([]Cell, error) { return GridPath(c, other) } func CellToLocalIJ(origin, cell Cell) (CoordIJ, error) { var out C.CoordIJ errC := C.cellToLocalIj(C.H3Index(origin), C.H3Index(cell), 0, &out) return CoordIJ{int(out.i), int(out.j)}, toErr(errC) } func LocalIJToCell(origin Cell, ij CoordIJ) (Cell, error) { var out C.H3Index errC := C.localIjToCell(C.H3Index(origin), ij.toCPtr(), 0, &out) return Cell(out), toErr(errC) } func CellToVertex(c Cell, vertexNum int) (Cell, error) { var out C.H3Index errC := C.cellToVertex(C.H3Index(c), C.int(vertexNum), &out) return Cell(out), toErr(errC) } func CellToVertexes(c Cell) ([]Cell, error) { out := make([]C.H3Index, numCellVertexes) if err := toErr(C.cellToVertexes(C.H3Index(c), &out[0])); err != nil { return nil, err } return cellsFromC(out, true, false), nil } func VertexToLatLng(vertex Cell) (LatLng, error) { var out C.LatLng errC := C.vertexToLatLng(C.H3Index(vertex), &out) return latLngFromC(out), toErr(errC) } func IsValidVertex(c Cell) bool { return C.isValidVertex(C.H3Index(c)) == 1 } func maxGridDiskSize(k int) int { return 3*k*(k+1) + 1 } func latLngFromC(cg C.LatLng) LatLng { g := LatLng{} g.Lat = RadsToDegs * float64(cg.lat) g.Lng = RadsToDegs * float64(cg.lng) return g } func cellBndryFromC(cb *C.CellBoundary) CellBoundary { g := make(CellBoundary, 0, MaxCellBndryVerts) for i := C.int(0); i < cb.numVerts; i++ { g = append(g, latLngFromC(cb.verts[i])) } return g } func ringSize(k int) int { if k == 0 { return 1 } return 6 * k //nolint:mnd // math formula } // Convert slice of LatLngs to an array of C LatLngs (represented in C-style as // a pointer to the first item in the array). The caller must free the returned // pointer when finished with it. func latLngsToC(coords []LatLng) *C.LatLng { if len(coords) == 0 { return nil } // Use malloc to construct a C-style struct array for the output cverts := C.malloc(C.size_t(C.sizeof_LatLng * len(coords))) pv := cverts for _, gc := range coords { *((*C.LatLng)(pv)) = *gc.toCPtr() pv = unsafe.Pointer(uintptr(pv) + C.sizeof_LatLng) } return (*C.LatLng)(cverts) } // Convert geofences (slices of slices of LatLnginates) to C geofences (represented in C-style as // a pointer to the first item in the array). The caller must free the returned pointer and any // pointer on the verts field when finished using it. func geoLoopsToC(geofences []GeoLoop) *C.GeoLoop { if len(geofences) == 0 { return nil } // Use malloc to construct a C-style struct array for the output cgeofences := C.malloc(C.size_t(C.sizeof_GeoLoop * len(geofences))) pcgeofences := cgeofences for _, coords := range geofences { cverts := latLngsToC(coords) *((*C.GeoLoop)(pcgeofences)) = C.GeoLoop{ verts: cverts, numVerts: C.int(len(coords)), } pcgeofences = unsafe.Pointer(uintptr(pcgeofences) + C.sizeof_GeoLoop) } return (*C.GeoLoop)(cgeofences) } // Convert GeoPolygon struct to C equivalent struct. func allocCGeoPolygon(gp GeoPolygon) C.GeoPolygon { cverts := latLngsToC(gp.GeoLoop) choles := geoLoopsToC(gp.Holes) return C.GeoPolygon{ geoloop: C.GeoLoop{ numVerts: C.int(len(gp.GeoLoop)), verts: cverts, }, numHoles: C.int(len(gp.Holes)), holes: choles, } } // Free pointer values on a C GeoPolygon struct func freeCGeoPolygon(cgp *C.GeoPolygon) { C.free(unsafe.Pointer(cgp.geoloop.verts)) cgp.geoloop.verts = nil ph := unsafe.Pointer(cgp.holes) for i := C.int(0); i < cgp.numHoles; i++ { C.free(unsafe.Pointer((*C.GeoLoop)(ph).verts)) (*C.GeoLoop)(ph).verts = nil ph = unsafe.Pointer(uintptr(ph) + uintptr(C.sizeof_GeoLoop)) } C.free(unsafe.Pointer(cgp.holes)) cgp.holes = nil } // https://stackoverflow.com/questions/64108933/how-to-use-math-pow-with-integers-in-golang func intPow(n, m int) int { if m == 0 { return 1 } result := n for i := 2; i <= m; i++ { result *= n } return result } func cellsFromC(chs []C.H3Index, prune, refit bool) []Cell { // OPT: This could be more efficient if we unsafely cast the C array to a // []H3Index. out := make([]Cell, 0, len(chs)) for i := range chs { if prune && chs[i] <= 0 { continue } out = append(out, Cell(chs[i])) } if refit { // Some algorithms require a maximum sized array, but only use a subset // of the memory. refit sizes the slice to the last non-empty element. for i := len(out) - 1; i >= 0; i-- { if out[i] == 0 { out = out[:i] } } } return out } func edgesFromC(chs []C.H3Index) []DirectedEdge { out := make([]DirectedEdge, 0, len(chs)) for i := range chs { if chs[i] <= 0 { continue } out = append(out, DirectedEdge(chs[i])) } return out } func cellsToC(chs []Cell) []C.H3Index { // OPT: This could be more efficient if we unsafely cast the array to a // []C.H3Index. out := make([]C.H3Index, len(chs)) for i := range chs { out[i] = C.H3Index(chs[i]) } return out } func intsFromC(chs []C.int) []int { out := make([]int, 0, len(chs)) for i := range chs { // C API returns a sparse array of indexes in the event pentagons and // deleted sequences are encountered. if chs[i] != -1 { out = append(out, int(chs[i])) } } return out } func (g LatLng) String() string { return fmt.Sprintf("(%.5f, %.5f)", g.Lat, g.Lng) } func (g LatLng) toCPtr() *C.LatLng { return &C.LatLng{ lat: C.double(DegsToRads * g.Lat), lng: C.double(DegsToRads * g.Lng), } } func (ij CoordIJ) toCPtr() *C.CoordIJ { return &C.CoordIJ{ i: C.int(ij.I), j: C.int(ij.J), } } func toErr(errC C.uint32_t) error { err, ok := errMap[errC] if ok { return err } return ErrUnknown }