diff --git a/.gitattributes b/.gitattributes index 4481e61..b679d47 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ # treat C source and header files as binary as they are script managed -*.c binary -*.h binary +*.c binary linguist-generated +*.h binary linguist-generated diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1364653..cd3f18b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,31 +2,45 @@ name: Build on: [push, pull_request] +permissions: + contents: read + jobs: - build: + golangci: + name: lint + runs-on: ubuntu-latest strategy: matrix: - go-version: ['1.11', '1.12', '1.13', '1.14', '1.15'] + go-version: ["1.18"] + + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.47.3 + test: + name: test runs-on: ubuntu-latest + strategy: + matrix: + go-version: ["1.18", "1.19"] steps: - - name: Setup Go ${{ matrix.go-version }} - uses: actions/setup-go@v2 - with: - go-version: ${{ matrix.go-version }} - - uses: actions/checkout@v2 - - name: go get - run: | - go get -t ./... - - name: Run Unit tests - run: | - go test -race -covermode atomic -coverprofile=covprofile ./... - - name: Install goveralls - #env: - #GO111MODULE: off - run: go get github.com/mattn/goveralls - - name: Send coverage - env: - COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: goveralls -coverprofile=covprofile -service=github + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - name: unit-tests + run: | + go test -count=2 -race -covermode atomic -coverprofile=covprofile ./... + - name: goveralls + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + go install github.com/mattn/goveralls@latest + goveralls -coverprofile=covprofile -service=github diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..f4b30d5 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,64 @@ +linters-settings: + errcheck: + check-type-assertions: true + goconst: + min-len: 2 + min-occurrences: 3 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + govet: + check-shadowing: true + nolintlint: + require-explanation: true + require-specific: true + wsl: + allow-assign-and-anything: true + allow-cuddle-declarations: true + allow-assign-and-call: true + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + - exportloopref + - exhaustive + - goconst + # - gocritic + - gofmt + - goimports + - gomnd + - gocyclo + - gosec + - gosimple + - govet + - ineffassign + - misspell + - nolintlint + - nakedret + - prealloc + - predeclared + - revive + - staticcheck + - structcheck + - stylecheck + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - varcheck + - whitespace + - wsl + +run: + issues-exit-code: 1 diff --git a/H3_VERSION b/H3_VERSION index c219f72..5eb1630 100644 --- a/H3_VERSION +++ b/H3_VERSION @@ -1 +1 @@ -v3.4.0 +v4.0.0-rc4 diff --git a/README.md b/README.md index 19618d8..303bff1 100644 --- a/README.md +++ b/README.md @@ -11,93 +11,130 @@ # H3-Go This library provides Golang bindings for the -[H3 Core Library](https://github.com/uber/h3). For API reference, please see the +[H3 Core Library](https://github.com/uber/h3). For API reference, see the [H3 Documentation](https://uber.github.io/h3/). -# Usage - -## Prerequisites - -H3-Go requires [CGO](https://golang.org/cmd/cgo/) (`CGO_ENABLED=1`) in order to be built. Generally, Go should do the right thing when including this library: - -> The cgo tool is enabled by default for native builds on systems where it is expected to work. It is disabled by default when cross-compiling. You can control this by setting the CGO_ENABLED environment variable when running the go tool: set it to 1 to enable the use of cgo, and to 0 to disable it. The go tool will set the build constraint "cgo" if cgo is enabled. The special import "C" implies the "cgo" build constraint, as though the file also said "// +build cgo". Therefore, if cgo is disabled, files that import "C" will not be built by the go tool. (For more about build constraints see https://golang.org/pkg/go/build/#hdr-Build_Constraints). +**This is v4 of this library, supporting H3 v4.** -If you see errors/warnings like _"build constraints exclude all Go files..."_, then the `cgo` build constraint is likely disabled; try setting `CGO_ENABLED=1` environment variable for your build step. +Check out [v3](https://github.com/uber/h3-go/tree/v3.7.1) or checkout the git tag for the version you need. -## Installation +**Migrating from v3?** -### [golang/dep](https://github.com/golang/dep) +Check out [v3 to v4 migration guide](https://h3geo.org/docs/next/library/migrating-3.x/). +There have been no breaking changes to the format of H3 indexes. Indexes +generated by older versions can be parsed in v4, and vice-versa. -```bash -dep ensure -add github.com/uber/h3-go -``` +# Usage -Note: h3-go includes non-go directories that, by default, `dep` will -[prune](https://golang.github.io/dep/docs/Gopkg.toml.html#prune). You can -prevent this by including the following prune directive in your `Gopkg.toml`: +## Prerequisites -```toml -[prune] - [[prune.project]] - name = "github.com/uber/h3-go" - non-go = false - unused-packages = false -``` +H3-Go requires [CGO](https://golang.org/cmd/cgo/) (`CGO_ENABLED=1`) in order to +be built. Go should do the right thing when including this library: -### [golang/cmd/go](https://golang.org/cmd/go/) +> The cgo tool is enabled by default for native builds on systems where it is +> expected to work. It is disabled by default when cross-compiling. You can +> control this by setting the CGO_ENABLED environment variable when running the go +> tool: set it to 1 to enable the use of cgo, and to 0 to disable it. The go tool +> will set the build constraint "cgo" if cgo is enabled. The special import "C" +> implies the "cgo" build constraint, as though the file also said "// +build +> cgo". Therefore, if cgo is disabled, files that import "C" will not be built by +> the go tool. (For more about build constraints see +> https://golang.org/pkg/go/build/#hdr-Build_Constraints). -```bash -go get github.com/uber/h3-go -``` +If you see errors/warnings like _"build constraints exclude all Go files..."_, +then the `cgo` build constraint is likely disabled; try setting `CGO_ENABLED=1` +environment variable in your `go build` step. -### [Glide](https://github.com/Masterminds/glide) +## Installation ```bash -glide install github.com/uber/h3-go +go get github.com/uber/h3-go/v4 ``` ## Quickstart ```go -import "github.com/uber/h3-go/v3" - -func ExampleFromGeo() { - geo := h3.GeoCoord{ - Latitude: 37.775938728915946, - Longitude: -122.41795063018799, - } - resolution := 9 - fmt.Printf("%#x\n", h3.FromGeo(geo, resolution)) - // Output: - // 0x8928308280fffff -} -``` +import "github.com/uber/h3-go/v4" -# Notes +func ExampleLatLngToCell() { + latLng := h3.NewLatLng(37.775938728915946, -122.41795063018799) + resolution := 9 // between 0 (biggest cell) and 15 (smallest cell) -## API Differences + cell := h3.LatLngToCell(latLng, resolution) -* All `GeoCoord` structs return `Latitude` and `Longitude` as degrees, instead - of radians. + fmt.Printf("%s", cell) + // Output: + // 8928308280fffff +} -Some superficial changes have been made relative to the H3 C core API in order -to adhere to idiomatic Go styling. Most notable are the following: +``` -* H3 C API function prefixes of `H3` have been dropped to reduce stutter in - usage, e.g. `h3.ToGeo(h)`. -* H3 C functions that convert **to** `H3Index` have their names inverted to - convert **from** something else to `H3Index`, e.g. `GeoToH3` is renamed to - `h3.FromGeo`. -* H3 C API function prefixes of `Get` have been dropped in support of Golang's - `Getter` [naming style](https://golang.org/doc/effective_go.html#Getters). +# C API + +## Notes +* `LatLng` returns `Lat` and `Lng` as degrees, instead of radians. +* H3 C API function prefixes of `get` have been dropped in support of Golang's + `Getter` [naming style](https://golang.org/doc/effective_go.html#Getters). +* Convenience methods have been added to various types where that type was the + main or only argument. + +## Bindings + +| C API | Go API | +| ---------------------------- | -------------------------------------------------- | +| `latLngToCell` | `LatLngToCell`, `LatLng#Cell` | +| `cellToLatLng` | `CellToLatLng`, `Cell#LatLng` | +| `cellToBoundary` | `CellToBoundary`, `Cell#Boundary` | +| `gridDisk` | `GridDisk`, `Cell#GridDisk` | +| `gridDiskDistances` | `GridDiskDistances`, `Cell#GridDiskDistances` | +| `gridRingUnsafe` | N/A | +| `polygonToCells` | `PolygonToCells`, `GeoPolygon#Cells` | +| `cellsToMultiPolygon` | TODO | +| `degsToRads` | `DegsToRads` | +| `radsToDegs` | `RadsToDegs` | +| `greatCircleDistance` | `GreatCircleDistance* (3/3)` | +| `getHexagonAreaAvg` | `HexagonAreaAvg* (3/3)` | +| `cellArea` | `CellArea* (3/3)` | +| `getHexagonEdgeLengthAvg` | `HexagonEdgeLengthAvg* (2/2)` | +| `exactEdgeLength` | `ExactEdgeLength* (3/3)` | +| `getNumCells` | `NumCells` | +| `getRes0Cells` | `Res0Cells` | +| `getPentagons` | `Pentagons` | +| `getResolution` | `Resolution` | +| `getBaseCellNumber` | `BaseCellNumber`, `Cell#BaseCellNumber` | +| `stringToH3` | `IndexFromString`, `Cell#UnmarshalText` | +| `h3ToString` | `IndexToString`, `Cell#String`, `Cell#MarshalText` | +| `isValidCell` | `Cell#IsValid` | +| `cellToParent` | `Cell#Parent`, `Cell#ImmediateParent` | +| `cellToChildren` | `Cell#Children` `Cell#ImmediateChildren` | +| `cellToCenterChild` | `Cell#CenterChild` | +| `compactCells` | `CompactCells` | +| `uncompactCells` | `UncompactCells` | +| `isResClassIII` | `Cell#IsResClassIII` | +| `isPentagon` | `Cell#IsPentagon` | +| `getIcosahedronFaces` | `Cell#IcosahedronFaces` | +| `areNeighborCells` | `Cell#IsNeighbor` | +| `cellsToDirectedEdge` | `Cell#DirectedEdge` | +| `isValidDirectedEdge` | `DirectedEdge#IsValid` | +| `getDirectedEdgeOrigin` | `DirectedEdge#Origin` | +| `getDirectedEdgeDestination` | `DirectedEdge#Destination` | +| `directedEdgeToCells` | `DirectedEdge#Cells` | +| `originToDirectedEdges` | `Cell#DirectedEdges` | +| `directedEdgeToBoundary` | `DirectedEdge#Boundary` | +| `cellToVertex` | TODO | +| `cellToVertexes` | TODO | +| `vertexToLatLng` | TODO | +| `isValidVertex` | TODO | +| `gridDistance` | `GridDistance`, `Cell#GridDistance` | +| `gridPathCells` | `GridPath`, `Cell#GridPath` | +| `cellToLocalIj` | `CellToLocalIJ` | +| `localIjToCell` | `LocalIJToCell` | ## CGO The H3 C source code and header files are copied into this project to optimize -for portability. By including the C source files in the `h3` Go package, there -is no need to introduce a build process or a system dependency on an H3 binary. -Effectively, this decision makes `h3` as easy to use in a Go project as adding -it as a dependency with your favorite dependency manager. +for portability. `h3-go` can be imported into any Go project for any platform +that CGO supports. # Contributing diff --git a/bench_test.go b/bench_test.go index 581c10a..cb6b0d3 100644 --- a/bench_test.go +++ b/bench_test.go @@ -4,80 +4,56 @@ import ( "testing" ) -// buckets for preventing compiler optimizing out calls +// buckets for preventing compiler optimizing out calls. var ( - geo = GeoCoord{ - Latitude: 37, - Longitude: -122, - } - h3idx = FromGeo(geo, 15) - h3addr = ToString(h3idx) - geoBndry GeoBoundary - h3idxs []H3Index + geo = LatLng{ + Lat: 37, + Lng: -122, + } + cell = LatLngToCell(geo, 15) + addr = cell.String() + geoBndry CellBoundary + cells []Cell ) func BenchmarkToString(b *testing.B) { for n := 0; n < b.N; n++ { - h3addr = ToString(h3idx) + addr = cell.String() } } func BenchmarkFromString(b *testing.B) { for n := 0; n < b.N; n++ { - h3idx = FromString("850dab63fffffff") + cell = Cell(IndexFromString("850dab63fffffff")) } } func BenchmarkToGeoRes15(b *testing.B) { for n := 0; n < b.N; n++ { - geo = ToGeo(h3idx) + geo = CellToLatLng(cell) } } func BenchmarkFromGeoRes15(b *testing.B) { for n := 0; n < b.N; n++ { - h3idx = FromGeo(geo, 15) + cell = LatLngToCell(geo, 15) } } func BenchmarkToGeoBndryRes15(b *testing.B) { for n := 0; n < b.N; n++ { - geoBndry = ToGeoBoundary(h3idx) + geoBndry = CellToBoundary(cell) } } func BenchmarkHexRange(b *testing.B) { for n := 0; n < b.N; n++ { - h3idxs, _ = HexRange(h3idx, 10) + cells = cell.GridDisk(10) } } func BenchmarkPolyfill(b *testing.B) { for n := 0; n < b.N; n++ { - h3idxs = Polyfill(validGeopolygonWithHoles, 6) - } -} - -var ( - hexes [][]H3Index - hexRangesCenter = H3Index(0x8928308280fffff) - hexRangeK = 5 -) - -func BenchmarkHexRangesNative(b *testing.B) { - group := KRing(hexRangesCenter, hexRangeK) - for n := 0; n < b.N; n++ { - hexes = make([][]H3Index, len(group)) - for i, originHex := range group { - out, _ := HexRange(originHex, hexRangeK) - hexes[i] = out - } - } -} - -func BenchmarkHexRangesC(b *testing.B) { - group := KRing(hexRangesCenter, hexRangeK) - for n := 0; n < b.N; n++ { - hexes, _ = HexRanges(group, hexRangeK) + cells = PolygonToCells(validGeoPolygonHoles, 15) } } diff --git a/example_test.go b/example_test.go index ec8e1ab..5450d3c 100644 --- a/example_test.go +++ b/example_test.go @@ -1,16 +1,16 @@ -package h3 +package h3_test import ( "fmt" + + "github.com/uber/h3-go/v4" ) -func ExampleFromGeo() { - geo := GeoCoord{ - Latitude: 37.775938728915946, - Longitude: -122.41795063018799, - } +func ExampleLatLngToCell() { + latLng := h3.NewLatLng(37.775938728915946, -122.41795063018799) resolution := 9 - fmt.Printf("%#x\n", FromGeo(geo, resolution)) + c := h3.LatLngToCell(latLng, resolution) + fmt.Printf("%s", c) // Output: - // 0x8928308280fffff + // 8928308280fffff } diff --git a/go.mod b/go.mod index 0b6634c..2dd6f9d 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,3 @@ -module github.com/uber/h3-go +module github.com/uber/h3-go/v4 -go 1.13 - -require github.com/stretchr/testify v1.4.0 +go 1.18 diff --git a/go.sum b/go.sum index 8fdee58..e69de29 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +0,0 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/h3.go b/h3.go index d7c97f4..81bf962 100644 --- a/h3.go +++ b/h3.go @@ -1,3 +1,7 @@ +// 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. * @@ -14,14 +18,9 @@ * limitations under the License. */ -// 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 - /* #cgo CFLAGS: -std=c99 #cgo CFLAGS: -DH3_HAVE_VLA=1 -#cgo CFLAGS: -I ${SRCDIR} #cgo LDFLAGS: -lm #include #include @@ -30,8 +29,10 @@ package h3 import "C" import ( "errors" + "fmt" "math" "strconv" + "strings" "unsafe" ) @@ -40,7 +41,7 @@ const ( // to represent the shape of a cell. MaxCellBndryVerts = C.MAX_CELL_BNDRY_VERTS - // MaxResolution is the maximum H3 resolution a GeoCoord can be indexed to. + // MaxResolution is the maximum H3 resolution a LatLng can be indexed to. MaxResolution = C.MAX_H3_RES // The number of faces on an icosahedron @@ -49,450 +50,682 @@ const ( // 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_INVALID_INDEX + InvalidH3Index = C.H3_NULL + + base16 = 16 + bitSize = 64 + + numCellEdges = 6 + numEdgeCells = 2 + + DegsToRads = math.Pi / 180.0 + RadsToDegs = 180.0 / math.Pi ) -var ( - // ErrPentagonEncountered is returned by functions that encounter a pentagon - // and cannot handle it. - ErrPentagonEncountered = errors.New("pentagon encountered") +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 + } - // ErrInvalidResolution is returned when the requested resolution is not valid - ErrInvalidResolution = errors.New("resolution invalid") + // CellBoundary is a slice of LatLng. Note, len(CellBoundary) will never + // exceed MaxCellBndryVerts. + CellBoundary []LatLng - // conversion units for faster maths - deg2rad = math.Pi / 180.0 - rad2deg = 180.0 / math.Pi + // 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 + } + + // LinkedGeoPolygon is a linked-list of GeoPolygons. + // TODO: not implemented. + LinkedGeoPolygon struct{} ) -// H3Index is a type alias for the C type `H3Index`. Effectively H3Index is a -// `uint64`. -type H3Index = C.H3Index +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 { + var i C.H3Index + + C.latLngToCell(latLng.toCPtr(), C.int(resolution), &i) -// GeoBoundary is a slice of `GeoCoord`. Note, `len(GeoBoundary)` will never -// exceed `MaxCellBndryVerts`. -type GeoBoundary []GeoCoord + return Cell(i) +} + +// Cell returns the Cell at resolution for a geographic coordinate. +func (g LatLng) Cell(resolution int) Cell { + return LatLngToCell(g, resolution) +} + +// CellToLatLng returns the geographic centerpoint of a Cell. +func CellToLatLng(c Cell) LatLng { + var g C.LatLng + + C.cellToLatLng(C.H3Index(c), &g) + + return latLngFromC(g) +} -// GeoCoord is a struct for geographic coordinates. -type GeoCoord struct { - Latitude, Longitude float64 +// LatLng returns the Cell at resolution for a geographic coordinate. +func (c Cell) LatLng() LatLng { + return CellToLatLng(c) } -func (g GeoCoord) toCPtr() *C.GeoCoord { - return &C.GeoCoord{ - lat: C.double(deg2rad * g.Latitude), - lon: C.double(deg2rad * g.Longitude), +// CellToBoundary returns a CellBoundary of the Cell. +func CellToBoundary(c Cell) CellBoundary { + var cb C.CellBoundary + + C.cellToBoundary(C.H3Index(c), &cb) + + return cellBndryFromC(&cb) +} + +// Boundary returns a CellBoundary of the Cell. +func (c Cell) Boundary() CellBoundary { + 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 { + out := make([]C.H3Index, maxGridDiskSize(k)) + C.gridDisk(C.H3Index(origin), C.int(k), &out[0]) + // QUESTION: should we prune zeroes from the output? + return cellsFromC(out, true, false) +} + +// 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 { + 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 { + 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]) + + 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 } -func (g GeoCoord) toC() C.GeoCoord { - return *g.toCPtr() +// 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 { + return GridDiskDistances(c, k) } -// GeoPolygon is a geofence with 0 or more geofence holes -type GeoPolygon struct { - // Geofence is the exterior boundary of the polygon - Geofence []GeoCoord +// 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 { + if len(polygon.GeoLoop) == 0 { + return nil + } + cpoly := allocCGeoPolygon(polygon) - // Holes is a slice of interior boundary (holes) in the polygon - Holes [][]GeoCoord + defer freeCGeoPolygon(&cpoly) + + maxLen := new(C.int64_t) + C.maxPolygonToCellsSize(&cpoly, C.int(resolution), 0, maxLen) + + out := make([]C.H3Index, *maxLen) + C.polygonToCells(&cpoly, C.int(resolution), 0, &out[0]) + + return cellsFromC(out, true, false) } -// --- INDEXING --- +// PolygonToCells takes a given GeoJSON-like data structure fills it with the +// hexagon cells that are contained by the GeoJSON-like data structure. // -// This section defines bindings for H3 indexing functions. -// Additional documentation available at -// https://uber.github.io/h3/#/documentation/api-reference/indexing +// 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 { + return PolygonToCells(p, resolution) +} -// FromGeo returns the H3Index at resolution `res` for a geographic coordinate. -func FromGeo(geoCoord GeoCoord, res int) H3Index { - return H3Index(C.geoToH3(geoCoord.toCPtr(), C.int(res))) +func CellsToMultiPolygon(cells []Cell) *LinkedGeoPolygon { + panic("not implemented") } -// ToGeo returns the geographic centerpoint of an H3Index `h`. -func ToGeo(h H3Index) GeoCoord { - g := C.GeoCoord{} - C.h3ToGeo(h, &g) - return geoCoordFromC(g) +// 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())) } -// ToGeoBoundary returns a `GeoBoundary` of the H3Index `h`. -func ToGeoBoundary(h H3Index) GeoBoundary { - gb := new(C.GeoBoundary) - C.h3ToGeoBoundary(h, gb) - return geoBndryFromC(gb) +// 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())) } -// --- INSPECTION --- -// This section defines bindings for H3 inspection functions. -// Additional documentation available at -// https://uber.github.io/h3/#/documentation/api-reference/inspection +// 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 { + var out C.double -// Resolution returns the resolution of `h`. -func Resolution(h H3Index) int { - return int(C.h3GetResolution(h)) + C.getHexagonAreaAvgKm2(C.int(resolution), &out) + + return float64(out) } -// BaseCell returns the integer ID of the base cell the H3Index `h` belongs to. -func BaseCell(h H3Index) int { - return int(C.h3GetBaseCell(h)) +// HexAreaM2 returns the average hexagon area in square meters at the given +// resolution. +func HexagonAreaAvgM2(resolution int) float64 { + var out C.double + + C.getHexagonAreaAvgM2(C.int(resolution), &out) + + return float64(out) } -// FromString returns an H3Index parsed from a string. -func FromString(hStr string) H3Index { - h, err := strconv.ParseUint(hStr, 16, 64) - if err != nil { - return 0 - } - return H3Index(h) +// CellAreaRads2 returns the exact area of specific cell in square radians. +func CellAreaRads2(c Cell) float64 { + var out C.double + + C.cellAreaRads2(C.H3Index(c), &out) + + return float64(out) } -// ToString returns a string representation of an H3Index. -func ToString(h H3Index) string { - return strconv.FormatUint(uint64(h), 16) +// CellAreaKm2 returns the exact area of specific cell in square kilometers. +func CellAreaKm2(c Cell) float64 { + var out C.double + + C.cellAreaKm2(C.H3Index(c), &out) + + return float64(out) } -// IsValid returns true if `h` is valid. -func IsValid(h H3Index) bool { - return C.h3IsValid(h) == 1 +// CellAreaM2 returns the exact area of specific cell in square meters. +func CellAreaM2(c Cell) float64 { + var out C.double + + C.cellAreaM2(C.H3Index(c), &out) + + return float64(out) } -// IsResClassIII returns true if `h` is a class III index. If false, `h` is a -// class II index. -func IsResClassIII(h H3Index) bool { - return C.h3IsResClassIII(h) == 1 +// HexagonEdgeLengthAvgKm returns the average hexagon edge length in kilometers +// at the given resolution. +func HexagonEdgeLengthAvgKm(resolution int) float64 { + var out C.double + + C.getHexagonEdgeLengthAvgKm(C.int(resolution), &out) + + return float64(out) } -// IsPentagon returns true if `h` is a pentagon. -func IsPentagon(h H3Index) bool { - return C.h3IsPentagon(h) == 1 +// HexagonEdgeLengthAvgM returns the average hexagon edge length in meters at +// the given resolution. +func HexagonEdgeLengthAvgM(resolution int) float64 { + var out C.double + + C.getHexagonEdgeLengthAvgM(C.int(resolution), &out) + + return float64(out) } -// --- NEIGHBORS --- -// This section defines bindings for H3 neighbor traversal functions. -// Additional documentation available at -// https://uber.github.io/h3/#/documentation/api-reference/neighbors +// ExactEdgeLengthRads returns the exact edge length of specific unidirectional +// edge in radians. +func ExactEdgeLengthRads(e DirectedEdge) float64 { + var out C.double -// KRing implements the C function `kRing`. -func KRing(origin H3Index, k int) []H3Index { - out := make([]C.H3Index, rangeSize(k)) - C.kRing(origin, C.int(k), &out[0]) - return h3SliceFromC(out) + C.exactEdgeLengthRads(C.H3Index(e), &out) + + return float64(out) } -// KRingDistances implements the C function `kRingDistances`. -func KRingDistances(origin H3Index, k int) [][]H3Index { - rsz := rangeSize(k) - outHexes := make([]C.H3Index, rsz) - outDists := make([]C.int, rsz) - C.kRingDistances(origin, C.int(k), &outHexes[0], &outDists[0]) +// ExactEdgeLengthKm returns the exact edge length of specific unidirectional +// edge in kilometers. +func ExactEdgeLengthKm(e DirectedEdge) float64 { + var out C.double - ret := make([][]H3Index, k+1) - for i := 0; i <= k; i++ { - ret[i] = make([]H3Index, 0, ringSize(i)) - } + C.exactEdgeLengthKm(C.H3Index(e), &out) - for i, d := range outDists { - ret[d] = append(ret[d], H3Index(outHexes[i])) - } - return ret + return float64(out) } -// HexRange implements the C function `hexRange`. -func HexRange(origin H3Index, k int) ([]H3Index, error) { - out := make([]C.H3Index, rangeSize(k)) - if rv := C.hexRange(origin, C.int(k), &out[0]); rv != 0 { - return nil, ErrPentagonEncountered - } - return h3SliceFromC(out), nil +// ExactEdgeLengthM returns the exact edge length of specific unidirectional +// edge in meters. +func ExactEdgeLengthM(e DirectedEdge) float64 { + var out C.double + + C.exactEdgeLengthM(C.H3Index(e), &out) + + return float64(out) } -// HexRangeDistances implements the C function `hexRangeDistances`. -func HexRangeDistances(origin H3Index, k int) ([][]H3Index, error) { - rsz := rangeSize(k) - outHexes := make([]C.H3Index, rsz) - outDists := make([]C.int, rsz) - rv := C.hexRangeDistances(origin, C.int(k), &outHexes[0], &outDists[0]) - if rv != 0 { - return nil, ErrPentagonEncountered - } +// 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:gomnd // math formula +} - ret := make([][]H3Index, k+1) - for i := 0; i <= k; i++ { - ret[i] = make([]H3Index, 0, ringSize(i)) - } +// Res0Cells returns all the cells at resolution 0. +func Res0Cells() []Cell { + out := make([]C.H3Index, C.res0CellCount()) + C.getRes0Cells(&out[0]) - for i, d := range outDists { - ret[d] = append(ret[d], H3Index(outHexes[i])) - } - return ret, nil + return cellsFromC(out, false, false) } -// HexRanges implements the C function `hexRanges`. -func HexRanges(origins []H3Index, k int) ([][]H3Index, error) { - rsz := rangeSize(k) - outHexes := make([]C.H3Index, rsz*len(origins)) - inHexes := h3SliceToC(origins) - rv := C.hexRanges(&inHexes[0], C.int(len(origins)), C.int(k), &outHexes[0]) - if rv != 0 { - return nil, ErrPentagonEncountered - } +// Pentagons returns all the pentagons at resolution. +func Pentagons(resolution int) []Cell { + out := make([]C.H3Index, NumPentagons) + C.getPentagons(C.int(resolution), &out[0]) - ret := make([][]H3Index, len(origins)) - for i := 0; i < len(origins); i++ { - ret[i] = make([]H3Index, rsz) - for j := 0; j < rsz; j++ { - ret[i][j] = H3Index(outHexes[i*rsz+j]) - } + return cellsFromC(out, false, false) +} + +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:] } - return ret, nil + c, _ := strconv.ParseUint(s, base16, bitSize) + + return c } -// HexRing implements the C function `hexRing`. -func HexRing(origin H3Index, k int) ([]H3Index, error) { - out := make([]C.H3Index, ringSize(k)) - if rv := C.hexRing(origin, C.int(k), &out[0]); rv != 0 { - return nil, ErrPentagonEncountered +// 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 h3SliceFromC(out), nil + + return nil } -// AreNeighbors returns true if `h1` and `h2` are neighbors. Two -// indexes are neighbors if they share an edge. -func AreNeighbors(h1, h2 H3Index) bool { - return C.h3IndexesAreNeighbors(h1, h2) == 1 +// 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 } -// --- HIERARCHY --- -// This section defines bindings for H3 hierarchical functions. -// Additional documentation available at -// https://uber.github.io/h3/#/documentation/api-reference/hierarchy +// Parent returns the parent or grandparent Cell of this Cell. +func (c Cell) Parent(resolution int) Cell { + var out C.H3Index -// ToParent returns the `H3Index` of the cell that contains `child` at -// resolution `parentRes`. `parentRes` must be less than the resolution of -// `child`. -func ToParent(child H3Index, parentRes int) (parent H3Index) { - return H3Index(C.h3ToParent(C.H3Index(child), C.int(parentRes))) + C.cellToParent(C.H3Index(c), C.int(resolution), &out) + + return Cell(out) +} + +// Parent returns the parent or grandparent Cell of this Cell. +func (c Cell) ImmediateParent() Cell { + return c.Parent(c.Resolution() - 1) +} + +// Children returns the children or grandchildren cells of this Cell. +func (c Cell) Children(resolution int) []Cell { + var outsz C.int64_t + + C.cellToChildrenSize(C.H3Index(c), C.int(resolution), &outsz) + out := make([]C.H3Index, outsz) + + C.cellToChildren(C.H3Index(c), C.int(resolution), &out[0]) + + return cellsFromC(out, false, false) } -// ToChildren returns all the `H3Index`es of `parent` at resolution `childRes`. -// `childRes` must be larger than the resolution of `parent`. -func ToChildren(parent H3Index, childRes int) []H3Index { - p := C.H3Index(parent) - csz := C.int(childRes) - out := make([]C.H3Index, int(C.maxH3ToChildrenSize(p, csz))) - C.h3ToChildren(p, csz, &out[0]) - return h3SliceFromC(out) +// ImmediateChildren returns the children or grandchildren cells of this Cell. +func (c Cell) ImmediateChildren() []Cell { + return c.Children(c.Resolution() + 1) +} + +// CenterChild returns the center child Cell of this Cell. +func (c Cell) CenterChild(resolution int) Cell { + var out C.H3Index + + C.cellToCenterChild(C.H3Index(c), C.int(resolution), &out) + + return Cell(out) +} + +// 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 { + var outsz C.int + + C.maxFaceCount(C.H3Index(c), &outsz) + out := make([]C.int, outsz) + + C.getIcosahedronFaces(C.H3Index(c), &out[0]) + + return intsFromC(out) +} + +// IsNeighbor returns true if this Cell is a neighbor of the other Cell. +func (c Cell) IsNeighbor(other Cell) bool { + var out C.int + C.areNeighborCells(C.H3Index(c), C.H3Index(other), &out) + + return out == 1 +} + +// DirectedEdge returns a DirectedEdge from this Cell to other. +func (c Cell) DirectedEdge(other Cell) DirectedEdge { + var out C.H3Index + C.cellsToDirectedEdge(C.H3Index(c), C.H3Index(other), &out) + + return DirectedEdge(out) +} + +// DirectedEdges returns 6 directed edges with h as the origin. +func (c Cell) DirectedEdges() []DirectedEdge { + out := make([]C.H3Index, numCellEdges) // always 6 directed edges + C.originToDirectedEdges(C.H3Index(c), &out[0]) + + return edgesFromC(out) } -// Compact merges full sets of children into their parent `H3Index` +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 { + var out C.H3Index + C.getDirectedEdgeOrigin(C.H3Index(e), &out) + + return Cell(out) +} + +// Destination returns the destination cell of this directed edge. +func (e DirectedEdge) Destination() Cell { + var out C.H3Index + C.getDirectedEdgeDestination(C.H3Index(e), &out) + + return Cell(out) +} + +// Cells returns the origin and destination cells in that order. +func (e DirectedEdge) Cells() []Cell { + out := make([]C.H3Index, numEdgeCells) + C.directedEdgeToCells(C.H3Index(e), &out[0]) + + return cellsFromC(out, false, false) +} + +// 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 { + var out C.CellBoundary + C.directedEdgeToBoundary(C.H3Index(e), &out) + + return cellBndryFromC(&out) +} + +// CompactCells merges full sets of children into their parent H3Index // recursively, until no more merges are possible. -func Compact(in []H3Index) []H3Index { - cin := h3SliceToC(in) - csz := C.int(len(in)) +func CompactCells(in []Cell) []Cell { + 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.compact(&cin[0], &cout[0], csz) - return h3SliceFromC(cout) -} - -// Uncompact splits every `H3Index` in `in` if its resolution is greater than -// `res` recursively. Returns all the `H3Index`es at resolution `res`. -func Uncompact(in []H3Index, res int) ([]H3Index, error) { - cin := h3SliceToC(in) - maxUncompactSz := C.maxUncompactSize(&cin[0], C.int(len(in)), C.int(res)) - if maxUncompactSz < 0 { - // A size of less than zero indicates an error uncompacting such as the - // requested resolution being less than the resolution of the hexagons. - return nil, ErrInvalidResolution - } - cout := make([]C.H3Index, maxUncompactSz) - C.uncompact( - &cin[0], C.int(len(in)), - &cout[0], maxUncompactSz, - C.int(res)) - return h3SliceFromC(cout), nil -} + C.compactCells(&cin[0], &cout[0], csz) -// --- REGIONS --- + return cellsFromC(cout, false, true) +} -// Polyfill returns the hexagons at the given resolution whose centers are within the -// geofences given in the GeoPolygon struct. -func Polyfill(gp GeoPolygon, res int) []H3Index { - cgp := geoPolygonToC(gp) - defer freeCGeoPolygon(&cgp) +// 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 { + cin := cellsToC(in) + var csz C.int64_t + C.uncompactCellsSize(&cin[0], C.int64_t(len(cin)), C.int(resolution), &csz) - maxSize := C.maxPolyfillSize(&cgp, C.int(res)) - cout := make([]C.H3Index, maxSize) - C.polyfill(&cgp, C.int(res), &cout[0]) + cout := make([]C.H3Index, csz) + C.uncompactCells( + &cin[0], C.int64_t(len(in)), + &cout[0], csz, + C.int(resolution)) - return h3SliceFromC(cout) + return cellsFromC(cout, false, true) } -// --- UNIDIRECTIONAL EDGE FUNCTIONS --- +func GridDistance(a, b Cell) int { + var out C.int64_t + C.gridDistance(C.H3Index(a), C.H3Index(b), &out) -// UnidirectionalEdge returns a unidirectional `H3Index` from `origin` to -// `destination`. -func UnidirectionalEdge(origin, destination H3Index) H3Index { - return H3Index(C.getH3UnidirectionalEdge(origin, destination)) + return int(out) } -// UnidirectionalEdgeIsValid returns true if `edge` is a valid unidirectional -// edge index. -func UnidirectionalEdgeIsValid(edge H3Index) bool { - return C.h3UnidirectionalEdgeIsValid(edge) == 1 +func (c Cell) GridDistance(other Cell) int { + return GridDistance(c, other) } -// OriginFromUnidirectionalEdge returns the origin of a unidirectional -// edge. -func OriginFromUnidirectionalEdge(edge H3Index) H3Index { - return H3Index(C.getOriginH3IndexFromUnidirectionalEdge(edge)) -} +func GridPath(a, b Cell) []Cell { + var outsz C.int64_t + C.gridPathCellsSize(C.H3Index(a), C.H3Index(b), &outsz) -// DestinationFromUnidirectionalEdge returns the destination of a -// unidirectional edge. -func DestinationFromUnidirectionalEdge(edge H3Index) H3Index { - return H3Index(C.getDestinationH3IndexFromUnidirectionalEdge(edge)) -} + out := make([]C.H3Index, outsz) + C.gridPathCells(C.H3Index(a), C.H3Index(b), &out[0]) -// FromUnidirectionalEdge returns the origin and destination from a -// unidirectional edge. -func FromUnidirectionalEdge( - edge H3Index, -) (origin, destination H3Index) { - cout := make([]C.H3Index, 2) - C.getH3IndexesFromUnidirectionalEdge(edge, &cout[0]) - origin = H3Index(cout[0]) - destination = H3Index(cout[1]) - return + return cellsFromC(out, false, false) } -// ToUnidirectionalEdges returns the six (or five if pentagon) unidirectional -// edges from `h` to each of `h`'s neighbors. -func ToUnidirectionalEdges(h H3Index) []H3Index { - // allocating max size, `h3SliceFromC` will adjust cap - cout := make([]C.H3Index, 6) - C.getH3UnidirectionalEdgesFromHexagon(h, &cout[0]) - return h3SliceFromC(cout) +func (c Cell) GridPath(other Cell) []Cell { + return GridPath(c, other) } -// UnidirectionalEdgeBoundary returns the geocoordinates of a unidirectional -// edge boundary. -func UnidirectionalEdgeBoundary(edge H3Index) GeoBoundary { - gb := new(C.GeoBoundary) - C.getH3UnidirectionalEdgeBoundary(edge, gb) - return geoBndryFromC(gb) +func CellToLocalIJ(origin, cell Cell) CoordIJ { + var out C.CoordIJ + C.cellToLocalIj(C.H3Index(origin), C.H3Index(cell), 0, &out) + + return CoordIJ{int(out.i), int(out.j)} } -// Line returns the line of h3 indexes connecting two indexes -func Line(start, end H3Index) []H3Index { - n := C.h3LineSize(start, end) - cout := make([]C.H3Index, n) - C.h3Line(start, end, &cout[0]) - return h3SliceFromC(cout) +func LocalIJToCell(origin Cell, ij CoordIJ) Cell { + var out C.H3Index + C.localIjToCell(C.H3Index(origin), ij.toCPtr(), 0, &out) + + return Cell(out) } -func geoCoordFromC(cg C.GeoCoord) GeoCoord { - g := GeoCoord{} - g.Latitude = rad2deg * float64(cg.lat) - g.Longitude = rad2deg * float64(cg.lon) - return g +func maxGridDiskSize(k int) int { + return 3*k*(k+1) + 1 } -func geoBndryFromC(cb *C.GeoBoundary) GeoBoundary { - g := make(GeoBoundary, 0, MaxCellBndryVerts) - for i := C.int(0); i < cb.numVerts; i++ { - g = append(g, geoCoordFromC(cb.verts[i])) - } +func latLngFromC(cg C.LatLng) LatLng { + g := LatLng{} + g.Lat = RadsToDegs * float64(cg.lat) + g.Lng = RadsToDegs * float64(cg.lng) + return g } -func h3SliceFromC(chs []C.H3Index) []H3Index { - out := make([]H3Index, 0, len(chs)) - for _, ch := range chs { - // C API returns a sparse array of indexes in the event pentagons and - // deleted sequences are encountered. - if ch == InvalidH3Index { - continue - } - out = append(out, H3Index(ch)) +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 out -} -func h3SliceToC(hs []H3Index) []C.H3Index { - out := make([]C.H3Index, len(hs)) - for i, h := range hs { - out[i] = h - } - return out + return g } func ringSize(k int) int { if k == 0 { return 1 } - return 6 * k -} -func rangeSize(k int) int { - return int(C.maxKringSize(C.int(k))) + return 6 * k //nolint:gomnd // math formula } -// Convert slice of geocoordinates to an array of C geocoordinates (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 geoCoordsToC(coords []GeoCoord) *C.GeoCoord { +// 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_GeoCoord * len(coords))) + cverts := C.malloc(C.size_t(C.sizeof_LatLng * len(coords))) pv := cverts + for _, gc := range coords { - *((*C.GeoCoord)(pv)) = gc.toC() - pv = unsafe.Pointer(uintptr(pv) + C.sizeof_GeoCoord) + *((*C.LatLng)(pv)) = *gc.toCPtr() + pv = unsafe.Pointer(uintptr(pv) + C.sizeof_LatLng) } - return (*C.GeoCoord)(cverts) + return (*C.LatLng)(cverts) } -// Convert geofences (slices of slices of geocoordinates) to C geofences (represented in C-style as +// 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 geofencesToC(geofences [][]GeoCoord) *C.Geofence { +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_Geofence * len(geofences))) + cgeofences := C.malloc(C.size_t(C.sizeof_GeoLoop * len(geofences))) pcgeofences := cgeofences + for _, coords := range geofences { - cverts := geoCoordsToC(coords) + cverts := latLngsToC(coords) - *((*C.Geofence)(pcgeofences)) = C.Geofence{ + *((*C.GeoLoop)(pcgeofences)) = C.GeoLoop{ verts: cverts, numVerts: C.int(len(coords)), } - pcgeofences = unsafe.Pointer(uintptr(pcgeofences) + C.sizeof_Geofence) + pcgeofences = unsafe.Pointer(uintptr(pcgeofences) + C.sizeof_GeoLoop) } - return (*C.Geofence)(cgeofences) + return (*C.GeoLoop)(cgeofences) } // Convert GeoPolygon struct to C equivalent struct. -func geoPolygonToC(gp GeoPolygon) C.GeoPolygon { - cverts := geoCoordsToC(gp.Geofence) - choles := geofencesToC(gp.Holes) +func allocCGeoPolygon(gp GeoPolygon) C.GeoPolygon { + cverts := latLngsToC(gp.GeoLoop) + choles := geoLoopsToC(gp.Holes) return C.GeoPolygon{ - geofence: C.Geofence{ - numVerts: C.int(len(gp.Geofence)), + geoloop: C.GeoLoop{ + numVerts: C.int(len(gp.GeoLoop)), verts: cverts, }, numHoles: C.int(len(gp.Holes)), @@ -502,16 +735,114 @@ func geoPolygonToC(gp GeoPolygon) C.GeoPolygon { // Free pointer values on a C GeoPolygon struct func freeCGeoPolygon(cgp *C.GeoPolygon) { - C.free(unsafe.Pointer(cgp.geofence.verts)) - cgp.geofence.verts = nil + 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.Geofence)(ph).verts)) - (*C.Geofence)(ph).verts = nil - ph = unsafe.Pointer(uintptr(ph) + uintptr(C.sizeof_Geofence)) + 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), + } +} diff --git a/h3_algos.c b/h3_algos.c index cdb6492..0f4db0e 100644 --- a/h3_algos.c +++ b/h3_algos.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2021 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. @@ -18,28 +18,31 @@ */ #include "h3_algos.h" + +#include #include #include #include #include +#include + +#include "h3_alloc.h" #include "h3_baseCells.h" #include "h3_bbox.h" #include "h3_faceijk.h" -#include "h3_geoCoord.h" #include "h3_h3Index.h" #include "h3_h3api.h" +#include "h3_latLng.h" #include "h3_linkedGeo.h" #include "h3_polygon.h" -#include "h3_stackAlloc.h" #include "h3_vertexGraph.h" /* - * Return codes from hexRange and related functions. + * Return codes from gridDiskUnsafe and related functions. */ -#define HEX_RANGE_SUCCESS 0 -#define HEX_RANGE_PENTAGON 1 -#define HEX_RANGE_K_SUBSEQUENCE 2 +#define MAX_ONE_RING_SIZE 7 +#define POLYGON_TO_CELLS_BUFFER 12 /** * Directions used for traversing a hexagonal ring counterclockwise around @@ -149,81 +152,109 @@ static const Direction NEW_ADJUSTMENT_III[7][7] = { CENTER_DIGIT, IJ_AXES_DIGIT}}; /** - * Maximum number of indices that result from the kRing algorithm with the given - * k. Formula source and proof: https://oeis.org/A003215 + * Maximum number of cells that result from the gridDisk algorithm with the + * given k. Formula source and proof: https://oeis.org/A003215 * - * @param k k value, k >= 0. + * @param k k value, k >= 0. + * @param out size in indexes */ -int H3_EXPORT(maxKringSize)(int k) { return 3 * k * (k + 1) + 1; } +H3Error H3_EXPORT(maxGridDiskSize)(int k, int64_t *out) { + if (k < 0) { + return E_DOMAIN; + } + *out = 3 * (int64_t)k * ((int64_t)k + 1) + 1; + return E_SUCCESS; +} /** - * k-rings produces indices within k distance of the origin index. + * Produce cells within grid distance k of the origin cell. * - * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and - * all neighboring indices, and so on. + * 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 the provided array in no particular order. Elements of * the output array may be left zero, as can happen when crossing a pentagon. * - * @param origin Origin location. - * @param k k >= 0 - * @param out Zero-filled array which must be of size maxKringSize(k). + * @param origin origin cell + * @param k k >= 0 + * @param out zero-filled array which must be of size maxGridDiskSize(k) */ -void H3_EXPORT(kRing)(H3Index origin, int k, H3Index* out) { - int maxIdx = H3_EXPORT(maxKringSize)(k); - int* distances = malloc(maxIdx * sizeof(int)); - H3_EXPORT(kRingDistances)(origin, k, out, distances); - free(distances); +H3Error H3_EXPORT(gridDisk)(H3Index origin, int k, H3Index *out) { + return H3_EXPORT(gridDiskDistances)(origin, k, out, NULL); } /** - * k-rings produces indices within k distance of the origin index. + * Produce cells and their distances from the given origin cell, up to + * distance k. * - * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and - * all neighboring indices, and so on. + * 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 the provided array in no particular order. Elements of * the output array may be left zero, as can happen when crossing a pentagon. * - * @param origin Origin location. - * @param k k >= 0 - * @param out Zero-filled array which must be of size maxKringSize(k). - * @param distances Zero-filled array which must be of size maxKringSize(k). + * @param origin origin cell + * @param k k >= 0 + * @param out zero-filled array which must be of size + * maxGridDiskSize(k) + * @param distances NULL or a zero-filled array which must be of size + * maxGridDiskSize(k) */ -void H3_EXPORT(kRingDistances)(H3Index origin, int k, H3Index* out, - int* distances) { - const int maxIdx = H3_EXPORT(maxKringSize)(k); - // Optimistically try the faster hexRange algorithm first - const bool failed = H3_EXPORT(hexRangeDistances)(origin, k, out, distances); +H3Error H3_EXPORT(gridDiskDistances)(H3Index origin, int k, H3Index *out, + int *distances) { + // Optimistically try the faster gridDiskUnsafe algorithm first + const H3Error failed = + H3_EXPORT(gridDiskDistancesUnsafe)(origin, k, out, distances); if (failed) { + int64_t maxIdx; + H3Error err = H3_EXPORT(maxGridDiskSize)(k, &maxIdx); + if (err) { + return err; + } // Fast algo failed, fall back to slower, correct algo // and also wipe out array because contents untrustworthy - memset(out, 0, maxIdx * sizeof(out[0])); - memset(distances, 0, maxIdx * sizeof(distances[0])); - _kRingInternal(origin, k, out, distances, maxIdx, 0); + memset(out, 0, maxIdx * sizeof(H3Index)); + + if (distances == NULL) { + distances = H3_MEMORY(calloc)(maxIdx, sizeof(int)); + if (!distances) { + return E_MEMORY_ALLOC; + } + H3Error result = _gridDiskDistancesInternal(origin, k, out, + distances, maxIdx, 0); + H3_MEMORY(free)(distances); + return result; + } else { + memset(distances, 0, maxIdx * sizeof(int)); + return _gridDiskDistancesInternal(origin, k, out, distances, maxIdx, + 0); + } + } else { + return E_SUCCESS; } } /** - * Internal helper function called recursively for kRingDistances. + * Internal algorithm for the safe but slow version of gridDiskDistances * - * Adds the origin index to the output set (treating it as a hash set) + * Adds the origin cell to the output set (treating it as a hash set) * and recurses to its neighbors, if needed. * - * @param origin - * @param k Maximum distance to move from the origin. - * @param out Array treated as a hash set, elements being either H3Index or 0. - * @param distances Scratch area, with elements paralleling the out array. - * Elements indicate ijk distance from the origin index to the output index. - * @param maxIdx Size of out and scratch arrays (must be maxKringSize(k)) - * @param curK Current distance from the origin. + * @param origin Origin cell + * @param k Maximum distance to move from the origin + * @param out Array treated as a hash set, elements being either + * H3Index or 0. + * @param distances Scratch area, with elements paralleling the out array. + * Elements indicate ijk distance from the origin cell to + * the output cell + * @param maxIdx Size of out and scratch arrays (must be + * maxGridDiskSize(k)) + * @param curK Current distance from the origin */ -void _kRingInternal(H3Index origin, int k, H3Index* out, int* distances, - int maxIdx, int curK) { - if (origin == 0) return; - +H3Error _gridDiskDistancesInternal(H3Index origin, int k, H3Index *out, + int *distances, int64_t maxIdx, int curK) { // Put origin in the output array. out is used as a hash set. - int off = origin % maxIdx; + int64_t off = origin % maxIdx; while (out[off] != 0 && out[off] != origin) { off = (off + 1) % maxIdx; } @@ -231,20 +262,58 @@ void _kRingInternal(H3Index origin, int k, H3Index* out, int* distances, // We either got a free slot in the hash set or hit a duplicate // We might need to process the duplicate anyways because we got // here on a longer path before. - if (out[off] == origin && distances[off] <= curK) return; + if (out[off] == origin && distances[off] <= curK) return E_SUCCESS; out[off] = origin; distances[off] = curK; // Base case: reached an index k away from the origin. - if (curK >= k) return; + if (curK >= k) return E_SUCCESS; // Recurse to all neighbors in no particular order. for (int i = 0; i < 6; i++) { int rotations = 0; - _kRingInternal(h3NeighborRotations(origin, DIRECTIONS[i], &rotations), - k, out, distances, maxIdx, curK + 1); + H3Index nextNeighbor; + H3Error neighborResult = h3NeighborRotations(origin, DIRECTIONS[i], + &rotations, &nextNeighbor); + if (neighborResult != E_PENTAGON) { + // E_PENTAGON is an expected case when trying to traverse off of + // pentagons. + if (neighborResult != E_SUCCESS) { + return neighborResult; + } + neighborResult = _gridDiskDistancesInternal( + nextNeighbor, k, out, distances, maxIdx, curK + 1); + if (neighborResult) { + return neighborResult; + } + } + } + return E_SUCCESS; +} + +/** + * Safe but slow version of gridDiskDistances (also called by it when needed). + * + * Adds the origin cell to the output set (treating it as a hash set) + * and recurses to its neighbors, if needed. + * + * @param origin Origin cell + * @param k Maximum distance to move from the origin + * @param out Array treated as a hash set, elements being either + * H3Index or 0. + * @param distances Scratch area, with elements paralleling the out array. + * Elements indicate ijk distance from the origin cell to + * the output cell + */ +H3Error H3_EXPORT(gridDiskDistancesSafe)(H3Index origin, int k, H3Index *out, + int *distances) { + int64_t maxIdx; + H3Error err = H3_EXPORT(maxGridDiskSize)(k, &maxIdx); + if (err) { + return err; } + return _gridDiskDistancesInternal(origin, k, out, distances, maxIdx, 0); } /** @@ -259,50 +328,63 @@ void _kRingInternal(H3Index origin, int k, H3Index* out, int* distances, * @param rotations Number of ccw rotations to perform to reorient the * translation vector. Will be modified to the new number of * rotations to perform (such as when crossing a face edge.) - * @return H3Index of the specified neighbor or 0 if deleted k-subsequence - * distortion is encountered. + * @param out H3Index of the specified neighbor if succesful + * @return E_SUCCESS on success */ -H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) { - H3Index out = origin; +H3Error h3NeighborRotations(H3Index origin, Direction dir, int *rotations, + H3Index *out) { + H3Index current = origin; + if (dir < CENTER_DIGIT || dir >= INVALID_DIGIT) { + return E_FAILED; + } for (int i = 0; i < *rotations; i++) { dir = _rotate60ccw(dir); } int newRotations = 0; - int oldBaseCell = H3_GET_BASE_CELL(out); - Direction oldLeadingDigit = _h3LeadingNonZeroDigit(out); + int oldBaseCell = H3_GET_BASE_CELL(current); + if (oldBaseCell < 0 || + oldBaseCell >= NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } + Direction oldLeadingDigit = _h3LeadingNonZeroDigit(current); // Adjust the indexing digits and, if needed, the base cell. - int r = H3_GET_RESOLUTION(out) - 1; + int r = H3_GET_RESOLUTION(current) - 1; while (true) { if (r == -1) { - H3_SET_BASE_CELL(out, baseCellNeighbors[oldBaseCell][dir]); + H3_SET_BASE_CELL(current, baseCellNeighbors[oldBaseCell][dir]); newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir]; - if (H3_GET_BASE_CELL(out) == INVALID_BASE_CELL) { + if (H3_GET_BASE_CELL(current) == INVALID_BASE_CELL) { // Adjust for the deleted k vertex at the base cell level. // This edge actually borders a different neighbor. - H3_SET_BASE_CELL(out, + H3_SET_BASE_CELL(current, baseCellNeighbors[oldBaseCell][IK_AXES_DIGIT]); newRotations = baseCellNeighbor60CCWRots[oldBaseCell][IK_AXES_DIGIT]; // perform the adjustment for the k-subsequence we're skipping // over. - out = _h3Rotate60ccw(out); + current = _h3Rotate60ccw(current); *rotations = *rotations + 1; } break; } else { - Direction oldDigit = H3_GET_INDEX_DIGIT(out, r + 1); + Direction oldDigit = H3_GET_INDEX_DIGIT(current, r + 1); Direction nextDir; - if (isResClassIII(r + 1)) { - H3_SET_INDEX_DIGIT(out, r + 1, NEW_DIGIT_II[oldDigit][dir]); + if (oldDigit == INVALID_DIGIT) { + // Only possible on invalid input + return E_CELL_INVALID; + } else if (isResolutionClassIII(r + 1)) { + H3_SET_INDEX_DIGIT(current, r + 1, NEW_DIGIT_II[oldDigit][dir]); nextDir = NEW_ADJUSTMENT_II[oldDigit][dir]; } else { - H3_SET_INDEX_DIGIT(out, r + 1, NEW_DIGIT_III[oldDigit][dir]); + H3_SET_INDEX_DIGIT(current, r + 1, + NEW_DIGIT_III[oldDigit][dir]); nextDir = NEW_ADJUSTMENT_III[oldDigit][dir]; } @@ -316,12 +398,12 @@ H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) { } } - int newBaseCell = H3_GET_BASE_CELL(out); + int newBaseCell = H3_GET_BASE_CELL(current); if (_isBaseCellPentagon(newBaseCell)) { int alreadyAdjustedKSubsequence = 0; // force rotation out of missing k-axes sub-sequence - if (_h3LeadingNonZeroDigit(out) == K_AXES_DIGIT) { + if (_h3LeadingNonZeroDigit(current) == K_AXES_DIGIT) { if (oldBaseCell != newBaseCell) { // in this case, we traversed into the deleted // k subsequence of a pentagon base cell. @@ -331,11 +413,11 @@ H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) { if (_baseCellIsCwOffset( newBaseCell, baseCellData[oldBaseCell].homeFijk.face)) { - out = _h3Rotate60cw(out); + current = _h3Rotate60cw(current); } else { - // See cwOffsetPent in testKRing.c for why this is + // See cwOffsetPent in testGridDisk.c for why this is // unreachable. - out = _h3Rotate60ccw(out); // LCOV_EXCL_LINE + current = _h3Rotate60ccw(current); // LCOV_EXCL_LINE } alreadyAdjustedKSubsequence = 1; } else { @@ -344,27 +426,28 @@ H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) { // base cell. if (oldLeadingDigit == CENTER_DIGIT) { // Undefined: the k direction is deleted from here - return H3_INVALID_INDEX; + return E_PENTAGON; } else if (oldLeadingDigit == JK_AXES_DIGIT) { // Rotate out of the deleted k subsequence // We also need an additional change to the direction we're // moving in - out = _h3Rotate60ccw(out); + current = _h3Rotate60ccw(current); *rotations = *rotations + 1; } else if (oldLeadingDigit == IK_AXES_DIGIT) { // Rotate out of the deleted k subsequence // We also need an additional change to the direction we're // moving in - out = _h3Rotate60cw(out); + current = _h3Rotate60cw(current); *rotations = *rotations + 5; } else { // Should never occur - return H3_INVALID_INDEX; // LCOV_EXCL_LINE + return E_FAILED; // LCOV_EXCL_LINE } } } - for (int i = 0; i < newRotations; i++) out = _h3RotatePent60ccw(out); + for (int i = 0; i < newRotations; i++) + current = _h3RotatePent60ccw(current); // Account for differing orientation of the base cells (this edge // might not follow properties of some other edges.) @@ -373,10 +456,10 @@ H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) { // 'polar' base cells behave differently because they have all // i neighbors. if (oldBaseCell != 118 && oldBaseCell != 8 && - _h3LeadingNonZeroDigit(out) != JK_AXES_DIGIT) { + _h3LeadingNonZeroDigit(current) != JK_AXES_DIGIT) { *rotations = *rotations + 1; } - } else if (_h3LeadingNonZeroDigit(out) == IK_AXES_DIGIT && + } else if (_h3LeadingNonZeroDigit(current) == IK_AXES_DIGIT && !alreadyAdjustedKSubsequence) { // account for distortion introduced to the 5 neighbor by the // deleted k subsequence. @@ -384,16 +467,44 @@ H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) { } } } else { - for (int i = 0; i < newRotations; i++) out = _h3Rotate60ccw(out); + for (int i = 0; i < newRotations; i++) + current = _h3Rotate60ccw(current); } *rotations = (*rotations + newRotations) % 6; + *out = current; - return out; + return E_SUCCESS; } /** - * hexRange produces indexes within k distance of the origin index. + * Get the direction from the origin to a given neighbor. This is effectively + * the reverse operation for h3NeighborRotations. Returns INVALID_DIGIT if the + * cells are not neighbors. + * + * TODO: This is currently a brute-force algorithm, but as it's O(6) that's + * probably acceptable. + */ +Direction directionForNeighbor(H3Index origin, H3Index destination) { + bool isPent = H3_EXPORT(isPentagon)(origin); + // Checks each neighbor, in order, to determine which direction the + // destination neighbor is located. Skips CENTER_DIGIT since that + // would be the origin; skips deleted K direction for pentagons. + for (Direction direction = isPent ? J_AXES_DIGIT : K_AXES_DIGIT; + direction < NUM_DIGITS; direction++) { + H3Index neighbor; + int rotations = 0; + H3Error neighborError = + h3NeighborRotations(origin, direction, &rotations, &neighbor); + if (!neighborError && neighbor == destination) { + return direction; + } + } + return INVALID_DIGIT; +} + +/** + * gridDiskUnsafe produces indexes within k distance of the origin index. * Output behavior is undefined when one of the indexes returned by this * function is a pentagon or is in the pentagon distortion area. * @@ -405,16 +516,16 @@ H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) { * * @param origin Origin location. * @param k k >= 0 - * @param out Array which must be of size maxKringSize(k). + * @param out Array which must be of size maxGridDiskSize(k). * @return 0 if no pentagon or pentagonal distortion area was encountered. */ -int H3_EXPORT(hexRange)(H3Index origin, int k, H3Index* out) { - return H3_EXPORT(hexRangeDistances)(origin, k, out, 0); +H3Error H3_EXPORT(gridDiskUnsafe)(H3Index origin, int k, H3Index *out) { + return H3_EXPORT(gridDiskDistancesUnsafe)(origin, k, out, NULL); } /** - * hexRange produces indexes within k distance of the origin index. - * Output behavior is undefined when one of the indexes returned by this + * gridDiskDistancesUnsafe produces indexes within k distance of the origin + * index. Output behavior is undefined when one of the indexes returned by this * function is a pentagon or is in the pentagon distortion area. * * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and @@ -426,18 +537,21 @@ int H3_EXPORT(hexRange)(H3Index origin, int k, H3Index* out) { * * @param origin Origin location. * @param k k >= 0 - * @param out Array which must be of size maxKringSize(k). - * @param distances Null or array which must be of size maxKringSize(k). + * @param out Array which must be of size maxGridDiskSize(k). + * @param distances Null or array which must be of size maxGridDiskSize(k). * @return 0 if no pentagon or pentagonal distortion area was encountered. */ -int H3_EXPORT(hexRangeDistances)(H3Index origin, int k, H3Index* out, - int* distances) { +H3Error H3_EXPORT(gridDiskDistancesUnsafe)(H3Index origin, int k, H3Index *out, + int *distances) { // Return codes: // 1 Pentagon was encountered // 2 Pentagon distortion (deleted k subsequence) was encountered // Pentagon being encountered is not itself a problem; really the deleted // k-subsequence is the problem, but for compatibility reasons we fail on // the pentagon. + if (k < 0) { + return E_DOMAIN; + } // k must be >= 0, so origin is always needed int idx = 0; @@ -447,9 +561,9 @@ int H3_EXPORT(hexRangeDistances)(H3Index origin, int k, H3Index* out, } idx++; - if (H3_EXPORT(h3IsPentagon)(origin)) { + if (H3_EXPORT(isPentagon)(origin)) { // Pentagon was encountered; bail out as user doesn't want this. - return HEX_RANGE_PENTAGON; + return E_PENTAGON; } // 0 < ring <= k, current ring @@ -466,25 +580,24 @@ int H3_EXPORT(hexRangeDistances)(H3Index origin, int k, H3Index* out, if (direction == 0 && i == 0) { // Not putting in the output set as it will be done later, at // the end of this ring. - origin = - h3NeighborRotations(origin, NEXT_RING_DIRECTION, &rotations); - if (origin == 0) { + H3Error neighborResult = h3NeighborRotations( + origin, NEXT_RING_DIRECTION, &rotations, &origin); + if (neighborResult) { // LCOV_EXCL_BR_LINE // Should not be possible because `origin` would have to be a // pentagon - return HEX_RANGE_K_SUBSEQUENCE; // LCOV_EXCL_LINE + return neighborResult; // LCOV_EXCL_LINE } - if (H3_EXPORT(h3IsPentagon)(origin)) { + if (H3_EXPORT(isPentagon)(origin)) { // Pentagon was encountered; bail out as user doesn't want this. - return HEX_RANGE_PENTAGON; + return E_PENTAGON; } } - origin = h3NeighborRotations(origin, DIRECTIONS[direction], &rotations); - if (origin == 0) { - // Should not be possible because `origin` would have to be a - // pentagon - return HEX_RANGE_K_SUBSEQUENCE; // LCOV_EXCL_LINE + H3Error neighborResult = h3NeighborRotations( + origin, DIRECTIONS[direction], &rotations, &origin); + if (neighborResult) { + return neighborResult; } out[idx] = origin; if (distances) { @@ -504,73 +617,83 @@ int H3_EXPORT(hexRangeDistances)(H3Index origin, int k, H3Index* out, } } - if (H3_EXPORT(h3IsPentagon)(origin)) { + if (H3_EXPORT(isPentagon)(origin)) { // Pentagon was encountered; bail out as user doesn't want this. - return HEX_RANGE_PENTAGON; + return E_PENTAGON; } } - return HEX_RANGE_SUCCESS; + return E_SUCCESS; } /** - * hexRanges takes an array of input hex IDs and a max k-ring and returns an - * array of hexagon IDs sorted first by the original hex IDs and then by the + * gridDisksUnsafe takes an array of input hex IDs and a max k-ring and returns + * an array of hexagon IDs sorted first by the original hex IDs and then by the * k-ring (0 to max), with no guaranteed sorting within each k-ring group. * * @param h3Set A pointer to an array of H3Indexes * @param length The total number of H3Indexes in h3Set * @param k The number of rings to generate * @param out A pointer to the output memory to dump the new set of H3Indexes to - * The memory block should be equal to maxKringSize(k) * length + * The memory block should be equal to maxGridDiskSize(k) * length * @return 0 if no pentagon is encountered. Cannot trust output otherwise */ -int H3_EXPORT(hexRanges)(H3Index* h3Set, int length, int k, H3Index* out) { - int success = 0; - H3Index* segment; - int segmentSize = H3_EXPORT(maxKringSize)(k); +H3Error H3_EXPORT(gridDisksUnsafe)(H3Index *h3Set, int length, int k, + H3Index *out) { + H3Index *segment; + int64_t segmentSize; + H3Error err = H3_EXPORT(maxGridDiskSize)(k, &segmentSize); + if (err) { + return err; + } for (int i = 0; i < length; i++) { // Determine the appropriate segment of the output array to operate on segment = out + i * segmentSize; - success = H3_EXPORT(hexRange)(h3Set[i], k, segment); - if (success != 0) return success; + H3Error failed = H3_EXPORT(gridDiskUnsafe)(h3Set[i], k, segment); + if (failed) return failed; } - return 0; + return E_SUCCESS; } /** - * Returns the hollow hexagonal ring centered at origin with sides of length k. + * Returns the "hollow" ring of hexagons at exactly grid distance k from + * the origin hexagon. In particular, k=0 returns just the origin hexagon. + * + * A nonzero failure code may be returned in some cases, for example, + * if a pentagon is encountered. + * Failure cases may be fixed in future versions. * * @param origin Origin location. * @param k k >= 0 * @param out Array which must be of size 6 * k (or 1 if k == 0) - * @return 0 if no pentagonal distortion was encountered. + * @return 0 if successful; nonzero otherwise. */ -int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index* out) { +H3Error H3_EXPORT(gridRingUnsafe)(H3Index origin, int k, H3Index *out) { // Short-circuit on 'identity' ring if (k == 0) { out[0] = origin; - return 0; + return E_SUCCESS; } int idx = 0; // Number of 60 degree ccw rotations to perform on the direction (based on // which faces have been crossed.) int rotations = 0; // Scratch structure for checking for pentagons - if (H3_EXPORT(h3IsPentagon)(origin)) { + if (H3_EXPORT(isPentagon)(origin)) { // Pentagon was encountered; bail out as user doesn't want this. - return HEX_RANGE_PENTAGON; + return E_PENTAGON; } for (int ring = 0; ring < k; ring++) { - origin = h3NeighborRotations(origin, NEXT_RING_DIRECTION, &rotations); - if (origin == 0) { + H3Error neighborResult = h3NeighborRotations( + origin, NEXT_RING_DIRECTION, &rotations, &origin); + if (neighborResult) { // LCOV_EXCL_BR_LINE // Should not be possible because `origin` would have to be a // pentagon - return HEX_RANGE_K_SUBSEQUENCE; // LCOV_EXCL_LINE + return neighborResult; // LCOV_EXCL_LINE } - if (H3_EXPORT(h3IsPentagon)(origin)) { - return HEX_RANGE_PENTAGON; + if (H3_EXPORT(isPentagon)(origin)) { + return E_PENTAGON; } } @@ -581,12 +704,12 @@ int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index* out) { for (int direction = 0; direction < 6; direction++) { for (int pos = 0; pos < k; pos++) { - origin = - h3NeighborRotations(origin, DIRECTIONS[direction], &rotations); - if (origin == 0) { + H3Error neighborResult = h3NeighborRotations( + origin, DIRECTIONS[direction], &rotations, &origin); + if (neighborResult) { // LCOV_EXCL_BR_LINE // Should not be possible because `origin` would have to be a // pentagon - return HEX_RANGE_K_SUBSEQUENCE; // LCOV_EXCL_LINE + return neighborResult; // LCOV_EXCL_LINE } // Skip the very last index, it was already added. We do @@ -596,8 +719,8 @@ int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index* out) { out[idx] = origin; idx++; - if (H3_EXPORT(h3IsPentagon)(origin)) { - return HEX_RANGE_PENTAGON; + if (H3_EXPORT(isPentagon)(origin)) { + return E_PENTAGON; } } } @@ -607,100 +730,307 @@ int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index* out) { // it indicates pentagonal distortion occurred and we should report // failure. if (lastIndex != origin) { - return HEX_RANGE_PENTAGON; + return E_PENTAGON; } else { - return HEX_RANGE_SUCCESS; + return E_SUCCESS; } } /** - * maxPolyfillSize returns the number of hexagons to allocate space for when - * performing a polyfill on the given GeoJSON-like data structure. + * maxPolygonToCellsSize returns the number of cells to allocate space for + * when performing a polygonToCells on the given GeoJSON-like data structure. * - * Currently a laughably padded response, being a k-ring that wholly contains - * a bounding box of the GeoJSON, but still less wasted memory than initializing - * a Python application? ;) + * The size is the maximum of either the number of points in the geoloop or the + * number of cells in the bounding box of the geoloop. * * @param geoPolygon A GeoJSON-like data structure indicating the poly to fill * @param res Hexagon resolution (0-15) - * @return number of hexagons to allocate for + * @param out number of cells to allocate for + * @return 0 (E_SUCCESS) on success. */ -int H3_EXPORT(maxPolyfillSize)(const GeoPolygon* geoPolygon, int res) { +H3Error H3_EXPORT(maxPolygonToCellsSize)(const GeoPolygon *geoPolygon, int res, + uint32_t flags, int64_t *out) { + if (flags != 0) { + return E_OPTION_INVALID; + } // Get the bounding box for the GeoJSON-like struct BBox bbox; - bboxFromGeofence(&geoPolygon->geofence, &bbox); - int minK = bboxHexRadius(&bbox, res); + const GeoLoop geoloop = geoPolygon->geoloop; + bboxFromGeoLoop(&geoloop, &bbox); + int64_t numHexagons = bboxHexEstimate(&bbox, res); + // This algorithm assumes that the number of vertices is usually less than + // the number of hexagons, but when it's wrong, this will keep it from + // failing + int totalVerts = geoloop.numVerts; + for (int i = 0; i < geoPolygon->numHoles; i++) { + totalVerts += geoPolygon->holes[i].numVerts; + } + if (numHexagons < totalVerts) numHexagons = totalVerts; + // When the polygon is very small, near an icosahedron edge and is an odd + // resolution, the line tracing needs an extra buffer than the estimator + // function provides (but beefing that up to cover causes most situations to + // overallocate memory) + numHexagons += POLYGON_TO_CELLS_BUFFER; + *out = numHexagons; + return E_SUCCESS; +} - // The total number of hexagons to allocate can now be determined by - // the k-ring hex allocation helper function. - return H3_EXPORT(maxKringSize)(minK); +/** + * _getEdgeHexagons takes a given geoloop ring (either the main geoloop or + * one of the holes) and traces it with hexagons and updates the search and + * found memory blocks. This is used for determining the initial hexagon set + * for the polygonToCells algorithm to execute on. + * + * @param geoloop The geoloop (or hole) to be traced + * @param numHexagons The maximum number of hexagons possible for the geoloop + * (also the bounds of the search and found arrays) + * @param res The hexagon resolution (0-15) + * @param numSearchHexes The number of hexagons found so far to be searched + * @param search The block of memory containing the hexagons to search from + * @param found The block of memory containing the hexagons found from the + * search + * + * @return An error code if the hash function cannot insert a found hexagon + * into the found array. + */ +H3Error _getEdgeHexagons(const GeoLoop *geoloop, int64_t numHexagons, int res, + int64_t *numSearchHexes, H3Index *search, + H3Index *found) { + for (int i = 0; i < geoloop->numVerts; i++) { + LatLng origin = geoloop->verts[i]; + LatLng destination = i == geoloop->numVerts - 1 ? geoloop->verts[0] + : geoloop->verts[i + 1]; + const int64_t numHexesEstimate = + lineHexEstimate(&origin, &destination, res); + for (int64_t j = 0; j < numHexesEstimate; j++) { + LatLng interpolate; + interpolate.lat = + (origin.lat * (numHexesEstimate - j) / numHexesEstimate) + + (destination.lat * j / numHexesEstimate); + interpolate.lng = + (origin.lng * (numHexesEstimate - j) / numHexesEstimate) + + (destination.lng * j / numHexesEstimate); + H3Index pointHex; + H3Error e = H3_EXPORT(latLngToCell)(&interpolate, res, &pointHex); + if (e) { + return e; + } + // A simple hash to store the hexagon, or move to another place if + // needed + int64_t loc = (int64_t)(pointHex % numHexagons); + int64_t loopCount = 0; + while (found[loc] != 0) { + // If this conditional is reached, the `found` memory block is + // too small for the given polygon. This should not happen. + if (loopCount > numHexagons) return E_FAILED; // LCOV_EXCL_LINE + if (found[loc] == pointHex) + break; // At least two points of the geoloop index to the + // same cell + loc = (loc + 1) % numHexagons; + loopCount++; + } + if (found[loc] == pointHex) + continue; // Skip this hex, already exists in the found hash + // Otherwise, set it in the found hash for now + found[loc] = pointHex; + + search[*numSearchHexes] = pointHex; + (*numSearchHexes)++; + } + } + return E_SUCCESS; } /** - * polyfill takes a given GeoJSON-like data structure and preallocated, + * polygonToCells takes a given GeoJSON-like data structure and preallocated, * zeroed memory, and fills it with the hexagons that are contained by * the GeoJSON-like data structure. * - * The current implementation is very primitive and slow, but correct, - * performing a point-in-poly operation on every hexagon in a k-ring defined - * around the given geofence. + * 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. * - * @param geoPolygon The geofence and holes defining the relevant area + * @param geoPolygon The geoloop and holes defining the relevant area * @param res The Hexagon resolution (0-15) * @param out The slab of zeroed memory to write to. Assumed to be big enough. */ -void H3_EXPORT(polyfill)(const GeoPolygon* geoPolygon, int res, H3Index* out) { - // One of the goals of the polyfill algorithm is that two adjacent polygons - // with zero overlap have zero overlapping hexagons. That the hexagons are - // uniquely assigned. There are a few approaches to take here, such as - // deciding based on which polygon has the greatest overlapping area of the - // hexagon, or the most number of contained points on the hexagon (using the - // center point as a tiebreaker). +H3Error H3_EXPORT(polygonToCells)(const GeoPolygon *geoPolygon, int res, + uint32_t flags, H3Index *out) { + if (flags != 0) { + return E_OPTION_INVALID; + } + // One of the goals of the polygonToCells algorithm is that two adjacent + // polygons with zero overlap have zero overlapping hexagons. That the + // hexagons are uniquely assigned. There are a few approaches to take here, + // such as deciding based on which polygon has the greatest overlapping area + // of the hexagon, or the most number of contained points on the hexagon + // (using the center point as a tiebreaker). // // But if the polygons are convex, both of these more complex algorithms can // be reduced down to checking whether or not the center of the hexagon is - // contained in the polygon, and so this is the approach that this polyfill - // algorithm will follow, as it's simpler, faster, and the error for concave - // polygons is still minimal (only affecting concave shapes on the order of - // magnitude of the hexagon size or smaller, not impacting larger concave - // shapes) + // contained in the polygon, and so this is the approach that this + // polygonToCells algorithm will follow, as it's simpler, faster, and the + // error for concave polygons is still minimal (only affecting concave + // shapes on the order of magnitude of the hexagon size or smaller, not + // impacting larger concave shapes) // - // This first part is identical to the maxPolyfillSize above. + // This first part is identical to the maxPolygonToCellsSize above. // Get the bounding boxes for the polygon and any holes - BBox* bboxes = malloc((geoPolygon->numHoles + 1) * sizeof(BBox)); - assert(bboxes != NULL); + BBox *bboxes = H3_MEMORY(malloc)((geoPolygon->numHoles + 1) * sizeof(BBox)); + if (!bboxes) { + return E_MEMORY_ALLOC; + } bboxesFromGeoPolygon(geoPolygon, bboxes); - int minK = bboxHexRadius(&bboxes[0], res); - int numHexagons = H3_EXPORT(maxKringSize)(minK); - - // Get the center hex - GeoCoord center; - bboxCenter(&bboxes[0], ¢er); - H3Index centerH3 = H3_EXPORT(geoToH3)(¢er, res); - - // From here on it works differently, first we get all potential - // hexagons inserted into the available memory - H3_EXPORT(kRing)(centerH3, minK, out); - - // Next we iterate through each hexagon, and test its center point to see if - // it's contained in the GeoJSON-like struct - for (int i = 0; i < numHexagons; i++) { - // Skip records that are already zeroed - if (out[i] == 0) { - continue; + + // Get the estimated number of hexagons and allocate some temporary memory + // for the hexagons + int64_t numHexagons; + H3Error numHexagonsError = + H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &numHexagons); + if (numHexagonsError) { + H3_MEMORY(free)(bboxes); + return numHexagonsError; + } + H3Index *search = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index)); + if (!search) { + H3_MEMORY(free)(bboxes); + return E_MEMORY_ALLOC; + } + H3Index *found = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index)); + if (!found) { + H3_MEMORY(free)(bboxes); + H3_MEMORY(free)(search); + return E_MEMORY_ALLOC; + } + + // Some metadata for tracking the state of the search and found memory + // blocks + int64_t numSearchHexes = 0; + int64_t numFoundHexes = 0; + + // 1. Trace the hexagons along the polygon defining the outer geoloop and + // add them to the search hash. The hexagon containing the geoloop point + // may or may not be contained by the geoloop (as the hexagon's center + // point may be outside of the boundary.) + const GeoLoop geoloop = geoPolygon->geoloop; + H3Error edgeHexError = _getEdgeHexagons(&geoloop, numHexagons, res, + &numSearchHexes, search, found); + // If this branch is reached, we have exceeded the maximum number of + // hexagons possible and need to clean up the allocated memory. + // LCOV_EXCL_START + if (edgeHexError) { + H3_MEMORY(free)(search); + H3_MEMORY(free)(found); + H3_MEMORY(free)(bboxes); + return edgeHexError; + } + // LCOV_EXCL_STOP + + // 2. Iterate over all holes, trace the polygons defining the holes with + // hexagons and add to only the search hash. We're going to temporarily use + // the `found` hash to use for dedupe purposes and then re-zero it once + // we're done here, otherwise we'd have to scan the whole set on each insert + // to make sure there's no duplicates, which is very inefficient. + for (int i = 0; i < geoPolygon->numHoles; i++) { + GeoLoop *hole = &(geoPolygon->holes[i]); + edgeHexError = _getEdgeHexagons(hole, numHexagons, res, &numSearchHexes, + search, found); + // If this branch is reached, we have exceeded the maximum number of + // hexagons possible and need to clean up the allocated memory. + // LCOV_EXCL_START + if (edgeHexError) { + H3_MEMORY(free)(search); + H3_MEMORY(free)(found); + H3_MEMORY(free)(bboxes); + return edgeHexError; } - // Check if hexagon is inside of polygon - GeoCoord hexCenter; - H3_EXPORT(h3ToGeo)(out[i], &hexCenter); - hexCenter.lat = constrainLat(hexCenter.lat); - hexCenter.lon = constrainLng(hexCenter.lon); - // And remove from list if not - if (!pointInsidePolygon(geoPolygon, bboxes, &hexCenter)) { - out[i] = H3_INVALID_INDEX; + // LCOV_EXCL_STOP + } + + // 3. Re-zero the found hash so it can be used in the main loop below + for (int64_t i = 0; i < numHexagons; i++) found[i] = H3_NULL; + + // 4. Begin main loop. While the search hash is not empty do the following + while (numSearchHexes > 0) { + // Iterate through all hexagons in the current search hash, then loop + // through all neighbors and test Point-in-Poly, if point-in-poly + // succeeds, add to out and found hashes if not already there. + int64_t currentSearchNum = 0; + int64_t i = 0; + while (currentSearchNum < numSearchHexes) { + H3Index ring[MAX_ONE_RING_SIZE] = {0}; + H3Index searchHex = search[i]; + H3_EXPORT(gridDisk)(searchHex, 1, ring); + for (int j = 0; j < MAX_ONE_RING_SIZE; j++) { + if (ring[j] == H3_NULL) { + continue; // Skip if this was a pentagon and only had 5 + // neighbors + } + + H3Index hex = ring[j]; + + // A simple hash to store the hexagon, or move to another place + // if needed. This MUST be done before the point-in-poly check + // since that's far more expensive + int64_t loc = (int64_t)(hex % numHexagons); + int64_t loopCount = 0; + while (out[loc] != 0) { + // If this branch is reached, we have exceeded the maximum + // number of hexagons possible and need to clean up the + // allocated memory. + // LCOV_EXCL_START + if (loopCount > numHexagons) { + H3_MEMORY(free)(search); + H3_MEMORY(free)(found); + H3_MEMORY(free)(bboxes); + return E_FAILED; + } + // LCOV_EXCL_STOP + if (out[loc] == hex) break; // Skip duplicates found + loc = (loc + 1) % numHexagons; + loopCount++; + } + if (out[loc] == hex) { + continue; // Skip this hex, already exists in the out hash + } + + // Check if the hexagon is in the polygon or not + LatLng hexCenter; + H3_EXPORT(cellToLatLng)(hex, &hexCenter); + + // If not, skip + if (!pointInsidePolygon(geoPolygon, bboxes, &hexCenter)) { + continue; + } + + // Otherwise set it in the output array + out[loc] = hex; + + // Set the hexagon in the found hash + found[numFoundHexes] = hex; + numFoundHexes++; + } + currentSearchNum++; + i++; } + + // Swap the search and found pointers, copy the found hex count to the + // search hex count, and zero everything related to the found memory. + H3Index *temp = search; + search = found; + found = temp; + for (int64_t j = 0; j < numSearchHexes; j++) found[j] = 0; + numSearchHexes = numFoundHexes; + numFoundHexes = 0; + // Repeat until no new hexagons are found } - free(bboxes); + // The out memory structure should be complete, end it here + H3_MEMORY(free)(bboxes); + H3_MEMORY(free)(search); + H3_MEMORY(free)(found); + return E_SUCCESS; } /** @@ -712,17 +1042,17 @@ void H3_EXPORT(polyfill)(const GeoPolygon* geoPolygon, int res, H3Index* out) { * @param numHexes Number of hexagons in the set * @param graph Output graph */ -void h3SetToVertexGraph(const H3Index* h3Set, const int numHexes, - VertexGraph* graph) { - GeoBoundary vertices; - GeoCoord* fromVtx; - GeoCoord* toVtx; - VertexNode* edge; +H3Error h3SetToVertexGraph(const H3Index *h3Set, const int numHexes, + VertexGraph *graph) { + CellBoundary vertices; + LatLng *fromVtx; + LatLng *toVtx; + VertexNode *edge; if (numHexes < 1) { // We still need to init the graph, or calls to destroyVertexGraph will // fail initVertexGraph(graph, 0, 0); - return; + return E_SUCCESS; } int res = H3_GET_RESOLUTION(h3Set[0]); const int minBuckets = 6; @@ -731,7 +1061,12 @@ void h3SetToVertexGraph(const H3Index* h3Set, const int numHexes, initVertexGraph(graph, numBuckets, res); // Iterate through every hexagon for (int i = 0; i < numHexes; i++) { - H3_EXPORT(h3ToGeoBoundary)(h3Set[i], &vertices); + H3Error boundaryErr = H3_EXPORT(cellToBoundary)(h3Set[i], &vertices); + if (boundaryErr) { + // Destroy vertex graph as caller will not know to do so. + destroyVertexGraph(graph); + return boundaryErr; + } // iterate through every edge for (int j = 0; j < vertices.numVerts; j++) { fromVtx = &vertices.verts[j]; @@ -748,21 +1083,23 @@ void h3SetToVertexGraph(const H3Index* h3Set, const int numHexes, } } } + return E_SUCCESS; } /** * Internal: Create a LinkedGeoPolygon from a vertex graph. It is the - * responsibility of the caller to call destroyLinkedPolygon on the populated - * linked geo structure, or the memory for that structure will not be freed. + * responsibility of the caller to call destroyLinkedMultiPolygon on the + * populated linked geo structure, or the memory for that structure will not be + * freed. * @private * @param graph Input graph * @param out Output polygon */ -void _vertexGraphToLinkedGeo(VertexGraph* graph, LinkedGeoPolygon* out) { +void _vertexGraphToLinkedGeo(VertexGraph *graph, LinkedGeoPolygon *out) { *out = (LinkedGeoPolygon){0}; - LinkedGeoLoop* loop; - VertexNode* edge; - GeoCoord nextVtx; + LinkedGeoLoop *loop; + VertexNode *edge; + LatLng nextVtx; // Find the next unused entry point while ((edge = firstVertexNode(graph)) != NULL) { loop = addNewLinkedLoop(out); @@ -782,9 +1119,9 @@ void _vertexGraphToLinkedGeo(VertexGraph* graph, LinkedGeoPolygon* out) { * 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 the responsibility of the caller to call destroyLinkedPolygon on the - * populated linked geo structure, or the memory for that structure will - * not be freed. + * It is the responsibility of the caller to call destroyLinkedMultiPolygon on + * the populated linked geo structure, or the memory for that structure will not + * be freed. * * 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 @@ -795,13 +1132,18 @@ void _vertexGraphToLinkedGeo(VertexGraph* graph, LinkedGeoPolygon* out) { * @param numHexes Number of hexagons in set * @param out Output polygon */ -void H3_EXPORT(h3SetToLinkedGeo)(const H3Index* h3Set, const int numHexes, - LinkedGeoPolygon* out) { +H3Error H3_EXPORT(cellsToLinkedMultiPolygon)(const H3Index *h3Set, + const int numHexes, + LinkedGeoPolygon *out) { VertexGraph graph; - h3SetToVertexGraph(h3Set, numHexes, &graph); + H3Error err = h3SetToVertexGraph(h3Set, numHexes, &graph); + if (err) { + return err; + } _vertexGraphToLinkedGeo(&graph, out); - // TODO: The return value, possibly indicating an error, is discarded here - - // we should use this when we update the API to return a value - normalizeMultiPolygon(out); + if (normalizeMultiPolygon(out)) { + return E_FAILED; + } destroyVertexGraph(&graph); + return E_SUCCESS; } diff --git a/h3_algos.h b/h3_algos.h index 8c9c13c..c5d7080 100644 --- a/h3_algos.h +++ b/h3_algos.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2018, 2020 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. @@ -27,17 +27,30 @@ #include "h3_vertexGraph.h" // neighbor along the ijk coordinate system of the current face, rotated -H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations); +H3Error h3NeighborRotations(H3Index origin, Direction dir, int *rotations, + H3Index *out); + +// IJK direction of neighbor +Direction directionForNeighbor(H3Index origin, H3Index destination); // k-ring implementation -void _kRingInternal(H3Index origin, int k, H3Index* out, int* distances, +void _kRingInternal(H3Index origin, int k, H3Index *out, int *distances, int maxIdx, int curK); // Create a vertex graph from a set of hexagons -void h3SetToVertexGraph(const H3Index* h3Set, const int numHexes, - VertexGraph* out); +H3Error h3SetToVertexGraph(const H3Index *h3Set, const int numHexes, + VertexGraph *out); // Create a LinkedGeoPolygon from a vertex graph -void _vertexGraphToLinkedGeo(VertexGraph* graph, LinkedGeoPolygon* out); +void _vertexGraphToLinkedGeo(VertexGraph *graph, LinkedGeoPolygon *out); + +// Internal function for polygonToCells that traces a geoloop with hexagons of +// a specific size +H3Error _getEdgeHexagons(const GeoLoop *geoloop, int64_t numHexagons, int res, + int64_t *numSearchHexes, H3Index *search, + H3Index *found); +// The safe gridDiskDistances algorithm. +H3Error _gridDiskDistancesInternal(H3Index origin, int k, H3Index *out, + int *distances, int64_t maxIdx, int curK); #endif diff --git a/v3/h3_alloc.h b/h3_alloc.h similarity index 94% rename from v3/h3_alloc.h rename to h3_alloc.h index 98f3852..ac0f24a 100644 --- a/v3/h3_alloc.h +++ b/h3_alloc.h @@ -29,10 +29,19 @@ #ifdef H3_ALLOC_PREFIX #define H3_MEMORY(name) TJOIN(H3_ALLOC_PREFIX, name) +#ifdef __cplusplus +extern "C" { +#endif + void* H3_MEMORY(malloc)(size_t size); void* H3_MEMORY(calloc)(size_t num, size_t size); void* H3_MEMORY(realloc)(void* ptr, size_t size); void H3_MEMORY(free)(void* ptr); + +#ifdef __cplusplus +} +#endif + #else #define H3_MEMORY(name) name #endif diff --git a/h3_baseCells.c b/h3_baseCells.c index 0cadfe2..822e9e6 100644 --- a/h3_baseCells.c +++ b/h3_baseCells.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2020 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. @@ -18,16 +18,17 @@ */ #include "h3_baseCells.h" + #include "h3_h3Index.h" -/** @struct BaseCellOrient +/** @struct BaseCellRotation * @brief base cell at a given ijk and required rotations into its system */ typedef struct { int baseCell; ///< base cell number int ccwRot60; ///< number of ccw 60 degree rotations relative to current /// face -} BaseCellOrient; +} BaseCellRotation; /** @brief Neighboring base cell ID in each IJK direction. * @@ -304,7 +305,7 @@ const int baseCellNeighbor60CCWRots[NUM_BASE_CELLS][7] = { * This table can be accessed using the functions `_faceIjkToBaseCell` and * `_faceIjkToBaseCellCCWrot60` */ -static const BaseCellOrient faceIjkBaseCells[NUM_ICOSA_FACES][3][3][3] = { +static const BaseCellRotation faceIjkBaseCells[NUM_ICOSA_FACES][3][3][3] = { {// face 0 { // i 0 @@ -821,6 +822,10 @@ const BaseCellData baseCellData[NUM_BASE_CELLS] = { /** @brief Return whether or not the indicated base cell is a pentagon. */ int _isBaseCellPentagon(int baseCell) { + if (baseCell < 0 || baseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return false; + } return baseCellData[baseCell].isPentagon; } @@ -838,7 +843,7 @@ bool _isBaseCellPolarPentagon(int baseCell) { * * Valid ijk+ lookup coordinates are from (0, 0, 0) to (2, 2, 2). */ -int _faceIjkToBaseCell(const FaceIJK* h) { +int _faceIjkToBaseCell(const FaceIJK *h) { return faceIjkBaseCells[h->face][h->coord.i][h->coord.j][h->coord.k] .baseCell; } @@ -851,17 +856,38 @@ int _faceIjkToBaseCell(const FaceIJK* h) { * * Valid ijk+ lookup coordinates are from (0, 0, 0) to (2, 2, 2). */ -int _faceIjkToBaseCellCCWrot60(const FaceIJK* h) { +int _faceIjkToBaseCellCCWrot60(const FaceIJK *h) { return faceIjkBaseCells[h->face][h->coord.i][h->coord.j][h->coord.k] .ccwRot60; } /** @brief Find the FaceIJK given a base cell. */ -void _baseCellToFaceIjk(int baseCell, FaceIJK* h) { +void _baseCellToFaceIjk(int baseCell, FaceIJK *h) { *h = baseCellData[baseCell].homeFijk; } +/** + * @brief Given a base cell and the face it appears on, return + * the number of 60' ccw rotations for that base cell's + * coordinate system. + * @returns The number of rotations, or INVALID_ROTATIONS if the base + * cell is not found on the given face + */ +int _baseCellToCCWrot60(int baseCell, int face) { + if (face < 0 || face > NUM_ICOSA_FACES) return INVALID_ROTATIONS; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + for (int k = 0; k < 3; k++) { + if (faceIjkBaseCells[face][i][j][k].baseCell == baseCell) { + return faceIjkBaseCells[face][i][j][k].ccwRot60; + } + } + } + } + return INVALID_ROTATIONS; +} + /** @brief Return whether or not the tested face is a cw offset face. */ bool _baseCellIsCwOffset(int baseCell, int testFace) { @@ -889,23 +915,25 @@ Direction _getBaseCellDirection(int originBaseCell, int neighboringBaseCell) { } /** - * res0IndexCount returns the number of resolution 0 indexes + * res0CellCount returns the number of resolution 0 cells * - * @return int count of resolution 0 indexes + * @return int count of resolution 0 cells */ -int H3_EXPORT(res0IndexCount)() { return NUM_BASE_CELLS; } +int H3_EXPORT(res0CellCount)() { return NUM_BASE_CELLS; } /** - * getRes0Indexes generates all base cells storing them into the provided + * getRes0Cells generates all base cells storing them into the provided * memory pointer. Buffer must be of size NUM_BASE_CELLS * sizeof(H3Index). * * @param out H3Index* the memory to store the resulting base cells in + * @returns E_SUCCESS. */ -void H3_EXPORT(getRes0Indexes)(H3Index* out) { +H3Error H3_EXPORT(getRes0Cells)(H3Index *out) { for (int bc = 0; bc < NUM_BASE_CELLS; bc++) { H3Index baseCell = H3_INIT; - H3_SET_MODE(baseCell, H3_HEXAGON_MODE); + H3_SET_MODE(baseCell, H3_CELL_MODE); H3_SET_BASE_CELL(baseCell, bc); out[bc] = baseCell; } + return E_SUCCESS; } diff --git a/h3_baseCells.h b/h3_baseCells.h index 9e22773..b117300 100644 --- a/h3_baseCells.h +++ b/h3_baseCells.h @@ -45,12 +45,16 @@ extern const BaseCellData baseCellData[NUM_BASE_CELLS]; /** Maximum input for any component to face-to-base-cell lookup functions */ #define MAX_FACE_COORD 2 +/** Invalid number of rotations */ +#define INVALID_ROTATIONS -1 + // Internal functions int _isBaseCellPentagon(int baseCell); bool _isBaseCellPolarPentagon(int baseCell); -int _faceIjkToBaseCell(const FaceIJK* h); -int _faceIjkToBaseCellCCWrot60(const FaceIJK* h); -void _baseCellToFaceIjk(int baseCell, FaceIJK* h); +int _faceIjkToBaseCell(const FaceIJK *h); +int _faceIjkToBaseCellCCWrot60(const FaceIJK *h); +int _baseCellToCCWrot60(int baseCell, int face); +void _baseCellToFaceIjk(int baseCell, FaceIJK *h); bool _baseCellIsCwOffset(int baseCell, int testFace); int _getBaseCellNeighbor(int baseCell, Direction dir); Direction _getBaseCellDirection(int originBaseCell, int destinationBaseCell); diff --git a/h3_bbox.c b/h3_bbox.c index 2944d72..1f3b919 100644 --- a/h3_bbox.c +++ b/h3_bbox.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Uber Technologies, Inc. + * Copyright 2016-2021 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. @@ -18,30 +18,32 @@ */ #include "h3_bbox.h" + #include #include #include + #include "h3_constants.h" -#include "h3_geoCoord.h" #include "h3_h3Index.h" +#include "h3_latLng.h" /** * Whether the given bounding box crosses the antimeridian * @param bbox Bounding box to inspect * @return is transmeridian */ -bool bboxIsTransmeridian(const BBox* bbox) { return bbox->east < bbox->west; } +bool bboxIsTransmeridian(const BBox *bbox) { return bbox->east < bbox->west; } /** * Get the center of a bounding box * @param bbox Input bounding box * @param center Output center coordinate */ -void bboxCenter(const BBox* bbox, GeoCoord* center) { +void bboxCenter(const BBox *bbox, LatLng *center) { center->lat = (bbox->north + bbox->south) / 2.0; // If the bbox crosses the antimeridian, shift east 360 degrees double east = bboxIsTransmeridian(bbox) ? bbox->east + M_2PI : bbox->east; - center->lon = constrainLng((east + bbox->west) / 2.0); + center->lng = constrainLng((east + bbox->west) / 2.0); } /** @@ -50,14 +52,14 @@ void bboxCenter(const BBox* bbox, GeoCoord* center) { * @param point Point to test * @return Whether the point is contained */ -bool bboxContains(const BBox* bbox, const GeoCoord* point) { +bool bboxContains(const BBox *bbox, const LatLng *point) { return point->lat >= bbox->south && point->lat <= bbox->north && (bboxIsTransmeridian(bbox) ? // transmeridian case - (point->lon >= bbox->west || point->lon <= bbox->east) + (point->lng >= bbox->west || point->lng <= bbox->east) : // standard case - (point->lon >= bbox->west && point->lon <= bbox->east)); + (point->lng >= bbox->west && point->lng <= bbox->east)); } /** @@ -66,7 +68,7 @@ bool bboxContains(const BBox* bbox, const GeoCoord* point) { * @param b2 Bounding box 2 * @return Whether the boxes are equal */ -bool bboxEquals(const BBox* b1, const BBox* b2) { +bool bboxEquals(const BBox *b1, const BBox *b2) { return b1->north == b2->north && b1->south == b2->south && b1->east == b2->east && b1->west == b2->west; } @@ -80,42 +82,71 @@ bool bboxEquals(const BBox* b1, const BBox* b2) { double _hexRadiusKm(H3Index h3Index) { // There is probably a cheaper way to determine the radius of a // hexagon, but this way is conceptually simple - GeoCoord h3Center; - GeoBoundary h3Boundary; - H3_EXPORT(h3ToGeo)(h3Index, &h3Center); - H3_EXPORT(h3ToGeoBoundary)(h3Index, &h3Boundary); - return _geoDistKm(&h3Center, h3Boundary.verts); + LatLng h3Center; + CellBoundary h3Boundary; + H3_EXPORT(cellToLatLng)(h3Index, &h3Center); + H3_EXPORT(cellToBoundary)(h3Index, &h3Boundary); + return H3_EXPORT(greatCircleDistanceKm)(&h3Center, h3Boundary.verts); } /** - * Get the radius of the bbox in hexagons - i.e. the radius of a k-ring centered - * on the bbox center and covering the entire bbox. - * @param bbox Bounding box to measure - * @param res Resolution of hexagons to use in measurement - * @return Radius in hexagons + * bboxHexEstimate returns an estimated number of hexagons that fit + * within the cartesian-projected bounding box + * + * @param bbox the bounding box to estimate the hexagon fill level + * @param res the resolution of the H3 hexagons to fill the bounding box + * @return the estimated number of hexagons to fill the bounding box */ -int bboxHexRadius(const BBox* bbox, int res) { - // Determine the center of the bounding box - GeoCoord center; - bboxCenter(bbox, ¢er); +int64_t bboxHexEstimate(const BBox *bbox, int res) { + // Get the area of the pentagon as the maximally-distorted area possible + H3Index pentagons[12] = {0}; + // TODO: Return error here + H3_EXPORT(getPentagons)(res, pentagons); + double pentagonRadiusKm = _hexRadiusKm(pentagons[0]); + // Area of a regular hexagon is 3/2*sqrt(3) * r * r + // The pentagon has the most distortion (smallest edges) and shares its + // edges with hexagons, so the most-distorted hexagons have this area, + // shrunk by 20% off chance that the bounding box perfectly bounds a + // pentagon. + double pentagonAreaKm2 = + 0.8 * (2.59807621135 * pentagonRadiusKm * pentagonRadiusKm); - // Use a vertex on the side closest to the equator, to ensure the longest - // radius in cases with significant distortion. East/west is arbitrary. - double lat = - fabs(bbox->north) > fabs(bbox->south) ? bbox->south : bbox->north; - GeoCoord vertex = {lat, bbox->east}; + // Then get the area of the bounding box of the geoloop in question + LatLng p1, p2; + p1.lat = bbox->north; + p1.lng = bbox->east; + p2.lat = bbox->south; + p2.lng = bbox->west; + double d = H3_EXPORT(greatCircleDistanceKm)(&p1, &p2); + // Derived constant based on: https://math.stackexchange.com/a/1921940 + // Clamped to 3 as higher values tend to rapidly drag the estimate to zero. + double a = d * d / fmin(3.0, fabs((p1.lng - p2.lng) / (p1.lat - p2.lat))); - // Determine the length of the bounding box "radius" to then use - // as a circle on the earth that the k-rings must be greater than - double bboxRadiusKm = _geoDistKm(¢er, &vertex); + // Divide the two to get an estimate of the number of hexagons needed + int64_t estimate = (int64_t)ceil(a / pentagonAreaKm2); + if (estimate == 0) estimate = 1; + return estimate; +} - // Determine the radius of the center hexagon - double centerHexRadiusKm = _hexRadiusKm(H3_EXPORT(geoToH3)(¢er, res)); +/** + * lineHexEstimate returns an estimated number of hexagons that trace + * the cartesian-projected line + * + * @param origin the origin coordinates + * @param destination the destination coordinates + * @param res the resolution of the H3 hexagons to trace the line + * @return the estimated number of hexagons required to trace the line + */ +int64_t lineHexEstimate(const LatLng *origin, const LatLng *destination, + int res) { + // Get the area of the pentagon as the maximally-distorted area possible + H3Index pentagons[12] = {0}; + // TODO: Return error here + H3_EXPORT(getPentagons)(res, pentagons); + double pentagonRadiusKm = _hexRadiusKm(pentagons[0]); - // The closest point along a hexagon drawn through the center points - // of a k-ring aggregation is exactly 1.5 radii of the hexagon. For - // any orientation of the GeoJSON encased in a circle defined by the - // bounding box radius and center, it is guaranteed to fit in this k-ring - // Rounded *up* to guarantee containment - return (int)ceil(bboxRadiusKm / (1.5 * centerHexRadiusKm)); + double dist = H3_EXPORT(greatCircleDistanceKm)(origin, destination); + int64_t estimate = (int64_t)ceil(dist / (2 * pentagonRadiusKm)); + if (estimate == 0) estimate = 1; + return estimate; } diff --git a/h3_bbox.h b/h3_bbox.h index 9944795..2363f16 100644 --- a/h3_bbox.h +++ b/h3_bbox.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Uber Technologies, Inc. + * Copyright 2016-2017, 2020-2021 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. @@ -21,7 +21,8 @@ #define BBOX_H #include -#include "h3_geoCoord.h" + +#include "h3_latLng.h" /** @struct BBox * @brief Geographic bounding box with coordinates defined in radians @@ -33,10 +34,12 @@ typedef struct { double west; ///< west longitude } BBox; -bool bboxIsTransmeridian(const BBox* bbox); -void bboxCenter(const BBox* bbox, GeoCoord* center); -bool bboxContains(const BBox* bbox, const GeoCoord* point); -bool bboxEquals(const BBox* b1, const BBox* b2); -int bboxHexRadius(const BBox* bbox, int res); +bool bboxIsTransmeridian(const BBox *bbox); +void bboxCenter(const BBox *bbox, LatLng *center); +bool bboxContains(const BBox *bbox, const LatLng *point); +bool bboxEquals(const BBox *b1, const BBox *b2); +int64_t bboxHexEstimate(const BBox *bbox, int res); +int64_t lineHexEstimate(const LatLng *origin, const LatLng *destination, + int res); #endif diff --git a/h3_constants.h b/h3_constants.h index d6ef3ae..43863ef 100644 --- a/h3_constants.h +++ b/h3_constants.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Uber Technologies, Inc. + * Copyright 2016-2017, 2020 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. @@ -74,9 +74,13 @@ #define NUM_HEX_VERTS 6 /** The number of vertices in a pentagon */ #define NUM_PENT_VERTS 5 +/** The number of pentagons per resolution **/ +#define NUM_PENTAGONS 12 /** H3 index modes */ -#define H3_HEXAGON_MODE 1 -#define H3_UNIEDGE_MODE 2 +#define H3_CELL_MODE 1 +#define H3_DIRECTEDEDGE_MODE 2 +#define H3_EDGE_MODE 3 +#define H3_VERTEX_MODE 4 #endif diff --git a/h3_coordijk.c b/h3_coordijk.c index ac2254a..922d33d 100644 --- a/h3_coordijk.c +++ b/h3_coordijk.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2018, 2020-2021 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. @@ -15,16 +15,18 @@ */ /** @file coordijk.c * @brief Hex IJK coordinate systems functions including conversions to/from - * lat/lon. + * lat/lng. */ #include "h3_coordijk.h" + #include #include #include #include + #include "h3_constants.h" -#include "h3_geoCoord.h" +#include "h3_latLng.h" #include "h3_mathExtensions.h" /** @@ -35,7 +37,7 @@ * @param j The desired j component value. * @param k The desired k component value. */ -void _setIJK(CoordIJK* ijk, int i, int j, int k) { +void _setIJK(CoordIJK *ijk, int i, int j, int k) { ijk->i = i; ijk->j = j; ijk->k = k; @@ -48,7 +50,7 @@ void _setIJK(CoordIJK* ijk, int i, int j, int k) { * @param v The 2D cartesian coordinate vector. * @param h The ijk+ coordinates of the containing hex. */ -void _hex2dToCoordIJK(const Vec2d* v, CoordIJK* h) { +void _hex2dToCoordIJK(const Vec2d *v, CoordIJK *h) { double a1, a2; double x1, x2; int m1, m2; @@ -147,7 +149,7 @@ void _hex2dToCoordIJK(const Vec2d* v, CoordIJK* h) { * @param h The ijk coordinates of the hex. * @param v The 2D cartesian coordinates of the hex center point. */ -void _ijkToHex2d(const CoordIJK* h, Vec2d* v) { +void _ijkToHex2d(const CoordIJK *h, Vec2d *v) { int i = h->i - h->k; int j = h->j - h->k; @@ -163,7 +165,7 @@ void _ijkToHex2d(const CoordIJK* h, Vec2d* v) { * @param c2 The second set of ijk coordinates. * @return 1 if the two addresses match, 0 if they do not. */ -int _ijkMatches(const CoordIJK* c1, const CoordIJK* c2) { +int _ijkMatches(const CoordIJK *c1, const CoordIJK *c2) { return (c1->i == c2->i && c1->j == c2->j && c1->k == c2->k); } @@ -174,7 +176,7 @@ int _ijkMatches(const CoordIJK* c1, const CoordIJK* c2) { * @param h2 The second set of ijk coordinates. * @param sum The sum of the two sets of ijk coordinates. */ -void _ijkAdd(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* sum) { +void _ijkAdd(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *sum) { sum->i = h1->i + h2->i; sum->j = h1->j + h2->j; sum->k = h1->k + h2->k; @@ -187,7 +189,7 @@ void _ijkAdd(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* sum) { * @param h2 The second set of ijk coordinates. * @param diff The difference of the two sets of ijk coordinates (h1 - h2). */ -void _ijkSub(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* diff) { +void _ijkSub(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *diff) { diff->i = h1->i - h2->i; diff->j = h1->j - h2->j; diff->k = h1->k - h2->k; @@ -199,7 +201,7 @@ void _ijkSub(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* diff) { * @param c The ijk coordinates to scale. * @param factor The scaling factor. */ -void _ijkScale(CoordIJK* c, int factor) { +void _ijkScale(CoordIJK *c, int factor) { c->i *= factor; c->j *= factor; c->k *= factor; @@ -211,7 +213,7 @@ void _ijkScale(CoordIJK* c, int factor) { * * @param c The ijk coordinates to normalize. */ -void _ijkNormalize(CoordIJK* c) { +void _ijkNormalize(CoordIJK *c) { // remove any negative values if (c->i < 0) { c->j -= c->i; @@ -249,7 +251,7 @@ void _ijkNormalize(CoordIJK* c) { * @return The H3 digit (0-6) corresponding to the ijk unit vector, or * INVALID_DIGIT on failure. */ -Direction _unitIjkToDigit(const CoordIJK* ijk) { +Direction _unitIjkToDigit(const CoordIJK *ijk) { CoordIJK c = *ijk; _ijkNormalize(&c); @@ -270,7 +272,7 @@ Direction _unitIjkToDigit(const CoordIJK* ijk) { * * @param ijk The ijk coordinates. */ -void _upAp7(CoordIJK* ijk) { +void _upAp7(CoordIJK *ijk) { // convert to CoordIJ int i = ijk->i - ijk->k; int j = ijk->j - ijk->k; @@ -287,7 +289,7 @@ void _upAp7(CoordIJK* ijk) { * * @param ijk The ijk coordinates. */ -void _upAp7r(CoordIJK* ijk) { +void _upAp7r(CoordIJK *ijk) { // convert to CoordIJ int i = ijk->i - ijk->k; int j = ijk->j - ijk->k; @@ -305,7 +307,7 @@ void _upAp7r(CoordIJK* ijk) { * * @param ijk The ijk coordinates. */ -void _downAp7(CoordIJK* ijk) { +void _downAp7(CoordIJK *ijk) { // res r unit vectors in res r+1 CoordIJK iVec = {3, 0, 1}; CoordIJK jVec = {1, 3, 0}; @@ -327,7 +329,7 @@ void _downAp7(CoordIJK* ijk) { * * @param ijk The ijk coordinates. */ -void _downAp7r(CoordIJK* ijk) { +void _downAp7r(CoordIJK *ijk) { // res r unit vectors in res r+1 CoordIJK iVec = {3, 1, 0}; CoordIJK jVec = {0, 3, 1}; @@ -350,7 +352,7 @@ void _downAp7r(CoordIJK* ijk) { * @param ijk The ijk coordinates. * @param digit The digit direction from the original ijk coordinates. */ -void _neighbor(CoordIJK* ijk, Direction digit) { +void _neighbor(CoordIJK *ijk, Direction digit) { if (digit > CENTER_DIGIT && digit < NUM_DIGITS) { _ijkAdd(ijk, &UNIT_VECS[digit], ijk); _ijkNormalize(ijk); @@ -362,7 +364,7 @@ void _neighbor(CoordIJK* ijk, Direction digit) { * * @param ijk The ijk coordinates. */ -void _ijkRotate60ccw(CoordIJK* ijk) { +void _ijkRotate60ccw(CoordIJK *ijk) { // unit vector rotations CoordIJK iVec = {1, 1, 0}; CoordIJK jVec = {0, 1, 1}; @@ -383,7 +385,7 @@ void _ijkRotate60ccw(CoordIJK* ijk) { * * @param ijk The ijk coordinates. */ -void _ijkRotate60cw(CoordIJK* ijk) { +void _ijkRotate60cw(CoordIJK *ijk) { // unit vector rotations CoordIJK iVec = {1, 0, 1}; CoordIJK jVec = {1, 1, 0}; @@ -454,7 +456,7 @@ Direction _rotate60cw(Direction digit) { * * @param ijk The ijk coordinates. */ -void _downAp3(CoordIJK* ijk) { +void _downAp3(CoordIJK *ijk) { // res r unit vectors in res r+1 CoordIJK iVec = {2, 0, 1}; CoordIJK jVec = {1, 2, 0}; @@ -476,7 +478,7 @@ void _downAp3(CoordIJK* ijk) { * * @param ijk The ijk coordinates. */ -void _downAp3r(CoordIJK* ijk) { +void _downAp3r(CoordIJK *ijk) { // res r unit vectors in res r+1 CoordIJK iVec = {2, 1, 0}; CoordIJK jVec = {0, 2, 1}; @@ -498,7 +500,7 @@ void _downAp3r(CoordIJK* ijk) { * @param c1 The first set of ijk coordinates. * @param c2 The second set of ijk coordinates. */ -int ijkDistance(const CoordIJK* c1, const CoordIJK* c2) { +int ijkDistance(const CoordIJK *c1, const CoordIJK *c2) { CoordIJK diff; _ijkSub(c1, c2, &diff); _ijkNormalize(&diff); @@ -513,7 +515,7 @@ int ijkDistance(const CoordIJK* c1, const CoordIJK* c2) { * @param ijk The input IJK+ coordinates * @param ij The output IJ coordinates */ -void ijkToIj(const CoordIJK* ijk, CoordIJ* ij) { +void ijkToIj(const CoordIJK *ijk, CoordIJ *ij) { ij->i = ijk->i - ijk->k; ij->j = ijk->j - ijk->k; } @@ -525,7 +527,7 @@ void ijkToIj(const CoordIJK* ijk, CoordIJ* ij) { * @param ij The input IJ coordinates * @param ijk The output IJK+ coordinates */ -void ijToIjk(const CoordIJ* ij, CoordIJK* ijk) { +void ijToIjk(const CoordIJ *ij, CoordIJK *ijk) { ijk->i = ij->i; ijk->j = ij->j; ijk->k = 0; @@ -537,7 +539,7 @@ void ijToIjk(const CoordIJ* ij, CoordIJK* ijk) { * Convert IJK coordinates to cube coordinates, in place * @param ijk Coordinate to convert */ -void ijkToCube(CoordIJK* ijk) { +void ijkToCube(CoordIJK *ijk) { ijk->i = -ijk->i + ijk->k; ijk->j = ijk->j - ijk->k; ijk->k = -ijk->i - ijk->j; @@ -547,7 +549,7 @@ void ijkToCube(CoordIJK* ijk) { * Convert cube coordinates to IJK coordinates, in place * @param ijk Coordinate to convert */ -void cubeToIjk(CoordIJK* ijk) { +void cubeToIjk(CoordIJK *ijk) { ijk->i = -ijk->i; ijk->k = 0; _ijkNormalize(ijk); diff --git a/h3_coordijk.h b/h3_coordijk.h index e847fc7..38fd8a2 100644 --- a/h3_coordijk.h +++ b/h3_coordijk.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2018, 2020-2021 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. @@ -14,7 +14,7 @@ * limitations under the License. */ /** @file coordijk.h - * @brief Header file for CoordIJK functions including conversion from lat/lon + * @brief Header file for CoordIJK functions including conversion from lat/lng * * References two Vec2d cartesian coordinate systems: * @@ -30,8 +30,8 @@ #ifndef COORDIJK_H #define COORDIJK_H -#include "h3_geoCoord.h" #include "h3_h3api.h" +#include "h3_latLng.h" #include "h3_vec2d.h" /** @struct CoordIJK @@ -79,35 +79,37 @@ typedef enum { INVALID_DIGIT = 7, /** Valid digits will be less than this value. Same value as INVALID_DIGIT. */ - NUM_DIGITS = INVALID_DIGIT + NUM_DIGITS = INVALID_DIGIT, + /** Child digit which is skipped for pentagons */ + PENTAGON_SKIPPED_DIGIT = K_AXES_DIGIT /* 1 */ } Direction; // Internal functions -void _setIJK(CoordIJK* ijk, int i, int j, int k); -void _hex2dToCoordIJK(const Vec2d* v, CoordIJK* h); -void _ijkToHex2d(const CoordIJK* h, Vec2d* v); -int _ijkMatches(const CoordIJK* c1, const CoordIJK* c2); -void _ijkAdd(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* sum); -void _ijkSub(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* diff); -void _ijkScale(CoordIJK* c, int factor); -void _ijkNormalize(CoordIJK* c); -Direction _unitIjkToDigit(const CoordIJK* ijk); -void _upAp7(CoordIJK* ijk); -void _upAp7r(CoordIJK* ijk); -void _downAp7(CoordIJK* ijk); -void _downAp7r(CoordIJK* ijk); -void _downAp3(CoordIJK* ijk); -void _downAp3r(CoordIJK* ijk); -void _neighbor(CoordIJK* ijk, Direction digit); -void _ijkRotate60ccw(CoordIJK* ijk); -void _ijkRotate60cw(CoordIJK* ijk); +void _setIJK(CoordIJK *ijk, int i, int j, int k); +void _hex2dToCoordIJK(const Vec2d *v, CoordIJK *h); +void _ijkToHex2d(const CoordIJK *h, Vec2d *v); +int _ijkMatches(const CoordIJK *c1, const CoordIJK *c2); +void _ijkAdd(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *sum); +void _ijkSub(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *diff); +void _ijkScale(CoordIJK *c, int factor); +void _ijkNormalize(CoordIJK *c); +Direction _unitIjkToDigit(const CoordIJK *ijk); +void _upAp7(CoordIJK *ijk); +void _upAp7r(CoordIJK *ijk); +void _downAp7(CoordIJK *ijk); +void _downAp7r(CoordIJK *ijk); +void _downAp3(CoordIJK *ijk); +void _downAp3r(CoordIJK *ijk); +void _neighbor(CoordIJK *ijk, Direction digit); +void _ijkRotate60ccw(CoordIJK *ijk); +void _ijkRotate60cw(CoordIJK *ijk); Direction _rotate60ccw(Direction digit); Direction _rotate60cw(Direction digit); -int ijkDistance(const CoordIJK* a, const CoordIJK* b); -void ijkToIj(const CoordIJK* ijk, CoordIJ* ij); -void ijToIjk(const CoordIJ* ij, CoordIJK* ijk); -void ijkToCube(CoordIJK* ijk); -void cubeToIjk(CoordIJK* ijk); +int ijkDistance(const CoordIJK *a, const CoordIJK *b); +void ijkToIj(const CoordIJK *ijk, CoordIJ *ij); +void ijToIjk(const CoordIJ *ij, CoordIJK *ijk); +void ijkToCube(CoordIJK *ijk); +void cubeToIjk(CoordIJK *ijk); #endif diff --git a/h3_directedEdge.c b/h3_directedEdge.c new file mode 100644 index 0000000..7b0c08f --- /dev/null +++ b/h3_directedEdge.c @@ -0,0 +1,279 @@ +/* + * Copyright 2017-2018, 2020-2021 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. + */ +/** @file directedEdge.c + * @brief DirectedEdge functions for manipulating directed edge indexes. + */ + +#include +#include + +#include "h3_algos.h" +#include "h3_constants.h" +#include "h3_coordijk.h" +#include "h3_h3Index.h" +#include "h3_latLng.h" +#include "h3_vertex.h" + +/** + * Returns whether or not the provided H3Indexes are neighbors. + * @param origin The origin H3 index. + * @param destination The destination H3 index. + * @param out Set to 1 if the indexes are neighbors, 0 otherwise + * @return Error code if the origin or destination are invalid or incomparable. + */ +H3Error H3_EXPORT(areNeighborCells)(H3Index origin, H3Index destination, + int *out) { + // Make sure they're hexagon indexes + if (H3_GET_MODE(origin) != H3_CELL_MODE || + H3_GET_MODE(destination) != H3_CELL_MODE) { + return E_CELL_INVALID; + } + + // Hexagons cannot be neighbors with themselves + if (origin == destination) { + *out = 0; + return E_SUCCESS; + } + + // Only hexagons in the same resolution can be neighbors + if (H3_GET_RESOLUTION(origin) != H3_GET_RESOLUTION(destination)) { + return E_RES_MISMATCH; + } + + // H3 Indexes that share the same parent are very likely to be neighbors + // Child 0 is neighbor with all of its parent's 'offspring', the other + // children are neighbors with 3 of the 7 children. So a simple comparison + // of origin and destination parents and then a lookup table of the children + // is a super-cheap way to possibly determine they are neighbors. + int parentRes = H3_GET_RESOLUTION(origin) - 1; + if (parentRes > 0) { + // TODO: Return error codes here + H3Index originParent; + H3_EXPORT(cellToParent)(origin, parentRes, &originParent); + H3Index destinationParent; + H3_EXPORT(cellToParent)(destination, parentRes, &destinationParent); + if (originParent == destinationParent) { + Direction originResDigit = + H3_GET_INDEX_DIGIT(origin, parentRes + 1); + Direction destinationResDigit = + H3_GET_INDEX_DIGIT(destination, parentRes + 1); + if (originResDigit == CENTER_DIGIT || + destinationResDigit == CENTER_DIGIT) { + *out = 1; + return E_SUCCESS; + } + // These sets are the relevant neighbors in the clockwise + // and counter-clockwise + const Direction neighborSetClockwise[] = { + CENTER_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, J_AXES_DIGIT, + IK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT}; + const Direction neighborSetCounterclockwise[] = { + CENTER_DIGIT, IK_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, + IJ_AXES_DIGIT, I_AXES_DIGIT, J_AXES_DIGIT}; + if (neighborSetClockwise[originResDigit] == destinationResDigit || + neighborSetCounterclockwise[originResDigit] == + destinationResDigit) { + *out = 1; + return E_SUCCESS; + } + } + } + + // Otherwise, we have to determine the neighbor relationship the "hard" way. + H3Index neighborRing[7] = {0}; + H3_EXPORT(gridDisk)(origin, 1, neighborRing); + for (int i = 0; i < 7; i++) { + if (neighborRing[i] == destination) { + *out = 1; + return E_SUCCESS; + } + } + + // Made it here, they definitely aren't neighbors + *out = 0; + return E_SUCCESS; +} + +/** + * Returns a directed edge H3 index based on the provided origin and + * destination + * @param origin The origin H3 hexagon index + * @param destination The destination H3 hexagon index + * @return The directed edge H3Index, or H3_NULL on failure. + */ +H3Error H3_EXPORT(cellsToDirectedEdge)(H3Index origin, H3Index destination, + H3Index *out) { + // Determine the IJK direction from the origin to the destination + Direction direction = directionForNeighbor(origin, destination); + + // The direction will be invalid if the cells are not neighbors + if (direction == INVALID_DIGIT) { + return E_NOT_NEIGHBORS; + } + + // Create the edge index for the neighbor direction + H3Index output = origin; + H3_SET_MODE(output, H3_DIRECTEDEDGE_MODE); + H3_SET_RESERVED_BITS(output, direction); + + *out = output; + return E_SUCCESS; +} + +/** + * Returns the origin hexagon from the directed edge H3Index + * @param edge The edge H3 index + * @return The origin H3 hexagon index, or H3_NULL on failure + */ +H3Error H3_EXPORT(getDirectedEdgeOrigin)(H3Index edge, H3Index *out) { + if (H3_GET_MODE(edge) != H3_DIRECTEDEDGE_MODE) { + return E_DIR_EDGE_INVALID; + } + H3Index origin = edge; + H3_SET_MODE(origin, H3_CELL_MODE); + H3_SET_RESERVED_BITS(origin, 0); + *out = origin; + return E_SUCCESS; +} + +/** + * Returns the destination hexagon from the directed edge H3Index + * @param edge The edge H3 index + * @return The destination H3 hexagon index, or H3_NULL on failure + */ +H3Error H3_EXPORT(getDirectedEdgeDestination)(H3Index edge, H3Index *out) { + Direction direction = H3_GET_RESERVED_BITS(edge); + int rotations = 0; + H3Index origin; + // Note: This call is also checking for H3_DIRECTEDEDGE_MODE + H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin); + if (originResult) { + return originResult; + } + return h3NeighborRotations(origin, direction, &rotations, out); +} + +/** + * Determines if the provided H3Index is a valid directed edge index + * @param edge The directed edge H3Index + * @return 1 if it is a directed edge H3Index, otherwise 0. + */ +int H3_EXPORT(isValidDirectedEdge)(H3Index edge) { + Direction neighborDirection = H3_GET_RESERVED_BITS(edge); + if (neighborDirection <= CENTER_DIGIT || neighborDirection >= NUM_DIGITS) { + return 0; + } + + H3Index origin; + // Note: This call is also checking for H3_DIRECTEDEDGE_MODE + H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin); + if (originResult) { + return 0; + } + if (H3_EXPORT(isPentagon)(origin) && neighborDirection == K_AXES_DIGIT) { + return 0; + } + + return H3_EXPORT(isValidCell)(origin); +} + +/** + * Returns the origin, destination pair of hexagon IDs for the given edge ID + * @param edge The directed edge H3Index + * @param originDestination Pointer to memory to store origin and destination + * IDs + */ +H3Error H3_EXPORT(directedEdgeToCells)(H3Index edge, + H3Index *originDestination) { + H3Error originResult = + H3_EXPORT(getDirectedEdgeOrigin)(edge, &originDestination[0]); + if (originResult) { + return originResult; + } + H3Error destinationResult = + H3_EXPORT(getDirectedEdgeDestination)(edge, &originDestination[1]); + if (destinationResult) { + return destinationResult; + } + return E_SUCCESS; +} + +/** + * Provides all of the directed edges from the current H3Index. + * @param origin The origin hexagon H3Index to find edges for. + * @param edges The memory to store all of the edges inside. + */ +H3Error H3_EXPORT(originToDirectedEdges)(H3Index origin, H3Index *edges) { + // Determine if the origin is a pentagon and special treatment needed. + int isPent = H3_EXPORT(isPentagon)(origin); + + // This is actually quite simple. Just modify the bits of the origin + // slightly for each direction, except the 'k' direction in pentagons, + // which is zeroed. + for (int i = 0; i < 6; i++) { + if (isPent && i == 0) { + edges[i] = H3_NULL; + } else { + edges[i] = origin; + H3_SET_MODE(edges[i], H3_DIRECTEDEDGE_MODE); + H3_SET_RESERVED_BITS(edges[i], i + 1); + } + } + return E_SUCCESS; +} + +/** + * Provides the coordinates defining the directed edge. + * @param edge The directed edge H3Index + * @param cb The cellboundary object to store the edge coordinates. + */ +H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, CellBoundary *cb) { + // Get the origin and neighbor direction from the edge + Direction direction = H3_GET_RESERVED_BITS(edge); + H3Index origin; + H3Error originResult = H3_EXPORT(getDirectedEdgeOrigin)(edge, &origin); + if (originResult) { + return originResult; + } + + // Get the start vertex for the edge + int startVertex = vertexNumForDirection(origin, direction); + if (startVertex == INVALID_VERTEX_NUM) { + // This is not actually an edge (i.e. no valid direction), + // so return no vertices. + cb->numVerts = 0; + return E_DIR_EDGE_INVALID; + } + + // Get the geo boundary for the appropriate vertexes of the origin. Note + // that while there are always 2 topological vertexes per edge, the + // resulting edge boundary may have an additional distortion vertex if it + // crosses an edge of the icosahedron. + FaceIJK fijk; + H3Error fijkResult = _h3ToFaceIjk(origin, &fijk); + if (fijkResult) { + return fijkResult; + } + int res = H3_GET_RESOLUTION(origin); + int isPent = H3_EXPORT(isPentagon)(origin); + + if (isPent) { + _faceIjkPentToCellBoundary(&fijk, res, startVertex, 2, cb); + } else { + _faceIjkToCellBoundary(&fijk, res, startVertex, 2, cb); + } + return E_SUCCESS; +} diff --git a/v3/h3_h3UniEdge.h b/h3_directedEdge.h similarity index 77% rename from v3/h3_h3UniEdge.h rename to h3_directedEdge.h index bb4d913..3c7d97a 100644 --- a/v3/h3_h3UniEdge.h +++ b/h3_directedEdge.h @@ -1,5 +1,5 @@ /* - * Copyright 2017 Uber Technologies, Inc. + * Copyright 2017, 2020 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. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file h3UniEdge.h - * @brief H3UniEdge functions for manipulating unidirectional edge indexes. +/** @file directedEdge.h + * @brief DirectedEdge functions for manipulating directed edge indexes. */ -#ifndef H3UNIEDGE_H -#define H3UNIEDGE_H +#ifndef DIRECTEDEDGE_H +#define DIRECTEDEDGE_H #include "h3_algos.h" #include "h3_h3Index.h" diff --git a/h3_faceijk.c b/h3_faceijk.c index bf8b968..d31713e 100644 --- a/h3_faceijk.c +++ b/h3_faceijk.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Uber Technologies, Inc. + * Copyright 2016-2021 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. @@ -19,22 +19,24 @@ */ #include "h3_faceijk.h" + #include #include #include #include #include + #include "h3_constants.h" #include "h3_coordijk.h" -#include "h3_geoCoord.h" #include "h3_h3Index.h" +#include "h3_latLng.h" #include "h3_vec3d.h" /** square root of 7 */ #define M_SQRT7 2.6457513110645905905016157536392604257102L -/** @brief icosahedron face centers in lat/lon radians */ -const GeoCoord faceCenterGeo[NUM_ICOSA_FACES] = { +/** @brief icosahedron face centers in lat/lng radians */ +const LatLng faceCenterGeo[NUM_ICOSA_FACES] = { {0.803582649718989942, 1.248397419617396099}, // face 0 {1.307747883455638156, 2.536945009877921159}, // face 1 {1.054751253523952054, -1.347517358900396623}, // face 2 @@ -366,7 +368,7 @@ static const int unitScaleByCIIres[] = { * @param res The desired H3 resolution for the encoding. * @param h The FaceIJK address of the containing cell at resolution res. */ -void _geoToFaceIjk(const GeoCoord* g, int res, FaceIJK* h) { +void _geoToFaceIjk(const LatLng *g, int res, FaceIJK *h) { // first convert to hex2d Vec2d v; _geoToHex2d(g, res, &h->face, &v); @@ -384,20 +386,10 @@ void _geoToFaceIjk(const GeoCoord* g, int res, FaceIJK* h) { * @param face The icosahedral face containing the spherical coordinates. * @param v The 2D hex coordinates of the cell containing the point. */ -void _geoToHex2d(const GeoCoord* g, int res, int* face, Vec2d* v) { - Vec3d v3d; - _geoToVec3d(g, &v3d); - +void _geoToHex2d(const LatLng *g, int res, int *face, Vec2d *v) { // determine the icosahedron face - *face = 0; - double sqd = _pointSquareDist(&faceCenterPoint[0], &v3d); - for (int f = 1; f < NUM_ICOSA_FACES; f++) { - double sqdT = _pointSquareDist(&faceCenterPoint[f], &v3d); - if (sqdT < sqd) { - *face = f; - sqd = sqdT; - } - } + double sqd; + _geoToClosestFace(g, face, &sqd); // cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2 double r = acos(1 - sqd / 2); @@ -413,7 +405,8 @@ void _geoToHex2d(const GeoCoord* g, int res, int* face, Vec2d* v) { _posAngleRads(_geoAzimuthRads(&faceCenterGeo[*face], g))); // adjust theta for Class III (odd resolutions) - if (isResClassIII(res)) theta = _posAngleRads(theta - M_AP7_ROT_RADS); + if (isResolutionClassIII(res)) + theta = _posAngleRads(theta - M_AP7_ROT_RADS); // perform gnomonic scaling of r r = tan(r); @@ -441,8 +434,7 @@ void _geoToHex2d(const GeoCoord* g, int res, int* face, Vec2d* v) { * grid relative to the specified resolution. * @param g The spherical coordinates of the cell center point. */ -void _hex2dToGeo(const Vec2d* v, int face, int res, int substrate, - GeoCoord* g) { +void _hex2dToGeo(const Vec2d *v, int face, int res, int substrate, LatLng *g) { // calculate (r, theta) in hex2d double r = _v2dMag(v); @@ -459,7 +451,7 @@ void _hex2dToGeo(const Vec2d* v, int face, int res, int substrate, // scale accordingly if this is a substrate grid if (substrate) { r /= 3.0; - if (isResClassIII(res)) r /= M_SQRT7; + if (isResolutionClassIII(res)) r /= M_SQRT7; } r *= RES0_U_GNOMONIC; @@ -469,7 +461,7 @@ void _hex2dToGeo(const Vec2d* v, int face, int res, int substrate, // adjust theta for Class III // if a substrate grid, then it's already been adjusted for Class III - if (!substrate && isResClassIII(res)) + if (!substrate && isResolutionClassIII(res)) theta = _posAngleRads(theta + M_AP7_ROT_RADS); // find theta as an azimuth @@ -487,7 +479,7 @@ void _hex2dToGeo(const Vec2d* v, int face, int res, int substrate, * @param res The H3 resolution of the cell. * @param g The spherical coordinates of the cell center point. */ -void _faceIjkToGeo(const FaceIJK* h, int res, GeoCoord* g) { +void _faceIjkToGeo(const FaceIJK *h, int res, LatLng *g) { Vec2d v; _ijkToHex2d(&h->coord, &v); _hex2dToGeo(&v, h->face, res, 0, g); @@ -499,89 +491,38 @@ void _faceIjkToGeo(const FaceIJK* h, int res, GeoCoord* g) { * * @param h The FaceIJK address of the pentagonal cell. * @param res The H3 resolution of the cell. + * @param start The first topological vertex to return. + * @param length The number of topological vertexes to return. * @param g The spherical coordinates of the cell boundary. */ -void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, GeoBoundary* g) { - // the vertexes of an origin-centered pentagon in a Class II resolution on a - // substrate grid with aperture sequence 33r. The aperture 3 gets us the - // vertices, and the 3r gets us back to Class II. - // vertices listed ccw from the i-axes - CoordIJK vertsCII[NUM_PENT_VERTS] = { - {2, 1, 0}, // 0 - {1, 2, 0}, // 1 - {0, 2, 1}, // 2 - {0, 1, 2}, // 3 - {1, 0, 2}, // 4 - }; - - // the vertexes of an origin-centered pentagon in a Class III resolution on - // a substrate grid with aperture sequence 33r7r. The aperture 3 gets us the - // vertices, and the 3r7r gets us to Class II. vertices listed ccw from the - // i-axes - CoordIJK vertsCIII[NUM_PENT_VERTS] = { - {5, 4, 0}, // 0 - {1, 5, 0}, // 1 - {0, 5, 4}, // 2 - {0, 1, 5}, // 3 - {4, 0, 5}, // 4 - }; - - // get the correct set of substrate vertices for this resolution - CoordIJK* verts; - if (isResClassIII(res)) - verts = vertsCIII; - else - verts = vertsCII; - - // adjust the center point to be in an aperture 33r substrate grid - // these should be composed for speed - FaceIJK centerIJK = *h; - _downAp3(¢erIJK.coord); - _downAp3r(¢erIJK.coord); - - // if res is Class III we need to add a cw aperture 7 to get to - // icosahedral Class II +void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, + int length, CellBoundary *g) { int adjRes = res; - if (isResClassIII(res)) { - _downAp7r(¢erIJK.coord); - adjRes++; - } - - // The center point is now in the same substrate grid as the origin - // cell vertices. Add the center point substate coordinates - // to each vertex to translate the vertices to that cell. + FaceIJK centerIJK = *h; FaceIJK fijkVerts[NUM_PENT_VERTS]; - for (int v = 0; v < NUM_PENT_VERTS; v++) { - fijkVerts[v].face = centerIJK.face; - _ijkAdd(¢erIJK.coord, &verts[v], &fijkVerts[v].coord); - _ijkNormalize(&fijkVerts[v].coord); - } + _faceIjkPentToVerts(¢erIJK, &adjRes, fijkVerts); - // convert each vertex to lat/lon + // If we're returning the entire loop, we need one more iteration in case + // of a distortion vertex on the last edge + int additionalIteration = length == NUM_PENT_VERTS ? 1 : 0; + + // convert each vertex to lat/lng // adjust the face of each vertex as appropriate and introduce // edge-crossing vertices as needed g->numVerts = 0; FaceIJK lastFijk; - for (int vert = 0; vert < NUM_PENT_VERTS + 1; vert++) { + for (int vert = start; vert < start + length + additionalIteration; + vert++) { int v = vert % NUM_PENT_VERTS; FaceIJK fijk = fijkVerts[v]; - int pentLeading4 = 0; - int overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); - if (overage == 2) // in a different triangle - { - while (1) { - overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); - if (overage != 2) // not in a different triangle - break; - } - } + _adjustPentVertOverage(&fijk, adjRes); // all Class III pentagon edges cross icosa edges // note that Class II pentagons have vertices on the edge, // not edge intersections - if (isResClassIII(res) && vert > 0) { + if (isResolutionClassIII(res) && vert > start) { // find hex2d of the two vertexes on the last face FaceIJK tmpFijk = fijk; @@ -591,11 +532,11 @@ void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, GeoBoundary* g) { int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; - const FaceOrientIJK* fijkOrient = + const FaceOrientIJK *fijkOrient = &faceNeighbors[tmpFijk.face][currentToLastDir]; tmpFijk.face = fijkOrient->face; - CoordIJK* ijk = &tmpFijk.coord; + CoordIJK *ijk = &tmpFijk.coord; // rotate and translate for adjacent face for (int i = 0; i < fijkOrient->ccwRot60; i++) _ijkRotate60ccw(ijk); @@ -614,8 +555,8 @@ void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, GeoBoundary* g) { Vec2d v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; Vec2d v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; - Vec2d* edge0; - Vec2d* edge1; + Vec2d *edge0; + Vec2d *edge1; switch (adjacentFaceDir[tmpFijk.face][fijk.face]) { case IJ: edge0 = &v0; @@ -633,7 +574,7 @@ void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, GeoBoundary* g) { break; } - // find the intersection and add the lat/lon point to the result + // find the intersection and add the lat/lng point to the result Vec2d inter; _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); _hex2dToGeo(&inter, tmpFijk.face, adjRes, 1, @@ -641,10 +582,10 @@ void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, GeoBoundary* g) { g->numVerts++; } - // convert vertex to lat/lon and add to the result - // vert == NUM_PENT_VERTS is only used to test for possible intersection - // on last edge - if (vert < NUM_PENT_VERTS) { + // convert vertex to lat/lng and add to the result + // vert == start + NUM_PENT_VERTS is only used to test for possible + // intersection on last edge + if (vert < start + NUM_PENT_VERTS) { Vec2d vec; _ijkToHex2d(&fijk.coord, &vec); _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); @@ -656,91 +597,102 @@ void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, GeoBoundary* g) { } /** - * Generates the cell boundary in spherical coordinates for a cell given by a - * FaceIJK address at a specified resolution. + * Get the vertices of a pentagon cell as substrate FaceIJK addresses * - * @param h The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. - * @param isPentagon Whether or not the cell is a pentagon. - * @param g The spherical coordinates of the cell boundary. + * @param fijk The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. This may be adjusted if + * necessary for the substrate grid resolution. + * @param fijkVerts Output array for the vertices */ -void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, - GeoBoundary* g) { - if (isPentagon) { - _faceIjkPentToGeoBoundary(h, res, g); - return; - } - - // the vertexes of an origin-centered cell in a Class II resolution on a +void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { + // the vertexes of an origin-centered pentagon in a Class II resolution on a // substrate grid with aperture sequence 33r. The aperture 3 gets us the // vertices, and the 3r gets us back to Class II. // vertices listed ccw from the i-axes - CoordIJK vertsCII[NUM_HEX_VERTS] = { + CoordIJK vertsCII[NUM_PENT_VERTS] = { {2, 1, 0}, // 0 {1, 2, 0}, // 1 {0, 2, 1}, // 2 {0, 1, 2}, // 3 {1, 0, 2}, // 4 - {2, 0, 1} // 5 }; - // the vertexes of an origin-centered cell in a Class III resolution on a - // substrate grid with aperture sequence 33r7r. The aperture 3 gets us the - // vertices, and the 3r7r gets us to Class II. - // vertices listed ccw from the i-axes - CoordIJK vertsCIII[NUM_HEX_VERTS] = { + // the vertexes of an origin-centered pentagon in a Class III resolution on + // a substrate grid with aperture sequence 33r7r. The aperture 3 gets us the + // vertices, and the 3r7r gets us to Class II. vertices listed ccw from the + // i-axes + CoordIJK vertsCIII[NUM_PENT_VERTS] = { {5, 4, 0}, // 0 {1, 5, 0}, // 1 {0, 5, 4}, // 2 {0, 1, 5}, // 3 {4, 0, 5}, // 4 - {5, 0, 1} // 5 }; // get the correct set of substrate vertices for this resolution - CoordIJK* verts; - if (isResClassIII(res)) + CoordIJK *verts; + if (isResolutionClassIII(*res)) verts = vertsCIII; else verts = vertsCII; // adjust the center point to be in an aperture 33r substrate grid // these should be composed for speed - FaceIJK centerIJK = *h; - _downAp3(¢erIJK.coord); - _downAp3r(¢erIJK.coord); + _downAp3(&fijk->coord); + _downAp3r(&fijk->coord); // if res is Class III we need to add a cw aperture 7 to get to // icosahedral Class II - int adjRes = res; - if (isResClassIII(res)) { - _downAp7r(¢erIJK.coord); - adjRes++; + if (isResolutionClassIII(*res)) { + _downAp7r(&fijk->coord); + *res += 1; } // The center point is now in the same substrate grid as the origin // cell vertices. Add the center point substate coordinates // to each vertex to translate the vertices to that cell. - FaceIJK fijkVerts[NUM_HEX_VERTS]; - for (int v = 0; v < NUM_HEX_VERTS; v++) { - fijkVerts[v].face = centerIJK.face; - _ijkAdd(¢erIJK.coord, &verts[v], &fijkVerts[v].coord); + for (int v = 0; v < NUM_PENT_VERTS; v++) { + fijkVerts[v].face = fijk->face; + _ijkAdd(&fijk->coord, &verts[v], &fijkVerts[v].coord); _ijkNormalize(&fijkVerts[v].coord); } +} - // convert each vertex to lat/lon +/** + * Generates the cell boundary in spherical coordinates for a cell given by a + * FaceIJK address at a specified resolution. + * + * @param h The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. + * @param start The first topological vertex to return. + * @param length The number of topological vertexes to return. + * @param g The spherical coordinates of the cell boundary. + */ +void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, + CellBoundary *g) { + int adjRes = res; + FaceIJK centerIJK = *h; + FaceIJK fijkVerts[NUM_HEX_VERTS]; + _faceIjkToVerts(¢erIJK, &adjRes, fijkVerts); + + // If we're returning the entire loop, we need one more iteration in case + // of a distortion vertex on the last edge + int additionalIteration = length == NUM_HEX_VERTS ? 1 : 0; + + // convert each vertex to lat/lng // adjust the face of each vertex as appropriate and introduce // edge-crossing vertices as needed g->numVerts = 0; int lastFace = -1; - int lastOverage = 0; // 0: none; 1: edge; 2: overage - for (int vert = 0; vert < NUM_HEX_VERTS + 1; vert++) { + Overage lastOverage = NO_OVERAGE; + for (int vert = start; vert < start + length + additionalIteration; + vert++) { int v = vert % NUM_HEX_VERTS; FaceIJK fijk = fijkVerts[v]; - int pentLeading4 = 0; - int overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); + const int pentLeading4 = 0; + Overage overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); /* Check for edge-crossing. Each face of the underlying icosahedron is a @@ -751,8 +703,8 @@ void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, projection. Note that Class II cell edges have vertices on the face edge, with no edge line intersections. */ - if (isResClassIII(res) && vert > 0 && fijk.face != lastFace && - lastOverage != 1) { + if (isResolutionClassIII(res) && vert > start && + fijk.face != lastFace && lastOverage != FACE_EDGE) { // find hex2d of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; Vec2d orig2d0; @@ -768,8 +720,8 @@ void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, Vec2d v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; int face2 = ((lastFace == centerIJK.face) ? fijk.face : lastFace); - Vec2d* edge0; - Vec2d* edge1; + Vec2d *edge0; + Vec2d *edge1; switch (adjacentFaceDir[centerIJK.face][face2]) { case IJ: edge0 = &v0; @@ -779,7 +731,7 @@ void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, edge0 = &v1; edge1 = &v2; break; - case KI: + // case KI: default: assert(adjacentFaceDir[centerIJK.face][face2] == KI); edge0 = &v2; @@ -787,7 +739,7 @@ void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, break; } - // find the intersection and add the lat/lon point to the result + // find the intersection and add the lat/lng point to the result Vec2d inter; _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); /* @@ -804,10 +756,10 @@ void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, } } - // convert vertex to lat/lon and add to the result - // vert == NUM_HEX_VERTS is only used to test for possible intersection - // on last edge - if (vert < NUM_HEX_VERTS) { + // convert vertex to lat/lng and add to the result + // vert == start + NUM_HEX_VERTS is only used to test for possible + // intersection on last edge + if (vert < start + NUM_HEX_VERTS) { Vec2d vec; _ijkToHex2d(&fijk.coord, &vec); _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); @@ -819,6 +771,70 @@ void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, } } +/** + * Get the vertices of a cell as substrate FaceIJK addresses + * + * @param fijk The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. This may be adjusted if + * necessary for the substrate grid resolution. + * @param fijkVerts Output array for the vertices + */ +void _faceIjkToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { + // the vertexes of an origin-centered cell in a Class II resolution on a + // substrate grid with aperture sequence 33r. The aperture 3 gets us the + // vertices, and the 3r gets us back to Class II. + // vertices listed ccw from the i-axes + CoordIJK vertsCII[NUM_HEX_VERTS] = { + {2, 1, 0}, // 0 + {1, 2, 0}, // 1 + {0, 2, 1}, // 2 + {0, 1, 2}, // 3 + {1, 0, 2}, // 4 + {2, 0, 1} // 5 + }; + + // the vertexes of an origin-centered cell in a Class III resolution on a + // substrate grid with aperture sequence 33r7r. The aperture 3 gets us the + // vertices, and the 3r7r gets us to Class II. + // vertices listed ccw from the i-axes + CoordIJK vertsCIII[NUM_HEX_VERTS] = { + {5, 4, 0}, // 0 + {1, 5, 0}, // 1 + {0, 5, 4}, // 2 + {0, 1, 5}, // 3 + {4, 0, 5}, // 4 + {5, 0, 1} // 5 + }; + + // get the correct set of substrate vertices for this resolution + CoordIJK *verts; + if (isResolutionClassIII(*res)) + verts = vertsCIII; + else + verts = vertsCII; + + // adjust the center point to be in an aperture 33r substrate grid + // these should be composed for speed + _downAp3(&fijk->coord); + _downAp3r(&fijk->coord); + + // if res is Class III we need to add a cw aperture 7 to get to + // icosahedral Class II + if (isResolutionClassIII(*res)) { + _downAp7r(&fijk->coord); + *res += 1; + } + + // The center point is now in the same substrate grid as the origin + // cell vertices. Add the center point substate coordinates + // to each vertex to translate the vertices to that cell. + for (int v = 0; v < NUM_HEX_VERTS; v++) { + fijkVerts[v].face = fijk->face; + _ijkAdd(&fijk->coord, &verts[v], &fijkVerts[v].coord); + _ijkNormalize(&fijkVerts[v].coord); + } +} + /** * Adjusts a FaceIJK address in place so that the resulting cell address is * relative to the correct icosahedral face. @@ -831,11 +847,11 @@ void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, * @return 0 if on original face (no overage); 1 if on face edge (only occurs * on substrate grids); 2 if overage on new face interior */ -int _adjustOverageClassII(FaceIJK* fijk, int res, int pentLeading4, - int substrate) { - int overage = 0; +Overage _adjustOverageClassII(FaceIJK *fijk, int res, int pentLeading4, + int substrate) { + Overage overage = NO_OVERAGE; - CoordIJK* ijk = &fijk->coord; + CoordIJK *ijk = &fijk->coord; // get the maximum dimension value; scale if a substrate grid int maxDim = maxDimByCIIres[res]; @@ -843,12 +859,12 @@ int _adjustOverageClassII(FaceIJK* fijk, int res, int pentLeading4, // check for overage if (substrate && ijk->i + ijk->j + ijk->k == maxDim) // on edge - overage = 1; + overage = FACE_EDGE; else if (ijk->i + ijk->j + ijk->k > maxDim) // overage { - overage = 2; + overage = NEW_FACE; - const FaceOrientIJK* fijkOrient; + const FaceOrientIJK *fijkOrient; if (ijk->k > 0) { if (ijk->j > 0) // jk "quadrant" fijkOrient = &faceNeighbors[fijk->face][JK]; @@ -886,8 +902,51 @@ int _adjustOverageClassII(FaceIJK* fijk, int res, int pentLeading4, // overage points on pentagon boundaries can end up on edges if (substrate && ijk->i + ijk->j + ijk->k == maxDim) // on edge - overage = 1; + overage = FACE_EDGE; } return overage; } + +/** + * Adjusts a FaceIJK address for a pentagon vertex in a substrate grid in + * place so that the resulting cell address is relative to the correct + * icosahedral face. + * + * @param fijk The FaceIJK address of the cell. + * @param res The H3 resolution of the cell. + */ +Overage _adjustPentVertOverage(FaceIJK *fijk, int res) { + int pentLeading4 = 0; + Overage overage; + do { + overage = _adjustOverageClassII(fijk, res, pentLeading4, 1); + } while (overage == NEW_FACE); + return overage; +} + +/** + * Encodes a coordinate on the sphere to the corresponding icosahedral face and + * containing the squared euclidean distance to that face center. + * + * @param g The spherical coordinates to encode. + * @param face The icosahedral face containing the spherical coordinates. + * @param sqd The squared euclidean distance to its icosahedral face center. + */ +void _geoToClosestFace(const LatLng *g, int *face, double *sqd) { + Vec3d v3d; + _geoToVec3d(g, &v3d); + + // determine the icosahedron face + *face = 0; + // The distance between two farthest points is 2.0, therefore the square of + // the distance between two points should always be less or equal than 4.0 . + *sqd = 5.0; + for (int f = 0; f < NUM_ICOSA_FACES; ++f) { + double sqdT = _pointSquareDist(&faceCenterPoint[f], &v3d); + if (sqdT < *sqd) { + *face = f; + *sqd = sqdT; + } + } +} diff --git a/h3_faceijk.h b/h3_faceijk.h index 75aa2ba..c0ca107 100644 --- a/h3_faceijk.h +++ b/h3_faceijk.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2021 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. @@ -14,7 +14,7 @@ * limitations under the License. */ /** @file faceijk.h - * @brief FaceIJK functions including conversion to/from lat/lon. + * @brief FaceIJK functions including conversion to/from lat/lng. * * References the Vec2d cartesian coordinate systems hex2d: local face-centered * coordinate system scaled a specific H3 grid resolution unit length and @@ -25,7 +25,7 @@ #define FACEIJK_H #include "h3_coordijk.h" -#include "h3_geoCoord.h" +#include "h3_latLng.h" #include "h3_vec2d.h" /** @struct FaceIJK @@ -47,13 +47,9 @@ typedef struct { /// face } FaceOrientIJK; -extern const GeoCoord faceCenterGeo[NUM_ICOSA_FACES]; +extern const LatLng faceCenterGeo[NUM_ICOSA_FACES]; // indexes for faceNeighbors table -/** Invalid faceNeighbors table direction */ -#define INVALID -1 -/** Center faceNeighbors table direction */ -#define CENTER 0 /** IJ quadrant faceNeighbors table direction */ #define IJ 1 /** KI quadrant faceNeighbors table direction */ @@ -61,16 +57,34 @@ extern const GeoCoord faceCenterGeo[NUM_ICOSA_FACES]; /** JK quadrant faceNeighbors table direction */ #define JK 3 +/** Invalid face index */ +#define INVALID_FACE -1 + +/** Digit representing overage type */ +typedef enum { + /** No overage (on original face) */ + NO_OVERAGE = 0, + /** On face edge (only occurs on substrate grids) */ + FACE_EDGE = 1, + /** Overage on new face interior */ + NEW_FACE = 2 +} Overage; + // Internal functions -void _geoToFaceIjk(const GeoCoord* g, int res, FaceIJK* h); -void _geoToHex2d(const GeoCoord* g, int res, int* face, Vec2d* v); -void _faceIjkToGeo(const FaceIJK* h, int res, GeoCoord* g); -void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, - GeoBoundary* g); -void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, GeoBoundary* g); -void _hex2dToGeo(const Vec2d* v, int face, int res, int substrate, GeoCoord* g); -int _adjustOverageClassII(FaceIJK* fijk, int res, int pentLeading4, - int substrate); +void _geoToFaceIjk(const LatLng *g, int res, FaceIJK *h); +void _geoToHex2d(const LatLng *g, int res, int *face, Vec2d *v); +void _faceIjkToGeo(const FaceIJK *h, int res, LatLng *g); +void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, + CellBoundary *g); +void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, + int length, CellBoundary *g); +void _faceIjkToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts); +void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts); +void _hex2dToGeo(const Vec2d *v, int face, int res, int substrate, LatLng *g); +Overage _adjustOverageClassII(FaceIJK *fijk, int res, int pentLeading4, + int substrate); +Overage _adjustPentVertOverage(FaceIJK *fijk, int res); +void _geoToClosestFace(const LatLng *g, int *face, double *sqd); #endif diff --git a/h3_geoCoord.c b/h3_geoCoord.c deleted file mode 100644 index 0d40038..0000000 --- a/h3_geoCoord.c +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright 2016-2017 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. - */ -/** @file geoCoord.c - * @brief Functions for working with lat/lon coordinates. - */ - -#include "h3_geoCoord.h" -#include -#include -#include "h3_constants.h" -#include "h3_h3api.h" - -/** - * Normalizes radians to a value between 0.0 and two PI. - * - * @param rads The input radians value. - * @return The normalized radians value. - */ -double _posAngleRads(double rads) { - double tmp = ((rads < 0.0L) ? rads + M_2PI : rads); - if (rads >= M_2PI) tmp -= M_2PI; - return tmp; -} - -/** - * Determines if the components of two spherical coordinates are within some - * threshold distance of each other. - * - * @param p1 The first spherical coordinates. - * @param p2 The second spherical coordinates. - * @param threshold The threshold distance. - * @return Whether or not the two coordinates are within the threshold distance - * of each other. - */ -bool geoAlmostEqualThreshold(const GeoCoord* p1, const GeoCoord* p2, - double threshold) { - return fabs(p1->lat - p2->lat) < threshold && - fabs(p1->lon - p2->lon) < threshold; -} - -/** - * Determines if the components of two spherical coordinates are within our - * standard epsilon distance of each other. - * - * @param p1 The first spherical coordinates. - * @param p2 The second spherical coordinates. - * @return Whether or not the two coordinates are within the epsilon distance - * of each other. - */ -bool geoAlmostEqual(const GeoCoord* p1, const GeoCoord* p2) { - return geoAlmostEqualThreshold(p1, p2, EPSILON_RAD); -} - -/** - * Set the components of spherical coordinates in decimal degrees. - * - * @param p The spherical coodinates. - * @param latDegs The desired latitidue in decimal degrees. - * @param lonDegs The desired longitude in decimal degrees. - */ -void setGeoDegs(GeoCoord* p, double latDegs, double lonDegs) { - _setGeoRads(p, H3_EXPORT(degsToRads)(latDegs), - H3_EXPORT(degsToRads)(lonDegs)); -} - -/** - * Set the components of spherical coordinates in radians. - * - * @param p The spherical coodinates. - * @param latRads The desired latitidue in decimal radians. - * @param lonRads The desired longitude in decimal radians. - */ -void _setGeoRads(GeoCoord* p, double latRads, double lonRads) { - p->lat = latRads; - p->lon = lonRads; -} - -/** - * Convert from decimal degrees to radians. - * - * @param degrees The decimal degrees. - * @return The corresponding radians. - */ -double H3_EXPORT(degsToRads)(double degrees) { return degrees * M_PI_180; } - -/** - * Convert from radians to decimal degrees. - * - * @param radians The radians. - * @return The corresponding decimal degrees. - */ -double H3_EXPORT(radsToDegs)(double radians) { return radians * M_180_PI; } - -/** - * constrainLat makes sure latitudes are in the proper bounds - * - * @param lat The original lat value - * @return The corrected lat value - */ -double constrainLat(double lat) { - while (lat > M_PI_2) { - lat = lat - M_PI; - } - return lat; -} - -/** - * constrainLng makes sure longitudes are in the proper bounds - * - * @param lng The origin lng value - * @return The corrected lng value - */ -double constrainLng(double lng) { - while (lng > M_PI) { - lng = lng - (2 * M_PI); - } - while (lng < -M_PI) { - lng = lng + (2 * M_PI); - } - return lng; -} - -/** - * Find the great circle distance in radians between two spherical coordinates. - * - * @param p1 The first spherical coordinates. - * @param p2 The second spherical coordinates. - * @return The great circle distance in radians between p1 and p2. - */ -double _geoDistRads(const GeoCoord* p1, const GeoCoord* p2) { - // use spherical triangle with p1 at A, p2 at B, and north pole at C - double bigC = fabs(p2->lon - p1->lon); - if (bigC > M_PI) // assume we want the complement - { - // note that in this case they can't both be negative - double lon1 = p1->lon; - if (lon1 < 0.0L) lon1 += 2.0L * M_PI; - double lon2 = p2->lon; - if (lon2 < 0.0L) lon2 += 2.0L * M_PI; - - bigC = fabs(lon2 - lon1); - } - - double b = M_PI_2 - p1->lat; - double a = M_PI_2 - p2->lat; - - // use law of cosines to find c - double cosc = cos(a) * cos(b) + sin(a) * sin(b) * cos(bigC); - if (cosc > 1.0L) cosc = 1.0L; - if (cosc < -1.0L) cosc = -1.0L; - - return acos(cosc); -} - -/** - * Find the great circle distance in kilometers between two spherical - * coordinates. - * - * @param p1 The first spherical coordinates. - * @param p2 The second spherical coordinates. - * @return The distance in kilometers between p1 and p2. - */ -double _geoDistKm(const GeoCoord* p1, const GeoCoord* p2) { - return EARTH_RADIUS_KM * _geoDistRads(p1, p2); -} - -/** - * Determines the azimuth to p2 from p1 in radians. - * - * @param p1 The first spherical coordinates. - * @param p2 The second spherical coordinates. - * @return The azimuth in radians from p1 to p2. - */ -double _geoAzimuthRads(const GeoCoord* p1, const GeoCoord* p2) { - return atan2(cos(p2->lat) * sin(p2->lon - p1->lon), - cos(p1->lat) * sin(p2->lat) - - sin(p1->lat) * cos(p2->lat) * cos(p2->lon - p1->lon)); -} - -/** - * Computes the point on the sphere a specified azimuth and distance from - * another point. - * - * @param p1 The first spherical coordinates. - * @param az The desired azimuth from p1. - * @param distance The desired distance from p1, must be non-negative. - * @param p2 The spherical coordinates at the desired azimuth and distance from - * p1. - */ -void _geoAzDistanceRads(const GeoCoord* p1, double az, double distance, - GeoCoord* p2) { - if (distance < EPSILON) { - *p2 = *p1; - return; - } - - double sinlat, sinlon, coslon; - - az = _posAngleRads(az); - - // check for due north/south azimuth - if (az < EPSILON || fabs(az - M_PI) < EPSILON) { - if (az < EPSILON) // due north - p2->lat = p1->lat + distance; - else // due south - p2->lat = p1->lat - distance; - - if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole - { - p2->lat = M_PI_2; - p2->lon = 0.0L; - } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole - { - p2->lat = -M_PI_2; - p2->lon = 0.0L; - } else - p2->lon = constrainLng(p1->lon); - } else // not due north or south - { - sinlat = sin(p1->lat) * cos(distance) + - cos(p1->lat) * sin(distance) * cos(az); - if (sinlat > 1.0L) sinlat = 1.0L; - if (sinlat < -1.0L) sinlat = -1.0L; - p2->lat = asin(sinlat); - if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole - { - p2->lat = M_PI_2; - p2->lon = 0.0L; - } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole - { - p2->lat = -M_PI_2; - p2->lon = 0.0L; - } else { - sinlon = sin(az) * sin(distance) / cos(p2->lat); - coslon = (cos(distance) - sin(p1->lat) * sin(p2->lat)) / - cos(p1->lat) / cos(p2->lat); - if (sinlon > 1.0L) sinlon = 1.0L; - if (sinlon < -1.0L) sinlon = -1.0L; - if (coslon > 1.0L) sinlon = 1.0L; - if (coslon < -1.0L) sinlon = -1.0L; - p2->lon = constrainLng(p1->lon + atan2(sinlon, coslon)); - } - } -} - -/* - * The following functions provide meta information about the H3 hexagons at - * each zoom level. Since there are only 16 total levels, these are current - * handled with hardwired static values, but it may be worthwhile to put these - * static values into another file that can be autogenerated by source code in - * the future. - */ - -double H3_EXPORT(hexAreaKm2)(int res) { - static const double areas[] = { - 4250546.848, 607220.9782, 86745.85403, 12392.26486, - 1770.323552, 252.9033645, 36.1290521, 5.1612932, - 0.7373276, 0.1053325, 0.0150475, 0.0021496, - 0.0003071, 0.0000439, 0.0000063, 0.0000009}; - return areas[res]; -} - -double H3_EXPORT(hexAreaM2)(int res) { - static const double areas[] = { - 4.25055E+12, 6.07221E+11, 86745854035, 12392264862, - 1770323552, 252903364.5, 36129052.1, 5161293.2, - 737327.6, 105332.5, 15047.5, 2149.6, - 307.1, 43.9, 6.3, 0.9}; - return areas[res]; -} - -double H3_EXPORT(edgeLengthKm)(int res) { - static const double lens[] = { - 1107.712591, 418.6760055, 158.2446558, 59.81085794, - 22.6063794, 8.544408276, 3.229482772, 1.220629759, - 0.461354684, 0.174375668, 0.065907807, 0.024910561, - 0.009415526, 0.003559893, 0.001348575, 0.000509713}; - return lens[res]; -} - -double H3_EXPORT(edgeLengthM)(int res) { - static const double lens[] = { - 1107712.591, 418676.0055, 158244.6558, 59810.85794, - 22606.3794, 8544.408276, 3229.482772, 1220.629759, - 461.3546837, 174.3756681, 65.90780749, 24.9105614, - 9.415526211, 3.559893033, 1.348574562, 0.509713273}; - return lens[res]; -} - -/** @brief Number of unique valid H3Indexes at given resolution. */ -int64_t H3_EXPORT(numHexagons)(int res) { - static const int64_t nums[] = {122L, - 842L, - 5882L, - 41162L, - 288122L, - 2016842L, - 14117882L, - 98825162L, - 691776122L, - 4842432842L, - 33897029882L, - 237279209162L, - 1660954464122L, - 11626681248842L, - 81386768741882L, - 569707381193162L}; - return nums[res]; -} diff --git a/h3_geoCoord.h b/h3_geoCoord.h deleted file mode 100644 index 91f1b99..0000000 --- a/h3_geoCoord.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2016-2017 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. - */ -/** @file geoCoord.h - * @brief Geodetic (lat/lon) functions. - */ - -#ifndef GEOCOORD_H -#define GEOCOORD_H - -#include -#include -#include -#include "h3_constants.h" -#include "h3_h3api.h" - -/** epsilon of ~0.1mm in degrees */ -#define EPSILON_DEG .000000001 -/** epsilon of ~0.1mm in radians */ -#define EPSILON_RAD (EPSILON_DEG * M_PI_180) - -void setGeoDegs(GeoCoord* p, double latDegs, double lonDegs); -double constrainLat(double lat); -double constrainLng(double lng); - -bool geoAlmostEqual(const GeoCoord* p1, const GeoCoord* p2); -bool geoAlmostEqualThreshold(const GeoCoord* p1, const GeoCoord* p2, - double threshold); - -// Internal functions - -double _posAngleRads(double rads); -void _setGeoRads(GeoCoord* p, double latRads, double lonRads); -double _geoDistRads(const GeoCoord* p1, const GeoCoord* p2); -double _geoDistKm(const GeoCoord* p1, const GeoCoord* p2); -double _geoAzimuthRads(const GeoCoord* p1, const GeoCoord* p2); -void _geoAzDistanceRads(const GeoCoord* p1, double az, double distance, - GeoCoord* p2); - -#endif diff --git a/h3_h3Index.c b/h3_h3Index.c index 7dd64db..2784405 100644 --- a/h3_h3Index.c +++ b/h3_h3Index.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2021 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. @@ -18,39 +18,52 @@ * (see h3api.h for the main library entry functions) */ #include "h3_h3Index.h" + + #include #include #include #include + +#include "h3_alloc.h" #include "h3_baseCells.h" #include "h3_faceijk.h" +#include "h3_iterators.h" #include "h3_mathExtensions.h" -#include "h3_stackAlloc.h" /** * Returns the H3 resolution of an H3 index. * @param h The H3 index. * @return The resolution of the H3 index argument. */ -int H3_EXPORT(h3GetResolution)(H3Index h) { return H3_GET_RESOLUTION(h); } +int H3_EXPORT(getResolution)(H3Index h) { return H3_GET_RESOLUTION(h); } /** - * Returns the H3 base cell number of an H3 index. - * @param h The H3 index. - * @return The base cell of the H3 index argument. + * Returns the H3 base cell "number" of an H3 cell (hexagon or pentagon). + * + * Note: Technically works on H3 edges, but will return base cell of the + * origin cell. + * + * @param h The H3 cell. + * @return The base cell "number" of the H3 cell argument. */ -int H3_EXPORT(h3GetBaseCell)(H3Index h) { return H3_GET_BASE_CELL(h); } +int H3_EXPORT(getBaseCellNumber)(H3Index h) { return H3_GET_BASE_CELL(h); } /** * Converts a string representation of an H3 index into an H3 index. * @param str The string representation of an H3 index. - * @return The H3 index corresponding to the string argument, or 0 if invalid. + * @return The H3 index corresponding to the string argument, or H3_NULL if + * invalid. */ -H3Index H3_EXPORT(stringToH3)(const char* str) { - H3Index h = H3_INVALID_INDEX; - // If failed, h will be unmodified and we should return 0 anyways. - sscanf(str, "%" PRIx64, &h); - return h; +H3Error H3_EXPORT(stringToH3)(const char *str, H3Index *out) { + H3Index h = H3_NULL; + // If failed, h will be unmodified and we should return H3_NULL anyways. + int read = sscanf(str, "%" PRIx64, &h); + if (read != 1) { + return E_FAILED; + } + *out = h; + return E_SUCCESS; } /** @@ -59,29 +72,40 @@ H3Index H3_EXPORT(stringToH3)(const char* str) { * @param str The string representation of the H3 index. * @param sz Size of the buffer `str` */ -void H3_EXPORT(h3ToString)(H3Index h, char* str, size_t sz) { +H3Error H3_EXPORT(h3ToString)(H3Index h, char *str, size_t sz) { // An unsigned 64 bit integer will be expressed in at most // 16 digits plus 1 for the null terminator. if (sz < 17) { // Buffer is potentially not large enough. - return; + return E_MEMORY_BOUNDS; } sprintf(str, "%" PRIx64, h); + return E_SUCCESS; } /** - * Returns whether or not an H3 index is valid. + * Returns whether or not an H3 index is a valid cell (hexagon or pentagon). * @param h The H3 index to validate. * @return 1 if the H3 index if valid, and 0 if it is not. */ -int H3_EXPORT(h3IsValid)(H3Index h) { - if (H3_GET_MODE(h) != H3_HEXAGON_MODE) return 0; +int H3_EXPORT(isValidCell)(H3Index h) { + if (H3_GET_HIGH_BIT(h) != 0) return 0; + + if (H3_GET_MODE(h) != H3_CELL_MODE) return 0; + + if (H3_GET_RESERVED_BITS(h) != 0) return 0; int baseCell = H3_GET_BASE_CELL(h); - if (baseCell < 0 || baseCell >= NUM_BASE_CELLS) return 0; + if (baseCell < 0 || baseCell >= NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + return 0; + } int res = H3_GET_RESOLUTION(h); - if (res < 0 || res > MAX_H3_RES) return 0; + if (res < 0 || res > MAX_H3_RES) { // LCOV_EXCL_BR_LINE + // Resolutions less than zero can not be represented in an index + return 0; + } bool foundFirstNonZeroDigit = false; for (int r = 1; r <= res; r++) { @@ -112,9 +136,9 @@ int H3_EXPORT(h3IsValid)(H3Index h) { * @param baseCell The H3 base cell to initialize the index to. * @param initDigit The H3 digit (0-7) to initialize all of the index digits to. */ -void setH3Index(H3Index* hp, int res, int baseCell, Direction initDigit) { +void setH3Index(H3Index *hp, int res, int baseCell, Direction initDigit) { H3Index h = H3_INIT; - H3_SET_MODE(h, H3_HEXAGON_MODE); + H3_SET_MODE(h, H3_CELL_MODE); H3_SET_RESOLUTION(h, res); H3_SET_BASE_CELL(h, baseCell); for (int r = 1; r <= res; r++) H3_SET_INDEX_DIGIT(h, r, initDigit); @@ -122,45 +146,69 @@ void setH3Index(H3Index* hp, int res, int baseCell, Direction initDigit) { } /** - * h3ToParent produces the parent index for a given H3 index + * cellToParent produces the parent index for a given H3 index * * @param h H3Index to find parent of * @param parentRes The resolution to switch to (parent, grandparent, etc) * - * @return H3Index of the parent, or 0 if you actually asked for a child + * @return H3Index of the parent, or H3_NULL if you actually asked for a child */ -H3Index H3_EXPORT(h3ToParent)(H3Index h, int parentRes) { +H3Error H3_EXPORT(cellToParent)(H3Index h, int parentRes, H3Index *out) { int childRes = H3_GET_RESOLUTION(h); - if (parentRes > childRes) { - return H3_INVALID_INDEX; + if (parentRes < 0 || parentRes > MAX_H3_RES) { + return E_RES_DOMAIN; + } else if (parentRes > childRes) { + return E_RES_MISMATCH; } else if (parentRes == childRes) { - return h; - } else if (parentRes < 0 || parentRes > MAX_H3_RES) { - return H3_INVALID_INDEX; + *out = h; + return E_SUCCESS; } H3Index parentH = H3_SET_RESOLUTION(h, parentRes); for (int i = parentRes + 1; i <= childRes; i++) { H3_SET_INDEX_DIGIT(parentH, i, H3_DIGIT_MASK); } - return parentH; + *out = parentH; + return E_SUCCESS; } /** - * maxH3ToChildrenSize returns the maximum number of children possible for a - * given child level. + * Determines whether one resolution is a valid child resolution for a cell. + * Each resolution is considered a valid child resolution of itself. * - * @param h H3Index to find the number of children of - * @param childRes The resolution of the child level you're interested in + * @param h h3Index parent cell + * @param childRes int resolution of the child * - * @return int count of maximum number of children (equal for hexagons, less for - * pentagons + * @return The validity of the child resolution */ -int H3_EXPORT(maxH3ToChildrenSize)(H3Index h, int childRes) { +static bool _hasChildAtRes(H3Index h, int childRes) { int parentRes = H3_GET_RESOLUTION(h); - if (parentRes > childRes) { - return 0; + if (childRes < parentRes || childRes > MAX_H3_RES) { + return false; } - return _ipow(7, (childRes - parentRes)); + return true; +} + +/** + * cellToChildrenSize returns the exact number of children for a cell at a + * given child resolution. + * + * @param h H3Index to find the number of children of + * @param childRes The child resolution you're interested in + * + * @return int Exact number of children (handles hexagons and pentagons + * correctly) + */ +H3Error H3_EXPORT(cellToChildrenSize)(H3Index h, int childRes, int64_t *out) { + if (!_hasChildAtRes(h, childRes)) return E_RES_DOMAIN; + + int n = childRes - H3_GET_RESOLUTION(h); + + if (H3_EXPORT(isPentagon)(h)) { + *out = 1 + 5 * (_ipow(7, n) - 1) / 6; + } else { + *out = _ipow(7, n); + } + return E_SUCCESS; } /** @@ -181,64 +229,96 @@ H3Index makeDirectChild(H3Index h, int cellNumber) { } /** - * h3ToChildren takes the given hexagon id and generates all of the children + * cellToChildren takes the given hexagon id and generates all of the children * at the specified resolution storing them into the provided memory pointer. - * It's assumed that maxH3ToChildrenSize was used to determine the allocation. + * It's assumed that cellToChildrenSize was used to determine the allocation. * * @param h H3Index to find the children of * @param childRes int the child level to produce * @param children H3Index* the memory to store the resulting addresses in */ -void H3_EXPORT(h3ToChildren)(H3Index h, int childRes, H3Index* children) { - int parentRes = H3_GET_RESOLUTION(h); - if (parentRes > childRes) { - return; - } else if (parentRes == childRes) { - *children = h; - return; - } - int bufferSize = H3_EXPORT(maxH3ToChildrenSize)(h, childRes); - int bufferChildStep = (bufferSize / 7); - int isAPentagon = H3_EXPORT(h3IsPentagon)(h); - for (int i = 0; i < 7; i++) { - if (isAPentagon && i == K_AXES_DIGIT) { - H3Index* nextChild = children + bufferChildStep; - while (children < nextChild) { - *children = H3_INVALID_INDEX; - children++; - } - } else { - H3_EXPORT(h3ToChildren)(makeDirectChild(h, i), childRes, children); - children += bufferChildStep; - } +H3Error H3_EXPORT(cellToChildren)(H3Index h, int childRes, H3Index *children) { + int64_t i = 0; + for (IterCellsChildren iter = iterInitParent(h, childRes); iter.h; + iterStepChild(&iter)) { + children[i] = iter.h; + i++; } + return E_SUCCESS; +} + +/** + * Zero out index digits from start to end, inclusive. + * No-op if start > end. + */ +H3Index _zeroIndexDigits(H3Index h, int start, int end) { + if (start > end) return h; + + H3Index m = 0; + + m = ~m; + m <<= H3_PER_DIGIT_OFFSET * (end - start + 1); + m = ~m; + m <<= H3_PER_DIGIT_OFFSET * (MAX_H3_RES - end); + m = ~m; + + return h & m; +} + +/** + * cellToCenterChild produces the center child index for a given H3 index at + * the specified resolution + * + * @param h H3Index to find center child of + * @param childRes The resolution to switch to + * @param child H3Index of the center child + * @return 0 (E_SUCCESS) on success + */ +H3Error H3_EXPORT(cellToCenterChild)(H3Index h, int childRes, H3Index *child) { + if (!_hasChildAtRes(h, childRes)) return E_RES_DOMAIN; + + h = _zeroIndexDigits(h, H3_GET_RESOLUTION(h) + 1, childRes); + H3_SET_RESOLUTION(h, childRes); + *child = h; + return E_SUCCESS; } /** - * compact takes a set of hexagons all at the same resolution and compresses - * them by pruning full child branches to the parent level. This is also done - * for all parents recursively to get the minimum number of hex addresses that - * perfectly cover the defined space. + * compactCells takes a set of hexagons all at the same resolution and + * compresses them by pruning full child branches to the parent level. This is + * also done for all parents recursively to get the minimum number of hex + * addresses that perfectly cover the defined space. * @param h3Set Set of hexagons * @param compactedSet The output array of compressed hexagons (preallocated) * @param numHexes The size of the input and output arrays (possible that no * contiguous regions exist in the set at all and no compression possible) * @return an error code on bad input data */ -int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet, - const int numHexes) { +// todo: update internal implementation for int64_t +H3Error H3_EXPORT(compactCells)(const H3Index *h3Set, H3Index *compactedSet, + const int64_t numHexes) { + if (numHexes == 0) { + return E_SUCCESS; + } int res = H3_GET_RESOLUTION(h3Set[0]); if (res == 0) { // No compaction possible, just copy the set to output for (int i = 0; i < numHexes; i++) { compactedSet[i] = h3Set[i]; } - return 0; + return E_SUCCESS; + } + H3Index *remainingHexes = H3_MEMORY(malloc)(numHexes * sizeof(H3Index)); + if (!remainingHexes) { + return E_MEMORY_ALLOC; } - H3Index* remainingHexes = malloc(numHexes * sizeof(H3Index)); memcpy(remainingHexes, h3Set, numHexes * sizeof(H3Index)); - H3Index* hashSetArray = calloc(numHexes, sizeof(H3Index)); - H3Index* compactedSetOffset = compactedSet; + H3Index *hashSetArray = H3_MEMORY(calloc)(numHexes, sizeof(H3Index)); + if (!hashSetArray) { + H3_MEMORY(free)(remainingHexes); + return E_MEMORY_ALLOC; + } + H3Index *compactedSetOffset = compactedSet; int numRemainingHexes = numHexes; while (numRemainingHexes) { res = H3_GET_RESOLUTION(remainingHexes[0]); @@ -249,33 +329,61 @@ int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet, for (int i = 0; i < numRemainingHexes; i++) { H3Index currIndex = remainingHexes[i]; if (currIndex != 0) { - H3Index parent = H3_EXPORT(h3ToParent)(currIndex, parentRes); + // If the reserved bits were set by the caller, the + // algorithm below may encounter undefined behavior + // because it expects to have set the reserved bits + // itself. + if (H3_GET_RESERVED_BITS(currIndex) != 0) { + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_CELL_INVALID; + } + + H3Index parent; + H3Error parentError = + H3_EXPORT(cellToParent)(currIndex, parentRes, &parent); + // Should never be reachable as a result of the compact + // algorithm. Can happen if cellToParent errors e.g. + // because of incompatible resolutions. + if (parentError) { + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return parentError; + } // Modulus hash the parent into the temp array int loc = (int)(parent % numRemainingHexes); int loopCount = 0; while (hashSetArray[loc] != 0) { - if (loopCount > numRemainingHexes) { + if (loopCount > numRemainingHexes) { // LCOV_EXCL_BR_LINE // LCOV_EXCL_START // This case should not be possible because at most one // index is placed into hashSetArray per // numRemainingHexes. - free(remainingHexes); - free(hashSetArray); - return -1; + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_FAILED; // LCOV_EXCL_STOP } H3Index tempIndex = hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; if (tempIndex == parent) { int count = H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; - if (count > 7) { + int limitCount = 7; + if (H3_EXPORT(isPentagon)(tempIndex & + H3_RESERVED_MASK_NEGATIVE)) { + limitCount--; + } + // One is added to count for this check to match one + // being added to count later in this function when + // checking for all children being present. + if (count + 1 > limitCount) { // Only possible on duplicate input - free(remainingHexes); - free(hashSetArray); - return -2; + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_DUPLICATE_INPUT; } H3_SET_RESERVED_BITS(parent, count); - hashSetArray[loc] = H3_INVALID_INDEX; + hashSetArray[loc] = H3_NULL; } else { loc = (loc + 1) % numRemainingHexes; } @@ -294,14 +402,19 @@ int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet, numRemainingHexes * sizeof(remainingHexes[0])); break; } - H3Index* compactableHexes = - malloc(maxCompactableCount * sizeof(H3Index)); + H3Index *compactableHexes = + H3_MEMORY(calloc)(maxCompactableCount, sizeof(H3Index)); + if (!compactableHexes) { + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_MEMORY_ALLOC; + } for (int i = 0; i < numRemainingHexes; i++) { if (hashSetArray[i] == 0) continue; int count = H3_GET_RESERVED_BITS(hashSetArray[i]) + 1; // Include the deleted direction for pentagons as implicitly "there" - if (H3_EXPORT(h3IsPentagon)(hashSetArray[i] & - H3_RESERVED_MASK_NEGATIVE)) { + if (H3_EXPORT(isPentagon)(hashSetArray[i] & + H3_RESERVED_MASK_NEGATIVE)) { // We need this later on, no need to recalculate H3_SET_RESERVED_BITS(hashSetArray[i], count); // Increment count after setting the reserved bits, @@ -321,8 +434,20 @@ int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet, int uncompactableCount = 0; for (int i = 0; i < numRemainingHexes; i++) { H3Index currIndex = remainingHexes[i]; - if (currIndex != H3_INVALID_INDEX) { - H3Index parent = H3_EXPORT(h3ToParent)(currIndex, parentRes); + if (currIndex != H3_NULL) { + H3Index parent; + H3Error parentError = + H3_EXPORT(cellToParent)(currIndex, parentRes, &parent); + // LCOV_EXCL_START + // Should never be reachable as a result of the compact + // algorithm. + if (parentError) { + // TODO: Determine if this is somehow reachable. + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return parentError; + } + // LCOV_EXCL_STOP // Modulus hash the parent into the temp array // to determine if this index was included in // the compactableHexes array @@ -330,14 +455,14 @@ int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet, int loopCount = 0; bool isUncompactable = true; do { - if (loopCount > numRemainingHexes) { + if (loopCount > numRemainingHexes) { // LCOV_EXCL_BR_LINE // LCOV_EXCL_START // This case should not be possible because at most one // index is placed into hashSetArray per input hexagon. - free(compactableHexes); - free(remainingHexes); - free(hashSetArray); - return -1; // Only possible on duplicate input + H3_MEMORY(free)(compactableHexes); + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_FAILED; // LCOV_EXCL_STOP } H3Index tempIndex = @@ -365,105 +490,90 @@ int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet, memcpy(remainingHexes, compactableHexes, compactableCount * sizeof(H3Index)); numRemainingHexes = compactableCount; - free(compactableHexes); + H3_MEMORY(free)(compactableHexes); } - free(remainingHexes); - free(hashSetArray); - return 0; + H3_MEMORY(free)(remainingHexes); + H3_MEMORY(free)(hashSetArray); + return E_SUCCESS; } /** - * uncompact takes a compressed set of hexagons and expands back to the - * original set of hexagons. - * @param compactedSet Set of hexagons - * @param numHexes The number of hexes in the input set - * @param h3Set Output array of decompressed hexagons (preallocated) - * @param maxHexes The size of the output array to bound check against - * @param res The hexagon resolution to decompress to - * @return An error code if output array is too small or any hexagon is - * smaller than the output resolution. - */ -int H3_EXPORT(uncompact)(const H3Index* compactedSet, const int numHexes, - H3Index* h3Set, const int maxHexes, const int res) { - int outOffset = 0; - for (int i = 0; i < numHexes; i++) { - if (outOffset >= maxHexes) { - // We went too far, abort! - return -1; - } - if (compactedSet[i] == 0) continue; - int currentRes = H3_GET_RESOLUTION(compactedSet[i]); - if (currentRes > res) { - // Nonsensical. Abort. - return -2; - } - if (currentRes == res) { - // Just copy and move along - h3Set[outOffset] = compactedSet[i]; - outOffset++; - } else { - // Bigger hexagon to reduce in size - int numHexesToGen = - H3_EXPORT(maxH3ToChildrenSize)(compactedSet[i], res); - if (outOffset + numHexesToGen > maxHexes) { - // We're about to go too far, abort! - return -1; - } - H3_EXPORT(h3ToChildren)(compactedSet[i], res, h3Set + outOffset); - outOffset += numHexesToGen; + * uncompactCells takes a compressed set of cells and expands back to the + * original set of cells. + * + * Skips elements that are H3_NULL (i.e., 0). + * + * @param compactSet Set of compacted cells + * @param numCompact The number of cells in the input compacted set + * @param outSet Output array for decompressed cells (preallocated) + * @param numOut The size of the output array to bound check against + * @param res The H3 resolution to decompress to + * @return An error code if output array is too small or any cell + * is smaller than the output resolution. + */ +H3Error H3_EXPORT(uncompactCells)(const H3Index *compactedSet, + const int64_t numCompacted, H3Index *outSet, + const int64_t numOut, const int res) { + int64_t i = 0; + + for (int64_t j = 0; j < numCompacted; j++) { + if (!_hasChildAtRes(compactedSet[j], res)) return E_RES_MISMATCH; + + for (IterCellsChildren iter = iterInitParent(compactedSet[j], res); + iter.h; i++, iterStepChild(&iter)) { + if (i >= numOut) return E_MEMORY_BOUNDS; // went too far; abort! + outSet[i] = iter.h; } } - return 0; + return E_SUCCESS; } /** - * maxUncompactSize takes a compacted set of hexagons are provides an - * upper-bound estimate of the size of the uncompacted set of hexagons. - * @param compactedSet Set of hexagons - * @param numHexes The number of hexes in the input set - * @param res The hexagon resolution to decompress to - * @return The number of hexagons to allocate memory for, or a negative - * number if an error occurs. - */ -int H3_EXPORT(maxUncompactSize)(const H3Index* compactedSet, const int numHexes, - const int res) { - int maxNumHexagons = 0; - for (int i = 0; i < numHexes; i++) { - if (compactedSet[i] == 0) continue; - int currentRes = H3_GET_RESOLUTION(compactedSet[i]); - if (currentRes > res) { - // Nonsensical. Abort. - return -1; - } - if (currentRes == res) { - maxNumHexagons++; - } else { - // Bigger hexagon to reduce in size - int numHexesToGen = - H3_EXPORT(maxH3ToChildrenSize)(compactedSet[i], res); - maxNumHexagons += numHexesToGen; + * uncompactCellsSize takes a compacted set of hexagons and provides + * the exact size of the uncompacted set of hexagons. + * + * @param compactedSet Set of hexagons + * @param numHexes The number of hexes in the input set + * @param res The hexagon resolution to decompress to + * @param out The number of hexagons to allocate memory for + * @returns E_SUCCESS on success, or another value on error + */ +H3Error H3_EXPORT(uncompactCellsSize)(const H3Index *compactedSet, + const int64_t numCompacted, const int res, + int64_t *out) { + int64_t numOut = 0; + for (int64_t i = 0; i < numCompacted; i++) { + if (compactedSet[i] == H3_NULL) continue; + + int64_t childrenSize; + H3Error childrenError = + H3_EXPORT(cellToChildrenSize)(compactedSet[i], res, &childrenSize); + if (childrenError) { + // The parent res does not contain `res`. + return E_RES_MISMATCH; } + numOut += childrenSize; } - return maxNumHexagons; + *out = numOut; + return E_SUCCESS; } /** - * h3IsResClassIII takes a hexagon ID and determines if it is in a + * isResClassIII takes a hexagon ID and determines if it is in a * Class III resolution (rotated versus the icosahedron and subject * to shape distortion adding extra points on icosahedron edges, making * them not true hexagons). * @param h The H3Index to check. * @return Returns 1 if the hexagon is class III, otherwise 0. */ -int H3_EXPORT(h3IsResClassIII)(H3Index h) { return H3_GET_RESOLUTION(h) % 2; } +int H3_EXPORT(isResClassIII)(H3Index h) { return H3_GET_RESOLUTION(h) % 2; } /** - * h3IsPentagon takes an H3Index and determines if it is actually a - * pentagon. + * isPentagon takes an H3Index and determines if it is actually a pentagon. * @param h The H3Index to check. * @return Returns 1 if it is a pentagon, otherwise 0. */ -int H3_EXPORT(h3IsPentagon)(H3Index h) { +int H3_EXPORT(isPentagon)(H3Index h) { return _isBaseCellPentagon(H3_GET_BASE_CELL(h)) && !_h3LeadingNonZeroDigit(h); } @@ -561,12 +671,12 @@ H3Index _h3Rotate60cw(H3Index h) { * Convert an FaceIJK address to the corresponding H3Index. * @param fijk The FaceIJK address. * @param res The cell resolution. - * @return The encoded H3Index (or 0 on failure). + * @return The encoded H3Index (or H3_NULL on failure). */ -H3Index _faceIjkToH3(const FaceIJK* fijk, int res) { +H3Index _faceIjkToH3(const FaceIJK *fijk, int res) { // initialize the index H3Index h = H3_INIT; - H3_SET_MODE(h, H3_HEXAGON_MODE); + H3_SET_MODE(h, H3_CELL_MODE); H3_SET_RESOLUTION(h, res); // check for res 0/base cell @@ -574,7 +684,7 @@ H3Index _faceIjkToH3(const FaceIJK* fijk, int res) { if (fijk->coord.i > MAX_FACE_COORD || fijk->coord.j > MAX_FACE_COORD || fijk->coord.k > MAX_FACE_COORD) { // out of range input - return H3_INVALID_INDEX; + return H3_NULL; } H3_SET_BASE_CELL(h, _faceIjkToBaseCell(fijk)); @@ -589,11 +699,11 @@ H3Index _faceIjkToH3(const FaceIJK* fijk, int res) { // build the H3Index from finest res up // adjust r for the fact that the res 0 base cell offsets the indexing // digits - CoordIJK* ijk = &fijkBC.coord; + CoordIJK *ijk = &fijkBC.coord; for (int r = res - 1; r >= 0; r--) { CoordIJK lastIJK = *ijk; CoordIJK lastCenter; - if (isResClassIII(r + 1)) { + if (isResolutionClassIII(r + 1)) { // rotate ccw _upAp7(ijk); lastCenter = *ijk; @@ -618,7 +728,7 @@ H3Index _faceIjkToH3(const FaceIJK* fijk, int res) { if (fijkBC.coord.i > MAX_FACE_COORD || fijkBC.coord.j > MAX_FACE_COORD || fijkBC.coord.k > MAX_FACE_COORD) { // out of range input - return H3_INVALID_INDEX; + return H3_NULL; } // lookup the correct base cell @@ -657,19 +767,25 @@ H3Index _faceIjkToH3(const FaceIJK* fijk, int res) { * * @param g The spherical coordinates to encode. * @param res The desired H3 resolution for the encoding. - * @return The encoded H3Index (or 0 on failure). + * @param out The encoded H3Index. + * @returns E_SUCCESS (0) on success, another value otherwise */ -H3Index H3_EXPORT(geoToH3)(const GeoCoord* g, int res) { +H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { if (res < 0 || res > MAX_H3_RES) { - return H3_INVALID_INDEX; + return E_RES_DOMAIN; } - if (!isfinite(g->lat) || !isfinite(g->lon)) { - return H3_INVALID_INDEX; + if (!isfinite(g->lat) || !isfinite(g->lng)) { + return E_LATLNG_DOMAIN; } FaceIJK fijk; _geoToFaceIjk(g, res, &fijk); - return _faceIjkToH3(&fijk, res); + *out = _faceIjkToH3(&fijk, res); + if (*out) { + return E_SUCCESS; + } else { + return E_FAILED; + } } /** @@ -679,8 +795,8 @@ H3Index H3_EXPORT(geoToH3)(const GeoCoord* g, int res) { * and normalized base cell coordinates. * @return Returns 1 if the possibility of overage exists, otherwise 0. */ -int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK* fijk) { - CoordIJK* ijk = &fijk->coord; +int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { + CoordIJK *ijk = &fijk->coord; int res = H3_GET_RESOLUTION(h); // center base cell hierarchy is entirely on this face @@ -691,7 +807,7 @@ int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK* fijk) { possibleOverage = 0; for (int r = 1; r <= res; r++) { - if (isResClassIII(r)) { + if (isResolutionClassIII(r)) { // Class III == rotate ccw _downAp7(ijk); } else { @@ -710,8 +826,15 @@ int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK* fijk) { * @param h The H3Index. * @param fijk The corresponding FaceIJK address. */ -void _h3ToFaceIjk(H3Index h, FaceIJK* fijk) { +H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { int baseCell = H3_GET_BASE_CELL(h); + if (baseCell < 0 || baseCell >= NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + // To prevent reading uninitialized memory, we zero the output. + fijk->face = 0; + fijk->coord.i = fijk->coord.j = fijk->coord.k = 0; + return E_CELL_INVALID; + } // adjust for the pentagonal missing sequence; all of sub-sequence 5 needs // to be adjusted (and some of sub-sequence 4 below) if (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 5) @@ -720,7 +843,7 @@ void _h3ToFaceIjk(H3Index h, FaceIJK* fijk) { // start with the "home" face and ijk+ coordinates for the base cell of c *fijk = baseCellData[baseCell].homeFijk; if (!_h3ToFaceIjkWithInitializedFijk(h, fijk)) - return; // no overage is possible; h lies on this face + return E_SUCCESS; // no overage is possible; h lies on this face // if we're here we have the potential for an "overage"; i.e., it is // possible that c lies on an adjacent face @@ -729,7 +852,7 @@ void _h3ToFaceIjk(H3Index h, FaceIJK* fijk) { // if we're in Class III, drop into the next finer Class II grid int res = H3_GET_RESOLUTION(h); - if (isResClassIII(res)) { + if (isResolutionClassIII(res)) { // Class III _downAp7r(&fijk->coord); res++; @@ -739,19 +862,19 @@ void _h3ToFaceIjk(H3Index h, FaceIJK* fijk) { // a pentagon base cell with a leading 4 digit requires special handling int pentLeading4 = (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 4); - if (_adjustOverageClassII(fijk, res, pentLeading4, 0)) { + if (_adjustOverageClassII(fijk, res, pentLeading4, 0) != NO_OVERAGE) { // if the base cell is a pentagon we have the potential for secondary // overages if (_isBaseCellPentagon(baseCell)) { - while (1) { - if (!_adjustOverageClassII(fijk, res, 0, 0)) break; - } + while (_adjustOverageClassII(fijk, res, 0, 0) != NO_OVERAGE) + continue; } if (res != H3_GET_RESOLUTION(h)) _upAp7r(&fijk->coord); } else if (res != H3_GET_RESOLUTION(h)) { fijk->coord = origIJK; } + return E_SUCCESS; } /** @@ -760,23 +883,161 @@ void _h3ToFaceIjk(H3Index h, FaceIJK* fijk) { * @param h3 The H3 index. * @param g The spherical coordinates of the H3 cell center. */ -void H3_EXPORT(h3ToGeo)(H3Index h3, GeoCoord* g) { +H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { FaceIJK fijk; - _h3ToFaceIjk(h3, &fijk); + H3Error e = _h3ToFaceIjk(h3, &fijk); + if (e) { + return e; + } _faceIjkToGeo(&fijk, H3_GET_RESOLUTION(h3), g); + return E_SUCCESS; } /** * Determines the cell boundary in spherical coordinates for an H3 index. * * @param h3 The H3 index. - * @param gb The boundary of the H3 cell in spherical coordinates. + * @param cb The boundary of the H3 cell in spherical coordinates. */ -void H3_EXPORT(h3ToGeoBoundary)(H3Index h3, GeoBoundary* gb) { +H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *cb) { FaceIJK fijk; - _h3ToFaceIjk(h3, &fijk); - _faceIjkToGeoBoundary(&fijk, H3_GET_RESOLUTION(h3), - H3_EXPORT(h3IsPentagon)(h3), gb); + H3Error e = _h3ToFaceIjk(h3, &fijk); + if (e) { + return e; + } + if (H3_EXPORT(isPentagon)(h3)) { + _faceIjkPentToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, + NUM_PENT_VERTS, cb); + } else { + _faceIjkToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_HEX_VERTS, + cb); + } + return E_SUCCESS; +} + +/** + * Returns the max number of possible icosahedron faces an H3 index + * may intersect. + * + * @return int count of faces + */ +H3Error H3_EXPORT(maxFaceCount)(H3Index h3, int *out) { + // a pentagon always intersects 5 faces, a hexagon never intersects more + // than 2 (but may only intersect 1) + *out = H3_EXPORT(isPentagon)(h3) ? 5 : 2; + return E_SUCCESS; +} + +/** + * Find all icosahedron faces intersected by a given H3 index, represented + * as integers from 0-19. The array is sparse; since 0 is a valid value, + * invalid array values are represented as -1. It is the responsibility of + * the caller to filter out invalid values. + * + * @param h3 The H3 index + * @param out Output array. Must be of size maxFaceCount(h3). + */ +H3Error H3_EXPORT(getIcosahedronFaces)(H3Index h3, int *out) { + int res = H3_GET_RESOLUTION(h3); + int isPent = H3_EXPORT(isPentagon)(h3); + + // We can't use the vertex-based approach here for class II pentagons, + // because all their vertices are on the icosahedron edges. Their + // direct child pentagons cross the same faces, so use those instead. + if (isPent && !isResolutionClassIII(res)) { + // Note that this would not work for res 15, but this is only run on + // Class II pentagons, it should never be invoked for a res 15 index. + H3Index childPentagon = makeDirectChild(h3, 0); + return H3_EXPORT(getIcosahedronFaces)(childPentagon, out); + } + + // convert to FaceIJK + FaceIJK fijk; + H3Error err = _h3ToFaceIjk(h3, &fijk); + if (err) { + return err; + } + + // Get all vertices as FaceIJK addresses. For simplicity, always + // initialize the array with 6 verts, ignoring the last one for pentagons + FaceIJK fijkVerts[NUM_HEX_VERTS]; + int vertexCount; + + if (isPent) { + vertexCount = NUM_PENT_VERTS; + _faceIjkPentToVerts(&fijk, &res, fijkVerts); + } else { + vertexCount = NUM_HEX_VERTS; + _faceIjkToVerts(&fijk, &res, fijkVerts); + } + + // We may not use all of the slots in the output array, + // so fill with invalid values to indicate unused slots + int faceCount; + H3Error maxFaceCountError = H3_EXPORT(maxFaceCount)(h3, &faceCount); + if (maxFaceCountError != E_SUCCESS) { + return maxFaceCountError; + } + for (int i = 0; i < faceCount; i++) { + out[i] = INVALID_FACE; + } + + // add each vertex face, using the output array as a hash set + for (int i = 0; i < vertexCount; i++) { + FaceIJK *vert = &fijkVerts[i]; + + // Adjust overage, determining whether this vertex is + // on another face + if (isPent) { + _adjustPentVertOverage(vert, res); + } else { + _adjustOverageClassII(vert, res, 0, 1); + } + + // Save the face to the output array + int face = vert->face; + int pos = 0; + // Find the first empty output position, or the first position + // matching the current face + while (out[pos] != INVALID_FACE && out[pos] != face) { + pos++; + if (pos >= faceCount) { + // Mismatch between the heuristic used in maxFaceCount and + // calculation here - indicates an invalid index. + return E_FAILED; + } + } + out[pos] = face; + } + return E_SUCCESS; +} + +/** + * pentagonCount returns the number of pentagons (same at any resolution) + * + * @return int count of pentagon indexes + */ +int H3_EXPORT(pentagonCount)() { return NUM_PENTAGONS; } + +/** + * Generates all pentagons at the specified resolution + * + * @param res The resolution to produce pentagons at. + * @param out Output array. Must be of size pentagonCount(). + */ +H3Error H3_EXPORT(getPentagons)(int res, H3Index *out) { + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + int i = 0; + for (int bc = 0; bc < NUM_BASE_CELLS; bc++) { + if (_isBaseCellPentagon(bc)) { + H3Index pentagon; + setH3Index(&pentagon, res, bc, 0); + out[i++] = pentagon; + } + } + return E_SUCCESS; } /** @@ -786,4 +1047,4 @@ void H3_EXPORT(h3ToGeoBoundary)(H3Index h3, GeoBoundary* gb) { * @return 1 if the resolution is a Class III grid, and 0 if the resolution is * a Class II grid. */ -int isResClassIII(int res) { return res % 2; } +int isResolutionClassIII(int res) { return res % 2; } diff --git a/h3_h3Index.h b/h3_h3Index.h index e2647dd..857e065 100644 --- a/h3_h3Index.h +++ b/h3_h3Index.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2018, 2020 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. @@ -46,6 +46,12 @@ /** The number of bits in a single H3 resolution digit. */ #define H3_PER_DIGIT_OFFSET 3 +/** 1 in the highest bit, 0's everywhere else. */ +#define H3_HIGH_BIT_MASK ((uint64_t)(1) << H3_MAX_OFFSET) + +/** 0 in the highest bit, 1's everywhere else. */ +#define H3_HIGH_BIT_MASK_NEGATIVE (~H3_HIGH_BIT_MASK) + /** 1's in the 4 mode bits, 0's everywhere else. */ #define H3_MODE_MASK ((uint64_t)(15) << H3_MODE_OFFSET) @@ -74,11 +80,27 @@ #define H3_DIGIT_MASK ((uint64_t)(7)) /** 0's in the 7 base cell bits, 1's everywhere else. */ -#define H3_DIGIT_MASK_NEGATIVE (~H3_DIGIT_MASK_NEGATIVE) +#define H3_DIGIT_MASK_NEGATIVE (~H3_DIGIT_MASK) -/** H3 index with mode 0, res 0, base cell 0, and 7 for all index digits. */ +/** + * H3 index with mode 0, res 0, base cell 0, and 7 for all index digits. + * Typically used to initialize the creation of an H3 cell index, which + * expects all direction digits to be 7 beyond the cell's resolution. + */ #define H3_INIT (UINT64_C(35184372088831)) +/** + * Gets the highest bit of the H3 index. + */ +#define H3_GET_HIGH_BIT(h3) ((int)((((h3)&H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))) + +/** + * Sets the highest bit of the h3 to v. + */ +#define H3_SET_HIGH_BIT(h3, v) \ + (h3) = (((h3)&H3_HIGH_BIT_MASK_NEGATIVE) | \ + (((uint64_t)(v)) << H3_MAX_OFFSET)) + /** * Gets the integer mode of h3. */ @@ -142,22 +164,19 @@ (((uint64_t)(digit)) \ << ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET))) -/** - * Invalid index used to indicate an error from geoToH3 and related functions. - */ -#define H3_INVALID_INDEX 0 - -void setH3Index(H3Index* h, int res, int baseCell, Direction initDigit); -int isResClassIII(int res); +void setH3Index(H3Index *h, int res, int baseCell, Direction initDigit); +int isResolutionClassIII(int r); // Internal functions -int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK* fijk); -H3Index _faceIjkToH3(const FaceIJK* fijk, int res); +int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk); +H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk); +H3Index _faceIjkToH3(const FaceIJK *fijk, int res); Direction _h3LeadingNonZeroDigit(H3Index h); H3Index _h3RotatePent60ccw(H3Index h); H3Index _h3RotatePent60cw(H3Index h); H3Index _h3Rotate60ccw(H3Index h); H3Index _h3Rotate60cw(H3Index h); +DECLSPEC H3Index _zeroIndexDigits(H3Index h, int start, int end); #endif diff --git a/h3_h3UniEdge.c b/h3_h3UniEdge.c deleted file mode 100644 index f3bfce8..0000000 --- a/h3_h3UniEdge.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright 2017-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. - */ -/** @file h3UniEdge.c - * @brief H3UniEdge functions for manipulating unidirectional edge indexes. - */ - -#include -#include -#include "h3_algos.h" -#include "h3_constants.h" -#include "h3_coordijk.h" -#include "h3_geoCoord.h" -#include "h3_h3Index.h" - -/** - * Returns whether or not the provided H3Indexes are neighbors. - * @param origin The origin H3 index. - * @param destination The destination H3 index. - * @return 1 if the indexes are neighbors, 0 otherwise; - */ -int H3_EXPORT(h3IndexesAreNeighbors)(H3Index origin, H3Index destination) { - // Make sure they're hexagon indexes - if (H3_GET_MODE(origin) != H3_HEXAGON_MODE || - H3_GET_MODE(destination) != H3_HEXAGON_MODE) { - return 0; - } - - // Hexagons cannot be neighbors with themselves - if (origin == destination) { - return 0; - } - - // Only hexagons in the same resolution can be neighbors - if (H3_GET_RESOLUTION(origin) != H3_GET_RESOLUTION(destination)) { - return 0; - } - - // H3 Indexes that share the same parent are very likely to be neighbors - // Child 0 is neighbor with all of its parent's 'offspring', the other - // children are neighbors with 3 of the 7 children. So a simple comparison - // of origin and destination parents and then a lookup table of the children - // is a super-cheap way to possibly determine they are neighbors. - int parentRes = H3_GET_RESOLUTION(origin) - 1; - if (parentRes > 0 && (H3_EXPORT(h3ToParent)(origin, parentRes) == - H3_EXPORT(h3ToParent)(destination, parentRes))) { - Direction originResDigit = H3_GET_INDEX_DIGIT(origin, parentRes + 1); - Direction destinationResDigit = - H3_GET_INDEX_DIGIT(destination, parentRes + 1); - if (originResDigit == CENTER_DIGIT || - destinationResDigit == CENTER_DIGIT) { - return 1; - } - // These sets are the relevant neighbors in the clockwise - // and counter-clockwise - const Direction neighborSetClockwise[] = { - CENTER_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, J_AXES_DIGIT, - IK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT}; - const Direction neighborSetCounterclockwise[] = { - CENTER_DIGIT, IK_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, - IJ_AXES_DIGIT, I_AXES_DIGIT, J_AXES_DIGIT}; - if (neighborSetClockwise[originResDigit] == destinationResDigit || - neighborSetCounterclockwise[originResDigit] == - destinationResDigit) { - return 1; - } - } - - // Otherwise, we have to determine the neighbor relationship the "hard" way. - H3Index neighborRing[7] = {0}; - H3_EXPORT(kRing)(origin, 1, neighborRing); - for (int i = 0; i < 7; i++) { - if (neighborRing[i] == destination) { - return 1; - } - } - - // Made it here, they definitely aren't neighbors - return 0; -} - -/** - * Returns a unidirectional edge H3 index based on the provided origin and - * destination - * @param origin The origin H3 hexagon index - * @param destination The destination H3 hexagon index - * @return The unidirectional edge H3Index, or 0 on failure. - */ -H3Index H3_EXPORT(getH3UnidirectionalEdge)(H3Index origin, - H3Index destination) { - // Short-circuit and return an invalid index value if they are not neighbors - if (H3_EXPORT(h3IndexesAreNeighbors)(origin, destination) == 0) { - return H3_INVALID_INDEX; - } - - // Otherwise, determine the IJK direction from the origin to the destination - H3Index output = origin; - H3_SET_MODE(output, H3_UNIEDGE_MODE); - - // Checks each neighbor, in order, to determine which direction the - // destination neighbor is located. Skips CENTER_DIGIT since that - // would be this index. - H3Index neighbor; - for (Direction direction = K_AXES_DIGIT; direction < NUM_DIGITS; - direction++) { - int rotations = 0; - neighbor = h3NeighborRotations(origin, direction, &rotations); - if (neighbor == destination) { - H3_SET_RESERVED_BITS(output, direction); - return output; - } - } - - // This should be impossible, return an invalid H3Index in this case; - return H3_INVALID_INDEX; // LCOV_EXCL_LINE -} - -/** - * Returns the origin hexagon from the unidirectional edge H3Index - * @param edge The edge H3 index - * @return The origin H3 hexagon index - */ -H3Index H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(H3Index edge) { - if (H3_GET_MODE(edge) != H3_UNIEDGE_MODE) { - return H3_INVALID_INDEX; - } - H3Index origin = edge; - H3_SET_MODE(origin, H3_HEXAGON_MODE); - H3_SET_RESERVED_BITS(origin, 0); - return origin; -} - -/** - * Returns the destination hexagon from the unidirectional edge H3Index - * @param edge The edge H3 index - * @return The destination H3 hexagon index - */ -H3Index H3_EXPORT(getDestinationH3IndexFromUnidirectionalEdge)(H3Index edge) { - if (H3_GET_MODE(edge) != H3_UNIEDGE_MODE) { - return H3_INVALID_INDEX; - } - Direction direction = H3_GET_RESERVED_BITS(edge); - int rotations = 0; - H3Index destination = h3NeighborRotations( - H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(edge), direction, - &rotations); - return destination; -} - -/** - * Determines if the provided H3Index is a valid unidirectional edge index - * @param edge The unidirectional edge H3Index - * @return 1 if it is a unidirectional edge H3Index, otherwise 0. - */ -int H3_EXPORT(h3UnidirectionalEdgeIsValid)(H3Index edge) { - if (H3_GET_MODE(edge) != H3_UNIEDGE_MODE) { - return 0; - } - - Direction neighborDirection = H3_GET_RESERVED_BITS(edge); - if (neighborDirection <= CENTER_DIGIT || neighborDirection >= NUM_DIGITS) { - return 0; - } - - H3Index origin = H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(edge); - if (H3_EXPORT(h3IsPentagon)(origin) && neighborDirection == K_AXES_DIGIT) { - return 0; - } - - return H3_EXPORT(h3IsValid)(origin); -} - -/** - * Returns the origin, destination pair of hexagon IDs for the given edge ID - * @param edge The unidirectional edge H3Index - * @param originDestination Pointer to memory to store origin and destination - * IDs - */ -void H3_EXPORT(getH3IndexesFromUnidirectionalEdge)(H3Index edge, - H3Index* originDestination) { - originDestination[0] = - H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(edge); - originDestination[1] = - H3_EXPORT(getDestinationH3IndexFromUnidirectionalEdge)(edge); -} - -/** - * Provides all of the unidirectional edges from the current H3Index. - * @param origin The origin hexagon H3Index to find edges for. - * @param edges The memory to store all of the edges inside. - */ -void H3_EXPORT(getH3UnidirectionalEdgesFromHexagon)(H3Index origin, - H3Index* edges) { - // Determine if the origin is a pentagon and special treatment needed. - int isPentagon = H3_EXPORT(h3IsPentagon)(origin); - - // This is actually quite simple. Just modify the bits of the origin - // slightly for each direction, except the 'k' direction in pentagons, - // which is zeroed. - for (int i = 0; i < 6; i++) { - if (isPentagon && i == 0) { - edges[i] = H3_INVALID_INDEX; - } else { - edges[i] = origin; - H3_SET_MODE(edges[i], H3_UNIEDGE_MODE); - H3_SET_RESERVED_BITS(edges[i], i + 1); - } - } -} - -/** - * Whether the given coordinate has a matching vertex in the given geo boundary. - * @param vertex Coordinate to check - * @param boundary Geo boundary to look in - * @return Whether a match was found - */ -static bool _hasMatchingVertex(const GeoCoord* vertex, - const GeoBoundary* boundary) { - for (int i = 0; i < boundary->numVerts; i++) { - if (geoAlmostEqualThreshold(vertex, &boundary->verts[i], 0.000001)) { - return true; - } - } - return false; -} - -/** - * Provides the coordinates defining the unidirectional edge. - * @param edge The unidirectional edge H3Index - * @param gb The geoboundary object to store the edge coordinates. - */ -void H3_EXPORT(getH3UnidirectionalEdgeBoundary)(H3Index edge, GeoBoundary* gb) { - // TODO: More efficient solution :) - GeoBoundary origin = {0}; - GeoBoundary destination = {0}; - GeoCoord postponedVertex = {0}; - bool hasPostponedVertex = false; - - H3_EXPORT(h3ToGeoBoundary) - (H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(edge), &origin); - H3_EXPORT(h3ToGeoBoundary) - (H3_EXPORT(getDestinationH3IndexFromUnidirectionalEdge)(edge), - &destination); - - int k = 0; - for (int i = 0; i < origin.numVerts; i++) { - if (_hasMatchingVertex(&origin.verts[i], &destination)) { - // If we are on vertex 0, we need to handle the case where it's the - // end of the edge, not the beginning. - if (i == 0 && - !_hasMatchingVertex(&origin.verts[i + 1], &destination)) { - postponedVertex = origin.verts[i]; - hasPostponedVertex = true; - } else { - gb->verts[k] = origin.verts[i]; - k++; - } - } - } - // If we postponed adding the last vertex, add it now - if (hasPostponedVertex) { - gb->verts[k] = postponedVertex; - k++; - } - gb->numVerts = k; -} diff --git a/h3_h3UniEdge.h b/h3_h3UniEdge.h deleted file mode 100644 index bb4d913..0000000 --- a/h3_h3UniEdge.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2017 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. - */ -/** @file h3UniEdge.h - * @brief H3UniEdge functions for manipulating unidirectional edge indexes. - */ - -#ifndef H3UNIEDGE_H -#define H3UNIEDGE_H - -#include "h3_algos.h" -#include "h3_h3Index.h" - -// nothing non-public in this file - -#endif diff --git a/h3_h3api.h b/h3_h3api.h index 8db158c..e34fb71 100644 --- a/h3_h3api.h +++ b/h3_h3api.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 Uber Technologies, Inc. + * Copyright 2016-2021 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. @@ -38,6 +38,17 @@ #define H3_EXPORT(name) name #endif +/* Windows DLL requires attributes indicating what to export */ +#if _WIN32 && BUILD_SHARED_LIBS +#if BUILDING_H3 +#define DECLSPEC __declspec(dllexport) +#else +#define DECLSPEC __declspec(dllimport) +#endif +#else +#define DECLSPEC +#endif + /* For uint64_t */ #include /* For size_t */ @@ -51,9 +62,48 @@ extern "C" { #endif -/** @brief the H3Index fits within a 64-bit unsigned integer */ +/** @brief Identifier for an object (cell, edge, etc) in the H3 system. + * + * The H3Index fits within a 64-bit unsigned integer. + */ typedef uint64_t H3Index; +/** + * Invalid index used to indicate an error from latLngToCell and related + * functions or missing data in arrays of H3 indices. Analogous to NaN in + * floating point. + */ +#define H3_NULL 0 + +/** @brief Result code (success or specific error) from an H3 operation */ +typedef uint32_t H3Error; + +typedef enum { + E_SUCCESS = 0, // Success (no error) + E_FAILED = + 1, // The operation failed but a more specific error is not available + E_DOMAIN = 2, // Argument was outside of acceptable range (when a more + // specific error code is not available) + E_LATLNG_DOMAIN = + 3, // Latitude or longitude arguments were outside of acceptable range + E_RES_DOMAIN = 4, // Resolution argument was outside of acceptable range + E_CELL_INVALID = 5, // `H3Index` cell argument was not valid + E_DIR_EDGE_INVALID = 6, // `H3Index` directed edge argument was not valid + E_UNDIR_EDGE_INVALID = + 7, // `H3Index` undirected edge argument was not valid + E_VERTEX_INVALID = 8, // `H3Index` vertex argument was not valid + E_PENTAGON = 9, // Pentagon distortion was encountered which the algorithm + // could not handle it + E_DUPLICATE_INPUT = 10, // Duplicate input was encountered in the arguments + // and the algorithm could not handle it + E_NOT_NEIGHBORS = 11, // `H3Index` cell arguments were not neighbors + E_RES_MISMATCH = + 12, // `H3Index` cell arguments had incompatible resolutions + E_MEMORY_ALLOC = 13, // Necessary memory allocation failed + E_MEMORY_BOUNDS = 14, // Bounds of provided memory were not large enough + E_OPTION_INVALID = 15 // Mode or flags argument was not valid. +} H3ErrorCodes; + /* library version numbers generated from VERSION file */ // clang-format off #define H3_VERSION_MAJOR @H3_VERSION_MAJOR@ @@ -66,37 +116,37 @@ typedef uint64_t H3Index; */ #define MAX_CELL_BNDRY_VERTS 10 -/** @struct GeoCoord +/** @struct LatLng @brief latitude/longitude in radians */ typedef struct { double lat; ///< latitude in radians - double lon; ///< longitude in radians -} GeoCoord; + double lng; ///< longitude in radians +} LatLng; -/** @struct GeoBoundary +/** @struct CellBoundary @brief cell boundary in latitude/longitude */ typedef struct { - int numVerts; ///< number of vertices - GeoCoord verts[MAX_CELL_BNDRY_VERTS]; ///< vertices in ccw order -} GeoBoundary; + int numVerts; ///< number of vertices + LatLng verts[MAX_CELL_BNDRY_VERTS]; ///< vertices in ccw order +} CellBoundary; -/** @struct Geofence - * @brief similar to GeoBoundary, but requires more alloc work +/** @struct GeoLoop + * @brief similar to CellBoundary, but requires more alloc work */ typedef struct { int numVerts; - GeoCoord *verts; -} Geofence; + LatLng *verts; +} GeoLoop; /** @struct GeoPolygon * @brief Simplified core of GeoJSON Polygon coordinates definition */ typedef struct { - Geofence geofence; ///< exterior boundary of the polygon - int numHoles; ///< number of elements in the array pointed to by holes - Geofence *holes; ///< interior boundaries (holes) in the polygon + GeoLoop geoloop; ///< exterior boundary of the polygon + int numHoles; ///< number of elements in the array pointed to by holes + GeoLoop *holes; ///< interior boundaries (holes) in the polygon } GeoPolygon; /** @struct GeoMultiPolygon @@ -107,13 +157,13 @@ typedef struct { GeoPolygon *polygons; } GeoMultiPolygon; -/** @struct LinkedGeoCoord +/** @struct LinkedLatLng * @brief A coordinate node in a linked geo structure, part of a linked list */ -typedef struct LinkedGeoCoord LinkedGeoCoord; -struct LinkedGeoCoord { - GeoCoord vertex; - LinkedGeoCoord *next; +typedef struct LinkedLatLng LinkedLatLng; +struct LinkedLatLng { + LatLng vertex; + LinkedLatLng *next; }; /** @struct LinkedGeoLoop @@ -121,8 +171,8 @@ struct LinkedGeoCoord { */ typedef struct LinkedGeoLoop LinkedGeoLoop; struct LinkedGeoLoop { - LinkedGeoCoord *first; - LinkedGeoCoord *last; + LinkedLatLng *first; + LinkedLatLng *last; LinkedGeoLoop *next; }; @@ -146,94 +196,106 @@ typedef struct { int j; ///< j component } CoordIJ; -/** @defgroup geoToH3 geoToH3 - * Functions for geoToH3 +/** @defgroup latLngToCell latLngToCell + * Functions for latLngToCell * @{ */ -/** @brief find the H3 index of the resolution res cell containing the lat/lon g +/** @brief find the H3 index of the resolution res cell containing the lat/lng */ -H3Index H3_EXPORT(geoToH3)(const GeoCoord *g, int res); +DECLSPEC H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, + H3Index *out); /** @} */ -/** @defgroup h3ToGeo h3ToGeo - * Functions for h3ToGeo +/** @defgroup cellToLatLng cellToLatLng + * Functions for cellToLatLng * @{ */ -/** @brief find the lat/lon center point g of the cell h3 */ -void H3_EXPORT(h3ToGeo)(H3Index h3, GeoCoord *g); +/** @brief find the lat/lng center point g of the cell h3 */ +DECLSPEC H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g); /** @} */ -/** @defgroup h3ToGeoBoundary h3ToGeoBoundary - * Functions for h3ToGeoBoundary +/** @defgroup cellToBoundary cellToBoundary + * Functions for cellToBoundary * @{ */ -/** @brief give the cell boundary in lat/lon coordinates for the cell h3 */ -void H3_EXPORT(h3ToGeoBoundary)(H3Index h3, GeoBoundary *gp); +/** @brief give the cell boundary in lat/lng coordinates for the cell h3 */ +DECLSPEC H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *gp); /** @} */ -/** @defgroup kRing kRing - * Functions for kRing +/** @defgroup gridDisk gridDisk + * Functions for gridDisk * @{ */ /** @brief maximum number of hexagons in k-ring */ -int H3_EXPORT(maxKringSize)(int k); +DECLSPEC H3Error H3_EXPORT(maxGridDiskSize)(int k, int64_t *out); /** @brief hexagons neighbors in all directions, assuming no pentagons */ -int H3_EXPORT(hexRange)(H3Index origin, int k, H3Index *out); +DECLSPEC H3Error H3_EXPORT(gridDiskUnsafe)(H3Index origin, int k, H3Index *out); /** @} */ /** @brief hexagons neighbors in all directions, assuming no pentagons, - * reporting - * distance from origin */ -int H3_EXPORT(hexRangeDistances)(H3Index origin, int k, H3Index *out, - int *distances); + * reporting distance from origin */ +DECLSPEC H3Error H3_EXPORT(gridDiskDistancesUnsafe)(H3Index origin, int k, + H3Index *out, + int *distances); + +/** @brief hexagons neighbors in all directions reporting distance from origin + */ +DECLSPEC H3Error H3_EXPORT(gridDiskDistancesSafe)(H3Index origin, int k, + H3Index *out, int *distances); /** @brief collection of hex rings sorted by ring for all given hexagons */ -int H3_EXPORT(hexRanges)(H3Index *h3Set, int length, int k, H3Index *out); +DECLSPEC H3Error H3_EXPORT(gridDisksUnsafe)(H3Index *h3Set, int length, int k, + H3Index *out); /** @brief hexagon neighbors in all directions */ -void H3_EXPORT(kRing)(H3Index origin, int k, H3Index *out); +DECLSPEC H3Error H3_EXPORT(gridDisk)(H3Index origin, int k, H3Index *out); /** @} */ -/** @defgroup kRingDistances kRingDistances - * Functions for kRingDistances +/** @defgroup gridDiskDistances gridDiskDistances + * Functions for gridDiskDistances * @{ */ /** @brief hexagon neighbors in all directions, reporting distance from origin */ -void H3_EXPORT(kRingDistances)(H3Index origin, int k, H3Index *out, - int *distances); +DECLSPEC H3Error H3_EXPORT(gridDiskDistances)(H3Index origin, int k, + H3Index *out, int *distances); /** @} */ -/** @defgroup hexRing hexRing - * Functions for hexRing +/** @defgroup gridRingUnsafe gridRingUnsafe + * Functions for gridRingUnsafe * @{ */ /** @brief hollow hexagon ring at some origin */ -int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index *out); +DECLSPEC H3Error H3_EXPORT(gridRingUnsafe)(H3Index origin, int k, H3Index *out); /** @} */ -/** @defgroup polyfill polyfill - * Functions for polyfill +/** @defgroup polygonToCells polygonToCells + * Functions for polygonToCells * @{ */ -/** @brief maximum number of hexagons in the geofence */ -int H3_EXPORT(maxPolyfillSize)(const GeoPolygon *geoPolygon, int res); +/** @brief maximum number of hexagons that could be in the geoloop */ +DECLSPEC H3Error H3_EXPORT(maxPolygonToCellsSize)(const GeoPolygon *geoPolygon, + int res, uint32_t flags, + int64_t *out); -/** @brief hexagons within the given geofence */ -void H3_EXPORT(polyfill)(const GeoPolygon *geoPolygon, int res, H3Index *out); +/** @brief hexagons within the given geopolygon */ +DECLSPEC H3Error H3_EXPORT(polygonToCells)(const GeoPolygon *geoPolygon, + int res, uint32_t flags, + H3Index *out); /** @} */ -/** @defgroup h3SetToMultiPolygon h3SetToMultiPolygon - * Functions for h3SetToMultiPolygon (currently a binding-only concept) +/** @defgroup cellsToMultiPolygon cellsToMultiPolygon + * Functions for cellsToMultiPolygon (currently a binding-only concept) * @{ */ /** @brief Create a LinkedGeoPolygon from a set of contiguous hexagons */ -void H3_EXPORT(h3SetToLinkedGeo)(const H3Index *h3Set, const int numHexes, - LinkedGeoPolygon *out); +DECLSPEC H3Error H3_EXPORT(cellsToLinkedMultiPolygon)(const H3Index *h3Set, + const int numHexes, + LinkedGeoPolygon *out); /** @brief Free all memory created for a LinkedGeoPolygon */ -void H3_EXPORT(destroyLinkedPolygon)(LinkedGeoPolygon *polygon); +DECLSPEC void H3_EXPORT(destroyLinkedMultiPolygon)(LinkedGeoPolygon *polygon); /** @} */ /** @defgroup degsToRads degsToRads @@ -241,7 +303,7 @@ void H3_EXPORT(destroyLinkedPolygon)(LinkedGeoPolygon *polygon); * @{ */ /** @brief converts degrees to radians */ -double H3_EXPORT(degsToRads)(double degrees); +DECLSPEC double H3_EXPORT(degsToRads)(double degrees); /** @} */ /** @defgroup radsToDegs radsToDegs @@ -249,64 +311,167 @@ double H3_EXPORT(degsToRads)(double degrees); * @{ */ /** @brief converts radians to degrees */ -double H3_EXPORT(radsToDegs)(double radians); +DECLSPEC double H3_EXPORT(radsToDegs)(double radians); /** @} */ -/** @defgroup hexArea hexArea - * Functions for hexArea +/** @defgroup greatCircleDistance greatCircleDistance + * Functions for distance * @{ */ -/** @brief hexagon area in square kilometers */ -double H3_EXPORT(hexAreaKm2)(int res); +/** @brief "great circle distance" between pairs of LatLng points in radians*/ +DECLSPEC double H3_EXPORT(greatCircleDistanceRads)(const LatLng *a, + const LatLng *b); -/** @brief hexagon area in square meters */ -double H3_EXPORT(hexAreaM2)(int res); +/** @brief "great circle distance" between pairs of LatLng points in + * kilometers*/ +DECLSPEC double H3_EXPORT(greatCircleDistanceKm)(const LatLng *a, + const LatLng *b); + +/** @brief "great circle distance" between pairs of LatLng points in meters*/ +DECLSPEC double H3_EXPORT(greatCircleDistanceM)(const LatLng *a, + const LatLng *b); +/** @} */ + +/** @defgroup getHexagonAreaAvg getHexagonAreaAvg + * Functions for getHexagonAreaAvg + * @{ + */ +/** @brief average hexagon area in square kilometers (excludes pentagons) */ +DECLSPEC H3Error H3_EXPORT(getHexagonAreaAvgKm2)(int res, double *out); + +/** @brief average hexagon area in square meters (excludes pentagons) */ +DECLSPEC H3Error H3_EXPORT(getHexagonAreaAvgM2)(int res, double *out); /** @} */ -/** @defgroup edgeLength edgeLength - * Functions for edgeLength +/** @defgroup cellArea cellArea + * Functions for cellArea * @{ */ -/** @brief hexagon edge length in kilometers */ -double H3_EXPORT(edgeLengthKm)(int res); +/** @brief exact area for a specific cell (hexagon or pentagon) in radians^2 */ +DECLSPEC H3Error H3_EXPORT(cellAreaRads2)(H3Index h, double *out); + +/** @brief exact area for a specific cell (hexagon or pentagon) in kilometers^2 + */ +DECLSPEC H3Error H3_EXPORT(cellAreaKm2)(H3Index h, double *out); + +/** @brief exact area for a specific cell (hexagon or pentagon) in meters^2 */ +DECLSPEC H3Error H3_EXPORT(cellAreaM2)(H3Index h, double *out); +/** @} */ -/** @brief hexagon edge length in meters */ -double H3_EXPORT(edgeLengthM)(int res); +/** @defgroup getHexagonEdgeLengthAvg getHexagonEdgeLengthAvg + * Functions for getHexagonEdgeLengthAvg + * @{ + */ +/** @brief average hexagon edge length in kilometers (excludes pentagons) */ +DECLSPEC H3Error H3_EXPORT(getHexagonEdgeLengthAvgKm)(int res, double *out); + +/** @brief average hexagon edge length in meters (excludes pentagons) */ +DECLSPEC H3Error H3_EXPORT(getHexagonEdgeLengthAvgM)(int res, double *out); +/** @} */ + +/** @defgroup exactEdgeLength exactEdgeLength + * Functions for exactEdgeLength + * @{ + */ +/** @brief exact length for a specific directed edge in radians*/ +DECLSPEC H3Error H3_EXPORT(exactEdgeLengthRads)(H3Index edge, double *length); + +/** @brief exact length for a specific directed edge in kilometers*/ +DECLSPEC H3Error H3_EXPORT(exactEdgeLengthKm)(H3Index edge, double *length); + +/** @brief exact length for a specific directed edge in meters*/ +DECLSPEC H3Error H3_EXPORT(exactEdgeLengthM)(H3Index edge, double *length); +/** @} */ + +/** @defgroup getNumCells getNumCells + * Functions for getNumCells + * @{ + */ +/** @brief number of cells (hexagons and pentagons) for a given resolution + * + * It works out to be `2 + 120*7^r` for resolution `r`. + * + * # Mathematical notes + * + * Let h(n) be the number of children n levels below + * a single *hexagon*. + * + * Then h(n) = 7^n. + * + * Let p(n) be the number of children n levels below + * a single *pentagon*. + * + * Then p(0) = 1, and p(1) = 6, since each pentagon + * has 5 hexagonal immediate children and 1 pentagonal + * immediate child. + * + * In general, we have the recurrence relation + * + * p(n) = 5*h(n-1) + p(n-1) + * = 5*7^(n-1) + p(n-1). + * + * Working through the recurrence, we get that + * + * p(n) = 1 + 5*\sum_{k=1}^n 7^{k-1} + * = 1 + 5*(7^n - 1)/6, + * + * using the closed form for a geometric series. + * + * Using the closed forms for h(n) and p(n), we can + * get a closed form for the total number of cells + * at resolution r: + * + * c(r) = 12*p(r) + 110*h(r) + * = 2 + 120*7^r. + * + * + * @param res H3 cell resolution + * + * @return number of cells at resolution `res` + */ +DECLSPEC H3Error H3_EXPORT(getNumCells)(int res, int64_t *out); /** @} */ -/** @defgroup numHexagons numHexagons - * Functions for numHexagons +/** @defgroup getRes0Cells getRes0Cells + * Functions for getRes0Cells * @{ */ -/** @brief number of hexagons for a given resolution */ -int64_t H3_EXPORT(numHexagons)(int res); +/** @brief returns the number of resolution 0 cells (hexagons and pentagons) */ +DECLSPEC int H3_EXPORT(res0CellCount)(); + +/** @brief provides all base cells in H3Index format*/ +DECLSPEC H3Error H3_EXPORT(getRes0Cells)(H3Index *out); /** @} */ -/** @defgroup getRes0Indexes getRes0Indexes - * Functions for getRes0Indexes +/** @defgroup getPentagons getPentagons + * Functions for getPentagons * @{ */ -/** @brief returns the number of resolution 0 indexes */ -int H3_EXPORT(res0IndexCount)(); +/** @brief returns the number of pentagons per resolution */ +DECLSPEC int H3_EXPORT(pentagonCount)(); -/** @brief provides all base cells */ -void H3_EXPORT(getRes0Indexes)(H3Index *out); +/** @brief generates all pentagons at the specified resolution */ +DECLSPEC H3Error H3_EXPORT(getPentagons)(int res, H3Index *out); /** @} */ -/** @defgroup h3GetResolution h3GetResolution - * Functions for h3GetResolution +/** @defgroup getResolution getResolution + * Functions for getResolution * @{ */ -/** @brief returns the resolution of the provided hexagon */ -int H3_EXPORT(h3GetResolution)(H3Index h); +/** @brief returns the resolution of the provided H3 index + * Works on both cells and directed edges. */ +DECLSPEC int H3_EXPORT(getResolution)(H3Index h); /** @} */ -/** @defgroup h3GetBaseCell h3GetBaseCell - * Functions for h3GetBaseCell +/** @defgroup getBaseCellNumber getBaseCellNumber + * Functions for getBaseCellNumber * @{ */ -/** @brief returns the base cell of the provided hexagon */ -int H3_EXPORT(h3GetBaseCell)(H3Index h); +/** @brief returns the base cell "number" (0 to 121) of the provided H3 cell + * + * Note: Technically works on H3 edges, but will return base cell of the + * origin cell. */ +DECLSPEC int H3_EXPORT(getBaseCellNumber)(H3Index h); /** @} */ /** @defgroup stringToH3 stringToH3 @@ -314,7 +479,7 @@ int H3_EXPORT(h3GetBaseCell)(H3Index h); * @{ */ /** @brief converts the canonical string format to H3Index format */ -H3Index H3_EXPORT(stringToH3)(const char *str); +DECLSPEC H3Error H3_EXPORT(stringToH3)(const char *str, H3Index *out); /** @} */ /** @defgroup h3ToString h3ToString @@ -322,188 +487,258 @@ H3Index H3_EXPORT(stringToH3)(const char *str); * @{ */ /** @brief converts an H3Index to a canonical string */ -void H3_EXPORT(h3ToString)(H3Index h, char *str, size_t sz); +DECLSPEC H3Error H3_EXPORT(h3ToString)(H3Index h, char *str, size_t sz); /** @} */ -/** @defgroup h3IsValid h3IsValid - * Functions for h3IsValid +/** @defgroup isValidCell isValidCell + * Functions for isValidCell * @{ */ -/** @brief confirms if an H3Index is valid */ -int H3_EXPORT(h3IsValid)(H3Index h); +/** @brief confirms if an H3Index is a valid cell (hexagon or pentagon) + * In particular, returns 0 (False) for H3 directed edges or invalid data + */ +DECLSPEC int H3_EXPORT(isValidCell)(H3Index h); /** @} */ -/** @defgroup h3ToParent h3ToParent - * Functions for h3ToParent +/** @defgroup cellToParent cellToParent + * Functions for cellToParent * @{ */ -/** @brief returns the parent (or grandparent, etc) hexagon of the given hexagon +/** @brief returns the parent (or grandparent, etc) cell of the given cell */ -H3Index H3_EXPORT(h3ToParent)(H3Index h, int parentRes); +DECLSPEC H3Error H3_EXPORT(cellToParent)(H3Index h, int parentRes, + H3Index *parent); /** @} */ -/** @defgroup h3ToChildren h3ToChildren - * Functions for h3ToChildren +/** @defgroup cellToChildren cellToChildren + * Functions for cellToChildren * @{ */ -/** @brief determines the maximum number of children (or grandchildren, etc) - * that - * could be returned for the given hexagon */ -int H3_EXPORT(maxH3ToChildrenSize)(H3Index h, int childRes); +/** @brief determines the exact number of children (or grandchildren, etc) + * that would be returned for the given cell */ +DECLSPEC H3Error H3_EXPORT(cellToChildrenSize)(H3Index h, int childRes, + int64_t *out); + +/** @brief provides the children (or grandchildren, etc) of the given cell */ +DECLSPEC H3Error H3_EXPORT(cellToChildren)(H3Index h, int childRes, + H3Index *children); +/** @} */ -/** @brief provides the children (or grandchildren, etc) of the given hexagon */ -void H3_EXPORT(h3ToChildren)(H3Index h, int childRes, H3Index *children); +/** @defgroup cellToCenterChild cellToCenterChild + * Functions for cellToCenterChild + * @{ + */ +/** @brief returns the center child of the given cell at the specified + * resolution */ +DECLSPEC H3Error H3_EXPORT(cellToCenterChild)(H3Index h, int childRes, + H3Index *child); /** @} */ -/** @defgroup compact compact - * Functions for compact +/** @defgroup compactCells compactCells + * Functions for compactCells * @{ */ /** @brief compacts the given set of hexagons as best as possible */ -int H3_EXPORT(compact)(const H3Index *h3Set, H3Index *compactedSet, - const int numHexes); +DECLSPEC H3Error H3_EXPORT(compactCells)(const H3Index *h3Set, + H3Index *compactedSet, + const int64_t numHexes); /** @} */ -/** @defgroup uncompact uncompact - * Functions for uncompact +/** @defgroup uncompactCells uncompactCells + * Functions for uncompactCells * @{ */ -/** @brief determines the maximum number of hexagons that could be uncompacted +/** @brief determines the exact number of hexagons that will be uncompacted * from the compacted set */ -int H3_EXPORT(maxUncompactSize)(const H3Index *compactedSet, const int numHexes, - const int res); +DECLSPEC H3Error H3_EXPORT(uncompactCellsSize)(const H3Index *compactedSet, + const int64_t numCompacted, + const int res, int64_t *out); /** @brief uncompacts the compacted hexagon set */ -int H3_EXPORT(uncompact)(const H3Index *compactedSet, const int numHexes, - H3Index *h3Set, const int maxHexes, const int res); +DECLSPEC H3Error H3_EXPORT(uncompactCells)(const H3Index *compactedSet, + const int64_t numCompacted, + H3Index *outSet, + const int64_t numOut, const int res); /** @} */ -/** @defgroup h3IsResClassIII h3IsResClassIII - * Functions for h3IsResClassIII +/** @defgroup isResClassIII isResClassIII + * Functions for isResClassIII * @{ */ /** @brief determines if a hexagon is Class III (or Class II) */ -int H3_EXPORT(h3IsResClassIII)(H3Index h); +DECLSPEC int H3_EXPORT(isResClassIII)(H3Index h); /** @} */ -/** @defgroup h3IsPentagon h3IsPentagon - * Functions for h3IsPentagon +/** @defgroup isPentagon isPentagon + * Functions for isPentagon * @{ */ -/** @brief determines if a hexagon is actually a pentagon */ -int H3_EXPORT(h3IsPentagon)(H3Index h); +/** @brief determines if an H3 cell is a pentagon */ +DECLSPEC int H3_EXPORT(isPentagon)(H3Index h); /** @} */ -/** @defgroup h3IndexesAreNeighbors h3IndexesAreNeighbors - * Functions for h3IndexesAreNeighbors +/** @defgroup getIcosahedronFaces getIcosahedronFaces + * Functions for getIcosahedronFaces + * @{ + */ +/** @brief Max number of icosahedron faces intersected by an index */ +DECLSPEC H3Error H3_EXPORT(maxFaceCount)(H3Index h3, int *out); + +/** @brief Find all icosahedron faces intersected by a given H3 index */ +DECLSPEC H3Error H3_EXPORT(getIcosahedronFaces)(H3Index h3, int *out); +/** @} */ + +/** @defgroup areNeighborCells areNeighborCells + * Functions for areNeighborCells * @{ */ /** @brief returns whether or not the provided hexagons border */ -int H3_EXPORT(h3IndexesAreNeighbors)(H3Index origin, H3Index destination); +DECLSPEC H3Error H3_EXPORT(areNeighborCells)(H3Index origin, + H3Index destination, int *out); /** @} */ -/** @defgroup getH3UnidirectionalEdge getH3UnidirectionalEdge - * Functions for getH3UnidirectionalEdge +/** @defgroup cellsToDirectedEdge cellsToDirectedEdge + * Functions for cellsToDirectedEdge * @{ */ -/** @brief returns the unidirectional edge H3Index for the specified origin and +/** @brief returns the directed edge H3Index for the specified origin and * destination */ -H3Index H3_EXPORT(getH3UnidirectionalEdge)(H3Index origin, H3Index destination); +DECLSPEC H3Error H3_EXPORT(cellsToDirectedEdge)(H3Index origin, + H3Index destination, + H3Index *out); /** @} */ -/** @defgroup h3UnidirectionalEdgeIsValid h3UnidirectionalEdgeIsValid - * Functions for h3UnidirectionalEdgeIsValid +/** @defgroup isValidDirectedEdge isValidDirectedEdge + * Functions for isValidDirectedEdge * @{ */ -/** @brief returns whether the H3Index is a valid unidirectional edge */ -int H3_EXPORT(h3UnidirectionalEdgeIsValid)(H3Index edge); +/** @brief returns whether the H3Index is a valid directed edge */ +DECLSPEC int H3_EXPORT(isValidDirectedEdge)(H3Index edge); /** @} */ -/** @defgroup getOriginH3IndexFromUnidirectionalEdge \ - * getOriginH3IndexFromUnidirectionalEdge - * Functions for getOriginH3IndexFromUnidirectionalEdge +/** @defgroup getDirectedEdgeOrigin \ + * getDirectedEdgeOrigin + * Functions for getDirectedEdgeOrigin * @{ */ -/** @brief Returns the origin hexagon H3Index from the unidirectional edge +/** @brief Returns the origin hexagon H3Index from the directed edge * H3Index */ -H3Index H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(H3Index edge); +DECLSPEC H3Error H3_EXPORT(getDirectedEdgeOrigin)(H3Index edge, H3Index *out); /** @} */ -/** @defgroup getDestinationH3IndexFromUnidirectionalEdge \ - * getDestinationH3IndexFromUnidirectionalEdge - * Functions for getDestinationH3IndexFromUnidirectionalEdge +/** @defgroup getDirectedEdgeDestination \ + * getDirectedEdgeDestination + * Functions for getDirectedEdgeDestination * @{ */ -/** @brief Returns the destination hexagon H3Index from the unidirectional edge +/** @brief Returns the destination hexagon H3Index from the directed edge * H3Index */ -H3Index H3_EXPORT(getDestinationH3IndexFromUnidirectionalEdge)(H3Index edge); +DECLSPEC H3Error H3_EXPORT(getDirectedEdgeDestination)(H3Index edge, + H3Index *out); /** @} */ -/** @defgroup getH3IndexesFromUnidirectionalEdge \ - * getH3IndexesFromUnidirectionalEdge - * Functions for getH3IndexesFromUnidirectionalEdge +/** @defgroup directedEdgeToCells \ + * directedEdgeToCells + * Functions for directedEdgeToCells * @{ */ -/** @brief Returns the origin and destination hexagons from the unidirectional +/** @brief Returns the origin and destination hexagons from the directed * edge H3Index */ -void H3_EXPORT(getH3IndexesFromUnidirectionalEdge)(H3Index edge, - H3Index *originDestination); +DECLSPEC H3Error H3_EXPORT(directedEdgeToCells)(H3Index edge, + H3Index *originDestination); /** @} */ -/** @defgroup getH3UnidirectionalEdgesFromHexagon \ - * getH3UnidirectionalEdgesFromHexagon - * Functions for getH3UnidirectionalEdgesFromHexagon +/** @defgroup originToDirectedEdges \ + * originToDirectedEdges + * Functions for originToDirectedEdges * @{ */ /** @brief Returns the 6 (or 5 for pentagons) edges associated with the H3Index */ -void H3_EXPORT(getH3UnidirectionalEdgesFromHexagon)(H3Index origin, - H3Index *edges); +DECLSPEC H3Error H3_EXPORT(originToDirectedEdges)(H3Index origin, + H3Index *edges); +/** @} */ + +/** @defgroup directedEdgeToBoundary directedEdgeToBoundary + * Functions for directedEdgeToBoundary + * @{ + */ +/** @brief Returns the CellBoundary containing the coordinates of the edge */ +DECLSPEC H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, + CellBoundary *gb); +/** @} */ + +/** @defgroup cellToVertex cellToVertex + * Functions for cellToVertex + * @{ + */ +/** @brief Returns a single vertex for a given cell, as an H3 index */ +DECLSPEC H3Error H3_EXPORT(cellToVertex)(H3Index origin, int vertexNum, + H3Index *out); +/** @} */ + +/** @defgroup cellToVertexes cellToVertexes + * Functions for cellToVertexes + * @{ + */ +/** @brief Returns all vertexes for a given cell, as H3 indexes */ +DECLSPEC H3Error H3_EXPORT(cellToVertexes)(H3Index origin, H3Index *vertexes); +/** @} */ + +/** @defgroup vertexToLatLng vertexToLatLng + * Functions for vertexToLatLng + * @{ + */ +/** @brief Returns a single vertex for a given cell, as an H3 index */ +DECLSPEC H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *point); /** @} */ -/** @defgroup getH3UnidirectionalEdgeBoundary getH3UnidirectionalEdgeBoundary - * Functions for getH3UnidirectionalEdgeBoundary +/** @defgroup isValidVertex isValidVertex + * Functions for isValidVertex * @{ */ -/** @brief Returns the GeoBoundary containing the coordinates of the edge */ -void H3_EXPORT(getH3UnidirectionalEdgeBoundary)(H3Index edge, GeoBoundary *gb); +/** @brief Whether the input is a valid H3 vertex */ +DECLSPEC int H3_EXPORT(isValidVertex)(H3Index vertex); /** @} */ -/** @defgroup h3Distance h3Distance - * Functions for h3Distance +/** @defgroup gridDistance gridDistance + * Functions for gridDistance * @{ */ /** @brief Returns grid distance between two indexes */ -int H3_EXPORT(h3Distance)(H3Index origin, H3Index h3); +DECLSPEC H3Error H3_EXPORT(gridDistance)(H3Index origin, H3Index h3, + int64_t *distance); /** @} */ -/** @defgroup h3Line h3Line - * Functions for h3Line +/** @defgroup gridPathCells gridPathCells + * Functions for gridPathCells * @{ */ /** @brief Number of indexes in a line connecting two indexes */ -int H3_EXPORT(h3LineSize)(H3Index start, H3Index end); +DECLSPEC H3Error H3_EXPORT(gridPathCellsSize)(H3Index start, H3Index end, + int64_t *size); /** @brief Line of h3 indexes connecting two indexes */ -int H3_EXPORT(h3Line)(H3Index start, H3Index end, H3Index *out); +DECLSPEC H3Error H3_EXPORT(gridPathCells)(H3Index start, H3Index end, + H3Index *out); /** @} */ -/** @defgroup experimentalH3ToLocalIj experimentalH3ToLocalIj - * Functions for experimentalH3ToLocalIj +/** @defgroup cellToLocalIj cellToLocalIj + * Functions for cellToLocalIj * @{ */ /** @brief Returns two dimensional coordinates for the given index */ -int H3_EXPORT(experimentalH3ToLocalIj)(H3Index origin, H3Index h3, - CoordIJ *out); +DECLSPEC H3Error H3_EXPORT(cellToLocalIj)(H3Index origin, H3Index h3, + uint32_t mode, CoordIJ *out); /** @} */ -/** @defgroup experimentalLocalIjToH3 experimentalLocalIjToH3 - * Functions for experimentalLocalIjToH3 +/** @defgroup localIjToCell localIjToCell + * Functions for localIjToCell * @{ */ /** @brief Returns index for the given two dimensional coordinates */ -int H3_EXPORT(experimentalLocalIjToH3)(H3Index origin, const CoordIJ *ij, - H3Index *out); +DECLSPEC H3Error H3_EXPORT(localIjToCell)(H3Index origin, const CoordIJ *ij, + uint32_t mode, H3Index *out); /** @} */ #ifdef __cplusplus diff --git a/h3_iterators.c b/h3_iterators.c new file mode 100644 index 0000000..70d83ad --- /dev/null +++ b/h3_iterators.c @@ -0,0 +1,324 @@ +/* + * Copyright 2021 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. + */ + +/** @file IterCellsChildren.c + * @brief Iterator structs and functions for the children of a cell, + * or cells at a given resolution. + */ + +#include "h3_iterators.h" + +#include "h3_h3Index.h" + +// extract the `res` digit (0--7) of the current cell +static int _getResDigit(IterCellsChildren *it, int res) { + return H3_GET_INDEX_DIGIT(it->h, res); +} + +// increment the digit (0--7) at location `res` +// H3_PER_DIGIT_OFFSET == 3 +static void _incrementResDigit(IterCellsChildren *it, int res) { + H3Index val = 1; + val <<= H3_PER_DIGIT_OFFSET * (MAX_H3_RES - res); + it->h += val; +} + +/** + * Create a fully nulled-out child iterator for when an iterator is exhausted. + * This helps minimize the chance that a user will depend on the iterator + * internal state after it's exhausted, like the child resolution, for + * example. + */ +static IterCellsChildren _null_iter() { + return (IterCellsChildren){ + .h = H3_NULL, ._parentRes = -1, ._skipDigit = -1}; +} + +/** + +## Logic for iterating through the children of a cell + +We'll describe the logic for .... + +- normal (non pentagon iteration) +- pentagon iteration. define "pentagon digit" + + +### Cell Index Component Diagrams + +The lower 56 bits of an H3 Cell Index describe the following index components: + +- the cell resolution (4 bits) +- the base cell number (7 bits) +- the child cell digit for each resolution from 1 to 15 (3*15 = 45 bits) + +These are the bits we'll be focused on when iterating through child cells. +To help describe the iteration logic, we'll use diagrams displaying the +(decimal) values for each component like: + + child digit for resolution 2 + / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | ... | +|-----|-------------|---|---|---|---|---|---|-----| +| 9 | 17 | 5 | 3 | 0 | 6 | 2 | 1 | ... | + + +### Iteration through children of a hexagon (but not a pentagon) + +Iteration through the children of a *hexagon* (but not a pentagon) +simply involves iterating through all the children values (0--6) +for each child digit (up to the child's resolution). + +For example, suppose a resolution 3 hexagon index has the following +components: + parent resolution + / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | ... | +|-----|-------------|---|---|---|---|---|---|-----| +| 3 | 17 | 3 | 5 | 1 | 7 | 7 | 7 | ... | + +The iteration through all children of resolution 6 would look like: + + + parent res child res + / / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... | +|-----|-------------|---|---|---|---|---|---|---|---|-----| +| 6 | 17 | 3 | 5 | 1 | 0 | 0 | 0 | 7 | 7 | ... | +| 6 | 17 | 3 | 5 | 1 | 0 | 0 | 1 | 7 | 7 | ... | +| ... | | | | | | | | | | | +| 6 | 17 | 3 | 5 | 1 | 0 | 0 | 6 | 7 | 7 | ... | +| 6 | 17 | 3 | 5 | 1 | 0 | 1 | 0 | 7 | 7 | ... | +| 6 | 17 | 3 | 5 | 1 | 0 | 1 | 1 | 7 | 7 | ... | +| ... | | | | | | | | | | | +| 6 | 17 | 3 | 5 | 1 | 6 | 6 | 6 | 7 | 7 | ... | + + +### Step sequence on a *pentagon* cell + +Pentagon cells have a base cell number (e.g., 97) corresponding to a +resolution 0 pentagon, and have all zeros from digit 1 to the digit +corresponding to the cell's resolution. +(We'll drop the ellipses from now on, knowing that digits should contain +7's beyond the cell resolution.) + + parent res child res + / / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 0 | 0 | + +Iteration through children of a *pentagon* is almost the same +as *hexagon* iteration, except that we skip the *first* 1 value +that appears in the "skip digit". This corresponds to the fact +that a pentagon only has 6 children, which are denoted with +the numbers {0,2,3,4,5,6}. + +The skip digit starts at the child resolution position. +When iterating through children more than one resolution below +the parent, we move the skip digit to the left +(up to the next coarser resolution) each time we skip the 1 value +in that digit. + +Iteration would start like: + + parent res child res + / / +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 0 | 0 | + \ + skip digit + +Noticing we skip the 1 value and move the skip digit, +the next iterate would be: + + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 0 | 2 | + \ + skip digit + +Iteration continues normally until we get to: + + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 0 | 6 | + \ + skip digit + +which is followed by (skipping the 1): + + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 2 | 0 | + \ + skip digit + +For the next iterate, we won't skip the `1` in the previous digit +because it is no longer the skip digit: + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 2 | 1 | + \ + skip digit + +Iteration continues normally until we're right before the next skip +digit: + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 0 | 6 | 6 | + \ + skip digit + +Which is followed by + +| res | base cell # | 1 | 2 | 3 | 4 | 5 | 6 | +|-----|-------------|---|---|---|---|---|---| +| 6 | 97 | 0 | 0 | 0 | 2 | 0 | 0 | + \ + skip digit + +and so on. + + */ + +/** + * Initialize a IterCellsChildren struct representing the sequence giving + * the children of cell `h` at resolution `childRes`. + * + * At any point in the iteration, starting once + * the struct is initialized, IterCellsChildren.h gives the current child. + * + * Also, IterCellsChildren.h == H3_NULL when all the children have been iterated + * through, or if the input to `iterInitParent` was invalid. + */ +IterCellsChildren iterInitParent(H3Index h, int childRes) { + IterCellsChildren it; + + it._parentRes = H3_GET_RESOLUTION(h); + + if (childRes < it._parentRes || childRes > MAX_H3_RES || h == H3_NULL) { + return _null_iter(); + } + + it.h = _zeroIndexDigits(h, it._parentRes + 1, childRes); + H3_SET_RESOLUTION(it.h, childRes); + + if (H3_EXPORT(isPentagon)(it.h)) { + // The skip digit skips `1` for pentagons. + // The "_skipDigit" moves to the left as we count up from the + // child resolution to the parent resolution. + it._skipDigit = childRes; + } else { + // if not a pentagon, we can ignore "skip digit" logic + it._skipDigit = -1; + } + + return it; +} + +/** + * Step a IterCellsChildren to the next child cell. + * When the iteration is over, IterCellsChildren.h will be H3_NULL. + * Handles iterating through hexagon and pentagon cells. + */ +void iterStepChild(IterCellsChildren *it) { + // once h == H3_NULL, the iterator returns an infinite sequence of H3_NULL + if (it->h == H3_NULL) return; + + int childRes = H3_GET_RESOLUTION(it->h); + + _incrementResDigit(it, childRes); + + for (int i = childRes; i >= it->_parentRes; i--) { + if (i == it->_parentRes) { + // if we're modifying the parent resolution digit, then we're done + *it = _null_iter(); + return; + } + + // PENTAGON_SKIPPED_DIGIT == 1 + if (i == it->_skipDigit && + _getResDigit(it, i) == PENTAGON_SKIPPED_DIGIT) { + // Then we are iterating through the children of a pentagon cell. + // All children of a pentagon have the property that the first + // nonzero digit between the parent and child resolutions is + // not 1. + // I.e., we never see a sequence like 00001. + // Thus, we skip the `1` in this digit. + _incrementResDigit(it, i); + it->_skipDigit -= 1; + return; + } + + // INVALID_DIGIT == 7 + if (_getResDigit(it, i) == INVALID_DIGIT) { + _incrementResDigit( + it, i); // zeros out it[i] and increments it[i-1] by 1 + } else { + break; + } + } +} + +// create iterator for children of base cell at given resolution +IterCellsChildren iterInitBaseCellNum(int baseCellNum, int childRes) { + if (baseCellNum < 0 || baseCellNum >= NUM_BASE_CELLS || childRes < 0 || + childRes > MAX_H3_RES) { + return _null_iter(); + } + + H3Index baseCell; + setH3Index(&baseCell, 0, baseCellNum, 0); + + return iterInitParent(baseCell, childRes); +} + +// create iterator for all cells at given resolution +IterCellsResolution iterInitRes(int res) { + IterCellsChildren itC = iterInitBaseCellNum(0, res); + + IterCellsResolution itR = { + .h = itC.h, ._baseCellNum = 0, ._res = res, ._itC = itC}; + + return itR; +} + +void iterStepRes(IterCellsResolution *itR) { + // reached the end of over iterator; emits H3_NULL from now on + if (itR->h == H3_NULL) return; + + // step child iterator + iterStepChild(&(itR->_itC)); + + // If the child iterator is exhausted and there are still + // base cells remaining, we initialize the next base cell child iterator + if ((itR->_itC.h == H3_NULL) && (itR->_baseCellNum + 1 < NUM_BASE_CELLS)) { + itR->_baseCellNum += 1; + itR->_itC = iterInitBaseCellNum(itR->_baseCellNum, itR->_res); + } + + // This overall iterator reflects the next cell in the child iterator. + // Note: This sets itR->h = H3_NULL if the base cells were + // exhausted in the check above. + itR->h = itR->_itC.h; +} diff --git a/h3_iterators.h b/h3_iterators.h new file mode 100644 index 0000000..fa08c33 --- /dev/null +++ b/h3_iterators.h @@ -0,0 +1,86 @@ +/* + * Copyright 2021 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. + */ + +/** @file iterators.h + * @brief Iterator structs and functions for the children of a cell, + * or cells at a given resolution. + */ + +#ifndef ITERATORS_H +#define ITERATORS_H + +#include + +#include "h3_h3api.h" + +/** + * IterCellsChildren: struct for iterating through the descendants of + * a given cell. + * + * Constructors: + * + * Initialize with either `iterInitParent` or `iterInitBaseCellNum`. + * `iterInitParent` sets up an iterator for all the children of a given + * parent cell at a given resolution. + * + * `iterInitBaseCellNum` sets up an iterator for children cells, given + * a base cell number (0--121). + * + * Iteration: + * + * Step iterator with `iterStepChild`. + * During the lifetime of the `IterCellsChildren`, the current iterate + * is accessed via the `IterCellsChildren.h` member. + * When the iterator is exhausted or if there was an error in initialization, + * `IterCellsChildren.h` will be `H3_NULL` even after calling `iterStepChild`. + */ +typedef struct { + H3Index h; + int _parentRes; // parent resolution + int _skipDigit; // this digit skips `1` for pentagons +} IterCellsChildren; + +DECLSPEC IterCellsChildren iterInitParent(H3Index h, int childRes); +DECLSPEC IterCellsChildren iterInitBaseCellNum(int baseCellNum, int childRes); +DECLSPEC void iterStepChild(IterCellsChildren *iter); + +/** + * IterCellsResolution: struct for iterating through all cells at a given + * resolution + * + * Constructor: + * + * Initialize with `IterCellsResolution`. + * + * Iteration: + * + * Step iterator with `iterStepRes`. + * During the lifetime of the iterator the current iterate + * is accessed via the `IterCellsResolution.h` member. + * When the iterator is exhausted or if there was an error in initialization, + * `IterCellsResolution.h` will be `H3_NULL` even after calling `iterStepRes`. + */ +typedef struct { + H3Index h; + int _baseCellNum; + int _res; + IterCellsChildren _itC; +} IterCellsResolution; + +DECLSPEC IterCellsResolution iterInitRes(int res); +DECLSPEC void iterStepRes(IterCellsResolution *iter); + +#endif diff --git a/v3/h3_geoCoord.c b/h3_latLng.c similarity index 57% rename from v3/h3_geoCoord.c rename to h3_latLng.c index 9d64be5..1465508 100644 --- a/v3/h3_geoCoord.c +++ b/h3_latLng.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 Uber Technologies, Inc. + * Copyright 2016-2021 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. @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file geoCoord.c - * @brief Functions for working with lat/lon coordinates. +/** @file latLng.c + * @brief Functions for working with lat/lng coordinates. */ -#include "h3_geoCoord.h" +#include "h3_latLng.h" #include #include #include "h3_constants.h" #include "h3_h3api.h" +#include "h3_mathExtensions.h" /** * Normalizes radians to a value between 0.0 and two PI. @@ -47,10 +48,10 @@ double _posAngleRads(double rads) { * @return Whether or not the two coordinates are within the threshold distance * of each other. */ -bool geoAlmostEqualThreshold(const GeoCoord *p1, const GeoCoord *p2, +bool geoAlmostEqualThreshold(const LatLng *p1, const LatLng *p2, double threshold) { return fabs(p1->lat - p2->lat) < threshold && - fabs(p1->lon - p2->lon) < threshold; + fabs(p1->lng - p2->lng) < threshold; } /** @@ -62,7 +63,7 @@ bool geoAlmostEqualThreshold(const GeoCoord *p1, const GeoCoord *p2, * @return Whether or not the two coordinates are within the epsilon distance * of each other. */ -bool geoAlmostEqual(const GeoCoord *p1, const GeoCoord *p2) { +bool geoAlmostEqual(const LatLng *p1, const LatLng *p2) { return geoAlmostEqualThreshold(p1, p2, EPSILON_RAD); } @@ -71,11 +72,11 @@ bool geoAlmostEqual(const GeoCoord *p1, const GeoCoord *p2) { * * @param p The spherical coordinates. * @param latDegs The desired latitude in decimal degrees. - * @param lonDegs The desired longitude in decimal degrees. + * @param lngDegs The desired longitude in decimal degrees. */ -void setGeoDegs(GeoCoord *p, double latDegs, double lonDegs) { +void setGeoDegs(LatLng *p, double latDegs, double lngDegs) { _setGeoRads(p, H3_EXPORT(degsToRads)(latDegs), - H3_EXPORT(degsToRads)(lonDegs)); + H3_EXPORT(degsToRads)(lngDegs)); } /** @@ -83,11 +84,11 @@ void setGeoDegs(GeoCoord *p, double latDegs, double lonDegs) { * * @param p The spherical coordinates. * @param latRads The desired latitude in decimal radians. - * @param lonRads The desired longitude in decimal radians. + * @param lngRads The desired longitude in decimal radians. */ -void _setGeoRads(GeoCoord *p, double latRads, double lonRads) { +void _setGeoRads(LatLng *p, double latRads, double lngRads) { p->lat = latRads; - p->lon = lonRads; + p->lng = lngRads; } /** @@ -148,9 +149,9 @@ double constrainLng(double lng) { * * @return the great circle distance in radians between a and b */ -double H3_EXPORT(pointDistRads)(const GeoCoord *a, const GeoCoord *b) { +double H3_EXPORT(greatCircleDistanceRads)(const LatLng *a, const LatLng *b) { double sinLat = sin((b->lat - a->lat) / 2.0); - double sinLng = sin((b->lon - a->lon) / 2.0); + double sinLng = sin((b->lng - a->lng) / 2.0); double A = sinLat * sinLat + cos(a->lat) * cos(b->lat) * sinLng * sinLng; @@ -160,15 +161,15 @@ double H3_EXPORT(pointDistRads)(const GeoCoord *a, const GeoCoord *b) { /** * The great circle distance in kilometers between two spherical coordinates. */ -double H3_EXPORT(pointDistKm)(const GeoCoord *a, const GeoCoord *b) { - return H3_EXPORT(pointDistRads)(a, b) * EARTH_RADIUS_KM; +double H3_EXPORT(greatCircleDistanceKm)(const LatLng *a, const LatLng *b) { + return H3_EXPORT(greatCircleDistanceRads)(a, b) * EARTH_RADIUS_KM; } /** * The great circle distance in meters between two spherical coordinates. */ -double H3_EXPORT(pointDistM)(const GeoCoord *a, const GeoCoord *b) { - return H3_EXPORT(pointDistKm)(a, b) * 1000; +double H3_EXPORT(greatCircleDistanceM)(const LatLng *a, const LatLng *b) { + return H3_EXPORT(greatCircleDistanceKm)(a, b) * 1000; } /** @@ -178,10 +179,10 @@ double H3_EXPORT(pointDistM)(const GeoCoord *a, const GeoCoord *b) { * @param p2 The second spherical coordinates. * @return The azimuth in radians from p1 to p2. */ -double _geoAzimuthRads(const GeoCoord *p1, const GeoCoord *p2) { - return atan2(cos(p2->lat) * sin(p2->lon - p1->lon), +double _geoAzimuthRads(const LatLng *p1, const LatLng *p2) { + return atan2(cos(p2->lat) * sin(p2->lng - p1->lng), cos(p1->lat) * sin(p2->lat) - - sin(p1->lat) * cos(p2->lat) * cos(p2->lon - p1->lon)); + sin(p1->lat) * cos(p2->lat) * cos(p2->lng - p1->lng)); } /** @@ -194,14 +195,14 @@ double _geoAzimuthRads(const GeoCoord *p1, const GeoCoord *p2) { * @param p2 The spherical coordinates at the desired azimuth and distance from * p1. */ -void _geoAzDistanceRads(const GeoCoord *p1, double az, double distance, - GeoCoord *p2) { +void _geoAzDistanceRads(const LatLng *p1, double az, double distance, + LatLng *p2) { if (distance < EPSILON) { *p2 = *p1; return; } - double sinlat, sinlon, coslon; + double sinlat, sinlng, coslng; az = _posAngleRads(az); @@ -215,13 +216,13 @@ void _geoAzDistanceRads(const GeoCoord *p1, double az, double distance, if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole { p2->lat = M_PI_2; - p2->lon = 0.0; + p2->lng = 0.0; } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole { p2->lat = -M_PI_2; - p2->lon = 0.0; + p2->lng = 0.0; } else - p2->lon = constrainLng(p1->lon); + p2->lng = constrainLng(p1->lng); } else // not due north or south { sinlat = sin(p1->lat) * cos(distance) + @@ -232,20 +233,20 @@ void _geoAzDistanceRads(const GeoCoord *p1, double az, double distance, if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole { p2->lat = M_PI_2; - p2->lon = 0.0; + p2->lng = 0.0; } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole { p2->lat = -M_PI_2; - p2->lon = 0.0; + p2->lng = 0.0; } else { - sinlon = sin(az) * sin(distance) / cos(p2->lat); - coslon = (cos(distance) - sin(p1->lat) * sin(p2->lat)) / + sinlng = sin(az) * sin(distance) / cos(p2->lat); + coslng = (cos(distance) - sin(p1->lat) * sin(p2->lat)) / cos(p1->lat) / cos(p2->lat); - if (sinlon > 1.0) sinlon = 1.0; - if (sinlon < -1.0) sinlon = -1.0; - if (coslon > 1.0) coslon = 1.0; - if (coslon < -1.0) coslon = -1.0; - p2->lon = constrainLng(p1->lon + atan2(sinlon, coslon)); + if (sinlng > 1.0) sinlng = 1.0; + if (sinlng < -1.0) sinlng = -1.0; + if (coslng > 1.0) coslng = 1.0; + if (coslng < -1.0) coslng = -1.0; + p2->lng = constrainLng(p1->lng + atan2(sinlng, coslng)); } } } @@ -258,71 +259,68 @@ void _geoAzDistanceRads(const GeoCoord *p1, double az, double distance, * the future. */ -double H3_EXPORT(hexAreaKm2)(int res) { +H3Error H3_EXPORT(getHexagonAreaAvgKm2)(int res, double *out) { static const double areas[] = { - 4250546.848, 607220.9782, 86745.85403, 12392.26486, - 1770.323552, 252.9033645, 36.1290521, 5.1612932, - 0.7373276, 0.1053325, 0.0150475, 0.0021496, - 0.0003071, 0.0000439, 0.0000063, 0.0000009}; - return areas[res]; + 4.357449416078383e+06, 6.097884417941332e+05, 8.680178039899720e+04, + 1.239343465508816e+04, 1.770347654491307e+03, 2.529038581819449e+02, + 3.612906216441245e+01, 5.161293359717191e+00, 7.373275975944177e-01, + 1.053325134272067e-01, 1.504750190766435e-02, 2.149643129451879e-03, + 3.070918756316060e-04, 4.387026794728296e-05, 6.267181135324313e-06, + 8.953115907605790e-07}; + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = areas[res]; + return E_SUCCESS; } -double H3_EXPORT(hexAreaM2)(int res) { +H3Error H3_EXPORT(getHexagonAreaAvgM2)(int res, double *out) { static const double areas[] = { - 4.25055E+12, 6.07221E+11, 86745854035, 12392264862, - 1770323552, 252903364.5, 36129052.1, 5161293.2, - 737327.6, 105332.5, 15047.5, 2149.6, - 307.1, 43.9, 6.3, 0.9}; - return areas[res]; + 4.357449416078390e+12, 6.097884417941339e+11, 8.680178039899731e+10, + 1.239343465508818e+10, 1.770347654491309e+09, 2.529038581819452e+08, + 3.612906216441250e+07, 5.161293359717198e+06, 7.373275975944188e+05, + 1.053325134272069e+05, 1.504750190766437e+04, 2.149643129451882e+03, + 3.070918756316063e+02, 4.387026794728301e+01, 6.267181135324322e+00, + 8.953115907605802e-01}; + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = areas[res]; + return E_SUCCESS; } -double H3_EXPORT(edgeLengthKm)(int res) { +H3Error H3_EXPORT(getHexagonEdgeLengthAvgKm)(int res, double *out) { static const double lens[] = { 1107.712591, 418.6760055, 158.2446558, 59.81085794, 22.6063794, 8.544408276, 3.229482772, 1.220629759, 0.461354684, 0.174375668, 0.065907807, 0.024910561, 0.009415526, 0.003559893, 0.001348575, 0.000509713}; - return lens[res]; + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = lens[res]; + return E_SUCCESS; } -double H3_EXPORT(edgeLengthM)(int res) { +H3Error H3_EXPORT(getHexagonEdgeLengthAvgM)(int res, double *out) { static const double lens[] = { 1107712.591, 418676.0055, 158244.6558, 59810.85794, 22606.3794, 8544.408276, 3229.482772, 1220.629759, 461.3546837, 174.3756681, 65.90780749, 24.9105614, 9.415526211, 3.559893033, 1.348574562, 0.509713273}; - return lens[res]; + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = lens[res]; + return E_SUCCESS; } -/** @brief Number of unique valid H3Indexes at given resolution. */ -int64_t H3_EXPORT(numHexagons)(int res) { - /** - * Note: this *actually* returns the number of *cells* - * (which includes the 12 pentagons) at each resolution. - * - * This table comes from the recurrence: - * - * num_cells(0) = 122 - * num_cells(i+1) = (num_cells(i)-12)*7 + 12*6 - * - */ - static const int64_t nums[] = {122L, - 842L, - 5882L, - 41162L, - 288122L, - 2016842L, - 14117882L, - 98825162L, - 691776122L, - 4842432842L, - 33897029882L, - 237279209162L, - 1660954464122L, - 11626681248842L, - 81386768741882L, - 569707381193162L}; - return nums[res]; +H3Error H3_EXPORT(getNumCells)(int res, int64_t *out) { + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + *out = 2 + 120 * _ipow(7, res); + return E_SUCCESS; } /** @@ -357,10 +355,10 @@ double triangleEdgeLengthsToArea(double a, double b, double c) { * * @return area of triangle on unit sphere, in radians^2 */ -double triangleArea(const GeoCoord *a, const GeoCoord *b, const GeoCoord *c) { - return triangleEdgeLengthsToArea(H3_EXPORT(pointDistRads)(a, b), - H3_EXPORT(pointDistRads)(b, c), - H3_EXPORT(pointDistRads)(c, a)); +double triangleArea(const LatLng *a, const LatLng *b, const LatLng *c) { + return triangleEdgeLengthsToArea(H3_EXPORT(greatCircleDistanceRads)(a, b), + H3_EXPORT(greatCircleDistanceRads)(b, c), + H3_EXPORT(greatCircleDistanceRads)(c, a)); } /** @@ -373,68 +371,93 @@ double triangleArea(const GeoCoord *a, const GeoCoord *b, const GeoCoord *c) { * todo: optimize the computation by re-using the edges shared between triangles * * @param cell H3 cell - * - * @return cell area in radians^2 + * @param out cell area in radians^2 + * @return E_SUCCESS on success, or an error code otherwise */ -double H3_EXPORT(cellAreaRads2)(H3Index cell) { - GeoCoord c; - GeoBoundary gb; - H3_EXPORT(h3ToGeo)(cell, &c); - H3_EXPORT(h3ToGeoBoundary)(cell, &gb); +H3Error H3_EXPORT(cellAreaRads2)(H3Index cell, double *out) { + LatLng c; + CellBoundary cb; + H3Error err = H3_EXPORT(cellToLatLng)(cell, &c); + if (err) { + return err; + } + err = H3_EXPORT(cellToBoundary)(cell, &cb); + if (err) { + // TODO: Uncoverable because cellToLatLng will have returned an error + // already + return err; + } double area = 0.0; - for (int i = 0; i < gb.numVerts; i++) { - int j = (i + 1) % gb.numVerts; - area += triangleArea(&gb.verts[i], &gb.verts[j], &c); + for (int i = 0; i < cb.numVerts; i++) { + int j = (i + 1) % cb.numVerts; + area += triangleArea(&cb.verts[i], &cb.verts[j], &c); } - return area; + *out = area; + return E_SUCCESS; } /** * Area of H3 cell in kilometers^2. */ -double H3_EXPORT(cellAreaKm2)(H3Index h) { - return H3_EXPORT(cellAreaRads2)(h) * EARTH_RADIUS_KM * EARTH_RADIUS_KM; +H3Error H3_EXPORT(cellAreaKm2)(H3Index cell, double *out) { + H3Error err = H3_EXPORT(cellAreaRads2)(cell, out); + if (!err) { + *out = *out * EARTH_RADIUS_KM * EARTH_RADIUS_KM; + } + return err; } /** * Area of H3 cell in meters^2. */ -double H3_EXPORT(cellAreaM2)(H3Index h) { - return H3_EXPORT(cellAreaKm2)(h) * 1000 * 1000; +H3Error H3_EXPORT(cellAreaM2)(H3Index cell, double *out) { + H3Error err = H3_EXPORT(cellAreaKm2)(cell, out); + if (!err) { + *out = *out * 1000 * 1000; + } + return err; } /** - * Length of a unidirectional edge in radians. + * Length of a directed edge in radians. * - * @param edge H3 unidirectional edge + * @param edge H3 directed edge * * @return length in radians */ -double H3_EXPORT(exactEdgeLengthRads)(H3Index edge) { - GeoBoundary gb; +H3Error H3_EXPORT(exactEdgeLengthRads)(H3Index edge, double *length) { + CellBoundary cb; - H3_EXPORT(getH3UnidirectionalEdgeBoundary)(edge, &gb); + H3Error err = H3_EXPORT(directedEdgeToBoundary)(edge, &cb); + if (err) { + return err; + } - double length = 0.0; - for (int i = 0; i < gb.numVerts - 1; i++) { - length += H3_EXPORT(pointDistRads)(&gb.verts[i], &gb.verts[i + 1]); + *length = 0.0; + for (int i = 0; i < cb.numVerts - 1; i++) { + *length += + H3_EXPORT(greatCircleDistanceRads)(&cb.verts[i], &cb.verts[i + 1]); } - return length; + return E_SUCCESS; } /** - * Length of a unidirectional edge in kilometers. + * Length of a directed edge in kilometers. */ -double H3_EXPORT(exactEdgeLengthKm)(H3Index edge) { - return H3_EXPORT(exactEdgeLengthRads)(edge) * EARTH_RADIUS_KM; +H3Error H3_EXPORT(exactEdgeLengthKm)(H3Index edge, double *length) { + H3Error err = H3_EXPORT(exactEdgeLengthRads)(edge, length); + *length = *length * EARTH_RADIUS_KM; + return err; } /** - * Length of a unidirectional edge in meters. + * Length of a directed edge in meters. */ -double H3_EXPORT(exactEdgeLengthM)(H3Index edge) { - return H3_EXPORT(exactEdgeLengthKm)(edge) * 1000; +H3Error H3_EXPORT(exactEdgeLengthM)(H3Index edge, double *length) { + H3Error err = H3_EXPORT(exactEdgeLengthKm)(edge, length); + *length = *length * 1000; + return err; } diff --git a/v3/h3_geoCoord.h b/h3_latLng.h similarity index 63% rename from v3/h3_geoCoord.h rename to h3_latLng.h index ebc1f95..2385a08 100644 --- a/v3/h3_geoCoord.h +++ b/h3_latLng.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 Uber Technologies, Inc. + * Copyright 2016-2021 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. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file geoCoord.h - * @brief Geodetic (lat/lon) functions. +/** @file latLng.h + * @brief Geodetic (lat/lng) functions. */ -#ifndef GEOCOORD_H -#define GEOCOORD_H +#ifndef GEOPOINT_H +#define GEOPOINT_H #include #include @@ -32,20 +32,20 @@ /** epsilon of ~0.1mm in radians */ #define EPSILON_RAD (EPSILON_DEG * M_PI_180) -void setGeoDegs(GeoCoord* p, double latDegs, double lonDegs); +void setGeoDegs(LatLng *p, double latDegs, double lngDegs); double constrainLat(double lat); double constrainLng(double lng); -bool geoAlmostEqual(const GeoCoord* p1, const GeoCoord* p2); -bool geoAlmostEqualThreshold(const GeoCoord* p1, const GeoCoord* p2, +bool geoAlmostEqual(const LatLng *p1, const LatLng *p2); +bool geoAlmostEqualThreshold(const LatLng *p1, const LatLng *p2, double threshold); // Internal functions double _posAngleRads(double rads); -void _setGeoRads(GeoCoord* p, double latRads, double lonRads); -double _geoAzimuthRads(const GeoCoord* p1, const GeoCoord* p2); -void _geoAzDistanceRads(const GeoCoord* p1, double az, double distance, - GeoCoord* p2); +void _setGeoRads(LatLng *p, double latRads, double lngRads); +double _geoAzimuthRads(const LatLng *p1, const LatLng *p2); +void _geoAzDistanceRads(const LatLng *p1, double az, double distance, + LatLng *p2); #endif diff --git a/h3_linkedGeo.c b/h3_linkedGeo.c index b637303..b660002 100644 --- a/h3_linkedGeo.c +++ b/h3_linkedGeo.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Uber Technologies, Inc. + * Copyright 2017-2018, 2020-2021 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. @@ -18,19 +18,22 @@ */ #include "h3_linkedGeo.h" + #include #include -#include "h3_geoCoord.h" + +#include "h3_alloc.h" #include "h3_h3api.h" +#include "h3_latLng.h" /** * Add a linked polygon to the current polygon * @param polygon Polygon to add link to * @return Pointer to new polygon */ -LinkedGeoPolygon* addNewLinkedPolygon(LinkedGeoPolygon* polygon) { +LinkedGeoPolygon *addNewLinkedPolygon(LinkedGeoPolygon *polygon) { assert(polygon->next == NULL); - LinkedGeoPolygon* next = calloc(1, sizeof(*next)); + LinkedGeoPolygon *next = H3_MEMORY(calloc)(1, sizeof(*next)); assert(next != NULL); polygon->next = next; return next; @@ -41,8 +44,8 @@ LinkedGeoPolygon* addNewLinkedPolygon(LinkedGeoPolygon* polygon) { * @param polygon Polygon to add loop to * @return Pointer to loop */ -LinkedGeoLoop* addNewLinkedLoop(LinkedGeoPolygon* polygon) { - LinkedGeoLoop* loop = calloc(1, sizeof(*loop)); +LinkedGeoLoop *addNewLinkedLoop(LinkedGeoPolygon *polygon) { + LinkedGeoLoop *loop = H3_MEMORY(calloc)(1, sizeof(*loop)); assert(loop != NULL); return addLinkedLoop(polygon, loop); } @@ -52,8 +55,8 @@ LinkedGeoLoop* addNewLinkedLoop(LinkedGeoPolygon* polygon) { * @param polygon Polygon to add loop to * @return Pointer to loop */ -LinkedGeoLoop* addLinkedLoop(LinkedGeoPolygon* polygon, LinkedGeoLoop* loop) { - LinkedGeoLoop* last = polygon->last; +LinkedGeoLoop *addLinkedLoop(LinkedGeoPolygon *polygon, LinkedGeoLoop *loop) { + LinkedGeoLoop *last = polygon->last; if (last == NULL) { assert(polygon->first == NULL); polygon->first = loop; @@ -70,11 +73,11 @@ LinkedGeoLoop* addLinkedLoop(LinkedGeoPolygon* polygon, LinkedGeoLoop* loop) { * @param vertex Coordinate to add * @return Pointer to the coordinate */ -LinkedGeoCoord* addLinkedCoord(LinkedGeoLoop* loop, const GeoCoord* vertex) { - LinkedGeoCoord* coord = malloc(sizeof(*coord)); +LinkedLatLng *addLinkedCoord(LinkedGeoLoop *loop, const LatLng *vertex) { + LinkedLatLng *coord = H3_MEMORY(malloc)(sizeof(*coord)); assert(coord != NULL); - *coord = (LinkedGeoCoord){.vertex = *vertex, .next = NULL}; - LinkedGeoCoord* last = loop->last; + *coord = (LinkedLatLng){.vertex = *vertex, .next = NULL}; + LinkedLatLng *last = loop->last; if (last == NULL) { assert(loop->first == NULL); loop->first = coord; @@ -90,12 +93,12 @@ LinkedGeoCoord* addLinkedCoord(LinkedGeoLoop* loop, const GeoCoord* vertex) { * responsible for freeing memory allocated to input loop struct. * @param loop Loop to free */ -void destroyLinkedGeoLoop(LinkedGeoLoop* loop) { - LinkedGeoCoord* nextCoord; - for (LinkedGeoCoord* currentCoord = loop->first; currentCoord != NULL; +void destroyLinkedGeoLoop(LinkedGeoLoop *loop) { + LinkedLatLng *nextCoord; + for (LinkedLatLng *currentCoord = loop->first; currentCoord != NULL; currentCoord = nextCoord) { nextCoord = currentCoord->next; - free(currentCoord); + H3_MEMORY(free)(currentCoord); } } @@ -104,25 +107,25 @@ void destroyLinkedGeoLoop(LinkedGeoLoop* loop) { * responsible for freeing memory allocated to input polygon struct. * @param polygon Pointer to the first polygon in the structure */ -void H3_EXPORT(destroyLinkedPolygon)(LinkedGeoPolygon* polygon) { +void H3_EXPORT(destroyLinkedMultiPolygon)(LinkedGeoPolygon *polygon) { // flag to skip the input polygon bool skip = true; - LinkedGeoPolygon* nextPolygon; - LinkedGeoLoop* nextLoop; - for (LinkedGeoPolygon* currentPolygon = polygon; currentPolygon != NULL; + LinkedGeoPolygon *nextPolygon; + LinkedGeoLoop *nextLoop; + for (LinkedGeoPolygon *currentPolygon = polygon; currentPolygon != NULL; currentPolygon = nextPolygon) { - for (LinkedGeoLoop* currentLoop = currentPolygon->first; + for (LinkedGeoLoop *currentLoop = currentPolygon->first; currentLoop != NULL; currentLoop = nextLoop) { destroyLinkedGeoLoop(currentLoop); nextLoop = currentLoop->next; - free(currentLoop); + H3_MEMORY(free)(currentLoop); } nextPolygon = currentPolygon->next; if (skip) { // do not free the input polygon skip = false; } else { - free(currentPolygon); + H3_MEMORY(free)(currentPolygon); } } } @@ -132,7 +135,7 @@ void H3_EXPORT(destroyLinkedPolygon)(LinkedGeoPolygon* polygon) { * @param polygon Starting polygon * @return Count */ -int countLinkedPolygons(LinkedGeoPolygon* polygon) { +int countLinkedPolygons(LinkedGeoPolygon *polygon) { int count = 0; while (polygon != NULL) { count++; @@ -146,8 +149,8 @@ int countLinkedPolygons(LinkedGeoPolygon* polygon) { * @param polygon Polygon to count loops for * @return Count */ -int countLinkedLoops(LinkedGeoPolygon* polygon) { - LinkedGeoLoop* loop = polygon->first; +int countLinkedLoops(LinkedGeoPolygon *polygon) { + LinkedGeoLoop *loop = polygon->first; int count = 0; while (loop != NULL) { count++; @@ -161,8 +164,8 @@ int countLinkedLoops(LinkedGeoPolygon* polygon) { * @param loop Loop to count coordinates for * @return Count */ -int countLinkedCoords(LinkedGeoLoop* loop) { - LinkedGeoCoord* coord = loop->first; +int countLinkedCoords(LinkedGeoLoop *loop) { + LinkedLatLng *coord = loop->first; int count = 0; while (coord != NULL) { count++; @@ -179,9 +182,9 @@ int countLinkedCoords(LinkedGeoLoop* loop) { * @param polygonCount Number of polygons in the test array * @return Number of polygons containing the loop */ -static int countContainers(const LinkedGeoLoop* loop, - const LinkedGeoPolygon** polygons, - const BBox** bboxes, const int polygonCount) { +static int countContainers(const LinkedGeoLoop *loop, + const LinkedGeoPolygon **polygons, + const BBox **bboxes, const int polygonCount) { int containerCount = 0; for (int i = 0; i < polygonCount; i++) { if (loop != polygons[i]->first && @@ -200,11 +203,11 @@ static int countContainers(const LinkedGeoLoop* loop, * @param polygonCount Number of polygons in the list * @return Deepest container, or null if list is empty */ -static const LinkedGeoPolygon* findDeepestContainer( - const LinkedGeoPolygon** polygons, const BBox** bboxes, +static const LinkedGeoPolygon *findDeepestContainer( + const LinkedGeoPolygon **polygons, const BBox **bboxes, const int polygonCount) { // Set the initial return value to the first candidate - const LinkedGeoPolygon* parent = polygonCount > 0 ? polygons[0] : NULL; + const LinkedGeoPolygon *parent = polygonCount > 0 ? polygons[0] : NULL; // If we have multiple polygons, they must be nested inside each other. // Find the innermost polygon by taking the one with the most containers @@ -233,18 +236,19 @@ static const LinkedGeoPolygon* findDeepestContainer( * @param polygonCount Number of polygons to check * @return Pointer to parent polygon, or null if not found */ -static const LinkedGeoPolygon* findPolygonForHole( - const LinkedGeoLoop* loop, const LinkedGeoPolygon* polygon, - const BBox* bboxes, const int polygonCount) { +static const LinkedGeoPolygon *findPolygonForHole( + const LinkedGeoLoop *loop, const LinkedGeoPolygon *polygon, + const BBox *bboxes, const int polygonCount) { // Early exit with no polygons if (polygonCount == 0) { return NULL; } // Initialize arrays for candidate loops and their bounding boxes - const LinkedGeoPolygon** candidates = - malloc(polygonCount * sizeof(LinkedGeoPolygon*)); + const LinkedGeoPolygon **candidates = + H3_MEMORY(malloc)(polygonCount * sizeof(LinkedGeoPolygon *)); assert(candidates != NULL); - const BBox** candidateBBoxes = malloc(polygonCount * sizeof(BBox*)); + const BBox **candidateBBoxes = + H3_MEMORY(malloc)(polygonCount * sizeof(BBox *)); assert(candidateBBoxes != NULL); // Find all polygons that contain the loop @@ -263,12 +267,12 @@ static const LinkedGeoPolygon* findPolygonForHole( } // The most deeply nested container is the immediate parent - const LinkedGeoPolygon* parent = + const LinkedGeoPolygon *parent = findDeepestContainer(candidates, candidateBBoxes, candidateCount); // Free allocated memory - free(candidates); - free(candidateBBoxes); + H3_MEMORY(free)(candidates); + H3_MEMORY(free)(candidateBBoxes); return parent; } @@ -286,7 +290,7 @@ static const LinkedGeoPolygon* findPolygonForHole( * @param root Root polygon including all loops * @return 0 on success, or an error code > 0 for invalid input */ -int normalizeMultiPolygon(LinkedGeoPolygon* root) { +int normalizeMultiPolygon(LinkedGeoPolygon *root) { // We assume that the input is a single polygon with loops; // if it has multiple polygons, don't touch it if (root->next) { @@ -300,23 +304,24 @@ int normalizeMultiPolygon(LinkedGeoPolygon* root) { } int resultCode = NORMALIZATION_SUCCESS; - LinkedGeoPolygon* polygon = NULL; - LinkedGeoLoop* next; + LinkedGeoPolygon *polygon = NULL; + LinkedGeoLoop *next; int innerCount = 0; int outerCount = 0; // Create an array to hold all of the inner loops. Note that // this array will never be full, as there will always be fewer // inner loops than outer loops. - LinkedGeoLoop** innerLoops = malloc(loopCount * sizeof(LinkedGeoLoop*)); + LinkedGeoLoop **innerLoops = + H3_MEMORY(malloc)(loopCount * sizeof(LinkedGeoLoop *)); assert(innerLoops != NULL); // Create an array to hold the bounding boxes for the outer loops - BBox* bboxes = malloc(loopCount * sizeof(BBox)); + BBox *bboxes = H3_MEMORY(malloc)(loopCount * sizeof(BBox)); assert(bboxes != NULL); // Get the first loop and unlink it from root - LinkedGeoLoop* loop = root->first; + LinkedGeoLoop *loop = root->first; *root = (LinkedGeoPolygon){0}; // Iterate over all loops, moving inner loops into an array and @@ -339,24 +344,24 @@ int normalizeMultiPolygon(LinkedGeoPolygon* root) { // Find polygon for each inner loop and assign the hole to it for (int i = 0; i < innerCount; i++) { - polygon = (LinkedGeoPolygon*)findPolygonForHole(innerLoops[i], root, - bboxes, outerCount); + polygon = (LinkedGeoPolygon *)findPolygonForHole(innerLoops[i], root, + bboxes, outerCount); if (polygon) { addLinkedLoop(polygon, innerLoops[i]); } else { // If we can't find a polygon (possible with invalid input), then // we need to release the memory for the hole, because the loop has // been unlinked from the root and the caller will no longer have - // a way to destroy it with destroyLinkedPolygon. + // a way to destroy it with destroyLinkedMultiPolygon. destroyLinkedGeoLoop(innerLoops[i]); - free(innerLoops[i]); + H3_MEMORY(free)(innerLoops[i]); resultCode = NORMALIZATION_ERR_UNASSIGNED_HOLES; } } // Free allocated memory - free(innerLoops); - free(bboxes); + H3_MEMORY(free)(innerLoops); + H3_MEMORY(free)(bboxes); return resultCode; } diff --git a/h3_linkedGeo.h b/h3_linkedGeo.h index 5fab9ea..d8ba08b 100644 --- a/h3_linkedGeo.h +++ b/h3_linkedGeo.h @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Uber Technologies, Inc. + * Copyright 2017-2018, 2020-2021 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. @@ -21,9 +21,10 @@ #define LINKED_GEO_H #include + #include "h3_bbox.h" -#include "h3_geoCoord.h" #include "h3_h3api.h" +#include "h3_latLng.h" // Error codes for normalizeMultiPolygon #define NORMALIZATION_SUCCESS 0 @@ -32,9 +33,9 @@ // Macros for use with polygonAlgos.h /** Macro: Init iteration vars for LinkedGeoLoop */ -#define INIT_ITERATION_LINKED_LOOP \ - LinkedGeoCoord* currentCoord = NULL; \ - LinkedGeoCoord* nextCoord = NULL +#define INIT_ITERATION_LINKED_LOOP \ + LinkedLatLng *currentCoord = NULL; \ + LinkedLatLng *nextCoord = NULL /** Macro: Get the next coord in a linked loop, wrapping if needed */ #define GET_NEXT_COORD(loop, coordToCheck) \ @@ -51,25 +52,25 @@ /** Macro: Whether a LinkedGeoLoop is empty */ #define IS_EMPTY_LINKED_LOOP(loop) loop->first == NULL -int normalizeMultiPolygon(LinkedGeoPolygon* root); -LinkedGeoPolygon* addNewLinkedPolygon(LinkedGeoPolygon* polygon); -LinkedGeoLoop* addNewLinkedLoop(LinkedGeoPolygon* polygon); -LinkedGeoLoop* addLinkedLoop(LinkedGeoPolygon* polygon, LinkedGeoLoop* loop); -LinkedGeoCoord* addLinkedCoord(LinkedGeoLoop* loop, const GeoCoord* vertex); -int countLinkedPolygons(LinkedGeoPolygon* polygon); -int countLinkedLoops(LinkedGeoPolygon* polygon); -int countLinkedCoords(LinkedGeoLoop* loop); -void destroyLinkedGeoLoop(LinkedGeoLoop* loop); +int normalizeMultiPolygon(LinkedGeoPolygon *root); +LinkedGeoPolygon *addNewLinkedPolygon(LinkedGeoPolygon *polygon); +LinkedGeoLoop *addNewLinkedLoop(LinkedGeoPolygon *polygon); +LinkedGeoLoop *addLinkedLoop(LinkedGeoPolygon *polygon, LinkedGeoLoop *loop); +LinkedLatLng *addLinkedCoord(LinkedGeoLoop *loop, const LatLng *vertex); +int countLinkedPolygons(LinkedGeoPolygon *polygon); +int countLinkedLoops(LinkedGeoPolygon *polygon); +int countLinkedCoords(LinkedGeoLoop *loop); +void destroyLinkedGeoLoop(LinkedGeoLoop *loop); // The following functions are created via macro in polygonAlgos.h, // so their signatures are documented here: /** * Create a bounding box from a LinkedGeoLoop - * @param geofence Input Geofence + * @param geoloop Input GeoLoop * @param bbox Output bbox */ -void bboxFromLinkedGeoLoop(const LinkedGeoLoop* loop, BBox* bbox); +void bboxFromLinkedGeoLoop(const LinkedGeoLoop *loop, BBox *bbox); /** * Take a given LinkedGeoLoop data structure and check if it @@ -79,14 +80,14 @@ void bboxFromLinkedGeoLoop(const LinkedGeoLoop* loop, BBox* bbox); * @param coord The coordinate to check * @return Whether the point is contained */ -bool pointInsideLinkedGeoLoop(const LinkedGeoLoop* loop, const BBox* bbox, - const GeoCoord* coord); +bool pointInsideLinkedGeoLoop(const LinkedGeoLoop *loop, const BBox *bbox, + const LatLng *coord); /** * Whether the winding order of a given LinkedGeoLoop is clockwise * @param loop The loop to check * @return Whether the loop is clockwise */ -bool isClockwiseLinkedGeoLoop(const LinkedGeoLoop* loop); +bool isClockwiseLinkedGeoLoop(const LinkedGeoLoop *loop); #endif diff --git a/h3_localij.c b/h3_localij.c index fda0458..72f7712 100644 --- a/h3_localij.c +++ b/h3_localij.c @@ -1,5 +1,5 @@ /* - * Copyright 2018 Uber Technologies, Inc. + * Copyright 2018-2020 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. @@ -19,15 +19,16 @@ * These functions try to provide a useful coordinate space in the vicinity of * an origin index. */ +#include #include #include #include #include + #include "h3_baseCells.h" #include "h3_faceijk.h" #include "h3_h3Index.h" #include "h3_mathExtensions.h" -#include "h3_stackAlloc.h" /** * Origin leading digit -> index leading digit -> rotations 60 cw @@ -86,24 +87,28 @@ const int PENTAGON_ROTATIONS_REVERSE_POLAR[7][7] = { {0, 1, 1, 0, 1, 1, 1}, // 6 }; -// Simply prohibit many pentagon distortion cases rather than handling them. -const bool FAILED_DIRECTIONS_II[7][7] = { - {false, false, false, false, false, false, false}, // 0 - {false, false, false, false, false, false, false}, // 1 - {false, false, false, false, true, false, false}, // 2 - {false, false, false, false, false, false, true}, // 3 - {false, false, false, true, false, false, false}, // 4 - {false, false, true, false, false, false, false}, // 5 - {false, false, false, false, false, true, false}, // 6 -}; -const bool FAILED_DIRECTIONS_III[7][7] = { +/** + * Prohibited directions when unfolding a pentagon. + * + * Indexes by two directions, both relative to the pentagon base cell. The first + * is the direction of the origin index and the second is the direction of the + * index to unfold. Direction refers to the direction from base cell to base + * cell if the indexes are on different base cells, or the leading digit if + * within the pentagon base cell. + * + * This previously included a Class II/Class III check but these were removed + * due to failure cases. It's possible this could be restricted to a narrower + * set of a failure cases. Currently, the logic is any unfolding across more + * than one icosahedron face is not permitted. + */ +const bool FAILED_DIRECTIONS[7][7] = { {false, false, false, false, false, false, false}, // 0 {false, false, false, false, false, false, false}, // 1 - {false, false, false, false, false, true, false}, // 2 - {false, false, false, false, true, false, false}, // 3 - {false, false, true, false, false, false, false}, // 4 - {false, false, false, false, false, false, true}, // 5 - {false, false, false, true, false, false, false}, // 6 + {false, false, false, false, true, true, false}, // 2 + {false, false, false, false, true, false, true}, // 3 + {false, false, true, true, false, false, false}, // 4 + {false, false, true, false, false, false, true}, // 5 + {false, false, false, true, false, true, false}, // 6 }; /** @@ -123,24 +128,34 @@ const bool FAILED_DIRECTIONS_III[7][7] = { * @param out ijk+ coordinates of the index will be placed here on success * @return 0 on success, or another value on failure. */ -int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) { +H3Error cellToLocalIjk(H3Index origin, H3Index h3, CoordIJK *out) { int res = H3_GET_RESOLUTION(origin); if (res != H3_GET_RESOLUTION(h3)) { - return 1; + return E_RES_MISMATCH; } int originBaseCell = H3_GET_BASE_CELL(origin); int baseCell = H3_GET_BASE_CELL(h3); + if (originBaseCell < 0 || // LCOV_EXCL_BR_LINE + originBaseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } + if (baseCell < 0 || baseCell >= NUM_BASE_CELLS) { // LCOV_EXCL_BR_LINE + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } + // Direction from origin base cell to index base cell - Direction dir = 0; - Direction revDir = 0; + Direction dir = CENTER_DIGIT; + Direction revDir = CENTER_DIGIT; if (originBaseCell != baseCell) { dir = _getBaseCellDirection(originBaseCell, baseCell); if (dir == INVALID_DIGIT) { // Base cells are not neighbors, can't unfold. - return 2; + return E_FAILED; } revDir = _getBaseCellDirection(baseCell, originBaseCell); assert(revDir != INVALID_DIGIT); @@ -182,16 +197,14 @@ int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) { if (originOnPent) { int originLeadingDigit = _h3LeadingNonZeroDigit(origin); - // TODO: This previously included the Class III-based checks - // as in the index-on-pentagon case below, but these were - // removed due to some failure cases. It is possible that we - // could restrict this error to a narrower set of cases. - // https://github.com/uber/h3/issues/163 - if (FAILED_DIRECTIONS_III[originLeadingDigit][dir] || - FAILED_DIRECTIONS_II[originLeadingDigit][dir]) { - // TODO this part of the pentagon might not be unfolded - // correctly. - return 3; + if (originLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + if (FAILED_DIRECTIONS[originLeadingDigit][dir]) { + // TODO: We may be unfolding the pentagon incorrectly in this + // case; return an error code until this is guaranteed to be + // correct. + return E_FAILED; } directionRotations = PENTAGON_ROTATIONS[originLeadingDigit][dir]; @@ -199,20 +212,23 @@ int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) { } else if (indexOnPent) { int indexLeadingDigit = _h3LeadingNonZeroDigit(h3); - if ((isResClassIII(res) && - FAILED_DIRECTIONS_III[indexLeadingDigit][revDir]) || - (!isResClassIII(res) && - FAILED_DIRECTIONS_II[indexLeadingDigit][revDir])) { - // TODO this part of the pentagon might not be unfolded - // correctly. - return 4; + if (indexLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + if (FAILED_DIRECTIONS[indexLeadingDigit][revDir]) { + // TODO: We may be unfolding the pentagon incorrectly in this + // case; return an error code until this is guaranteed to be + // correct. + return E_FAILED; } pentagonRotations = PENTAGON_ROTATIONS[revDir][indexLeadingDigit]; } - assert(pentagonRotations >= 0); - assert(directionRotations >= 0); + if (pentagonRotations < 0 || directionRotations < 0) { + // This occurs when an invalid K axis digit is present + return E_CELL_INVALID; + } for (int i = 0; i < pentagonRotations; i++) { _ijkRotate60cw(&indexFijk.coord); @@ -222,7 +238,7 @@ int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) { _neighbor(&offset, dir); // Scale offset based on resolution for (int r = res - 1; r >= 0; r--) { - if (isResClassIII(r + 1)) { + if (isResolutionClassIII(r + 1)) { // rotate ccw _downAp7(&offset); } else { @@ -247,11 +263,14 @@ int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) { int originLeadingDigit = _h3LeadingNonZeroDigit(origin); int indexLeadingDigit = _h3LeadingNonZeroDigit(h3); - if (FAILED_DIRECTIONS_III[originLeadingDigit][indexLeadingDigit] || - FAILED_DIRECTIONS_II[originLeadingDigit][indexLeadingDigit]) { - // TODO this part of the pentagon might not be unfolded - // correctly. - return 5; + if (originLeadingDigit == INVALID_DIGIT || + indexLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + if (FAILED_DIRECTIONS[originLeadingDigit][indexLeadingDigit]) { + // TODO: We may be unfolding the pentagon incorrectly in this case; + // return an error code until this is guaranteed to be correct. + return E_FAILED; } int withinPentagonRotations = @@ -263,7 +282,7 @@ int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) { } *out = indexFijk.coord; - return 0; + return E_SUCCESS; } /** @@ -280,32 +299,37 @@ int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) { * @param out The index will be placed here on success * @return 0 on success, or another value on failure. */ -int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { +H3Error localIjkToCell(H3Index origin, const CoordIJK *ijk, H3Index *out) { int res = H3_GET_RESOLUTION(origin); int originBaseCell = H3_GET_BASE_CELL(origin); + if (originBaseCell < 0 || // LCOV_EXCL_BR_LINE + originBaseCell >= NUM_BASE_CELLS) { + // Base cells less than zero can not be represented in an index + return E_CELL_INVALID; + } int originOnPent = _isBaseCellPentagon(originBaseCell); // This logic is very similar to faceIjkToH3 // initialize the index *out = H3_INIT; - H3_SET_MODE(*out, H3_HEXAGON_MODE); + H3_SET_MODE(*out, H3_CELL_MODE); H3_SET_RESOLUTION(*out, res); // check for res 0/base cell if (res == 0) { - if (ijk->i > 1 || ijk->i > 1 || ijk->i > 1) { + if (ijk->i > 1 || ijk->j > 1 || ijk->k > 1) { // out of range input - return 1; + return E_FAILED; } const Direction dir = _unitIjkToDigit(ijk); const int newBaseCell = _getBaseCellNeighbor(originBaseCell, dir); if (newBaseCell == INVALID_BASE_CELL) { // Moving in an invalid direction off a pentagon. - return 1; + return E_FAILED; } H3_SET_BASE_CELL(*out, newBaseCell); - return 0; + return E_SUCCESS; } // we need to find the correct base cell offset (if any) for this H3 index; @@ -319,7 +343,7 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { for (int r = res - 1; r >= 0; r--) { CoordIJK lastIJK = ijkCopy; CoordIJK lastCenter; - if (isResClassIII(r + 1)) { + if (isResolutionClassIII(r + 1)) { // rotate ccw _upAp7(&ijkCopy); lastCenter = ijkCopy; @@ -343,7 +367,7 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { if (ijkCopy.i > 1 || ijkCopy.j > 1 || ijkCopy.k > 1) { // out of range input - return 2; + return E_FAILED; } // lookup the correct base cell @@ -361,6 +385,9 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { int pentagonRotations = 0; if (originOnPent) { const Direction originLeadingDigit = _h3LeadingNonZeroDigit(origin); + if (originLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } pentagonRotations = PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][dir]; for (int i = 0; i < pentagonRotations; i++) { @@ -370,7 +397,7 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { // deleted direction. If it still happens, it means we're moving // into a deleted subsequence, so there is no index here. if (dir == K_AXES_DIGIT) { - return 3; + return E_PENTAGON; } baseCell = _getBaseCellNeighbor(originBaseCell, dir); @@ -403,6 +430,13 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { } const Direction indexLeadingDigit = _h3LeadingNonZeroDigit(*out); + // This case should be unreachable because this function is building + // *out, and should never generate an invalid digit, above. + // LCOV_EXCL_START + if (indexLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } + // LCOV_EXCL_STOP if (_isBaseCellPolarPentagon(baseCell)) { pentagonRotations = PENTAGON_ROTATIONS_REVERSE_POLAR[revDir][indexLeadingDigit]; @@ -411,13 +445,21 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { PENTAGON_ROTATIONS_REVERSE_NONPOLAR[revDir] [indexLeadingDigit]; } + // For this to occur, revDir would need to be 1. Since revDir is + // from the index base cell (which is a pentagon) towards the + // origin, this should never be the case. LCOV_EXCL_START + if (pentagonRotations < 0) { + return E_CELL_INVALID; + } + // LCOV_EXCL_STOP - assert(pentagonRotations >= 0); for (int i = 0; i < pentagonRotations; i++) { *out = _h3RotatePent60ccw(*out); } } else { - assert(pentagonRotations >= 0); + if (pentagonRotations < 0) { + return E_CELL_INVALID; + } for (int i = 0; i < pentagonRotations; i++) { *out = _h3Rotate60ccw(*out); } @@ -431,9 +473,16 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { const int originLeadingDigit = _h3LeadingNonZeroDigit(origin); const int indexLeadingDigit = _h3LeadingNonZeroDigit(*out); + if (originLeadingDigit == INVALID_DIGIT || + indexLeadingDigit == INVALID_DIGIT) { + return E_CELL_INVALID; + } const int withinPentagonRotations = PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][indexLeadingDigit]; - assert(withinPentagonRotations >= 0); + if (withinPentagonRotations < 0) { + // This occurs when an invalid K axis digit is present + return E_CELL_INVALID; + } for (int i = 0; i < withinPentagonRotations; i++) { *out = _h3Rotate60ccw(*out); @@ -441,16 +490,16 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { } if (indexOnPent) { - // TODO: There are cases in h3ToLocalIjk which are failed but not + // TODO: There are cases in cellToLocalIjk which are failed but not // accounted for here - instead just fail if the recovered index is // invalid. if (_h3LeadingNonZeroDigit(*out) == K_AXES_DIGIT) { - return 4; + return E_PENTAGON; } } H3_SET_BASE_CELL(*out, baseCell); - return 0; + return E_SUCCESS; } /** @@ -465,29 +514,29 @@ int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { * Failure may occur if the index is too far away from the origin * or if the index is on the other side of a pentagon. * - * This function is experimental, and its output is not guaranteed + * This function's output is not guaranteed * to be compatible across different versions of H3. * * @param origin An anchoring index for the ij coordinate system. * @param index Index to find the coordinates of + * @param mode Mode, must be 0 * @param out ij coordinates of the index will be placed here on success * @return 0 on success, or another value on failure. */ -int H3_EXPORT(experimentalH3ToLocalIj)(H3Index origin, H3Index h3, - CoordIJ* out) { - // This function is currently experimental. Once ready to be part of the - // non-experimental API, this function (with the experimental prefix) will - // be marked as deprecated and to be removed in the next major version. It - // will be replaced with a non-prefixed function name. +H3Error H3_EXPORT(cellToLocalIj)(H3Index origin, H3Index h3, uint32_t mode, + CoordIJ *out) { + if (mode != 0) { + return E_OPTION_INVALID; + } CoordIJK ijk; - int failed = h3ToLocalIjk(origin, h3, &ijk); + H3Error failed = cellToLocalIjk(origin, h3, &ijk); if (failed) { return failed; } ijkToIj(&ijk, out); - return 0; + return E_SUCCESS; } /** @@ -499,24 +548,24 @@ int H3_EXPORT(experimentalH3ToLocalIj)(H3Index origin, H3Index h3, * Failure may occur if the index is too far away from the origin * or if the index is on the other side of a pentagon. * - * This function is experimental, and its output is not guaranteed + * This function's output is not guaranteed * to be compatible across different versions of H3. * * @param origin An anchoring index for the ij coordinate system. * @param out ij coordinates to index. + * @param mode Mode, must be 0 * @param index Index will be placed here on success. * @return 0 on success, or another value on failure. */ -int H3_EXPORT(experimentalLocalIjToH3)(H3Index origin, const CoordIJ* ij, - H3Index* out) { - // This function is currently experimental. Once ready to be part of the - // non-experimental API, this function (with the experimental prefix) will - // be marked as deprecated and to be removed in the next major version. It - // will be replaced with a non-prefixed function name. +H3Error H3_EXPORT(localIjToCell)(H3Index origin, const CoordIJ *ij, + uint32_t mode, H3Index *out) { + if (mode != 0) { + return E_OPTION_INVALID; + } CoordIJK ijk; ijToIjk(ij, &ijk); - return localIjkToH3(origin, &ijk, out); + return localIjkToCell(origin, &ijk, out); } /** @@ -531,18 +580,19 @@ int H3_EXPORT(experimentalLocalIjToH3)(H3Index origin, const CoordIJ* ij, * @return The distance, or a negative number if the library could not * compute the distance. */ -int H3_EXPORT(h3Distance)(H3Index origin, H3Index h3) { +H3Error H3_EXPORT(gridDistance)(H3Index origin, H3Index h3, int64_t *out) { CoordIJK originIjk, h3Ijk; - if (h3ToLocalIjk(origin, origin, &originIjk)) { - // Currently there are no tests that would cause getting the coordinates - // for an index the same as the origin to fail. - return -1; // LCOV_EXCL_LINE + H3Error originError = cellToLocalIjk(origin, origin, &originIjk); + if (originError) { + return originError; } - if (h3ToLocalIjk(origin, h3, &h3Ijk)) { - return -1; + H3Error destError = cellToLocalIjk(origin, h3, &h3Ijk); + if (destError) { + return destError; } - return ijkDistance(&originIjk, &h3Ijk); + *out = ijkDistance(&originIjk, &h3Ijk); + return E_SUCCESS; } /** @@ -552,12 +602,18 @@ int H3_EXPORT(h3Distance)(H3Index origin, H3Index h3) { * * @param start Start index of the line * @param end End index of the line - * @return Size of the line, or a negative number if the line cannot - * be computed. + * @param size Size of the line + * @returns 0 on success, or another value on error */ -int H3_EXPORT(h3LineSize)(H3Index start, H3Index end) { - int distance = H3_EXPORT(h3Distance)(start, end); - return distance >= 0 ? distance + 1 : distance; +H3Error H3_EXPORT(gridPathCellsSize)(H3Index start, H3Index end, + int64_t *size) { + int64_t distance; + H3Error distanceError = H3_EXPORT(gridDistance)(start, end, &distance); + if (distanceError) { + return distanceError; + } + *size = distance + 1; + return E_SUCCESS; } /** @@ -568,7 +624,7 @@ int H3_EXPORT(h3LineSize)(H3Index start, H3Index end) { * @param k Floating-point K coord * @param ijk IJK coord struct, modified in place */ -static void cubeRound(double i, double j, double k, CoordIJK* ijk) { +static void cubeRound(double i, double j, double k, CoordIJK *ijk) { int ri = round(i); int rj = round(j); int rk = round(k); @@ -602,21 +658,22 @@ static void cubeRound(double i, double j, double k, CoordIJK* ijk) { * * - The specific output of this function should not be considered stable * across library versions. The only guarantees the library provides are - * that the line length will be `h3Distance(start, end) + 1` and that + * that the line length will be `gridDistance(start, end) + 1` and that * every index in the line will be a neighbor of the preceding index. * - Lines are drawn in grid space, and may not correspond exactly to either * Cartesian lines or great arcs. * * @param start Start index of the line * @param end End index of the line - * @param out Output array, which must be of size h3LineSize(start, end) + * @param out Output array, which must be of size gridPathCellsSize(start, end) * @return 0 on success, or another value on failure. */ -int H3_EXPORT(h3Line)(H3Index start, H3Index end, H3Index* out) { - int distance = H3_EXPORT(h3Distance)(start, end); +H3Error H3_EXPORT(gridPathCells)(H3Index start, H3Index end, H3Index *out) { + int64_t distance; + H3Error distanceError = H3_EXPORT(gridDistance)(start, end, &distance); // Early exit if we can't calculate the line - if (distance < 0) { - return distance; + if (distanceError) { + return distanceError; } // Get IJK coords for the start and end. We've already confirmed @@ -625,8 +682,16 @@ int H3_EXPORT(h3Line)(H3Index start, H3Index end, H3Index* out) { CoordIJK endIjk = {0}; // Convert H3 addresses to IJK coords - h3ToLocalIjk(start, start, &startIjk); - h3ToLocalIjk(start, end, &endIjk); + H3Error startError = cellToLocalIjk(start, start, &startIjk); + if (startError) { // LCOV_EXCL_BR_LINE + // Unreachable because this was called as part of gridDistance + return startError; // LCOV_EXCL_LINE + } + H3Error endError = cellToLocalIjk(start, end, &endIjk); + if (endError) { // LCOV_EXCL_BR_LINE + // Unreachable because this was called as part of gridDistance + return endError; // LCOV_EXCL_LINE + } // Convert IJK to cube coordinates suitable for linear interpolation ijkToCube(&startIjk); @@ -640,14 +705,19 @@ int H3_EXPORT(h3Line)(H3Index start, H3Index end, H3Index* out) { distance ? (double)(endIjk.k - startIjk.k) / (double)distance : 0; CoordIJK currentIjk = {startIjk.i, startIjk.j, startIjk.k}; - for (int n = 0; n <= distance; n++) { + for (int64_t n = 0; n <= distance; n++) { cubeRound((double)startIjk.i + iStep * n, (double)startIjk.j + jStep * n, (double)startIjk.k + kStep * n, ¤tIjk); // Convert cube -> ijk -> h3 index cubeToIjk(¤tIjk); - localIjkToH3(start, ¤tIjk, &out[n]); + H3Error currentError = localIjkToCell(start, ¤tIjk, &out[n]); + if (currentError) { // LCOV_EXCL_BR_LINE + // Expected to be unreachable since cells between `start` and `end` + // should have valid local IJK coordinates. + return currentError; // LCOV_EXCL_LINE + } } - return 0; + return E_SUCCESS; } diff --git a/h3_localij.h b/h3_localij.h index fa2ab5c..fe8bbfc 100644 --- a/h3_localij.h +++ b/h3_localij.h @@ -23,7 +23,7 @@ #include "h3_coordijk.h" #include "h3_h3api.h" -int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out); -int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out); +H3Error cellToLocalIjk(H3Index origin, H3Index h3, CoordIJK *out); +H3Error localIjkToCell(H3Index origin, const CoordIJK *ijk, H3Index *out); #endif diff --git a/h3_mathExtensions.c b/h3_mathExtensions.c index 1c544a1..3987da7 100644 --- a/h3_mathExtensions.c +++ b/h3_mathExtensions.c @@ -22,13 +22,13 @@ /** * _ipow does integer exponentiation efficiently. Taken from StackOverflow. * - * @param base the integer base - * @param exp the integer exponent + * @param base the integer base (can be positive or negative) + * @param exp the integer exponent (should be nonnegative) * * @return the exponentiated value */ -int _ipow(int base, int exp) { - int result = 1; +int64_t _ipow(int64_t base, int64_t exp) { + int64_t result = 1; while (exp) { if (exp & 1) result *= base; exp >>= 1; diff --git a/h3_mathExtensions.h b/h3_mathExtensions.h index 650d6a9..2ed47e3 100644 --- a/h3_mathExtensions.h +++ b/h3_mathExtensions.h @@ -20,12 +20,14 @@ #ifndef MATHEXTENSIONS_H #define MATHEXTENSIONS_H +#include + /** * MAX returns the maximum of two values. */ #define MAX(a, b) (((a) > (b)) ? (a) : (b)) // Internal functions -int _ipow(int base, int exp); +int64_t _ipow(int64_t base, int64_t exp); #endif diff --git a/h3_polygon.c b/h3_polygon.c index 60c9a0c..01ce874 100644 --- a/h3_polygon.c +++ b/h3_polygon.c @@ -1,5 +1,5 @@ /* - * Copyright 2018 Uber Technologies, Inc. + * Copyright 2018-2021 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. @@ -14,22 +14,23 @@ * limitations under the License. */ /** @file polygon.c - * @brief Polygon (Geofence) algorithms + * @brief Polygon (GeoLoop) algorithms */ #include "h3_polygon.h" -#include + #include #include #include + #include "h3_bbox.h" #include "h3_constants.h" -#include "h3_geoCoord.h" #include "h3_h3api.h" +#include "h3_latLng.h" #include "h3_linkedGeo.h" -// Define macros used in polygon algos for Geofence -#define TYPE Geofence +// Define macros used in polygon algos for GeoLoop +#define TYPE GeoLoop #define INIT_ITERATION INIT_ITERATION_GEOFENCE #define ITERATE ITERATE_GEOFENCE #define IS_EMPTY IS_EMPTY_GEOFENCE @@ -46,10 +47,10 @@ * @param polygon Input GeoPolygon * @param bboxes Output bboxes, one for the outer loop and one for each hole */ -void bboxesFromGeoPolygon(const GeoPolygon* polygon, BBox* bboxes) { - bboxFromGeofence(&polygon->geofence, &bboxes[0]); +void bboxesFromGeoPolygon(const GeoPolygon *polygon, BBox *bboxes) { + bboxFromGeoLoop(&polygon->geoloop, &bboxes[0]); for (int i = 0; i < polygon->numHoles; i++) { - bboxFromGeofence(&polygon->holes[i], &bboxes[i + 1]); + bboxFromGeoLoop(&polygon->holes[i], &bboxes[i + 1]); } } @@ -57,24 +58,24 @@ void bboxesFromGeoPolygon(const GeoPolygon* polygon, BBox* bboxes) { * pointInsidePolygon takes a given GeoPolygon data structure and * checks if it contains a given geo coordinate. * - * @param geoPolygon The geofence and holes defining the relevant area - * @param bboxes The bboxes for the main geofence and each of its holes + * @param geoPolygon The geoloop and holes defining the relevant area + * @param bboxes The bboxes for the main geoloop and each of its holes * @param coord The coordinate to check * @return Whether the point is contained */ -bool pointInsidePolygon(const GeoPolygon* geoPolygon, const BBox* bboxes, - const GeoCoord* coord) { - // Start with contains state of primary geofence +bool pointInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, + const LatLng *coord) { + // Start with contains state of primary geoloop bool contains = - pointInsideGeofence(&(geoPolygon->geofence), &bboxes[0], coord); + pointInsideGeoLoop(&(geoPolygon->geoloop), &bboxes[0], coord); - // If the point is contained in the primary geofence, but there are holes in - // the geofence iterate through all holes and return false if the point is + // If the point is contained in the primary geoloop, but there are holes in + // the geoloop iterate through all holes and return false if the point is // contained in any hole if (contains && geoPolygon->numHoles > 0) { for (int i = 0; i < geoPolygon->numHoles; i++) { - if (pointInsideGeofence(&(geoPolygon->holes[i]), &bboxes[i + 1], - coord)) { + if (pointInsideGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1], + coord)) { return false; } } diff --git a/h3_polygon.h b/h3_polygon.h index e77fb62..ec0b14d 100644 --- a/h3_polygon.h +++ b/h3_polygon.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Uber Technologies, Inc. + * Copyright 2018, 2020-2021 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. @@ -21,55 +21,56 @@ #define POLYGON_H #include + #include "h3_bbox.h" -#include "h3_geoCoord.h" #include "h3_h3api.h" +#include "h3_latLng.h" #include "h3_linkedGeo.h" // Macros for use with polygonAlgos.h -/** Macro: Init iteration vars for Geofence */ +/** Macro: Init iteration vars for GeoLoop */ #define INIT_ITERATION_GEOFENCE int loopIndex = -1 -/** Macro: Increment Geofence loop iteration, or break if done. */ -#define ITERATE_GEOFENCE(geofence, vertexA, vertexB) \ - if (++loopIndex >= geofence->numVerts) break; \ - vertexA = geofence->verts[loopIndex]; \ - vertexB = geofence->verts[(loopIndex + 1) % geofence->numVerts] +/** Macro: Increment GeoLoop loop iteration, or break if done. */ +#define ITERATE_GEOFENCE(geoloop, vertexA, vertexB) \ + if (++loopIndex >= geoloop->numVerts) break; \ + vertexA = geoloop->verts[loopIndex]; \ + vertexB = geoloop->verts[(loopIndex + 1) % geoloop->numVerts] -/** Macro: Whether a Geofence is empty */ -#define IS_EMPTY_GEOFENCE(geofence) geofence->numVerts == 0 +/** Macro: Whether a GeoLoop is empty */ +#define IS_EMPTY_GEOFENCE(geoloop) geoloop->numVerts == 0 // Defined directly in polygon.c: -void bboxesFromGeoPolygon(const GeoPolygon* polygon, BBox* bboxes); -bool pointInsidePolygon(const GeoPolygon* geoPolygon, const BBox* bboxes, - const GeoCoord* coord); +void bboxesFromGeoPolygon(const GeoPolygon *polygon, BBox *bboxes); +bool pointInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes, + const LatLng *coord); // The following functions are created via macro in polygonAlgos.h, // so their signatures are documented here: /** - * Create a bounding box from a Geofence - * @param geofence Input Geofence + * Create a bounding box from a GeoLoop + * @param geoloop Input GeoLoop * @param bbox Output bbox */ -void bboxFromGeofence(const Geofence* loop, BBox* bbox); +void bboxFromGeoLoop(const GeoLoop *loop, BBox *bbox); /** - * Take a given Geofence data structure and check if it + * Take a given GeoLoop data structure and check if it * contains a given geo coordinate. - * @param loop The geofence + * @param loop The geoloop * @param bbox The bbox for the loop * @param coord The coordinate to check * @return Whether the point is contained */ -bool pointInsideGeofence(const Geofence* loop, const BBox* bbox, - const GeoCoord* coord); +bool pointInsideGeoLoop(const GeoLoop *loop, const BBox *bbox, + const LatLng *coord); /** - * Whether the winding order of a given Geofence is clockwise + * Whether the winding order of a given GeoLoop is clockwise * @param loop The loop to check * @return Whether the loop is clockwise */ -bool isClockwiseGeofence(const Geofence* geofence); +bool isClockwiseGeoLoop(const GeoLoop *geoloop); #endif diff --git a/h3_polygonAlgos.h b/h3_polygonAlgos.h index ef0c87c..34acf3a 100644 --- a/h3_polygonAlgos.h +++ b/h3_polygonAlgos.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Uber Technologies, Inc. + * Copyright 2018, 2020-2021 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. @@ -16,7 +16,7 @@ /** @file * @brief Include file for poylgon algorithms. This includes the core * logic for algorithms acting over loops of coordinates, - * allowing them to be reused for both Geofence and + * allowing them to be reused for both GeoLoop and * LinkegGeoLoop structures. This file is intended to be * included inline in a file that defines the type-specific * macros required for iteration. @@ -25,10 +25,11 @@ #include #include #include + #include "h3_bbox.h" #include "h3_constants.h" -#include "h3_geoCoord.h" #include "h3_h3api.h" +#include "h3_latLng.h" #include "h3_linkedGeo.h" #include "h3_polygon.h" @@ -53,8 +54,8 @@ #define GENERIC_LOOP_ALGO(func) LOOP_ALGO_TJOIN(func, TYPE) /** Macro: Normalize longitude, dealing with transmeridian arcs */ -#define NORMALIZE_LON(lon, isTransmeridian) \ - (isTransmeridian && lon < 0 ? lon + (double)M_2PI : lon) +#define NORMALIZE_LNG(lng, isTransmeridian) \ + (isTransmeridian && lng < 0 ? lng + (double)M_2PI : lng) /** * pointInside is the core loop of the point-in-poly algorithm @@ -63,8 +64,8 @@ * @param coord The coordinate to check * @return Whether the point is contained */ -bool GENERIC_LOOP_ALGO(pointInside)(const TYPE* loop, const BBox* bbox, - const GeoCoord* coord) { +bool GENERIC_LOOP_ALGO(pointInside)(const TYPE *loop, const BBox *bbox, + const LatLng *coord) { // fail fast if we're outside the bounding box if (!bboxContains(bbox, coord)) { return false; @@ -73,10 +74,10 @@ bool GENERIC_LOOP_ALGO(pointInside)(const TYPE* loop, const BBox* bbox, bool contains = false; double lat = coord->lat; - double lng = NORMALIZE_LON(coord->lon, isTransmeridian); + double lng = NORMALIZE_LNG(coord->lng, isTransmeridian); - GeoCoord a; - GeoCoord b; + LatLng a; + LatLng b; INIT_ITERATION; @@ -86,19 +87,33 @@ bool GENERIC_LOOP_ALGO(pointInside)(const TYPE* loop, const BBox* bbox, // Ray casting algo requires the second point to always be higher // than the first, so swap if needed if (a.lat > b.lat) { - GeoCoord tmp = a; + LatLng tmp = a; a = b; b = tmp; } + // If the latitude matches exactly, we'll hit an edge case where + // the ray passes through the vertex twice on successive segment + // checks. To avoid this, adjust the latiude northward if needed. + // + // NOTE: This currently means that a point at the north pole cannot + // be contained in any polygon. This is acceptable in current usage, + // because the point we test in this function at present is always + // a cell center or vertex, and no cell has a center or vertex on the + // north pole. If we need to expand this algo to more generic uses we + // might need to handle this edge case. + if (lat == a.lat || lat == b.lat) { + lat += DBL_EPSILON; + } + // If we're totally above or below the latitude ranges, the test // ray cannot intersect the line segment, so let's move on if (lat < a.lat || lat > b.lat) { continue; } - double aLng = NORMALIZE_LON(a.lon, isTransmeridian); - double bLng = NORMALIZE_LON(b.lon, isTransmeridian); + double aLng = NORMALIZE_LNG(a.lng, isTransmeridian); + double bLng = NORMALIZE_LNG(b.lng, isTransmeridian); // Rays are cast in the longitudinal direction, in case a point // exactly matches, to decide tiebreakers, bias westerly @@ -113,7 +128,7 @@ bool GENERIC_LOOP_ALGO(pointInside)(const TYPE* loop, const BBox* bbox, // of a to b double ratio = (lat - a.lat) / (b.lat - a.lat); double testLng = - NORMALIZE_LON(aLng + (bLng - aLng) * ratio, isTransmeridian); + NORMALIZE_LNG(aLng + (bLng - aLng) * ratio, isTransmeridian); // Intersection of the ray if (testLng > lng) { @@ -133,7 +148,7 @@ bool GENERIC_LOOP_ALGO(pointInside)(const TYPE* loop, const BBox* bbox, * @param loop Loop of coordinates * @param bbox Output bbox */ -void GENERIC_LOOP_ALGO(bboxFrom)(const TYPE* loop, BBox* bbox) { +void GENERIC_LOOP_ALGO(bboxFrom)(const TYPE *loop, BBox *bbox) { // Early exit if there are no vertices if (IS_EMPTY(loop)) { *bbox = (BBox){0}; @@ -144,14 +159,14 @@ void GENERIC_LOOP_ALGO(bboxFrom)(const TYPE* loop, BBox* bbox) { bbox->west = DBL_MAX; bbox->north = -DBL_MAX; bbox->east = -DBL_MAX; - double minPosLon = DBL_MAX; - double maxNegLon = -DBL_MAX; + double minPosLng = DBL_MAX; + double maxNegLng = -DBL_MAX; bool isTransmeridian = false; double lat; - double lon; - GeoCoord coord; - GeoCoord next; + double lng; + LatLng coord; + LatLng next; INIT_ITERATION; @@ -159,24 +174,24 @@ void GENERIC_LOOP_ALGO(bboxFrom)(const TYPE* loop, BBox* bbox) { ITERATE(loop, coord, next); lat = coord.lat; - lon = coord.lon; + lng = coord.lng; if (lat < bbox->south) bbox->south = lat; - if (lon < bbox->west) bbox->west = lon; + if (lng < bbox->west) bbox->west = lng; if (lat > bbox->north) bbox->north = lat; - if (lon > bbox->east) bbox->east = lon; + if (lng > bbox->east) bbox->east = lng; // Save the min positive and max negative longitude for // use in the transmeridian case - if (lon > 0 && lon < minPosLon) minPosLon = lon; - if (lon < 0 && lon > maxNegLon) maxNegLon = lon; + if (lng > 0 && lng < minPosLng) minPosLng = lng; + if (lng < 0 && lng > maxNegLng) maxNegLng = lng; // check for arcs > 180 degrees longitude, flagging as transmeridian - if (fabs(lon - next.lon) > M_PI) { + if (fabs(lng - next.lng) > M_PI) { isTransmeridian = true; } } // Swap east and west if transmeridian if (isTransmeridian) { - bbox->east = maxNegLon; - bbox->west = minPosLon; + bbox->east = maxNegLng; + bbox->west = minPosLng; } } @@ -187,22 +202,22 @@ void GENERIC_LOOP_ALGO(bboxFrom)(const TYPE* loop, BBox* bbox) { * @param isTransmeridian Whether the loop crosses the antimeridian * @return Whether the loop is clockwise */ -static bool GENERIC_LOOP_ALGO(isClockwiseNormalized)(const TYPE* loop, +static bool GENERIC_LOOP_ALGO(isClockwiseNormalized)(const TYPE *loop, bool isTransmeridian) { double sum = 0; - GeoCoord a; - GeoCoord b; + LatLng a; + LatLng b; INIT_ITERATION; while (true) { ITERATE(loop, a, b); // If we identify a transmeridian arc (> 180 degrees longitude), // start over with the transmeridian flag set - if (!isTransmeridian && fabs(a.lon - b.lon) > M_PI) { + if (!isTransmeridian && fabs(a.lng - b.lng) > M_PI) { return GENERIC_LOOP_ALGO(isClockwiseNormalized)(loop, true); } - sum += ((NORMALIZE_LON(b.lon, isTransmeridian) - - NORMALIZE_LON(a.lon, isTransmeridian)) * + sum += ((NORMALIZE_LNG(b.lng, isTransmeridian) - + NORMALIZE_LNG(a.lng, isTransmeridian)) * (b.lat + a.lat)); } @@ -215,6 +230,6 @@ static bool GENERIC_LOOP_ALGO(isClockwiseNormalized)(const TYPE* loop, * @param loop The loop to check * @return Whether the loop is clockwise */ -bool GENERIC_LOOP_ALGO(isClockwise)(const TYPE* loop) { +bool GENERIC_LOOP_ALGO(isClockwise)(const TYPE *loop) { return GENERIC_LOOP_ALGO(isClockwiseNormalized)(loop, false); } diff --git a/h3_stackAlloc.h b/h3_stackAlloc.h deleted file mode 100644 index 479f66b..0000000 --- a/h3_stackAlloc.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2016-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. - */ -/** @file stackAlloc.h - * @brief Macro to provide cross-platform mechanism for allocating variable - * length arrays on the stack. - */ - -#ifndef STACKALLOC_H -#define STACKALLOC_H - -#include -#include - -#ifdef H3_HAVE_VLA - -#define STACK_ARRAY_CALLOC(type, name, numElements) \ - assert((numElements) > 0); \ - type name##Buffer[(numElements)]; \ - memset(name##Buffer, 0, (numElements) * sizeof(type)); \ - type* name = name##Buffer - -#elif defined(H3_HAVE_ALLOCA) - -#ifdef _MSC_VER - -#include - -#define STACK_ARRAY_CALLOC(type, name, numElements) \ - assert((numElements) > 0); \ - type* name = (type*)_alloca(sizeof(type) * (numElements)); \ - memset(name, 0, sizeof(type) * (numElements)) - -#else - -#include - -#define STACK_ARRAY_CALLOC(type, name, numElements) \ - assert((numElements) > 0); \ - type* name = (type*)alloca(sizeof(type) * (numElements)); \ - memset(name, 0, sizeof(type) * (numElements)) - -#endif - -#else - -#error \ - "This platform does not support stack array allocation, please submit an issue on https://github.com/uber/h3 to report this error" - -#endif - -#endif diff --git a/h3_test.go b/h3_test.go index 66606af..ccfcb50 100644 --- a/h3_test.go +++ b/h3_test.go @@ -17,27 +17,25 @@ package h3 import ( "fmt" + "math" "sort" "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) const eps = 1e-4 -// validH3Index resolution 5 +// validH3Index resolution 5. const ( - validH3Index = H3Index(0x850dab63fffffff) - pentagonH3Index = H3Index(0x821c07fffffffff) - validLineStartIndex = H3Index(0x89283082803ffff) - validLineEndIndex = H3Index(0x8929a5653c3ffff) + validCell = Cell(0x850dab63fffffff) + pentagonCell = Cell(0x821c07fffffffff) + lineStartCell = Cell(0x89283082803ffff) + lineEndCell = Cell(0x8929a5653c3ffff) ) var ( - validH3Rings1 = [][]H3Index{ + validDiskDist3_1 = [][]Cell{ { - validH3Index, + validCell, }, { 0x850dab73fffffff, @@ -62,361 +60,289 @@ var ( 0x850dab0ffffffff, }, } - validH3Rings2 = [][]H3Index{ - { - 0x8928308280fffff, - }, { - 0x8928308280bffff, - 0x89283082873ffff, - 0x89283082877ffff, - 0x8928308283bffff, - 0x89283082807ffff, - 0x89283082803ffff, - }, - { - 0x8928308281bffff, - 0x89283082857ffff, - 0x89283082847ffff, - 0x8928308287bffff, - 0x89283082863ffff, - 0x89283082867ffff, - 0x8928308282bffff, - 0x89283082823ffff, - 0x89283082833ffff, - 0x892830828abffff, - 0x89283082817ffff, - 0x89283082813ffff, - }, - } - validGeoCoord = GeoCoord{ - Latitude: 67.1509268640, - Longitude: -168.3908885810, + validLatLng1 = LatLng{ + Lat: 67.1509268640, + Lng: -168.3908885810, } - - validGeofence = GeoBoundary{ - {Latitude: 67.224749856, Longitude: -168.523006585}, - {Latitude: 67.140938355, Longitude: -168.626914333}, - {Latitude: 67.067252558, Longitude: -168.494913285}, - {Latitude: 67.077062918, Longitude: -168.259695931}, - {Latitude: 67.160561948, Longitude: -168.154801171}, - {Latitude: 67.234563187, Longitude: -168.286102782}, + validLatLng2 = LatLng{ + Lat: 37.775705522929044, + Lng: -122.41812765598296, } - validGeofenceHole1 = GeoBoundary{ - {Latitude: 67.2, Longitude: -168.4}, - {Latitude: 67.1, Longitude: -168.4}, - {Latitude: 67.1, Longitude: -168.3}, - {Latitude: 67.2, Longitude: -168.3}, + // validGeoLoop is the boundary of validCell_1. + validGeoLoop = GeoLoop{ + {Lat: 67.224749856, Lng: -168.523006585}, + {Lat: 67.140938355, Lng: -168.626914333}, + {Lat: 67.067252558, Lng: -168.494913285}, + {Lat: 67.077062918, Lng: -168.259695931}, + {Lat: 67.160561948, Lng: -168.154801171}, + {Lat: 67.234563187, Lng: -168.286102782}, } - validGeofenceHole2 = GeoBoundary{ - {Latitude: 67.21, Longitude: -168.41}, - {Latitude: 67.22, Longitude: -168.41}, - {Latitude: 67.22, Longitude: -168.42}, + validHole1 = GeoLoop{ + {Lat: 67.2, Lng: -168.4}, + {Lat: 67.1, Lng: -168.4}, + {Lat: 67.1, Lng: -168.3}, + {Lat: 67.2, Lng: -168.3}, } - validGeopolygonWithoutHoles = GeoPolygon{ - Geofence: validGeofence, + validHole2 = GeoLoop{ + {Lat: 67.21, Lng: -168.41}, + {Lat: 67.22, Lng: -168.41}, + {Lat: 67.22, Lng: -168.42}, } - validGeopolygonWithHoles = GeoPolygon{ - Geofence: validGeofence, - Holes: [][]GeoCoord{ - validGeofenceHole1, - validGeofenceHole2, + validGeoPolygonNoHoles = GeoPolygon{GeoLoop: validGeoLoop} + + validGeoPolygonHoles = GeoPolygon{ + GeoLoop: validGeoLoop, + Holes: []GeoLoop{ + validHole1, + validHole2, }, } - validGeoRing = []GeoCoord{{}} + validEdge = DirectedEdge(0x1250dab73fffffff) ) -func TestFromGeo(t *testing.T) { +func TestLatLngToCell(t *testing.T) { t.Parallel() - h := FromGeo(GeoCoord{ - Latitude: 67.194013596, - Longitude: 191.598258018, - }, 5) - assert.Equal(t, validH3Index, h, "expected %x but got %x", validH3Index, h) - assert.Equal(t, validH3Index, h) + c := LatLngToCell(validLatLng1, 5) + assertEqual(t, validCell, c) } -func TestToGeo(t *testing.T) { +func TestCellToLatLng(t *testing.T) { t.Parallel() - g := ToGeo(validH3Index) - assertGeoCoord(t, validGeoCoord, g) + g := CellToLatLng(validCell) + assertEqualLatLng(t, validLatLng1, g) } -func TestToGeoBoundary(t *testing.T) { +func TestToCellBoundary(t *testing.T) { t.Parallel() - boundary := ToGeoBoundary(validH3Index) - assertGeoCoords(t, validGeofence[:], boundary[:]) + boundary := validCell.Boundary() + assertEqualLatLngs(t, validGeoLoop[:], boundary[:]) } -func TestHexRing(t *testing.T) { - t.Parallel() - for k, expected := range validH3Rings1 { - t.Run(fmt.Sprintf("ring size %d", k), func(t *testing.T) { - actual, err := HexRing(validH3Index, k) - require.NoError(t, err) - assert.ElementsMatch(t, expected, actual) - }) - } - t.Run("pentagon err", func(t *testing.T) { - t.Parallel() - _, err := HexRing(pentagonH3Index, 1) - assert.Error(t, err) - }) -} - -func TestKRing(t *testing.T) { +func TestGridDisk(t *testing.T) { t.Parallel() t.Run("no pentagon", func(t *testing.T) { t.Parallel() - assertHexRange(t, validH3Rings1, KRing(validH3Index, len(validH3Rings1)-1)) + assertEqualDisks(t, + flattenDisks(validDiskDist3_1), + validCell.GridDisk(len(validDiskDist3_1)-1)) }) t.Run("pentagon ok", func(t *testing.T) { t.Parallel() - assert.NotPanics(t, func() { - KRing(pentagonH3Index, len(validH3Rings1)-1) + assertNoPanic(t, func() { + disk := GridDisk(pentagonCell, 1) + assertEqual(t, 6, len(disk), "expected pentagon disk to have 6 cells") }) }) } -func TestKRingDistances(t *testing.T) { +func TestGridDiskDistances(t *testing.T) { t.Parallel() t.Run("no pentagon", func(t *testing.T) { t.Parallel() - rings := KRingDistances(validH3Index, len(validH3Rings1)-1) - for i, ring := range validH3Rings1 { - assert.ElementsMatch(t, ring, rings[i]) - } + rings := validCell.GridDiskDistances(len(validDiskDist3_1) - 1) + assertEqualDiskDistances(t, validDiskDist3_1, rings) }) - t.Run("pentagon ok", func(t *testing.T) { + t.Run("pentagon centered", func(t *testing.T) { t.Parallel() - assert.NotPanics(t, func() { - KRingDistances(pentagonH3Index, len(validH3Rings1)-1) + assertNoPanic(t, func() { + rings := GridDiskDistances(pentagonCell, 1) + assertEqual(t, 2, len(rings), "expected 2 rings") + assertEqual(t, 5, len(rings[1]), "expected 5 cells in second ring") }) }) } -func TestHexRange(t *testing.T) { - t.Parallel() - t.Run("no pentagon", func(t *testing.T) { - t.Parallel() - hexes, err := HexRange(validH3Index, len(validH3Rings1)-1) - require.NoError(t, err) - assertHexRange(t, validH3Rings1, hexes) - }) - t.Run("pentagon err", func(t *testing.T) { - t.Parallel() - _, err := HexRange(pentagonH3Index, len(validH3Rings1)-1) - assert.Error(t, err) - }) -} - -func TestHexRangeDistances(t *testing.T) { +func TestIsValid(t *testing.T) { t.Parallel() - t.Run("no pentagon", func(t *testing.T) { - t.Parallel() - rings, err := HexRangeDistances(validH3Index, len(validH3Rings1)-1) - require.NoError(t, err) - for i, ring := range validH3Rings1 { - assert.ElementsMatch(t, ring, rings[i]) - } - }) - t.Run("pentagon err", func(t *testing.T) { - t.Parallel() - _, err := HexRangeDistances(pentagonH3Index, len(validH3Rings1)-1) - assert.Error(t, err) - }) + assertTrue(t, validCell.IsValid()) + assertFalse(t, Cell(0).IsValid()) } -func TestHexRanges(t *testing.T) { +func TestRoundtrip(t *testing.T) { t.Parallel() - t.Run("no pentagon", func(t *testing.T) { + t.Run("latlng", func(t *testing.T) { t.Parallel() - hexranges, err := HexRanges( - []H3Index{ - validH3Rings1[0][0], - validH3Rings2[0][0], - }, len(validH3Rings2)-1) - require.NoError(t, err) - require.Len(t, hexranges, 2) - assertHexRange(t, validH3Rings1, hexranges[0]) - assertHexRange(t, validH3Rings2, hexranges[1]) + expectedGeo := LatLng{Lat: 1, Lng: 2} + c := LatLngToCell(expectedGeo, MaxResolution) + actualGeo := CellToLatLng(c) + assertEqualLatLng(t, expectedGeo, actualGeo) + assertEqualLatLng(t, expectedGeo, expectedGeo.Cell(MaxResolution).LatLng()) }) - t.Run("pentagon err", func(t *testing.T) { - _, err := HexRanges( - []H3Index{ - validH3Rings1[0][0], - pentagonH3Index, - }, len(validH3Rings2)-1) - assert.Error(t, err) + t.Run("cell", func(t *testing.T) { t.Parallel() + geo := CellToLatLng(validCell) + actualCell := LatLngToCell(geo, validCell.Resolution()) + assertEqual(t, validCell, actualCell) }) } -func TestIsValid(t *testing.T) { +func TestResolution(t *testing.T) { t.Parallel() - assert.True(t, IsValid(validH3Index)) - assert.False(t, IsValid(0)) -} -func TestFromGeoToGeo(t *testing.T) { - t.Parallel() - expectedGeo := GeoCoord{Latitude: 1, Longitude: 2} - h := FromGeo(expectedGeo, 15) - actualGeo := ToGeo(h) - assertGeoCoord(t, expectedGeo, actualGeo) -} + for i := 1; i <= MaxResolution; i++ { + c := LatLngToCell(validLatLng1, i) + assertEqual(t, i, c.Resolution()) + } -func TestResolution(t *testing.T) { - t.Parallel() - for i := 1; i <= 15; i++ { - h := FromGeo(validGeoCoord, i) - assert.Equal(t, i, Resolution(h)) + for _, e := range validCell.DirectedEdges() { + assertEqual(t, validCell.Resolution(), e.Resolution()) } } -func TestBaseCell(t *testing.T) { +func TestBaseCellNumber(t *testing.T) { t.Parallel() - bcID := BaseCell(validH3Index) - assert.Equal(t, 6, bcID) + bcID := validCell.BaseCellNumber() + assertEqual(t, 6, bcID) } -func TestToParent(t *testing.T) { +func TestParent(t *testing.T) { t.Parallel() // get the index's parent by requesting that index's resolution+1 - parent := ToParent(validH3Index, Resolution(validH3Index)-1) + parent := validCell.ImmediateParent() // get the children at the resolution of the original index - children := ToChildren(parent, Resolution(validH3Index)) - - assertHexIn(t, validH3Index, children) -} + children := parent.ImmediateChildren() -func TestCompact(t *testing.T) { - t.Parallel() - in := append([]H3Index{}, validH3Rings1[0][0]) - in = append(in, validH3Rings1[1]...) - out := Compact(in) - require.Len(t, out, 1) - assert.Equal(t, ToParent(validH3Rings1[0][0], Resolution(validH3Rings1[0][0])-1), out[0]) + assertCellIn(t, validCell, children) } -func TestUncompact(t *testing.T) { +func TestCompactCells(t *testing.T) { t.Parallel() - // get the index's parent by requesting that index's resolution+1 - res := Resolution(validH3Index) - 1 - parent := ToParent(validH3Index, res) - out, err := Uncompact([]H3Index{parent}, res+1) - assert.NoError(t, err) - assertHexIn(t, validH3Index, out) + in := flattenDisks(validDiskDist3_1[:2]) + t.Logf("in: %v", in) + out := CompactCells(in) + t.Logf("out: %v", in) + assertEqual(t, 1, len(out)) + assertEqual(t, validDiskDist3_1[0][0].ImmediateParent(), out[0]) } -func TestUncompactError(t *testing.T) { +func TestUncompactCells(t *testing.T) { t.Parallel() - res := Resolution(validH3Index) - 1 - parent := ToParent(validH3Index, res) - - // use a resolution that is too small - out, err := Uncompact([]H3Index{parent}, res-1) - assert.Nil(t, out) - assert.Equal(t, ErrInvalidResolution, err) + // get the index's parent by requesting that index's resolution+1 + parent := validCell.ImmediateParent() + out := UncompactCells([]Cell{parent}, parent.Resolution()+1) + assertCellIn(t, validCell, out) } func TestIsResClassIII(t *testing.T) { t.Parallel() - res := Resolution(validH3Index) - 1 - parent := ToParent(validH3Index, res) - assert.True(t, IsResClassIII(validH3Index)) - assert.False(t, IsResClassIII(parent)) + assertTrue(t, validCell.IsResClassIII()) + assertFalse(t, validCell.ImmediateParent().IsResClassIII()) } func TestIsPentagon(t *testing.T) { t.Parallel() - assert.False(t, IsPentagon(validH3Index)) - assert.True(t, IsPentagon(pentagonH3Index)) + assertFalse(t, validCell.IsPentagon()) + assertTrue(t, pentagonCell.IsPentagon()) } -func TestAreNeighbors(t *testing.T) { +func TestIsNeighbor(t *testing.T) { t.Parallel() - assert.False(t, AreNeighbors(pentagonH3Index, validH3Index)) - assert.True(t, AreNeighbors(validH3Rings1[1][0], validH3Rings1[1][1])) + assertFalse(t, validCell.IsNeighbor(pentagonCell)) + assertTrue(t, validCell.DirectedEdges()[0].Destination().IsNeighbor(validCell)) } -func TestUnidirectionalEdge(t *testing.T) { +func TestDirectedEdge(t *testing.T) { t.Parallel() - origin := validH3Rings1[1][0] - destination := validH3Rings1[1][1] - edge := UnidirectionalEdge(origin, destination) + + origin := validDiskDist3_1[1][0] + destination := origin.DirectedEdges()[0].Destination() + edge := origin.DirectedEdge(destination) t.Run("is valid", func(t *testing.T) { t.Parallel() - assert.True(t, UnidirectionalEdgeIsValid(edge)) - assert.False(t, UnidirectionalEdgeIsValid(validH3Index)) + assertTrue(t, edge.IsValid()) + assertFalse(t, DirectedEdge(validCell).IsValid()) }) + t.Run("get origin/destination from edge", func(t *testing.T) { t.Parallel() - assert.Equal(t, origin, OriginFromUnidirectionalEdge(edge)) - assert.Equal(t, destination, DestinationFromUnidirectionalEdge(edge)) + assertEqual(t, origin, edge.Origin()) + assertEqual(t, destination, edge.Destination()) // shadow origin/destination - origin, destination := FromUnidirectionalEdge(edge) - assert.Equal(t, origin, OriginFromUnidirectionalEdge(edge)) - assert.Equal(t, destination, DestinationFromUnidirectionalEdge(edge)) + cells := edge.Cells() + origin, destination := cells[0], cells[1] + assertEqual(t, origin, edge.Origin()) + assertEqual(t, destination, edge.Destination()) }) + t.Run("get edges from hexagon", func(t *testing.T) { t.Parallel() - edges := ToUnidirectionalEdges(validH3Index) - assert.Len(t, edges, 6, "hexagon has 6 edges") + edges := validCell.DirectedEdges() + assertEqual(t, 6, len(edges), "hexagon has 6 edges") }) + t.Run("get edges from pentagon", func(t *testing.T) { t.Parallel() - edges := ToUnidirectionalEdges(pentagonH3Index) - require.Len(t, edges, 5, "pentagon has 5 edges") + edges := pentagonCell.DirectedEdges() + assertEqual(t, 5, len(edges), "pentagon has 5 edges") }) + t.Run("get boundary from edge", func(t *testing.T) { t.Parallel() - gb := UnidirectionalEdgeBoundary(edge) - assert.Len(t, gb, 2) + gb := edge.Boundary() + assertEqual(t, 2, len(gb), "edge has 2 boundary cells") }) } -func TestString(t *testing.T) { +func TestStrings(t *testing.T) { t.Parallel() + t.Run("bad string", func(t *testing.T) { t.Parallel() - h := FromString("oops") - assert.Equal(t, H3Index(0), h) + i := IndexFromString("oops") + assertEqual(t, 0, i) }) + t.Run("good string round trip", func(t *testing.T) { t.Parallel() - h := FromString(ToString(validH3Index)) - assert.Equal(t, validH3Index, h) + i := IndexFromString(validCell.String()) + assertEqual(t, validCell, Cell(i)) }) + t.Run("no 0x prefix", func(t *testing.T) { t.Parallel() - h3addr := ToString(validH3Index) - assert.Equal(t, "850dab63fffffff", h3addr) + h3addr := validCell.String() + assertEqual(t, "850dab63fffffff", h3addr) + }) + + t.Run("marshalling text", func(t *testing.T) { + t.Parallel() + c := Cell(0) + text, err := validCell.MarshalText() + assertNoErr(t, err) + + err = c.UnmarshalText([]byte("0x" + string(text))) + assertNoErr(t, err) + assertEqual(t, validCell, c) + + err = c.UnmarshalText([]byte("")) + assertErr(t, err) }) } -func TestPolyfill(t *testing.T) { +func TestPolygonToCells(t *testing.T) { t.Parallel() + t.Run("empty", func(t *testing.T) { t.Parallel() - indexes := Polyfill(GeoPolygon{}, 6) - assert.Len(t, indexes, 0) + cells := PolygonToCells(GeoPolygon{}, 6) + assertEqual(t, 0, len(cells)) }) + t.Run("without holes", func(t *testing.T) { t.Parallel() - indexes := Polyfill(validGeopolygonWithoutHoles, 6) - assert.Len(t, indexes, 7) - expectedIndexes := []H3Index{ + cells := validGeoPolygonNoHoles.Cells(6) + expectedIndexes := []Cell{ 0x860dab607ffffff, 0x860dab60fffffff, 0x860dab617ffffff, @@ -425,13 +351,13 @@ func TestPolyfill(t *testing.T) { 0x860dab62fffffff, 0x860dab637ffffff, } - assert.ElementsMatch(t, expectedIndexes, indexes) + assertEqualCells(t, expectedIndexes, cells) }) + t.Run("with hole", func(t *testing.T) { t.Parallel() - indexes := Polyfill(validGeopolygonWithHoles, 6) - assert.Len(t, indexes, 6) - expectedIndexes := []H3Index{ + cells := validGeoPolygonHoles.Cells(6) + expectedIndexes := []Cell{ 0x860dab60fffffff, 0x860dab617ffffff, 0x860dab61fffffff, @@ -439,46 +365,380 @@ func TestPolyfill(t *testing.T) { 0x860dab62fffffff, 0x860dab637ffffff, } - assert.ElementsMatch(t, expectedIndexes, indexes) + assertEqualCells(t, expectedIndexes, cells) }) } -func TestLine(t *testing.T) { +func TestGridPath(t *testing.T) { t.Parallel() - line := Line(validLineStartIndex, validLineEndIndex) - assert.Equal(t, validLineStartIndex, line[0]) - assert.Equal(t, validLineEndIndex, line[len(line)-1]) - for i := 0; i < len(line)-1; i++ { - assert.True(t, AreNeighbors(line[i], line[i+1])) + path := lineStartCell.GridPath(lineEndCell) + + 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])) } } -func almostEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { - assert.InEpsilon(t, expected, actual, eps, msgAndArgs...) +func TestHexAreaKm2(t *testing.T) { + t.Parallel() + t.Run("min resolution", func(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(4357449.4161), HexagonAreaAvgKm2(0)) + }) + t.Run("max resolution", func(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(0.0000009), HexagonAreaAvgKm2(15)) + }) + t.Run("mid resolution", func(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(0.7373276), HexagonAreaAvgKm2(8)) + }) +} + +func TestHexAreaM2(t *testing.T) { + t.Parallel() + t.Run("min resolution", func(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(4357449416078.3901), HexagonAreaAvgM2(0)) + }) + t.Run("max resolution", func(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(0.8953), HexagonAreaAvgM2(15)) + }) + t.Run("mid resolution", func(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(737327.5976), HexagonAreaAvgM2(8)) + }) +} + +func TestPointDistRads(t *testing.T) { + t.Parallel() + distance := GreatCircleDistanceRads(validLatLng1, validLatLng2) + assertEqualEps(t, float64(0.6796147656451452), distance) +} + +func TestPointDistKm(t *testing.T) { + t.Parallel() + distance := GreatCircleDistanceKm(validLatLng1, validLatLng2) + assertEqualEps(t, float64(4329.830552183446), distance) +} + +func TestPointDistM(t *testing.T) { + t.Parallel() + distance := GreatCircleDistanceM(validLatLng1, validLatLng2) + assertEqualEps(t, float64(4329830.5521834465), distance) +} + +func TestCellAreaRads2(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(0.000006643967854567278), CellAreaRads2(validCell)) +} + +func TestCellAreaKm2(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(269.6768779509321), CellAreaKm2(validCell)) +} + +func TestCellAreaM2(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(269676877.95093215), CellAreaM2(validCell)) } -func assertGeoCoord(t *testing.T, expected, actual GeoCoord) { - almostEqual(t, expected.Latitude, actual.Latitude, "latitude mismatch") - almostEqual(t, expected.Longitude, actual.Longitude, "longitude mismatch") +func TestHexagonEdgeLengthKm(t *testing.T) { + t.Parallel() + t.Run("min resolution", func(t *testing.T) { + t.Parallel() + assertEqual(t, float64(1107.712591), HexagonEdgeLengthAvgKm(0)) + }) + t.Run("max resolution", func(t *testing.T) { + t.Parallel() + assertEqual(t, float64(0.000509713), HexagonEdgeLengthAvgKm(15)) + }) + t.Run("mid resolution", func(t *testing.T) { + t.Parallel() + assertEqual(t, float64(0.461354684), HexagonEdgeLengthAvgKm(8)) + }) } -func assertGeoCoords(t *testing.T, expected, actual []GeoCoord) { - for i, gc := range expected { - assertGeoCoord(t, gc, actual[i]) +func TestHexagonEdgeLengthM(t *testing.T) { + t.Parallel() + t.Run("min resolution", func(t *testing.T) { + t.Parallel() + area := HexagonEdgeLengthAvgM(0) + assertEqual(t, float64(1107712.591), area) + }) + t.Run("max resolution", func(t *testing.T) { + t.Parallel() + area := HexagonEdgeLengthAvgM(15) + assertEqual(t, float64(0.509713273), area) + }) + t.Run("mid resolution", func(t *testing.T) { + t.Parallel() + area := HexagonEdgeLengthAvgM(8) + assertEqual(t, float64(461.3546837), area) + }) +} + +func TestExactEdgeLengthRads(t *testing.T) { + t.Parallel() + assertEqualEps(t, float64(0.001569665746947077), ExactEdgeLengthRads(validEdge)) +} + +func TestExactEdgeLengthKm(t *testing.T) { + t.Parallel() + + distance := ExactEdgeLengthKm(validEdge) + assertEqualEps(t, float64(10.00035174544159), distance) +} + +func TestExactEdgeLengthM(t *testing.T) { + t.Parallel() + + distance := ExactEdgeLengthM(validEdge) + assertEqualEps(t, float64(10000.351745441589), distance) +} + +func TestNumCells(t *testing.T) { + t.Parallel() + t.Run("min resolution", func(t *testing.T) { + t.Parallel() + assertEqual(t, 122, NumCells(0)) + }) + t.Run("max resolution", func(t *testing.T) { + t.Parallel() + assertEqual(t, 569707381193162, NumCells(15)) + }) + t.Run("mid resolution", func(t *testing.T) { + t.Parallel() + assertEqual(t, 691776122, NumCells(8)) + }) +} + +func TestRes0Cells(t *testing.T) { + t.Parallel() + cells := Res0Cells() + + assertEqual(t, 122, len(cells)) + assertEqual(t, Cell(0x8001fffffffffff), cells[0]) + assertEqual(t, Cell(0x80f3fffffffffff), cells[121]) +} + +func TestGridDistance(t *testing.T) { + t.Parallel() + assertEqual(t, 1823, lineStartCell.GridDistance(lineEndCell)) +} + +func TestCenterChild(t *testing.T) { + t.Parallel() + + child := validCell.CenterChild(15) + assertEqual(t, Cell(0x8f0dab600000000), child) +} + +func TestIcosahedronFaces(t *testing.T) { + t.Parallel() + + faces := validDiskDist3_1[1][1].IcosahedronFaces() + + assertEqual(t, 1, len(faces)) + assertEqual(t, 1, faces[0]) +} + +func TestPentagons(t *testing.T) { + t.Parallel() + + for _, res := range []int{0, 8, 15} { + t.Run(fmt.Sprintf("res=%d", res), func(t *testing.T) { + t.Parallel() + pentagons := Pentagons(res) + assertEqual(t, 12, len(pentagons)) + for _, pentagon := range pentagons { + assertTrue(t, pentagon.IsPentagon()) + assertEqual(t, res, pentagon.Resolution()) + } + }) + } +} + +func equalEps(expected, actual float64) bool { + return math.Abs(expected-actual) < eps +} + +func assertErr(t *testing.T, err error) { + t.Helper() + + if err == nil { + t.Errorf("expected error, got nil") + } +} + +func assertNoErr(t *testing.T, err error) { + t.Helper() + + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + +func assertEqual[T comparable](t *testing.T, expected, actual T, msgAndArgs ...interface{}) { + t.Helper() + + if expected != actual { + var ( + expStr, actStr string + + e, a interface{} = expected, actual + ) + + switch e.(type) { + case Cell: + expStr = fmt.Sprintf("%s (res=%d)", e.(Cell), e.(Cell).Resolution()) + actStr = fmt.Sprintf("%s (res=%d)", a.(Cell), a.(Cell).Resolution()) + default: + expStr = fmt.Sprintf("%v", e) + actStr = fmt.Sprintf("%v", a) + } + t.Errorf("%v != %v", expStr, actStr) + logMsgAndArgs(t, msgAndArgs...) } } -func assertHexRange(t *testing.T, expected [][]H3Index, actual []H3Index) { - for i, ring := range expected { - // each ring should be sorted by value because the order of a ring is - // undefined. - lower := rangeSize(i) - ringSize(i) - upper := rangeSize(i) - assert.ElementsMatch(t, ring, actual[lower:upper]) +func assertEqualEps(t *testing.T, expected, actual float64, msgAndArgs ...interface{}) { + t.Helper() + + if !equalEps(expected, actual) { + t.Errorf("%0.4f > %v (%0.4f - %0.4f)", math.Abs(expected-actual), eps, expected, actual) + logMsgAndArgs(t, msgAndArgs...) + } +} + +func assertEqualLatLng(t *testing.T, expected, actual LatLng) { + t.Helper() + assertEqualEps(t, expected.Lat, actual.Lat, "latitude mismatch") + assertEqualEps(t, expected.Lng, actual.Lng, "longitude mismatch") +} + +func assertEqualLatLngs(t *testing.T, expected, actual []LatLng, msgAndArgs ...interface{}) { + t.Helper() + + if len(expected) != len(actual) { + t.Errorf("length mismatch: %v != %v", len(expected), len(actual)) + logMsgAndArgs(t, msgAndArgs...) + + return + } + + count := 0 + + for i, ll := range expected { + equalLat := equalEps(ll.Lat, actual[i].Lat) + equalLng := equalEps(ll.Lng, actual[i].Lng) + + if !equalLat || !equalLng { + latStr := tern(equalLat, fmt.Sprintf("%v", ll.Lat), fmt.Sprintf("%v != %v", ll.Lat, actual[i].Lat)) + lngStr := tern(equalLng, fmt.Sprintf("%v", ll.Lng), fmt.Sprintf("%v != %v", ll.Lng, actual[i].Lng)) + + t.Errorf("LatLngs[%d]: (%s, %s)", i, latStr, lngStr) + logMsgAndArgs(t, msgAndArgs...) + count++ + + if count > 10 { + t.Log("...and more") + break + } + } } } -func assertHexIn(t *testing.T, needle H3Index, haystack []H3Index) { +func assertEqualCells(t *testing.T, expected, actual []Cell, msgAndArgs ...interface{}) { + t.Helper() + + if len(expected) != len(actual) { + t.Errorf("length mismatch: %v != %v", len(expected), len(actual)) + logMsgAndArgs(t, msgAndArgs...) + + return + } + + expected = sortCells(copyCells(expected)) + actual = sortCells(copyCells(actual)) + + count := 0 + + for i, c := range expected { + if c != actual[i] { + t.Errorf("Cells[%d]: %v != %v", i, c, actual[i]) + logMsgAndArgs(t, msgAndArgs...) + count++ + + if count > 10 { + t.Log("...and more") + break + } + } + } +} + +func assertEqualDiskDistances(t *testing.T, expected, actual [][]Cell) { + t.Helper() + + if len(expected) != len(actual) { + t.Errorf("number of rings mismatch: %v != %v", len(expected), len(actual)) + return + } + + expected = copyRings(expected) + actual = copyRings(actual) + + for i := range expected { + if len(expected[i]) != len(actual[i]) { + t.Errorf("ring[%d] length mismatch: %v != %v", i, len(expected[i]), len(actual[i])) + return + } + + expected[i] = sortCells(expected[i]) + actual[i] = sortCells(actual[i]) + + for j, cell := range expected[i] { + if cell != actual[i][j] { + t.Errorf("ring[%d][%d] mismatch: %v != %v", i, j, cell, actual[i][j]) + return + } + } + } +} + +func assertEqualDisks(t *testing.T, expected, actual []Cell) { + t.Helper() + + if len(expected) != len(actual) { + t.Errorf("disk size mismatch: %v != %v", len(expected), len(actual)) + return + } + + expected = sortCells(copyCells(expected)) + actual = sortCells(copyCells(actual)) + + count := 0 + + for i, cell := range expected { + if cell != actual[i] { + t.Errorf("cell[%d]: %v != %v", i, cell, actual[i]) + count++ + + if count > 5 { + t.Logf("... and more") + break + } + } + } +} + +func assertCellIn(t *testing.T, needle Cell, haystack []Cell) { + t.Helper() + var found bool for _, h := range haystack { found = needle == h @@ -486,29 +746,83 @@ func assertHexIn(t *testing.T, needle H3Index, haystack []H3Index) { break } } - assert.True(t, found, - "expected %+v in %+v", - needle, haystack) -} -func validHexRange() []H3Index { - out := []H3Index{} - for _, ring := range validH3Rings1 { - out = append(out, ring...) + if !found { + t.Errorf("%v not found in %+v", needle, haystack) } - return out } -func sortHexes(s []H3Index) []H3Index { +func assertNoPanic(t *testing.T, f func()) { + t.Helper() + + defer func() { + if r := recover(); r != nil { + t.Errorf("panic: %v", r) + } + }() + f() +} + +func assertFalse(t *testing.T, b bool) { + t.Helper() + assertEqual(t, false, b) +} +func assertTrue(t *testing.T, b bool) { + t.Helper() + assertEqual(t, true, b) +} + +func sortCells(s []Cell) []Cell { sort.SliceStable(s, func(i, j int) bool { return s[i] < s[j] }) + return s } -func max(x, y int) int { - if x > y { +func logMsgAndArgs(t *testing.T, msgAndArgs ...interface{}) { + t.Helper() + + if len(msgAndArgs) > 0 { + t.Logf(msgAndArgs[0].(string), msgAndArgs[1:]...) + } +} + +func flattenDisks(diskDist [][]Cell) []Cell { + if len(diskDist) == 0 { + return nil + } + + flat := make([]Cell, 0, maxGridDiskSize(len(diskDist)-1)) + for _, disk := range diskDist { + flat = append(flat, append([]Cell{}, disk...)...) + } + + return flat +} + +func tern(b bool, x, y string) string { + if b { return x } + return y } + +func copyRings(s [][]Cell) [][]Cell { + c := make([][]Cell, len(s)) + copy(c, s) + + for i := range c { + c[i] = append([]Cell{}, s[i]...) + } + + return c +} + +func copyCells(s []Cell) []Cell { + c := make([]Cell, len(s)) + copy(c, s) + + return c +} diff --git a/h3_vec2d.c b/h3_vec2d.c index c1dce2c..8dcb4f0 100644 --- a/h3_vec2d.c +++ b/h3_vec2d.c @@ -18,6 +18,7 @@ */ #include "h3_vec2d.h" + #include #include @@ -26,7 +27,7 @@ * @param v The 2D cartesian vector. * @return The magnitude of the vector. */ -double _v2dMag(const Vec2d* v) { return sqrt(v->x * v->x + v->y * v->y); } +double _v2dMag(const Vec2d *v) { return sqrt(v->x * v->x + v->y * v->y); } /** * Finds the intersection between two lines. Assumes that the lines intersect @@ -37,8 +38,8 @@ double _v2dMag(const Vec2d* v) { return sqrt(v->x * v->x + v->y * v->y); } * @param p3 The second endpoint of the second line. * @param inter The intersection point. */ -void _v2dIntersect(const Vec2d* p0, const Vec2d* p1, const Vec2d* p2, - const Vec2d* p3, Vec2d* inter) { +void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, + const Vec2d *p3, Vec2d *inter) { Vec2d s1, s2; s1.x = p1->x - p0->x; s1.y = p1->y - p0->y; @@ -60,6 +61,6 @@ void _v2dIntersect(const Vec2d* p0, const Vec2d* p1, const Vec2d* p2, * @param v2 Second vector to compare * @return Whether the vectors are equal */ -bool _v2dEquals(const Vec2d* v1, const Vec2d* v2) { +bool _v2dEquals(const Vec2d *v1, const Vec2d *v2) { return v1->x == v2->x && v1->y == v2->y; } diff --git a/h3_vec2d.h b/h3_vec2d.h index 44030cc..059e6f2 100644 --- a/h3_vec2d.h +++ b/h3_vec2d.h @@ -32,9 +32,9 @@ typedef struct { // Internal functions -double _v2dMag(const Vec2d* v); -void _v2dIntersect(const Vec2d* p0, const Vec2d* p1, const Vec2d* p2, - const Vec2d* p3, Vec2d* inter); -bool _v2dEquals(const Vec2d* p0, const Vec2d* p1); +double _v2dMag(const Vec2d *v); +void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, + const Vec2d *p3, Vec2d *inter); +bool _v2dEquals(const Vec2d *p0, const Vec2d *p1); #endif diff --git a/h3_vec3d.c b/h3_vec3d.c index 132dc9d..cd0e3f5 100644 --- a/h3_vec3d.c +++ b/h3_vec3d.c @@ -1,5 +1,5 @@ /* - * Copyright 2018 Uber Technologies, Inc. + * Copyright 2018, 2020-2021 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. @@ -18,6 +18,7 @@ */ #include "h3_vec3d.h" + #include /** @@ -35,7 +36,7 @@ double _square(double x) { return x * x; } * @param v2 The second 3D coordinate. * @return The square of the distance between the given points. */ -double _pointSquareDist(const Vec3d* v1, const Vec3d* v2) { +double _pointSquareDist(const Vec3d *v1, const Vec3d *v2) { return _square(v1->x - v2->x) + _square(v1->y - v2->y) + _square(v1->z - v2->z); } @@ -46,10 +47,10 @@ double _pointSquareDist(const Vec3d* v1, const Vec3d* v2) { * @param geo The latitude and longitude of the point. * @param v The 3D coordinate of the point. */ -void _geoToVec3d(const GeoCoord* geo, Vec3d* v) { +void _geoToVec3d(const LatLng *geo, Vec3d *v) { double r = cos(geo->lat); v->z = sin(geo->lat); - v->x = cos(geo->lon) * r; - v->y = sin(geo->lon) * r; + v->x = cos(geo->lng) * r; + v->y = sin(geo->lng) * r; } diff --git a/h3_vec3d.h b/h3_vec3d.h index 555464d..68e42e8 100644 --- a/h3_vec3d.h +++ b/h3_vec3d.h @@ -1,5 +1,5 @@ /* - * Copyright 2018 Uber Technologies, Inc. + * Copyright 2018, 2020-2021 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. @@ -20,7 +20,7 @@ #ifndef VEC3D_H #define VEC3D_H -#include "h3_geoCoord.h" +#include "h3_latLng.h" /** @struct Vec3D * @brief 3D floating point structure @@ -31,7 +31,7 @@ typedef struct { double z; ///< z component } Vec3d; -void _geoToVec3d(const GeoCoord* geo, Vec3d* point); -double _pointSquareDist(const Vec3d* p1, const Vec3d* p2); +void _geoToVec3d(const LatLng *geo, Vec3d *point); +double _pointSquareDist(const Vec3d *p1, const Vec3d *p2); #endif diff --git a/h3_vertex.c b/h3_vertex.c new file mode 100644 index 0000000..75b1045 --- /dev/null +++ b/h3_vertex.c @@ -0,0 +1,365 @@ +/* + * Copyright 2020-2021 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. + */ +/** @file vertex.h + * @brief Functions for working with cell vertexes. + */ + +#include "h3_vertex.h" + +#include +#include + +#include "h3_algos.h" +#include "h3_baseCells.h" +#include "h3_faceijk.h" +#include "h3_h3Index.h" +#include "h3_latLng.h" + +#define DIRECTION_INDEX_OFFSET 2 + +/** @brief Table of direction-to-face mapping for each pentagon + * + * Note that faces are in directional order, starting at J_AXES_DIGIT. + * This table is generated by the generatePentagonDirectionFaces script. + */ +static const PentagonDirectionFaces pentagonDirectionFaces[NUM_PENTAGONS] = { + {4, {4, 0, 2, 1, 3}}, {14, {6, 11, 2, 7, 1}}, + {24, {5, 10, 1, 6, 0}}, {38, {7, 12, 3, 8, 2}}, + {49, {9, 14, 0, 5, 4}}, {58, {8, 13, 4, 9, 3}}, + {63, {11, 6, 15, 10, 16}}, {72, {12, 7, 16, 11, 17}}, + {83, {10, 5, 19, 14, 15}}, {97, {13, 8, 17, 12, 18}}, + {107, {14, 9, 18, 13, 19}}, {117, {15, 19, 17, 18, 16}}, +}; + +/** + * Get the number of CCW rotations of the cell's vertex numbers + * compared to the directional layout of its neighbors. + * @param out Number of CCW rotations for the cell + */ +static H3Error vertexRotations(H3Index cell, int *out) { + // Get the face and other info for the origin + FaceIJK fijk; + H3Error err = _h3ToFaceIjk(cell, &fijk); + if (err) { + return err; + } + int baseCell = H3_EXPORT(getBaseCellNumber)(cell); + int cellLeadingDigit = _h3LeadingNonZeroDigit(cell); + + // get the base cell face + FaceIJK baseFijk; + _baseCellToFaceIjk(baseCell, &baseFijk); + + int ccwRot60 = _baseCellToCCWrot60(baseCell, fijk.face); + + if (_isBaseCellPentagon(baseCell)) { + // Find the appropriate direction-to-face mapping + PentagonDirectionFaces dirFaces; + // Excluding from branch coverage as we never hit the end condition + for (int p = 0; p < NUM_PENTAGONS; p++) { // LCOV_EXCL_BR_LINE + if (pentagonDirectionFaces[p].baseCell == baseCell) { + dirFaces = pentagonDirectionFaces[p]; + break; + } + } + + // additional CCW rotation for polar neighbors or IK neighbors + if (fijk.face != baseFijk.face && + (_isBaseCellPolarPentagon(baseCell) || + fijk.face == + dirFaces.faces[IK_AXES_DIGIT - DIRECTION_INDEX_OFFSET])) { + ccwRot60 = (ccwRot60 + 1) % 6; + } + + // Check whether the cell crosses a deleted pentagon subsequence + if (cellLeadingDigit == JK_AXES_DIGIT && + fijk.face == + dirFaces.faces[IK_AXES_DIGIT - DIRECTION_INDEX_OFFSET]) { + // Crosses from JK to IK: Rotate CW + ccwRot60 = (ccwRot60 + 5) % 6; + } else if (cellLeadingDigit == IK_AXES_DIGIT && + fijk.face == + dirFaces.faces[JK_AXES_DIGIT - DIRECTION_INDEX_OFFSET]) { + // Crosses from IK to JK: Rotate CCW + ccwRot60 = (ccwRot60 + 1) % 6; + } + } + *out = ccwRot60; + return E_SUCCESS; +} + +/** @brief Hexagon direction to vertex number relationships (same face). + * Note that we don't use direction 0 (center). + */ +static const int directionToVertexNumHex[NUM_DIGITS] = { + INVALID_DIGIT, 3, 1, 2, 5, 4, 0}; + +/** @brief Pentagon direction to vertex number relationships (same face). + * Note that we don't use directions 0 (center) or 1 (deleted K axis). + */ +static const int directionToVertexNumPent[NUM_DIGITS] = { + INVALID_DIGIT, INVALID_DIGIT, 1, 2, 4, 3, 0}; + +/** + * Get the first vertex number for a given direction. The neighbor in this + * direction is located between this vertex number and the next number in + * sequence. + * @returns The number for the first topological vertex, or INVALID_VERTEX_NUM + * if the direction is not valid for this cell + */ +int vertexNumForDirection(const H3Index origin, const Direction direction) { + int isPent = H3_EXPORT(isPentagon)(origin); + // Check for invalid directions + if (direction == CENTER_DIGIT || direction >= INVALID_DIGIT || + (isPent && direction == K_AXES_DIGIT)) + return INVALID_VERTEX_NUM; + + // Determine the vertex rotations for this cell + int rotations; + H3Error err = vertexRotations(origin, &rotations); + if (err) { + return INVALID_VERTEX_NUM; + } + + // Find the appropriate vertex, rotating CCW if necessary + if (isPent) { + return (directionToVertexNumPent[direction] + NUM_PENT_VERTS - + rotations) % + NUM_PENT_VERTS; + } else { + return (directionToVertexNumHex[direction] + NUM_HEX_VERTS - + rotations) % + NUM_HEX_VERTS; + } +} + +/** @brief Vertex number to hexagon direction relationships (same face). + */ +static const Direction vertexNumToDirectionHex[NUM_HEX_VERTS] = { + IJ_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, + K_AXES_DIGIT, IK_AXES_DIGIT, I_AXES_DIGIT}; + +/** @brief Vertex number to pentagon direction relationships (same face). + */ +static const Direction vertexNumToDirectionPent[NUM_PENT_VERTS] = { + IJ_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, IK_AXES_DIGIT, I_AXES_DIGIT}; + +/** + * Get the direction for a given vertex number. This returns the direction for + * the neighbor between the given vertex number and the next number in sequence. + * @returns The direction for this vertex, or INVALID_DIGIT if the vertex + * number is invalid. + */ +Direction directionForVertexNum(const H3Index origin, const int vertexNum) { + int isPent = H3_EXPORT(isPentagon)(origin); + // Check for invalid vertexes + if (vertexNum < 0 || + vertexNum > (isPent ? NUM_PENT_VERTS : NUM_HEX_VERTS) - 1) + return INVALID_DIGIT; + + // Determine the vertex rotations for this cell + int rotations; + H3Error err = vertexRotations(origin, &rotations); + if (err) { + return INVALID_DIGIT; + } + + // Find the appropriate direction, rotating CW if necessary + return isPent ? vertexNumToDirectionPent[(vertexNum + rotations) % + NUM_PENT_VERTS] + : vertexNumToDirectionHex[(vertexNum + rotations) % + NUM_HEX_VERTS]; +} + +/** @brief Directions in CCW order */ +static const Direction DIRECTIONS[NUM_HEX_VERTS] = { + J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, + IK_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT}; + +/** @brief Reverse direction from neighbor in each direction, + * given as an index into DIRECTIONS to facilitate rotation + */ +static const int revNeighborDirectionsHex[NUM_DIGITS] = { + INVALID_DIGIT, 5, 3, 4, 1, 0, 2}; + +/** + * Get a single vertex for a given cell, as an H3 index, or + * H3_NULL if the vertex is invalid + * @param cell Cell to get the vertex for + * @param vertexNum Number (index) of the vertex to calculate + */ +H3Error H3_EXPORT(cellToVertex)(H3Index cell, int vertexNum, H3Index *out) { + int cellIsPentagon = H3_EXPORT(isPentagon)(cell); + int cellNumVerts = cellIsPentagon ? NUM_PENT_VERTS : NUM_HEX_VERTS; + int res = H3_GET_RESOLUTION(cell); + + // Check for invalid vertexes + if (vertexNum < 0 || vertexNum > cellNumVerts - 1) return E_DOMAIN; + + // Default the owner and vertex number to the input cell + H3Index owner = cell; + int ownerVertexNum = vertexNum; + + // Determine the owner, looking at the three cells that share the vertex. + // By convention, the owner is the cell with the lowest numerical index. + + // If the cell is the center child of its parent, it will always have + // the lowest index of any neighbor, so we can skip determining the owner + if (res == 0 || H3_GET_INDEX_DIGIT(cell, res) != CENTER_DIGIT) { + // Get the left neighbor of the vertex, with its rotations + Direction left = directionForVertexNum(cell, vertexNum); + // This case should be unreachable; invalid verts fail earlier + if (left == INVALID_DIGIT) return E_FAILED; // LCOV_EXCL_LINE + int lRotations = 0; + H3Index leftNeighbor; + h3NeighborRotations(cell, left, &lRotations, &leftNeighbor); + // Set to owner if lowest index + if (leftNeighbor < owner) owner = leftNeighbor; + + // As above, skip the right neighbor if the left is known lowest + if (res == 0 || H3_GET_INDEX_DIGIT(leftNeighbor, res) != CENTER_DIGIT) { + // Get the right neighbor of the vertex, with its rotations + // Note that vertex - 1 is the right side, as vertex numbers are CCW + Direction right = directionForVertexNum( + cell, (vertexNum - 1 + cellNumVerts) % cellNumVerts); + // This case should be unreachable; invalid verts fail earlier + if (right == INVALID_DIGIT) return E_FAILED; // LCOV_EXCL_LINE + int rRotations = 0; + H3Index rightNeighbor; + h3NeighborRotations(cell, right, &rRotations, &rightNeighbor); + // Set to owner if lowest index + if (rightNeighbor < owner) { + owner = rightNeighbor; + Direction dir = + H3_EXPORT(isPentagon)(owner) + ? directionForNeighbor(owner, cell) + : DIRECTIONS[(revNeighborDirectionsHex[right] + + rRotations) % + NUM_HEX_VERTS]; + ownerVertexNum = vertexNumForDirection(owner, dir); + } + } + + // Determine the vertex number for the left neighbor + if (owner == leftNeighbor) { + int ownerIsPentagon = H3_EXPORT(isPentagon)(owner); + Direction dir = + ownerIsPentagon + ? directionForNeighbor(owner, cell) + : DIRECTIONS[(revNeighborDirectionsHex[left] + lRotations) % + NUM_HEX_VERTS]; + + // For the left neighbor, we need the second vertex of the + // edge, which may involve looping around the vertex nums + ownerVertexNum = vertexNumForDirection(owner, dir) + 1; + if (ownerVertexNum == NUM_HEX_VERTS || + (ownerIsPentagon && ownerVertexNum == NUM_PENT_VERTS)) { + ownerVertexNum = 0; + } + } + } + + // Create the vertex index + H3Index vertex = owner; + H3_SET_MODE(vertex, H3_VERTEX_MODE); + H3_SET_RESERVED_BITS(vertex, ownerVertexNum); + *out = vertex; + + return E_SUCCESS; +} + +/** + * Get all vertexes for the given cell + * @param cell Cell to get the vertexes for + * @param vertexes Array to hold vertex output. Must have length >= 6. + */ +H3Error H3_EXPORT(cellToVertexes)(H3Index cell, H3Index *vertexes) { + // Get all vertexes. If the cell is a pentagon, will fill the final slot + // with H3_NULL. + bool isPent = H3_EXPORT(isPentagon)(cell); + for (int i = 0; i < NUM_HEX_VERTS; i++) { + if (i == 5 && isPent) { + vertexes[i] = H3_NULL; + } else { + H3Error cellError = H3_EXPORT(cellToVertex)(cell, i, &vertexes[i]); + if (cellError) { + return cellError; + } + } + } + return E_SUCCESS; +} + +/** + * Get the geocoordinates of an H3 vertex + * @param vertex H3 index describing a vertex + * @param coord Output geo coordinate + */ +H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *coord) { + // Get the vertex number and owner from the vertex + int vertexNum = H3_GET_RESERVED_BITS(vertex); + H3Index owner = vertex; + H3_SET_MODE(owner, H3_CELL_MODE); + H3_SET_RESERVED_BITS(owner, 0); + + // Get the single vertex from the boundary + CellBoundary gb; + FaceIJK fijk; + H3Error fijkError = _h3ToFaceIjk(owner, &fijk); + if (fijkError) { + return fijkError; + } + int res = H3_GET_RESOLUTION(owner); + + if (H3_EXPORT(isPentagon)(owner)) { + _faceIjkPentToCellBoundary(&fijk, res, vertexNum, 1, &gb); + } else { + _faceIjkToCellBoundary(&fijk, res, vertexNum, 1, &gb); + } + + // Copy from boundary to output coord + *coord = gb.verts[0]; + return E_SUCCESS; +} + +/** + * Whether the input is a valid H3 vertex + * @param vertex H3 index possibly describing a vertex + * @return Whether the input is valid + */ +int H3_EXPORT(isValidVertex)(H3Index vertex) { + if (H3_GET_MODE(vertex) != H3_VERTEX_MODE) { + return 0; + } + + int vertexNum = H3_GET_RESERVED_BITS(vertex); + H3Index owner = vertex; + H3_SET_MODE(owner, H3_CELL_MODE); + H3_SET_RESERVED_BITS(owner, 0); + + if (!H3_EXPORT(isValidCell)(owner)) { + return 0; + } + + // The easiest way to ensure that the owner + vertex number is valid, + // and that the vertex is canonical, is to recreate and compare. + H3Index canonical; + if (H3_EXPORT(cellToVertex)(owner, vertexNum, &canonical)) { + return 0; + } + + return vertex == canonical ? 1 : 0; +} diff --git a/v3/h3_vertex.h b/h3_vertex.h similarity index 91% rename from v3/h3_vertex.h rename to h3_vertex.h index a9731b8..0818b6f 100644 --- a/v3/h3_vertex.h +++ b/h3_vertex.h @@ -1,5 +1,5 @@ /* - * Copyright 2020 Uber Technologies, Inc. + * Copyright 2020-2021 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. @@ -38,7 +38,7 @@ typedef struct { /** Max number of faces a base cell's descendants may appear on */ #define MAX_BASE_CELL_FACES 5 -int vertexRotations(H3Index cell); int vertexNumForDirection(const H3Index origin, const Direction direction); +Direction directionForVertexNum(const H3Index origin, const int vertexNum); #endif diff --git a/h3_vertexGraph.c b/h3_vertexGraph.c index bb4c786..aee2610 100644 --- a/h3_vertexGraph.c +++ b/h3_vertexGraph.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018 Uber Technologies, Inc. + * Copyright 2017-2018, 2020-2021 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. @@ -18,12 +18,15 @@ */ #include "h3_vertexGraph.h" + #include #include #include #include #include -#include "h3_geoCoord.h" + +#include "h3_alloc.h" +#include "h3_latLng.h" /** * Initialize a new VertexGraph @@ -31,9 +34,9 @@ * @param numBuckets Number of buckets to include in the graph * @param res Resolution of the hexagons whose vertices we're storing */ -void initVertexGraph(VertexGraph* graph, int numBuckets, int res) { +void initVertexGraph(VertexGraph *graph, int numBuckets, int res) { if (numBuckets > 0) { - graph->buckets = calloc(numBuckets, sizeof(VertexNode*)); + graph->buckets = H3_MEMORY(calloc)(numBuckets, sizeof(VertexNode *)); assert(graph->buckets != NULL); } else { graph->buckets = NULL; @@ -48,34 +51,34 @@ void initVertexGraph(VertexGraph* graph, int numBuckets, int res) { * responsible for freeing memory allocated to the VertexGraph struct itself. * @param graph Graph to destroy */ -void destroyVertexGraph(VertexGraph* graph) { - VertexNode* node; +void destroyVertexGraph(VertexGraph *graph) { + VertexNode *node; while ((node = firstVertexNode(graph)) != NULL) { removeVertexNode(graph, node); } - free(graph->buckets); + H3_MEMORY(free)(graph->buckets); } /** - * Get an integer hash for a lat/lon point, at a precision determined + * Get an integer hash for a lat/lng point, at a precision determined * by the current hexagon resolution. * TODO: Light testing suggests this might not be sufficient at resolutions * finer than 10. Design a better hash function if performance and collisions * seem to be an issue here. - * @param vertex Lat/lon vertex to hash + * @param vertex Lat/lng vertex to hash * @param res Resolution of the hexagon the vertex belongs to * @param numBuckets Number of buckets in the graph * @return Integer hash */ -uint32_t _hashVertex(const GeoCoord* vertex, int res, int numBuckets) { - // Simple hash: Take the sum of the lat and lon with a precision level +uint32_t _hashVertex(const LatLng *vertex, int res, int numBuckets) { + // Simple hash: Take the sum of the lat and lng with a precision level // determined by the resolution, converted to int, modulo bucket count. - return (uint32_t)fmod(fabs((vertex->lat + vertex->lon) * pow(10, 15 - res)), + return (uint32_t)fmod(fabs((vertex->lat + vertex->lng) * pow(10, 15 - res)), numBuckets); } -void _initVertexNode(VertexNode* node, const GeoCoord* fromVtx, - const GeoCoord* toVtx) { +void _initVertexNode(VertexNode *node, const LatLng *fromVtx, + const LatLng *toVtx) { node->from = *fromVtx; node->to = *toVtx; node->next = NULL; @@ -88,16 +91,16 @@ void _initVertexNode(VertexNode* node, const GeoCoord* fromVtx, * @param toVtx End vertex * @return Pointer to the new node */ -VertexNode* addVertexNode(VertexGraph* graph, const GeoCoord* fromVtx, - const GeoCoord* toVtx) { +VertexNode *addVertexNode(VertexGraph *graph, const LatLng *fromVtx, + const LatLng *toVtx) { // Make the new node - VertexNode* node = malloc(sizeof(VertexNode)); + VertexNode *node = H3_MEMORY(malloc)(sizeof(VertexNode)); assert(node != NULL); _initVertexNode(node, fromVtx, toVtx); // Determine location uint32_t index = _hashVertex(fromVtx, graph->res, graph->numBuckets); // Check whether there's an existing node in that spot - VertexNode* currentNode = graph->buckets[index]; + VertexNode *currentNode = graph->buckets[index]; if (currentNode == NULL) { // Set bucket to the new node graph->buckets[index] = node; @@ -108,7 +111,7 @@ VertexNode* addVertexNode(VertexGraph* graph, const GeoCoord* fromVtx, if (geoAlmostEqual(¤tNode->from, fromVtx) && geoAlmostEqual(¤tNode->to, toVtx)) { // already exists, bail - free(node); + H3_MEMORY(free)(node); return currentNode; } if (currentNode->next != NULL) { @@ -129,10 +132,10 @@ VertexNode* addVertexNode(VertexGraph* graph, const GeoCoord* fromVtx, * @param node Node to remove * @return 0 on success, 1 on failure (node not found) */ -int removeVertexNode(VertexGraph* graph, VertexNode* node) { +int removeVertexNode(VertexGraph *graph, VertexNode *node) { // Determine location uint32_t index = _hashVertex(&node->from, graph->res, graph->numBuckets); - VertexNode* currentNode = graph->buckets[index]; + VertexNode *currentNode = graph->buckets[index]; int found = 0; if (currentNode != NULL) { if (currentNode == node) { @@ -150,7 +153,7 @@ int removeVertexNode(VertexGraph* graph, VertexNode* node) { } } if (found) { - free(node); + H3_MEMORY(free)(node); graph->size--; return 0; } @@ -165,12 +168,12 @@ int removeVertexNode(VertexGraph* graph, VertexNode* node) { * @param toVtx End vertex, or NULL if we don't care * @return Pointer to the vertex node, if found */ -VertexNode* findNodeForEdge(const VertexGraph* graph, const GeoCoord* fromVtx, - const GeoCoord* toVtx) { +VertexNode *findNodeForEdge(const VertexGraph *graph, const LatLng *fromVtx, + const LatLng *toVtx) { // Determine location uint32_t index = _hashVertex(fromVtx, graph->res, graph->numBuckets); // Check whether there's an existing node in that spot - VertexNode* node = graph->buckets[index]; + VertexNode *node = graph->buckets[index]; if (node != NULL) { // Look through the list and see if we find the edge do { @@ -191,8 +194,7 @@ VertexNode* findNodeForEdge(const VertexGraph* graph, const GeoCoord* fromVtx, * @param fromVtx Start vertex * @return Pointer to the vertex node, if found */ -VertexNode* findNodeForVertex(const VertexGraph* graph, - const GeoCoord* fromVtx) { +VertexNode *findNodeForVertex(const VertexGraph *graph, const LatLng *fromVtx) { return findNodeForEdge(graph, fromVtx, NULL); } @@ -201,8 +203,8 @@ VertexNode* findNodeForVertex(const VertexGraph* graph, * @param graph Graph to iterate * @return Vertex node, or NULL if at the end */ -VertexNode* firstVertexNode(const VertexGraph* graph) { - VertexNode* node = NULL; +VertexNode *firstVertexNode(const VertexGraph *graph) { + VertexNode *node = NULL; int currentIndex = 0; while (node == NULL) { if (currentIndex < graph->numBuckets) { diff --git a/h3_vertexGraph.h b/h3_vertexGraph.h index 65ea96e..731c070 100644 --- a/h3_vertexGraph.h +++ b/h3_vertexGraph.h @@ -1,5 +1,5 @@ /* - * Copyright 2017 Uber Technologies, Inc. + * Copyright 2017, 2020-2021 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. @@ -22,48 +22,48 @@ #include #include -#include "h3_geoCoord.h" + +#include "h3_latLng.h" /** @struct VertexNode * @brief A single node in a vertex graph, part of a linked list */ typedef struct VertexNode VertexNode; struct VertexNode { - GeoCoord from; - GeoCoord to; - VertexNode* next; + LatLng from; + LatLng to; + VertexNode *next; }; /** @struct VertexGraph * @brief A data structure to store a graph of vertices */ typedef struct { - VertexNode** buckets; + VertexNode **buckets; int numBuckets; int size; int res; } VertexGraph; -void initVertexGraph(VertexGraph* graph, int numBuckets, int res); +void initVertexGraph(VertexGraph *graph, int numBuckets, int res); -void destroyVertexGraph(VertexGraph* graph); +void destroyVertexGraph(VertexGraph *graph); -VertexNode* addVertexNode(VertexGraph* graph, const GeoCoord* fromVtx, - const GeoCoord* toVtx); +VertexNode *addVertexNode(VertexGraph *graph, const LatLng *fromVtx, + const LatLng *toVtx); -int removeVertexNode(VertexGraph* graph, VertexNode* node); +int removeVertexNode(VertexGraph *graph, VertexNode *node); -VertexNode* findNodeForEdge(const VertexGraph* graph, const GeoCoord* fromVtx, - const GeoCoord* toVtx); +VertexNode *findNodeForEdge(const VertexGraph *graph, const LatLng *fromVtx, + const LatLng *toVtx); -VertexNode* findNodeForVertex(const VertexGraph* graph, - const GeoCoord* fromVtx); +VertexNode *findNodeForVertex(const VertexGraph *graph, const LatLng *fromVtx); -VertexNode* firstVertexNode(const VertexGraph* graph); +VertexNode *firstVertexNode(const VertexGraph *graph); // Internal functions -uint32_t _hashVertex(const GeoCoord* vertex, int res, int numBuckets); -void _initVertexNode(VertexNode* node, const GeoCoord* fromVtx, - const GeoCoord* toVtx); +uint32_t _hashVertex(const LatLng *vertex, int res, int numBuckets); +void _initVertexNode(VertexNode *node, const LatLng *fromVtx, + const LatLng *toVtx); #endif diff --git a/update-h3.sh b/update-h3.sh index 3a9fce9..69dd27c 100755 --- a/update-h3.sh +++ b/update-h3.sh @@ -54,6 +54,8 @@ CWD=$(pwd) # clean up existing C source code. find . -name "*.c" -depth 1 -exec rm {} \; +# clean up existing C headers. +find . -name "*.h" -depth 1 -exec rm {} \; echo Downloading H3 from "$GIT_REMOTE" @@ -73,7 +75,7 @@ pushd "$H3_SRC_DIR" || badexit echo Copying source files into working directory pushd ./src/h3lib/lib/ || badexit for f in *.c; do - sed -E 's/#include "(.*)"/#include "h3_\1"/' "$f" > "$CWD/h3_$f" || badexit + sed -E 's/#include "(.*)"/#include "h3_\1"/; s/#include / /' "$f" > "$CWD/h3_$f" || badexit done popd || badexit @@ -83,4 +85,9 @@ pushd "$H3_SRC_DIR" || badexit sed -E 's/#include "(.*)"/#include "h3_\1"/' "$f" > "$CWD/h3_$f" || badexit done popd || badexit + + echo Copying api header file into working directory + pushd ./src/h3lib/include/ || badexit + sed -E 's/#include "(.*)"/#include "h3_\1"/' "h3api.h.in" > "$CWD/h3_h3api.h" || badexit + popd || badexit popd || badexit diff --git a/v3/.gitattributes b/v3/.gitattributes deleted file mode 100644 index 4481e61..0000000 --- a/v3/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# treat C source and header files as binary as they are script managed -*.c binary -*.h binary diff --git a/v3/H3_VERSION b/v3/H3_VERSION deleted file mode 100644 index 2272683..0000000 --- a/v3/H3_VERSION +++ /dev/null @@ -1 +0,0 @@ -v3.7.2 diff --git a/v3/bench_test.go b/v3/bench_test.go deleted file mode 100644 index 43633d1..0000000 --- a/v3/bench_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package h3 - -import ( - "testing" -) - -// buckets for preventing compiler optimizing out calls -var ( - geo = GeoCoord{ - Latitude: 37, - Longitude: -122, - } - h3idx = FromGeo(geo, 15) - h3addr = ToString(h3idx) - geoBndry GeoBoundary - h3idxs []H3Index -) - -func BenchmarkToString(b *testing.B) { - for n := 0; n < b.N; n++ { - h3addr = ToString(h3idx) - } -} - -func BenchmarkFromString(b *testing.B) { - for n := 0; n < b.N; n++ { - h3idx = FromString("850dab63fffffff") - } -} - -func BenchmarkToGeoRes15(b *testing.B) { - for n := 0; n < b.N; n++ { - geo = ToGeo(h3idx) - } -} - -func BenchmarkFromGeoRes15(b *testing.B) { - for n := 0; n < b.N; n++ { - h3idx = FromGeo(geo, 15) - } -} - -func BenchmarkToGeoBndryRes15(b *testing.B) { - for n := 0; n < b.N; n++ { - geoBndry = ToGeoBoundary(h3idx) - } -} - -func BenchmarkHexRange(b *testing.B) { - for n := 0; n < b.N; n++ { - h3idxs, _ = HexRange(h3idx, 10) - } -} - -func BenchmarkPolyfill(b *testing.B) { - for n := 0; n < b.N; n++ { - h3idxs = Polyfill(validGeopolygonWithHoles, 6) - } -} diff --git a/v3/example_test.go b/v3/example_test.go deleted file mode 100644 index ec8e1ab..0000000 --- a/v3/example_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package h3 - -import ( - "fmt" -) - -func ExampleFromGeo() { - geo := GeoCoord{ - Latitude: 37.775938728915946, - Longitude: -122.41795063018799, - } - resolution := 9 - fmt.Printf("%#x\n", FromGeo(geo, resolution)) - // Output: - // 0x8928308280fffff -} diff --git a/v3/go.mod b/v3/go.mod deleted file mode 100644 index 60f9e1c..0000000 --- a/v3/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/uber/h3-go/v3 - -go 1.13 - -require github.com/stretchr/testify v1.4.0 diff --git a/v3/go.sum b/v3/go.sum deleted file mode 100644 index e863f51..0000000 --- a/v3/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/v3/h3.go b/v3/h3.go deleted file mode 100644 index cdf8ea4..0000000 --- a/v3/h3.go +++ /dev/null @@ -1,724 +0,0 @@ -/* - * 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. - */ - -// 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 - -/* -#cgo CFLAGS: -std=c99 -#cgo CFLAGS: -DH3_HAVE_VLA=1 -#cgo CFLAGS: -I ${SRCDIR} -#cgo LDFLAGS: -lm -#include -#include -#include -*/ -import "C" -import ( - "errors" - "math" - "strconv" - "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 GeoCoord 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 - - // InvalidH3Index is a sentinel value for an invalid H3 index. - InvalidH3Index = C.H3_NULL -) - -var ( - // ErrPentagonEncountered is returned by functions that encounter a pentagon - // and cannot handle it. - ErrPentagonEncountered = errors.New("pentagon encountered") - - // ErrInvalidResolution is returned when the requested resolution is not valid - ErrInvalidResolution = errors.New("resolution invalid") - - // conversion units for faster maths - deg2rad = math.Pi / 180.0 - rad2deg = 180.0 / math.Pi -) - -// H3Index is a type alias for the C type `H3Index`. Effectively H3Index is a -// `uint64`. -type H3Index = C.H3Index - -// GeoBoundary is a slice of `GeoCoord`. Note, `len(GeoBoundary)` will never -// exceed `MaxCellBndryVerts`. -type GeoBoundary []GeoCoord - -// GeoCoord is a struct for geographic coordinates. -type GeoCoord struct { - Latitude, Longitude float64 -} - -func (g GeoCoord) toCPtr() *C.GeoCoord { - return &C.GeoCoord{ - lat: C.double(deg2rad * g.Latitude), - lon: C.double(deg2rad * g.Longitude), - } -} - -func (g GeoCoord) toC() C.GeoCoord { - return *g.toCPtr() -} - -// GeoPolygon is a geofence with 0 or more geofence holes -type GeoPolygon struct { - // Geofence is the exterior boundary of the polygon - Geofence []GeoCoord - - // Holes is a slice of interior boundary (holes) in the polygon - Holes [][]GeoCoord -} - -type LinkedGeoCoord struct { - Vertex GeoCoord - Next *LinkedGeoCoord -} - -type LinkedGeoLoop struct { - First *LinkedGeoCoord - Last *LinkedGeoCoord - Next *LinkedGeoLoop -} - -type LinkedGeoPolygon struct { - First *LinkedGeoLoop - Last *LinkedGeoLoop - Next *LinkedGeoPolygon -} - -// --- INDEXING --- -// -// This section defines bindings for H3 indexing functions. -// Additional documentation available at -// https://uber.github.io/h3/#/documentation/api-reference/indexing - -// FromGeo returns the H3Index at resolution `res` for a geographic coordinate. -func FromGeo(geoCoord GeoCoord, res int) H3Index { - return H3Index(C.geoToH3(geoCoord.toCPtr(), C.int(res))) -} - -// ToGeo returns the geographic centerpoint of an H3Index `h`. -func ToGeo(h H3Index) GeoCoord { - g := C.GeoCoord{} - C.h3ToGeo(h, &g) - return geoCoordFromC(g) -} - -// ToGeoBoundary returns a `GeoBoundary` of the H3Index `h`. -func ToGeoBoundary(h H3Index) GeoBoundary { - gb := new(C.GeoBoundary) - C.h3ToGeoBoundary(h, gb) - return geoBndryFromC(gb) -} - -// --- INSPECTION --- -// This section defines bindings for H3 inspection functions. -// Additional documentation available at -// https://uber.github.io/h3/#/documentation/api-reference/inspection - -// Resolution returns the resolution of `h`. -func Resolution(h H3Index) int { - return int(C.h3GetResolution(h)) -} - -// BaseCell returns the integer ID of the base cell the H3Index `h` belongs to. -func BaseCell(h H3Index) int { - return int(C.h3GetBaseCell(h)) -} - -// FromString returns an H3Index parsed from a string. -func FromString(hStr string) H3Index { - h, err := strconv.ParseUint(hStr, 16, 64) - if err != nil { - return 0 - } - return H3Index(h) -} - -// ToString returns a string representation of an H3Index. -func ToString(h H3Index) string { - return strconv.FormatUint(uint64(h), 16) -} - -// IsValid returns true if `h` is valid. -func IsValid(h H3Index) bool { - return C.h3IsValid(h) == 1 -} - -// IsResClassIII returns true if `h` is a class III index. If false, `h` is a -// class II index. -func IsResClassIII(h H3Index) bool { - return C.h3IsResClassIII(h) == 1 -} - -// IsPentagon returns true if `h` is a pentagon. -func IsPentagon(h H3Index) bool { - return C.h3IsPentagon(h) == 1 -} - -// --- NEIGHBORS --- -// This section defines bindings for H3 neighbor traversal functions. -// Additional documentation available at -// https://uber.github.io/h3/#/documentation/api-reference/neighbors - -// KRing implements the C function `kRing`. -func KRing(origin H3Index, k int) []H3Index { - out := make([]C.H3Index, rangeSize(k)) - C.kRing(origin, C.int(k), &out[0]) - return h3SliceFromC(out) -} - -// KRingDistances implements the C function `kRingDistances`. -func KRingDistances(origin H3Index, k int) [][]H3Index { - rsz := rangeSize(k) - outHexes := make([]C.H3Index, rsz) - outDists := make([]C.int, rsz) - C.kRingDistances(origin, C.int(k), &outHexes[0], &outDists[0]) - - ret := make([][]H3Index, k+1) - for i := 0; i <= k; i++ { - ret[i] = make([]H3Index, 0, ringSize(i)) - } - - for i, d := range outDists { - ret[d] = append(ret[d], H3Index(outHexes[i])) - } - return ret -} - -// HexRange implements the C function `hexRange`. -func HexRange(origin H3Index, k int) ([]H3Index, error) { - out := make([]C.H3Index, rangeSize(k)) - if rv := C.hexRange(origin, C.int(k), &out[0]); rv != 0 { - return nil, ErrPentagonEncountered - } - return h3SliceFromC(out), nil -} - -// HexRangeDistances implements the C function `hexRangeDistances`. -func HexRangeDistances(origin H3Index, k int) ([][]H3Index, error) { - rsz := rangeSize(k) - outHexes := make([]C.H3Index, rsz) - outDists := make([]C.int, rsz) - rv := C.hexRangeDistances(origin, C.int(k), &outHexes[0], &outDists[0]) - if rv != 0 { - return nil, ErrPentagonEncountered - } - - ret := make([][]H3Index, k+1) - for i := 0; i <= k; i++ { - ret[i] = make([]H3Index, 0, ringSize(i)) - } - - for i, d := range outDists { - ret[d] = append(ret[d], H3Index(outHexes[i])) - } - return ret, nil -} - -// HexRanges implements the C function `hexRanges`. -func HexRanges(origins []H3Index, k int) ([][]H3Index, error) { - rsz := rangeSize(k) - outHexes := make([]C.H3Index, rsz*len(origins)) - inHexes := h3SliceToC(origins) - rv := C.hexRanges(&inHexes[0], C.int(len(origins)), C.int(k), &outHexes[0]) - if rv != 0 { - return nil, ErrPentagonEncountered - } - - ret := make([][]H3Index, len(origins)) - for i := 0; i < len(origins); i++ { - ret[i] = make([]H3Index, rsz) - for j := 0; j < rsz; j++ { - ret[i][j] = H3Index(outHexes[i*rsz+j]) - } - } - return ret, nil -} - -// HexRing implements the C function `hexRing`. -func HexRing(origin H3Index, k int) ([]H3Index, error) { - out := make([]C.H3Index, ringSize(k)) - if rv := C.hexRing(origin, C.int(k), &out[0]); rv != 0 { - return nil, ErrPentagonEncountered - } - return h3SliceFromC(out), nil -} - -// AreNeighbors returns true if `h1` and `h2` are neighbors. Two -// indexes are neighbors if they share an edge. -func AreNeighbors(h1, h2 H3Index) bool { - return C.h3IndexesAreNeighbors(h1, h2) == 1 -} - -// --- HIERARCHY --- -// This section defines bindings for H3 hierarchical functions. -// Additional documentation available at -// https://uber.github.io/h3/#/documentation/api-reference/hierarchy - -// ToParent returns the `H3Index` of the cell that contains `child` at -// resolution `parentRes`. `parentRes` must be less than the resolution of -// `child`. -func ToParent(child H3Index, parentRes int) (parent H3Index) { - return H3Index(C.h3ToParent(C.H3Index(child), C.int(parentRes))) -} - -// ToChildren returns all the `H3Index`es of `parent` at resolution `childRes`. -// `childRes` must be larger than the resolution of `parent`. -func ToChildren(parent H3Index, childRes int) []H3Index { - p := C.H3Index(parent) - csz := C.int(childRes) - out := make([]C.H3Index, int(C.maxH3ToChildrenSize(p, csz))) - C.h3ToChildren(p, csz, &out[0]) - return h3SliceFromC(out) -} - -// Compact merges full sets of children into their parent `H3Index` -// recursively, until no more merges are possible. -func Compact(in []H3Index) []H3Index { - cin := h3SliceToC(in) - csz := C.int(len(in)) - // worst case no compaction so we need a set **at least** as large as the - // input - cout := make([]C.H3Index, csz) - C.compact(&cin[0], &cout[0], csz) - return h3SliceFromC(cout) -} - -// Uncompact splits every `H3Index` in `in` if its resolution is greater than -// `res` recursively. Returns all the `H3Index`es at resolution `res`. -func Uncompact(in []H3Index, res int) ([]H3Index, error) { - cin := h3SliceToC(in) - maxUncompactSz := C.maxUncompactSize(&cin[0], C.int(len(in)), C.int(res)) - if maxUncompactSz < 0 { - // A size of less than zero indicates an error uncompacting such as the - // requested resolution being less than the resolution of the hexagons. - return nil, ErrInvalidResolution - } - cout := make([]C.H3Index, maxUncompactSz) - C.uncompact( - &cin[0], C.int(len(in)), - &cout[0], maxUncompactSz, - C.int(res)) - return h3SliceFromC(cout), nil -} - -// --- REGIONS --- - -// Polyfill returns the hexagons at the given resolution whose centers are within the -// geofences given in the GeoPolygon struct. -func Polyfill(gp GeoPolygon, res int) []H3Index { - cgp := geoPolygonToC(gp) - defer freeCGeoPolygon(&cgp) - - maxSize := C.maxPolyfillSize(&cgp, C.int(res)) - cout := make([]C.H3Index, maxSize) - C.polyfill(&cgp, C.int(res), &cout[0]) - - return h3SliceFromC(cout) -} - -// SetToLinkedGeo returns a LinkedGeoPolygon describing the outlines of a set of hexagons -func SetToLinkedGeo(in []H3Index) *LinkedGeoPolygon { - if len(in) == 0 { - return &LinkedGeoPolygon{} - } - - cin := h3SliceToC(in) - csz := C.int(len(in)) - - cout := new(C.LinkedGeoPolygon) - defer C.destroyLinkedPolygon(cout) - - C.h3SetToLinkedGeo(&cin[0], csz, cout) - - return linkedGeoPolygonFromC(cout) -} - -// --- UNIDIRECTIONAL EDGE FUNCTIONS --- - -// UnidirectionalEdge returns a unidirectional `H3Index` from `origin` to -// `destination`. -func UnidirectionalEdge(origin, destination H3Index) H3Index { - return H3Index(C.getH3UnidirectionalEdge(origin, destination)) -} - -// UnidirectionalEdgeIsValid returns true if `edge` is a valid unidirectional -// edge index. -func UnidirectionalEdgeIsValid(edge H3Index) bool { - return C.h3UnidirectionalEdgeIsValid(edge) == 1 -} - -// OriginFromUnidirectionalEdge returns the origin of a unidirectional -// edge. -func OriginFromUnidirectionalEdge(edge H3Index) H3Index { - return H3Index(C.getOriginH3IndexFromUnidirectionalEdge(edge)) -} - -// DestinationFromUnidirectionalEdge returns the destination of a -// unidirectional edge. -func DestinationFromUnidirectionalEdge(edge H3Index) H3Index { - return H3Index(C.getDestinationH3IndexFromUnidirectionalEdge(edge)) -} - -// FromUnidirectionalEdge returns the origin and destination from a -// unidirectional edge. -func FromUnidirectionalEdge( - edge H3Index, -) (origin, destination H3Index) { - cout := make([]C.H3Index, 2) - C.getH3IndexesFromUnidirectionalEdge(edge, &cout[0]) - origin = H3Index(cout[0]) - destination = H3Index(cout[1]) - return -} - -// ToUnidirectionalEdges returns the six (or five if pentagon) unidirectional -// edges from `h` to each of `h`'s neighbors. -func ToUnidirectionalEdges(h H3Index) []H3Index { - // allocating max size, `h3SliceFromC` will adjust cap - cout := make([]C.H3Index, 6) - C.getH3UnidirectionalEdgesFromHexagon(h, &cout[0]) - return h3SliceFromC(cout) -} - -// UnidirectionalEdgeBoundary returns the geocoordinates of a unidirectional -// edge boundary. -func UnidirectionalEdgeBoundary(edge H3Index) GeoBoundary { - gb := new(C.GeoBoundary) - C.getH3UnidirectionalEdgeBoundary(edge, gb) - return geoBndryFromC(gb) -} - -// Line returns the line of h3 indexes connecting two indexes -func Line(start, end H3Index) []H3Index { - n := C.h3LineSize(start, end) - cout := make([]C.H3Index, n) - C.h3Line(start, end, &cout[0]) - return h3SliceFromC(cout) -} - -// HexAreaKm2 returns the average hexagon area in square kilometers at the given resolution. -func HexAreaKm2(resolution int) float64 { - return float64(C.hexAreaKm2(C.int(resolution))) -} - -// HexAreaM2 returns the average hexagon area in square meters at the given resolution. -func HexAreaM2(resolution int) float64 { - return float64(C.hexAreaM2(C.int(resolution))) -} - -// PointDistRads returns the "great circle" or "haversine" distance between pairs of GeoCoord points (lat/lng pairs) in radians. -func PointDistRads(a, b GeoCoord) float64 { - return float64(C.pointDistRads(a.toCPtr(), b.toCPtr())) -} - -// PointDistKm returns the "great circle" or "haversine" distance between pairs of GeoCoord points (lat/lng pairs) in kilometers. -func PointDistKm(a, b GeoCoord) float64 { - return float64(C.pointDistKm(a.toCPtr(), b.toCPtr())) -} - -// PointDistM returns the "great circle" or "haversine" distance between pairs of GeoCoord points (lat/lng pairs) in meters. -func PointDistM(a, b GeoCoord) float64 { - return float64(C.pointDistM(a.toCPtr(), b.toCPtr())) -} - -// CellAreaRads2 returns the exact area of specific cell in square radians. -func CellAreaRads2(h H3Index) float64 { - return float64(C.cellAreaRads2(h)) -} - -// CellAreaKm2 returns the exact area of specific cell in square kilometers. -func CellAreaKm2(h H3Index) float64 { - return float64(C.cellAreaKm2(h)) -} - -// CellAreaM2 returns the exact area of specific cell in square meters. -func CellAreaM2(h H3Index) float64 { - return float64(C.cellAreaM2(h)) -} - -// EdgeLengthKm returns the average hexagon edge length in kilometers at the given resolution. -func EdgeLengthKm(resolution int) float64 { - return float64(C.edgeLengthKm(C.int(resolution))) -} - -// EdgeLengthM returns the average hexagon edge length in meters at the given resolution. -func EdgeLengthM(resolution int) float64 { - return float64(C.edgeLengthM(C.int(resolution))) -} - -// ExactEdgeLengthRads returns the exact edge length of specific unidirectional edge in radians. -func ExactEdgeLengthRads(h H3Index) float64 { - return float64(C.exactEdgeLengthRads(h)) -} - -// ExactEdgeLengthKm returns the exact edge length of specific unidirectional edge in kilometers. -func ExactEdgeLengthKm(h H3Index) float64 { - return float64(C.exactEdgeLengthKm(h)) -} - -// ExactEdgeLengthM returns the exact edge length of specific unidirectional edge in meters. -func ExactEdgeLengthM(h H3Index) float64 { - return float64(C.exactEdgeLengthM(h)) -} - -// NumHexagons returns the number of unique H3 indexes at the given resolution. -func NumHexagons(resolution int) int { - return int(C.numHexagons(C.int(resolution))) -} - -// Res0IndexCount returns the number of resolution 0 H3 indexes. -func Res0IndexCount() int { - return int(C.res0IndexCount()) -} - -// GetRes0Indexes returns all the resolution 0 H3 indexes. -func GetRes0Indexes() []H3Index { - out := make([]C.H3Index, Res0IndexCount()) - C.getRes0Indexes(&out[0]) - return h3SliceFromC(out) -} - -// DistanceBetween returns the distance in grid cells between the two indexes -func DistanceBetween(origin, dest H3Index) int { - return int(C.h3Distance(origin, dest)) -} - -// ToCenterChild returns the center child (finer) index contained by h at resolution. -func ToCenterChild(h H3Index, resolution int) H3Index { - return C.h3ToCenterChild(h, C.int(resolution)) -} - -// MaxFaceCount returns the maximum number of icosahedron faces the given H3 index may intersect. -func MaxFaceCount(h H3Index) int { - return int(C.maxFaceCount(h)) -} - -// GetFaces returns all icosahedron faces intersected by a given H3 index -func GetFaces(h H3Index) []int { - out := make([]C.int, MaxFaceCount(h)) - C.h3GetFaces(h, &out[0]) - return intSliceFromC(out) -} - -// PentagonIndexCount returns the number of pentagon H3 indexes per resolution (This is always 12) -func PentagonIndexCount() int { - return int(C.pentagonIndexCount()) -} - -// GetPentagonIndex returns all the pentagon H3 indexes at the specified resolution. -func GetPentagonIndexes(resolution int) []H3Index { - out := make([]C.H3Index, PentagonIndexCount()) - C.getPentagonIndexes(C.int(resolution), &out[0]) - return h3SliceFromC(out) -} - -func geoCoordFromC(cg C.GeoCoord) GeoCoord { - g := GeoCoord{} - g.Latitude = rad2deg * float64(cg.lat) - g.Longitude = rad2deg * float64(cg.lon) - return g -} - -func geoBndryFromC(cb *C.GeoBoundary) GeoBoundary { - g := make(GeoBoundary, 0, MaxCellBndryVerts) - for i := C.int(0); i < cb.numVerts; i++ { - g = append(g, geoCoordFromC(cb.verts[i])) - } - return g -} - -func h3SliceFromC(chs []C.H3Index) []H3Index { - out := make([]H3Index, 0, len(chs)) - for _, ch := range chs { - // C API returns a sparse array of indexes in the event pentagons and - // deleted sequences are encountered. - if ch == InvalidH3Index { - continue - } - out = append(out, H3Index(ch)) - } - return out -} - -func h3SliceToC(hs []H3Index) []C.H3Index { - out := make([]C.H3Index, len(hs)) - for i, h := range hs { - out[i] = h - } - return out -} - -func intSliceFromC(chs []C.int) []int { - out := make([]int, 0, len(chs)) - for _, ch := range chs { - // C API returns a sparse array of indexes in the event pentagons and - // deleted sequences are encountered. - if ch == -1 { - continue - } - out = append(out, int(ch)) - } - return out -} - -func ringSize(k int) int { - if k == 0 { - return 1 - } - return 6 * k -} - -func rangeSize(k int) int { - return int(C.maxKringSize(C.int(k))) -} - -func linkedGeoPolygonFromC(cg *C.LinkedGeoPolygon) *LinkedGeoPolygon { - g := &LinkedGeoPolygon{} - glf, gll := linkedGeoLoopFromC(cg.first) - g.First = glf - g.Last = gll - - if cg.next != nil { - next := linkedGeoPolygonFromC(cg.next) - g.Next = next - } - - return g -} - -func linkedGeoLoopFromC(cg *C.LinkedGeoLoop) (*LinkedGeoLoop, *LinkedGeoLoop) { - g := &LinkedGeoLoop{} - gcf, gcl := linkedGeoCoordFromC(cg.first) - g.First = gcf - g.Last = gcl - l := g - - if cg.next != nil { - next, last := linkedGeoLoopFromC(cg.next) - g.Next = next - l = last - } - - return g, l -} - -func linkedGeoCoordFromC(cg *C.LinkedGeoCoord) (*LinkedGeoCoord, *LinkedGeoCoord) { - g := &LinkedGeoCoord{} - g.Vertex = geoCoordFromC(cg.vertex) - l := g - - if cg.next != nil { - next, last := linkedGeoCoordFromC(cg.next) - g.Next = next - l = last - } - - return g, l -} - -// Convert slice of geocoordinates to an array of C geocoordinates (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 geoCoordsToC(coords []GeoCoord) *C.GeoCoord { - 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_GeoCoord * len(coords))) - pv := cverts - for _, gc := range coords { - *((*C.GeoCoord)(pv)) = gc.toC() - pv = unsafe.Pointer(uintptr(pv) + C.sizeof_GeoCoord) - } - - return (*C.GeoCoord)(cverts) -} - -// Convert geofences (slices of slices of geocoordinates) 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 geofencesToC(geofences [][]GeoCoord) *C.Geofence { - 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_Geofence * len(geofences))) - - pcgeofences := cgeofences - for _, coords := range geofences { - cverts := geoCoordsToC(coords) - - *((*C.Geofence)(pcgeofences)) = C.Geofence{ - verts: cverts, - numVerts: C.int(len(coords)), - } - pcgeofences = unsafe.Pointer(uintptr(pcgeofences) + C.sizeof_Geofence) - } - - return (*C.Geofence)(cgeofences) -} - -// Convert GeoPolygon struct to C equivalent struct. -func geoPolygonToC(gp GeoPolygon) C.GeoPolygon { - cverts := geoCoordsToC(gp.Geofence) - choles := geofencesToC(gp.Holes) - - return C.GeoPolygon{ - geofence: C.Geofence{ - numVerts: C.int(len(gp.Geofence)), - 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.geofence.verts)) - cgp.geofence.verts = nil - - ph := unsafe.Pointer(cgp.holes) - for i := C.int(0); i < cgp.numHoles; i++ { - C.free(unsafe.Pointer((*C.Geofence)(ph).verts)) - (*C.Geofence)(ph).verts = nil - ph = unsafe.Pointer(uintptr(ph) + uintptr(C.sizeof_Geofence)) - } - - C.free(unsafe.Pointer(cgp.holes)) - cgp.holes = nil -} diff --git a/v3/h3_algos.c b/v3/h3_algos.c deleted file mode 100644 index ac0a596..0000000 --- a/v3/h3_algos.c +++ /dev/null @@ -1,1041 +0,0 @@ -/* - * Copyright 2016-2019 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. - */ -/** @file algos.c - * @brief Hexagon grid algorithms - */ - -#include "h3_algos.h" - -#include -#include -#include -#include -#include -#include - -#include "h3_alloc.h" -#include "h3_baseCells.h" -#include "h3_bbox.h" -#include "h3_faceijk.h" -#include "h3_geoCoord.h" -#include "h3_h3Index.h" -#include "h3_h3api.h" -#include "h3_linkedGeo.h" -#include "h3_polygon.h" -#include "h3_vertexGraph.h" - -/* - * Return codes from hexRange and related functions. - */ - -#define HEX_RANGE_SUCCESS 0 -#define HEX_RANGE_PENTAGON 1 -#define HEX_RANGE_K_SUBSEQUENCE 2 -#define MAX_ONE_RING_SIZE 7 -#define HEX_HASH_OVERFLOW -1 -#define POLYFILL_BUFFER 12 - -/** - * Directions used for traversing a hexagonal ring counterclockwise around - * {1, 0, 0} - * - *
- *      _
- *    _/ \\_
- *   / \\5/ \\
- *   \\0/ \\4/
- *   / \\_/ \\
- *   \\1/ \\3/
- *     \\2/
- * 
- */ -static const Direction DIRECTIONS[6] = {J_AXES_DIGIT, JK_AXES_DIGIT, - K_AXES_DIGIT, IK_AXES_DIGIT, - I_AXES_DIGIT, IJ_AXES_DIGIT}; - -/** - * Direction used for traversing to the next outward hexagonal ring. - */ -static const Direction NEXT_RING_DIRECTION = I_AXES_DIGIT; - -/** - * New digit when traversing along class II grids. - * - * Current digit -> direction -> new digit. - */ -static const Direction NEW_DIGIT_II[7][7] = { - {CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, - IK_AXES_DIGIT, IJ_AXES_DIGIT}, - {K_AXES_DIGIT, I_AXES_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, IK_AXES_DIGIT, - J_AXES_DIGIT, CENTER_DIGIT}, - {J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT, - CENTER_DIGIT, IK_AXES_DIGIT}, - {JK_AXES_DIGIT, IJ_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT, - K_AXES_DIGIT, J_AXES_DIGIT}, - {I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, - JK_AXES_DIGIT, K_AXES_DIGIT}, - {IK_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, JK_AXES_DIGIT, - IJ_AXES_DIGIT, I_AXES_DIGIT}, - {IJ_AXES_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT, J_AXES_DIGIT, K_AXES_DIGIT, - I_AXES_DIGIT, JK_AXES_DIGIT}}; - -/** - * New traversal direction when traversing along class II grids. - * - * Current digit -> direction -> new ap7 move (at coarser level). - */ -static const Direction NEW_ADJUSTMENT_II[7][7] = { - {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, - CENTER_DIGIT, CENTER_DIGIT}, - {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, - IK_AXES_DIGIT, CENTER_DIGIT}, - {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, - CENTER_DIGIT, J_AXES_DIGIT}, - {CENTER_DIGIT, K_AXES_DIGIT, JK_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, - CENTER_DIGIT, CENTER_DIGIT}, - {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, - I_AXES_DIGIT, IJ_AXES_DIGIT}, - {CENTER_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, - IK_AXES_DIGIT, CENTER_DIGIT}, - {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT, - CENTER_DIGIT, IJ_AXES_DIGIT}}; - -/** - * New traversal direction when traversing along class III grids. - * - * Current digit -> direction -> new ap7 move (at coarser level). - */ -static const Direction NEW_DIGIT_III[7][7] = { - {CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, - IK_AXES_DIGIT, IJ_AXES_DIGIT}, - {K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, - IJ_AXES_DIGIT, CENTER_DIGIT}, - {J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, - CENTER_DIGIT, K_AXES_DIGIT}, - {JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, - K_AXES_DIGIT, J_AXES_DIGIT}, - {I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, - J_AXES_DIGIT, JK_AXES_DIGIT}, - {IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, - JK_AXES_DIGIT, I_AXES_DIGIT}, - {IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, - I_AXES_DIGIT, IK_AXES_DIGIT}}; - -/** - * New traversal direction when traversing along class III grids. - * - * Current digit -> direction -> new ap7 move (at coarser level). - */ -static const Direction NEW_ADJUSTMENT_III[7][7] = { - {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, - CENTER_DIGIT, CENTER_DIGIT}, - {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, - K_AXES_DIGIT, CENTER_DIGIT}, - {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, - CENTER_DIGIT, IJ_AXES_DIGIT}, - {CENTER_DIGIT, JK_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, - CENTER_DIGIT, CENTER_DIGIT}, - {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, - IK_AXES_DIGIT, I_AXES_DIGIT}, - {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT, - IK_AXES_DIGIT, CENTER_DIGIT}, - {CENTER_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, - CENTER_DIGIT, IJ_AXES_DIGIT}}; - -/** - * Maximum number of cells that result from the kRing algorithm with the given - * k. Formula source and proof: https://oeis.org/A003215 - * - * @param k k value, k >= 0. - */ -int H3_EXPORT(maxKringSize)(int k) { return 3 * k * (k + 1) + 1; } - -/** - * Produce 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 the provided array in no particular order. Elements of - * the output array may be left zero, as can happen when crossing a pentagon. - * - * @param origin origin cell - * @param k k >= 0 - * @param out zero-filled array which must be of size maxKringSize(k) - */ -void H3_EXPORT(kRing)(H3Index origin, int k, H3Index* out) { - H3_EXPORT(kRingDistances)(origin, k, out, NULL); -} - -/** - * Produce cells and their distances from the given origin cell, up to - * distance k. - * - * 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 the provided array in no particular order. Elements of - * the output array may be left zero, as can happen when crossing a pentagon. - * - * @param origin origin cell - * @param k k >= 0 - * @param out zero-filled array which must be of size maxKringSize(k) - * @param distances NULL or a zero-filled array which must be of size - * maxKringSize(k) - */ -void H3_EXPORT(kRingDistances)(H3Index origin, int k, H3Index* out, - int* distances) { - // Optimistically try the faster hexRange algorithm first - const bool failed = H3_EXPORT(hexRangeDistances)(origin, k, out, distances); - if (failed) { - const int maxIdx = H3_EXPORT(maxKringSize)(k); - // Fast algo failed, fall back to slower, correct algo - // and also wipe out array because contents untrustworthy - memset(out, 0, maxIdx * sizeof(H3Index)); - - if (distances == NULL) { - distances = H3_MEMORY(calloc)(maxIdx, sizeof(int)); - if (!distances) { - // TODO: Return an error code when this is not void - return; - } - _kRingInternal(origin, k, out, distances, maxIdx, 0); - H3_MEMORY(free)(distances); - } else { - memset(distances, 0, maxIdx * sizeof(int)); - _kRingInternal(origin, k, out, distances, maxIdx, 0); - } - } -} - -/** - * Internal helper function called recursively for kRingDistances. - * - * Adds the origin cell to the output set (treating it as a hash set) - * and recurses to its neighbors, if needed. - * - * @param origin Origin cell - * @param k Maximum distance to move from the origin - * @param out Array treated as a hash set, elements being either - * H3Index or 0. - * @param distances Scratch area, with elements paralleling the out array. - * Elements indicate ijk distance from the origin cell to - * the output cell - * @param maxIdx Size of out and scratch arrays (must be maxKringSize(k)) - * @param curK Current distance from the origin - */ -void _kRingInternal(H3Index origin, int k, H3Index* out, int* distances, - int maxIdx, int curK) { - if (origin == 0) return; - - // Put origin in the output array. out is used as a hash set. - int off = origin % maxIdx; - while (out[off] != 0 && out[off] != origin) { - off = (off + 1) % maxIdx; - } - - // We either got a free slot in the hash set or hit a duplicate - // We might need to process the duplicate anyways because we got - // here on a longer path before. - if (out[off] == origin && distances[off] <= curK) return; - - out[off] = origin; - distances[off] = curK; - - // Base case: reached an index k away from the origin. - if (curK >= k) return; - - // Recurse to all neighbors in no particular order. - for (int i = 0; i < 6; i++) { - int rotations = 0; - _kRingInternal(h3NeighborRotations(origin, DIRECTIONS[i], &rotations), - k, out, distances, maxIdx, curK + 1); - } -} - -/** - * Returns the hexagon index neighboring the origin, in the direction dir. - * - * Implementation note: The only reachable case where this returns 0 is if the - * origin is a pentagon and the translation is in the k direction. Thus, - * 0 can only be returned if origin is a pentagon. - * - * @param origin Origin index - * @param dir Direction to move in - * @param rotations Number of ccw rotations to perform to reorient the - * translation vector. Will be modified to the new number of - * rotations to perform (such as when crossing a face edge.) - * @return H3Index of the specified neighbor or H3_NULL if deleted k-subsequence - * distortion is encountered. - */ -H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations) { - H3Index out = origin; - - for (int i = 0; i < *rotations; i++) { - dir = _rotate60ccw(dir); - } - - int newRotations = 0; - int oldBaseCell = H3_GET_BASE_CELL(out); - Direction oldLeadingDigit = _h3LeadingNonZeroDigit(out); - - // Adjust the indexing digits and, if needed, the base cell. - int r = H3_GET_RESOLUTION(out) - 1; - while (true) { - if (r == -1) { - H3_SET_BASE_CELL(out, baseCellNeighbors[oldBaseCell][dir]); - newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir]; - - if (H3_GET_BASE_CELL(out) == INVALID_BASE_CELL) { - // Adjust for the deleted k vertex at the base cell level. - // This edge actually borders a different neighbor. - H3_SET_BASE_CELL(out, - baseCellNeighbors[oldBaseCell][IK_AXES_DIGIT]); - newRotations = - baseCellNeighbor60CCWRots[oldBaseCell][IK_AXES_DIGIT]; - - // perform the adjustment for the k-subsequence we're skipping - // over. - out = _h3Rotate60ccw(out); - *rotations = *rotations + 1; - } - - break; - } else { - Direction oldDigit = H3_GET_INDEX_DIGIT(out, r + 1); - Direction nextDir; - if (oldDigit == INVALID_DIGIT) { - // Only possible on invalid input - return H3_NULL; - } else if (isResClassIII(r + 1)) { - H3_SET_INDEX_DIGIT(out, r + 1, NEW_DIGIT_II[oldDigit][dir]); - nextDir = NEW_ADJUSTMENT_II[oldDigit][dir]; - } else { - H3_SET_INDEX_DIGIT(out, r + 1, NEW_DIGIT_III[oldDigit][dir]); - nextDir = NEW_ADJUSTMENT_III[oldDigit][dir]; - } - - if (nextDir != CENTER_DIGIT) { - dir = nextDir; - r--; - } else { - // No more adjustment to perform - break; - } - } - } - - int newBaseCell = H3_GET_BASE_CELL(out); - if (_isBaseCellPentagon(newBaseCell)) { - int alreadyAdjustedKSubsequence = 0; - - // force rotation out of missing k-axes sub-sequence - if (_h3LeadingNonZeroDigit(out) == K_AXES_DIGIT) { - if (oldBaseCell != newBaseCell) { - // in this case, we traversed into the deleted - // k subsequence of a pentagon base cell. - // We need to rotate out of that case depending - // on how we got here. - // check for a cw/ccw offset face; default is ccw - - if (_baseCellIsCwOffset( - newBaseCell, baseCellData[oldBaseCell].homeFijk.face)) { - out = _h3Rotate60cw(out); - } else { - // See cwOffsetPent in testKRing.c for why this is - // unreachable. - out = _h3Rotate60ccw(out); // LCOV_EXCL_LINE - } - alreadyAdjustedKSubsequence = 1; - } else { - // In this case, we traversed into the deleted - // k subsequence from within the same pentagon - // base cell. - if (oldLeadingDigit == CENTER_DIGIT) { - // Undefined: the k direction is deleted from here - return H3_NULL; - } else if (oldLeadingDigit == JK_AXES_DIGIT) { - // Rotate out of the deleted k subsequence - // We also need an additional change to the direction we're - // moving in - out = _h3Rotate60ccw(out); - *rotations = *rotations + 1; - } else if (oldLeadingDigit == IK_AXES_DIGIT) { - // Rotate out of the deleted k subsequence - // We also need an additional change to the direction we're - // moving in - out = _h3Rotate60cw(out); - *rotations = *rotations + 5; - } else { - // Should never occur - return H3_NULL; // LCOV_EXCL_LINE - } - } - } - - for (int i = 0; i < newRotations; i++) out = _h3RotatePent60ccw(out); - - // Account for differing orientation of the base cells (this edge - // might not follow properties of some other edges.) - if (oldBaseCell != newBaseCell) { - if (_isBaseCellPolarPentagon(newBaseCell)) { - // 'polar' base cells behave differently because they have all - // i neighbors. - if (oldBaseCell != 118 && oldBaseCell != 8 && - _h3LeadingNonZeroDigit(out) != JK_AXES_DIGIT) { - *rotations = *rotations + 1; - } - } else if (_h3LeadingNonZeroDigit(out) == IK_AXES_DIGIT && - !alreadyAdjustedKSubsequence) { - // account for distortion introduced to the 5 neighbor by the - // deleted k subsequence. - *rotations = *rotations + 1; - } - } - } else { - for (int i = 0; i < newRotations; i++) out = _h3Rotate60ccw(out); - } - - *rotations = (*rotations + newRotations) % 6; - - return out; -} - -/** - * hexRange produces indexes within k distance of the origin index. - * Output behavior is undefined when one of the indexes returned by this - * function is a pentagon or is in the pentagon distortion area. - * - * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and - * all neighboring indexes, and so on. - * - * Output is placed in the provided array in order of increasing distance from - * the origin. - * - * @param origin Origin location. - * @param k k >= 0 - * @param out Array which must be of size maxKringSize(k). - * @return 0 if no pentagon or pentagonal distortion area was encountered. - */ -int H3_EXPORT(hexRange)(H3Index origin, int k, H3Index* out) { - return H3_EXPORT(hexRangeDistances)(origin, k, out, NULL); -} - -/** - * hexRange produces indexes within k distance of the origin index. - * Output behavior is undefined when one of the indexes returned by this - * function is a pentagon or is in the pentagon distortion area. - * - * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and - * all neighboring indexes, and so on. - * - * Output is placed in the provided array in order of increasing distance from - * the origin. The distances in hexagons is placed in the distances array at - * the same offset. - * - * @param origin Origin location. - * @param k k >= 0 - * @param out Array which must be of size maxKringSize(k). - * @param distances Null or array which must be of size maxKringSize(k). - * @return 0 if no pentagon or pentagonal distortion area was encountered. - */ -int H3_EXPORT(hexRangeDistances)(H3Index origin, int k, H3Index* out, - int* distances) { - // Return codes: - // 1 Pentagon was encountered - // 2 Pentagon distortion (deleted k subsequence) was encountered - // Pentagon being encountered is not itself a problem; really the deleted - // k-subsequence is the problem, but for compatibility reasons we fail on - // the pentagon. - - // k must be >= 0, so origin is always needed - int idx = 0; - out[idx] = origin; - if (distances) { - distances[idx] = 0; - } - idx++; - - if (H3_EXPORT(h3IsPentagon)(origin)) { - // Pentagon was encountered; bail out as user doesn't want this. - return HEX_RANGE_PENTAGON; - } - - // 0 < ring <= k, current ring - int ring = 1; - // 0 <= direction < 6, current side of the ring - int direction = 0; - // 0 <= i < ring, current position on the side of the ring - int i = 0; - // Number of 60 degree ccw rotations to perform on the direction (based on - // which faces have been crossed.) - int rotations = 0; - - while (ring <= k) { - if (direction == 0 && i == 0) { - // Not putting in the output set as it will be done later, at - // the end of this ring. - origin = - h3NeighborRotations(origin, NEXT_RING_DIRECTION, &rotations); - if (origin == 0) { // LCOV_EXCL_BR_LINE - // Should not be possible because `origin` would have to be a - // pentagon - return HEX_RANGE_K_SUBSEQUENCE; // LCOV_EXCL_LINE - } - - if (H3_EXPORT(h3IsPentagon)(origin)) { - // Pentagon was encountered; bail out as user doesn't want this. - return HEX_RANGE_PENTAGON; - } - } - - origin = h3NeighborRotations(origin, DIRECTIONS[direction], &rotations); - if (origin == 0) { // LCOV_EXCL_BR_LINE - // Should not be possible because `origin` would have to be a - // pentagon - return HEX_RANGE_K_SUBSEQUENCE; // LCOV_EXCL_LINE - } - out[idx] = origin; - if (distances) { - distances[idx] = ring; - } - idx++; - - i++; - // Check if end of this side of the k-ring - if (i == ring) { - i = 0; - direction++; - // Check if end of this ring. - if (direction == 6) { - direction = 0; - ring++; - } - } - - if (H3_EXPORT(h3IsPentagon)(origin)) { - // Pentagon was encountered; bail out as user doesn't want this. - return HEX_RANGE_PENTAGON; - } - } - return HEX_RANGE_SUCCESS; -} - -/** - * hexRanges takes an array of input hex IDs and a max k-ring and returns an - * array of hexagon IDs sorted first by the original hex IDs and then by the - * k-ring (0 to max), with no guaranteed sorting within each k-ring group. - * - * @param h3Set A pointer to an array of H3Indexes - * @param length The total number of H3Indexes in h3Set - * @param k The number of rings to generate - * @param out A pointer to the output memory to dump the new set of H3Indexes to - * The memory block should be equal to maxKringSize(k) * length - * @return 0 if no pentagon is encountered. Cannot trust output otherwise - */ -int H3_EXPORT(hexRanges)(H3Index* h3Set, int length, int k, H3Index* out) { - int success = 0; - H3Index* segment; - int segmentSize = H3_EXPORT(maxKringSize)(k); - for (int i = 0; i < length; i++) { - // Determine the appropriate segment of the output array to operate on - segment = out + i * segmentSize; - success = H3_EXPORT(hexRange)(h3Set[i], k, segment); - if (success != 0) return success; - } - return 0; -} - -/** - * Returns the "hollow" ring of hexagons at exactly grid distance k from - * the origin hexagon. In particular, k=0 returns just the origin hexagon. - * - * A nonzero failure code may be returned in some cases, for example, - * if a pentagon is encountered. - * Failure cases may be fixed in future versions. - * - * @param origin Origin location. - * @param k k >= 0 - * @param out Array which must be of size 6 * k (or 1 if k == 0) - * @return 0 if successful; nonzero otherwise. - */ -int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index* out) { - // Short-circuit on 'identity' ring - if (k == 0) { - out[0] = origin; - return 0; - } - int idx = 0; - // Number of 60 degree ccw rotations to perform on the direction (based on - // which faces have been crossed.) - int rotations = 0; - // Scratch structure for checking for pentagons - if (H3_EXPORT(h3IsPentagon)(origin)) { - // Pentagon was encountered; bail out as user doesn't want this. - return HEX_RANGE_PENTAGON; - } - - for (int ring = 0; ring < k; ring++) { - origin = h3NeighborRotations(origin, NEXT_RING_DIRECTION, &rotations); - if (origin == 0) { // LCOV_EXCL_BR_LINE - // Should not be possible because `origin` would have to be a - // pentagon - return HEX_RANGE_K_SUBSEQUENCE; // LCOV_EXCL_LINE - } - - if (H3_EXPORT(h3IsPentagon)(origin)) { - return HEX_RANGE_PENTAGON; - } - } - - H3Index lastIndex = origin; - - out[idx] = origin; - idx++; - - for (int direction = 0; direction < 6; direction++) { - for (int pos = 0; pos < k; pos++) { - origin = - h3NeighborRotations(origin, DIRECTIONS[direction], &rotations); - if (origin == 0) { // LCOV_EXCL_BR_LINE - // Should not be possible because `origin` would have to be a - // pentagon - return HEX_RANGE_K_SUBSEQUENCE; // LCOV_EXCL_LINE - } - - // Skip the very last index, it was already added. We do - // however need to traverse to it because of the pentagonal - // distortion check, below. - if (pos != k - 1 || direction != 5) { - out[idx] = origin; - idx++; - - if (H3_EXPORT(h3IsPentagon)(origin)) { - return HEX_RANGE_PENTAGON; - } - } - } - } - - // Check that this matches the expected lastIndex, if it doesn't, - // it indicates pentagonal distortion occurred and we should report - // failure. - if (lastIndex != origin) { - return HEX_RANGE_PENTAGON; - } else { - return HEX_RANGE_SUCCESS; - } -} - -/** - * maxPolyfillSize returns the number of hexagons to allocate space for when - * performing a polyfill on the given GeoJSON-like data structure. - * - * The size is the maximum of either the number of points in the geofence or the - * number of hexagons in the bounding box of the geofence. - * - * @param geoPolygon A GeoJSON-like data structure indicating the poly to fill - * @param res Hexagon resolution (0-15) - * @return number of hexagons to allocate for - */ -int H3_EXPORT(maxPolyfillSize)(const GeoPolygon* geoPolygon, int res) { - // Get the bounding box for the GeoJSON-like struct - BBox bbox; - const Geofence geofence = geoPolygon->geofence; - bboxFromGeofence(&geofence, &bbox); - int numHexagons = bboxHexEstimate(&bbox, res); - // This algorithm assumes that the number of vertices is usually less than - // the number of hexagons, but when it's wrong, this will keep it from - // failing - int totalVerts = geofence.numVerts; - for (int i = 0; i < geoPolygon->numHoles; i++) { - totalVerts += geoPolygon->holes[i].numVerts; - } - if (numHexagons < totalVerts) numHexagons = totalVerts; - // When the polygon is very small, near an icosahedron edge and is an odd - // resolution, the line tracing needs an extra buffer than the estimator - // function provides (but beefing that up to cover causes most situations to - // overallocate memory) - numHexagons += POLYFILL_BUFFER; - return numHexagons; -} - -/** - * polyfill takes a given GeoJSON-like data structure and preallocated, - * zeroed memory, and fills it with the hexagons that are contained by - * the GeoJSON-like data structure. - * - * This implementation traces the GeoJSON geofence(s) in cartesian space with - * hexagons, tests them and their neighbors to be contained by the geofence(s), - * and then any newly found hexagons are used to test again until no new - * hexagons are found. - * - * @param geoPolygon The geofence and holes defining the relevant area - * @param res The Hexagon resolution (0-15) - * @param out The slab of zeroed memory to write to. Assumed to be big enough. - */ -void H3_EXPORT(polyfill)(const GeoPolygon* geoPolygon, int res, H3Index* out) { - // TODO: Eliminate this wrapper with the H3 4.0.0 release - int failure = _polyfillInternal(geoPolygon, res, out); - // The polyfill algorithm can theoretically fail if the allocated memory is - // not large enough for the polygon, but this should be impossible given the - // conservative overestimation of the number of hexagons possible. - // LCOV_EXCL_START - if (failure) { - int numHexagons = H3_EXPORT(maxPolyfillSize)(geoPolygon, res); - for (int i = 0; i < numHexagons; i++) out[i] = H3_NULL; - } - // LCOV_EXCL_STOP -} - -/** - * _getEdgeHexagons takes a given geofence ring (either the main geofence or - * one of the holes) and traces it with hexagons and updates the search and - * found memory blocks. This is used for determining the initial hexagon set - * for the polyfill algorithm to execute on. - * - * @param geofence The geofence (or hole) to be traced - * @param numHexagons The maximum number of hexagons possible for the geofence - * (also the bounds of the search and found arrays) - * @param res The hexagon resolution (0-15) - * @param numSearchHexes The number of hexagons found so far to be searched - * @param search The block of memory containing the hexagons to search from - * @param found The block of memory containing the hexagons found from the - * search - * - * @return An error code if the hash function cannot insert a found hexagon - * into the found array. - */ -int _getEdgeHexagons(const Geofence* geofence, int numHexagons, int res, - int* numSearchHexes, H3Index* search, H3Index* found) { - for (int i = 0; i < geofence->numVerts; i++) { - GeoCoord origin = geofence->verts[i]; - GeoCoord destination = i == geofence->numVerts - 1 - ? geofence->verts[0] - : geofence->verts[i + 1]; - const int numHexesEstimate = - lineHexEstimate(&origin, &destination, res); - for (int j = 0; j < numHexesEstimate; j++) { - GeoCoord interpolate; - interpolate.lat = - (origin.lat * (numHexesEstimate - j) / numHexesEstimate) + - (destination.lat * j / numHexesEstimate); - interpolate.lon = - (origin.lon * (numHexesEstimate - j) / numHexesEstimate) + - (destination.lon * j / numHexesEstimate); - H3Index pointHex = H3_EXPORT(geoToH3)(&interpolate, res); - // A simple hash to store the hexagon, or move to another place if - // needed - int loc = (int)(pointHex % numHexagons); - int loopCount = 0; - while (found[loc] != 0) { - // If this conditional is reached, the `found` memory block is - // too small for the given polygon. This should not happen. - if (loopCount > numHexagons) - return HEX_HASH_OVERFLOW; // LCOV_EXCL_LINE - if (found[loc] == pointHex) - break; // At least two points of the geofence index to the - // same cell - loc = (loc + 1) % numHexagons; - loopCount++; - } - if (found[loc] == pointHex) - continue; // Skip this hex, already exists in the found hash - // Otherwise, set it in the found hash for now - found[loc] = pointHex; - - search[*numSearchHexes] = pointHex; - (*numSearchHexes)++; - } - } - return 0; -} - -/** - * _polyfillInternal traces the provided geoPolygon data structure with hexagons - * and then iteratively searches through these hexagons and their immediate - * neighbors to see if they are contained within the polygon or not. Those that - * are found are added to the out array as well as the found array. Once all - * hexagons to search are checked, the found hexagons become the new search - * array and the found array is wiped and the process repeats until no new - * hexagons can be found. - * - * @param geoPolygon The geofence and holes defining the relevant area - * @param res The Hexagon resolution (0-15) - * @param out The slab of zeroed memory to write to. Assumed to be big enough. - * - * @return An error code if any of the hash operations fails to insert a hexagon - * into an array of memory. - */ -int _polyfillInternal(const GeoPolygon* geoPolygon, int res, H3Index* out) { - // One of the goals of the polyfill algorithm is that two adjacent polygons - // with zero overlap have zero overlapping hexagons. That the hexagons are - // uniquely assigned. There are a few approaches to take here, such as - // deciding based on which polygon has the greatest overlapping area of the - // hexagon, or the most number of contained points on the hexagon (using the - // center point as a tiebreaker). - // - // But if the polygons are convex, both of these more complex algorithms can - // be reduced down to checking whether or not the center of the hexagon is - // contained in the polygon, and so this is the approach that this polyfill - // algorithm will follow, as it's simpler, faster, and the error for concave - // polygons is still minimal (only affecting concave shapes on the order of - // magnitude of the hexagon size or smaller, not impacting larger concave - // shapes) - // - // This first part is identical to the maxPolyfillSize above. - - // Get the bounding boxes for the polygon and any holes - BBox* bboxes = H3_MEMORY(malloc)((geoPolygon->numHoles + 1) * sizeof(BBox)); - assert(bboxes != NULL); - bboxesFromGeoPolygon(geoPolygon, bboxes); - - // Get the estimated number of hexagons and allocate some temporary memory - // for the hexagons - int numHexagons = H3_EXPORT(maxPolyfillSize)(geoPolygon, res); - H3Index* search = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index)); - H3Index* found = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index)); - - // Some metadata for tracking the state of the search and found memory - // blocks - int numSearchHexes = 0; - int numFoundHexes = 0; - - // 1. Trace the hexagons along the polygon defining the outer geofence and - // add them to the search hash. The hexagon containing the geofence point - // may or may not be contained by the geofence (as the hexagon's center - // point may be outside of the boundary.) - const Geofence geofence = geoPolygon->geofence; - int failure = _getEdgeHexagons(&geofence, numHexagons, res, &numSearchHexes, - search, found); - // If this branch is reached, we have exceeded the maximum number of - // hexagons possible and need to clean up the allocated memory. - // LCOV_EXCL_START - if (failure) { - H3_MEMORY(free)(search); - H3_MEMORY(free)(found); - H3_MEMORY(free)(bboxes); - return failure; - } - // LCOV_EXCL_STOP - - // 2. Iterate over all holes, trace the polygons defining the holes with - // hexagons and add to only the search hash. We're going to temporarily use - // the `found` hash to use for dedupe purposes and then re-zero it once - // we're done here, otherwise we'd have to scan the whole set on each insert - // to make sure there's no duplicates, which is very inefficient. - for (int i = 0; i < geoPolygon->numHoles; i++) { - Geofence* hole = &(geoPolygon->holes[i]); - failure = _getEdgeHexagons(hole, numHexagons, res, &numSearchHexes, - search, found); - // If this branch is reached, we have exceeded the maximum number of - // hexagons possible and need to clean up the allocated memory. - // LCOV_EXCL_START - if (failure) { - H3_MEMORY(free)(search); - H3_MEMORY(free)(found); - H3_MEMORY(free)(bboxes); - return failure; - } - // LCOV_EXCL_STOP - } - - // 3. Re-zero the found hash so it can be used in the main loop below - for (int i = 0; i < numHexagons; i++) found[i] = 0; - - // 4. Begin main loop. While the search hash is not empty do the following - while (numSearchHexes > 0) { - // Iterate through all hexagons in the current search hash, then loop - // through all neighbors and test Point-in-Poly, if point-in-poly - // succeeds, add to out and found hashes if not already there. - int currentSearchNum = 0; - int i = 0; - while (currentSearchNum < numSearchHexes) { - H3Index ring[MAX_ONE_RING_SIZE] = {0}; - H3Index searchHex = search[i]; - H3_EXPORT(kRing)(searchHex, 1, ring); - for (int j = 0; j < MAX_ONE_RING_SIZE; j++) { - if (ring[j] == H3_NULL) { - continue; // Skip if this was a pentagon and only had 5 - // neighbors - } - - H3Index hex = ring[j]; - - // A simple hash to store the hexagon, or move to another place - // if needed. This MUST be done before the point-in-poly check - // since that's far more expensive - int loc = (int)(hex % numHexagons); - int loopCount = 0; - while (out[loc] != 0) { - // If this branch is reached, we have exceeded the maximum - // number of hexagons possible and need to clean up the - // allocated memory. - // LCOV_EXCL_START - if (loopCount > numHexagons) { - H3_MEMORY(free)(search); - H3_MEMORY(free)(found); - H3_MEMORY(free)(bboxes); - return -1; - } - // LCOV_EXCL_STOP - if (out[loc] == hex) break; // Skip duplicates found - loc = (loc + 1) % numHexagons; - loopCount++; - } - if (out[loc] == hex) { - continue; // Skip this hex, already exists in the out hash - } - - // Check if the hexagon is in the polygon or not - GeoCoord hexCenter; - H3_EXPORT(h3ToGeo)(hex, &hexCenter); - - // If not, skip - if (!pointInsidePolygon(geoPolygon, bboxes, &hexCenter)) { - continue; - } - - // Otherwise set it in the output array - out[loc] = hex; - - // Set the hexagon in the found hash - found[numFoundHexes] = hex; - numFoundHexes++; - } - currentSearchNum++; - i++; - } - - // Swap the search and found pointers, copy the found hex count to the - // search hex count, and zero everything related to the found memory. - H3Index* temp = search; - search = found; - found = temp; - for (int j = 0; j < numSearchHexes; j++) found[j] = 0; - numSearchHexes = numFoundHexes; - numFoundHexes = 0; - // Repeat until no new hexagons are found - } - // The out memory structure should be complete, end it here - H3_MEMORY(free)(bboxes); - H3_MEMORY(free)(search); - H3_MEMORY(free)(found); - return 0; -} - -/** - * Internal: Create a vertex graph from a set of hexagons. It is the - * responsibility of the caller to call destroyVertexGraph on the populated - * graph, otherwise the memory in the graph nodes will not be freed. - * @private - * @param h3Set Set of hexagons - * @param numHexes Number of hexagons in the set - * @param graph Output graph - */ -void h3SetToVertexGraph(const H3Index* h3Set, const int numHexes, - VertexGraph* graph) { - GeoBoundary vertices; - GeoCoord* fromVtx; - GeoCoord* toVtx; - VertexNode* edge; - if (numHexes < 1) { - // We still need to init the graph, or calls to destroyVertexGraph will - // fail - initVertexGraph(graph, 0, 0); - return; - } - int res = H3_GET_RESOLUTION(h3Set[0]); - const int minBuckets = 6; - // TODO: Better way to calculate/guess? - int numBuckets = numHexes > minBuckets ? numHexes : minBuckets; - initVertexGraph(graph, numBuckets, res); - // Iterate through every hexagon - for (int i = 0; i < numHexes; i++) { - H3_EXPORT(h3ToGeoBoundary)(h3Set[i], &vertices); - // iterate through every edge - for (int j = 0; j < vertices.numVerts; j++) { - fromVtx = &vertices.verts[j]; - toVtx = &vertices.verts[(j + 1) % vertices.numVerts]; - // If we've seen this edge already, it will be reversed - edge = findNodeForEdge(graph, toVtx, fromVtx); - if (edge != NULL) { - // If we've seen it, drop it. No edge is shared by more than 2 - // hexagons, so we'll never see it again. - removeVertexNode(graph, edge); - } else { - // Add a new node for this edge - addVertexNode(graph, fromVtx, toVtx); - } - } - } -} - -/** - * Internal: Create a LinkedGeoPolygon from a vertex graph. It is the - * responsibility of the caller to call destroyLinkedPolygon on the populated - * linked geo structure, or the memory for that structure will not be freed. - * @private - * @param graph Input graph - * @param out Output polygon - */ -void _vertexGraphToLinkedGeo(VertexGraph* graph, LinkedGeoPolygon* out) { - *out = (LinkedGeoPolygon){0}; - LinkedGeoLoop* loop; - VertexNode* edge; - GeoCoord nextVtx; - // Find the next unused entry point - while ((edge = firstVertexNode(graph)) != NULL) { - loop = addNewLinkedLoop(out); - // Walk the graph to get the outline - do { - addLinkedCoord(loop, &edge->from); - nextVtx = edge->to; - // Remove frees the node, so we can't use edge after this - removeVertexNode(graph, edge); - edge = findNodeForVertex(graph, &nextVtx); - } while (edge); - } -} - -/** - * Create a LinkedGeoPolygon 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 the responsibility of the caller to call destroyLinkedPolygon on the - * populated linked geo structure, or the memory for that structure will - * not be freed. - * - * 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. - * - * @param h3Set Set of hexagons - * @param numHexes Number of hexagons in set - * @param out Output polygon - */ -void H3_EXPORT(h3SetToLinkedGeo)(const H3Index* h3Set, const int numHexes, - LinkedGeoPolygon* out) { - VertexGraph graph; - h3SetToVertexGraph(h3Set, numHexes, &graph); - _vertexGraphToLinkedGeo(&graph, out); - // TODO: The return value, possibly indicating an error, is discarded here - - // we should use this when we update the API to return a value - normalizeMultiPolygon(out); - destroyVertexGraph(&graph); -} diff --git a/v3/h3_algos.h b/v3/h3_algos.h deleted file mode 100644 index d5d605f..0000000 --- a/v3/h3_algos.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2016-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. - */ -/** @file algos.h - * @brief Hexagon grid algorithms - */ - -#ifndef ALGOS_H -#define ALGOS_H - -#include "h3_bbox.h" -#include "h3_coordijk.h" -#include "h3_h3api.h" -#include "h3_linkedGeo.h" -#include "h3_vertexGraph.h" - -// neighbor along the ijk coordinate system of the current face, rotated -H3Index h3NeighborRotations(H3Index origin, Direction dir, int* rotations); - -// k-ring implementation -void _kRingInternal(H3Index origin, int k, H3Index* out, int* distances, - int maxIdx, int curK); - -// Create a vertex graph from a set of hexagons -void h3SetToVertexGraph(const H3Index* h3Set, const int numHexes, - VertexGraph* out); - -// Create a LinkedGeoPolygon from a vertex graph -void _vertexGraphToLinkedGeo(VertexGraph* graph, LinkedGeoPolygon* out); - -// Internal function for polyfill that traces a geofence with hexagons of a -// specific size -int _getEdgeHexagons(const Geofence* geofence, int numHexagons, int res, - int* numSearchHexes, H3Index* search, H3Index* found); - -// The polyfill algorithm. Separated out because it can theoretically fail -int _polyfillInternal(const GeoPolygon* geoPolygon, int res, H3Index* out); - -#endif diff --git a/v3/h3_baseCells.c b/v3/h3_baseCells.c deleted file mode 100644 index f20faa1..0000000 --- a/v3/h3_baseCells.c +++ /dev/null @@ -1,933 +0,0 @@ -/* - * Copyright 2016-2020 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. - */ -/** @file baseCells.c - * @brief Base cell related lookup tables and access functions. - */ - -#include "h3_baseCells.h" - -#include "h3_h3Index.h" - -/** @struct BaseCellRotation - * @brief base cell at a given ijk and required rotations into its system - */ -typedef struct { - int baseCell; ///< base cell number - int ccwRot60; ///< number of ccw 60 degree rotations relative to current - /// face -} BaseCellRotation; - -/** @brief Neighboring base cell ID in each IJK direction. - * - * For each base cell, for each direction, the neighboring base - * cell ID is given. 127 indicates there is no neighbor in that direction. - */ -const int baseCellNeighbors[NUM_BASE_CELLS][7] = { - {0, 1, 5, 2, 4, 3, 8}, // base cell 0 - {1, 7, 6, 9, 0, 3, 2}, // base cell 1 - {2, 6, 10, 11, 0, 1, 5}, // base cell 2 - {3, 13, 1, 7, 4, 12, 0}, // base cell 3 - {4, INVALID_BASE_CELL, 15, 8, 3, 0, 12}, // base cell 4 (pentagon) - {5, 2, 18, 10, 8, 0, 16}, // base cell 5 - {6, 14, 11, 17, 1, 9, 2}, // base cell 6 - {7, 21, 9, 19, 3, 13, 1}, // base cell 7 - {8, 5, 22, 16, 4, 0, 15}, // base cell 8 - {9, 19, 14, 20, 1, 7, 6}, // base cell 9 - {10, 11, 24, 23, 5, 2, 18}, // base cell 10 - {11, 17, 23, 25, 2, 6, 10}, // base cell 11 - {12, 28, 13, 26, 4, 15, 3}, // base cell 12 - {13, 26, 21, 29, 3, 12, 7}, // base cell 13 - {14, INVALID_BASE_CELL, 17, 27, 9, 20, 6}, // base cell 14 (pentagon) - {15, 22, 28, 31, 4, 8, 12}, // base cell 15 - {16, 18, 33, 30, 8, 5, 22}, // base cell 16 - {17, 11, 14, 6, 35, 25, 27}, // base cell 17 - {18, 24, 30, 32, 5, 10, 16}, // base cell 18 - {19, 34, 20, 36, 7, 21, 9}, // base cell 19 - {20, 14, 19, 9, 40, 27, 36}, // base cell 20 - {21, 38, 19, 34, 13, 29, 7}, // base cell 21 - {22, 16, 41, 33, 15, 8, 31}, // base cell 22 - {23, 24, 11, 10, 39, 37, 25}, // base cell 23 - {24, INVALID_BASE_CELL, 32, 37, 10, 23, 18}, // base cell 24 (pentagon) - {25, 23, 17, 11, 45, 39, 35}, // base cell 25 - {26, 42, 29, 43, 12, 28, 13}, // base cell 26 - {27, 40, 35, 46, 14, 20, 17}, // base cell 27 - {28, 31, 42, 44, 12, 15, 26}, // base cell 28 - {29, 43, 38, 47, 13, 26, 21}, // base cell 29 - {30, 32, 48, 50, 16, 18, 33}, // base cell 30 - {31, 41, 44, 53, 15, 22, 28}, // base cell 31 - {32, 30, 24, 18, 52, 50, 37}, // base cell 32 - {33, 30, 49, 48, 22, 16, 41}, // base cell 33 - {34, 19, 38, 21, 54, 36, 51}, // base cell 34 - {35, 46, 45, 56, 17, 27, 25}, // base cell 35 - {36, 20, 34, 19, 55, 40, 54}, // base cell 36 - {37, 39, 52, 57, 24, 23, 32}, // base cell 37 - {38, INVALID_BASE_CELL, 34, 51, 29, 47, 21}, // base cell 38 (pentagon) - {39, 37, 25, 23, 59, 57, 45}, // base cell 39 - {40, 27, 36, 20, 60, 46, 55}, // base cell 40 - {41, 49, 53, 61, 22, 33, 31}, // base cell 41 - {42, 58, 43, 62, 28, 44, 26}, // base cell 42 - {43, 62, 47, 64, 26, 42, 29}, // base cell 43 - {44, 53, 58, 65, 28, 31, 42}, // base cell 44 - {45, 39, 35, 25, 63, 59, 56}, // base cell 45 - {46, 60, 56, 68, 27, 40, 35}, // base cell 46 - {47, 38, 43, 29, 69, 51, 64}, // base cell 47 - {48, 49, 30, 33, 67, 66, 50}, // base cell 48 - {49, INVALID_BASE_CELL, 61, 66, 33, 48, 41}, // base cell 49 (pentagon) - {50, 48, 32, 30, 70, 67, 52}, // base cell 50 - {51, 69, 54, 71, 38, 47, 34}, // base cell 51 - {52, 57, 70, 74, 32, 37, 50}, // base cell 52 - {53, 61, 65, 75, 31, 41, 44}, // base cell 53 - {54, 71, 55, 73, 34, 51, 36}, // base cell 54 - {55, 40, 54, 36, 72, 60, 73}, // base cell 55 - {56, 68, 63, 77, 35, 46, 45}, // base cell 56 - {57, 59, 74, 78, 37, 39, 52}, // base cell 57 - {58, INVALID_BASE_CELL, 62, 76, 44, 65, 42}, // base cell 58 (pentagon) - {59, 63, 78, 79, 39, 45, 57}, // base cell 59 - {60, 72, 68, 80, 40, 55, 46}, // base cell 60 - {61, 53, 49, 41, 81, 75, 66}, // base cell 61 - {62, 43, 58, 42, 82, 64, 76}, // base cell 62 - {63, INVALID_BASE_CELL, 56, 45, 79, 59, 77}, // base cell 63 (pentagon) - {64, 47, 62, 43, 84, 69, 82}, // base cell 64 - {65, 58, 53, 44, 86, 76, 75}, // base cell 65 - {66, 67, 81, 85, 49, 48, 61}, // base cell 66 - {67, 66, 50, 48, 87, 85, 70}, // base cell 67 - {68, 56, 60, 46, 90, 77, 80}, // base cell 68 - {69, 51, 64, 47, 89, 71, 84}, // base cell 69 - {70, 67, 52, 50, 83, 87, 74}, // base cell 70 - {71, 89, 73, 91, 51, 69, 54}, // base cell 71 - {72, INVALID_BASE_CELL, 73, 55, 80, 60, 88}, // base cell 72 (pentagon) - {73, 91, 72, 88, 54, 71, 55}, // base cell 73 - {74, 78, 83, 92, 52, 57, 70}, // base cell 74 - {75, 65, 61, 53, 94, 86, 81}, // base cell 75 - {76, 86, 82, 96, 58, 65, 62}, // base cell 76 - {77, 63, 68, 56, 93, 79, 90}, // base cell 77 - {78, 74, 59, 57, 95, 92, 79}, // base cell 78 - {79, 78, 63, 59, 93, 95, 77}, // base cell 79 - {80, 68, 72, 60, 99, 90, 88}, // base cell 80 - {81, 85, 94, 101, 61, 66, 75}, // base cell 81 - {82, 96, 84, 98, 62, 76, 64}, // base cell 82 - {83, INVALID_BASE_CELL, 74, 70, 100, 87, 92}, // base cell 83 (pentagon) - {84, 69, 82, 64, 97, 89, 98}, // base cell 84 - {85, 87, 101, 102, 66, 67, 81}, // base cell 85 - {86, 76, 75, 65, 104, 96, 94}, // base cell 86 - {87, 83, 102, 100, 67, 70, 85}, // base cell 87 - {88, 72, 91, 73, 99, 80, 105}, // base cell 88 - {89, 97, 91, 103, 69, 84, 71}, // base cell 89 - {90, 77, 80, 68, 106, 93, 99}, // base cell 90 - {91, 73, 89, 71, 105, 88, 103}, // base cell 91 - {92, 83, 78, 74, 108, 100, 95}, // base cell 92 - {93, 79, 90, 77, 109, 95, 106}, // base cell 93 - {94, 86, 81, 75, 107, 104, 101}, // base cell 94 - {95, 92, 79, 78, 109, 108, 93}, // base cell 95 - {96, 104, 98, 110, 76, 86, 82}, // base cell 96 - {97, INVALID_BASE_CELL, 98, 84, 103, 89, 111}, // base cell 97 (pentagon) - {98, 110, 97, 111, 82, 96, 84}, // base cell 98 - {99, 80, 105, 88, 106, 90, 113}, // base cell 99 - {100, 102, 83, 87, 108, 114, 92}, // base cell 100 - {101, 102, 107, 112, 81, 85, 94}, // base cell 101 - {102, 101, 87, 85, 114, 112, 100}, // base cell 102 - {103, 91, 97, 89, 116, 105, 111}, // base cell 103 - {104, 107, 110, 115, 86, 94, 96}, // base cell 104 - {105, 88, 103, 91, 113, 99, 116}, // base cell 105 - {106, 93, 99, 90, 117, 109, 113}, // base cell 106 - {107, INVALID_BASE_CELL, 101, 94, 115, 104, - 112}, // base cell 107 (pentagon) - {108, 100, 95, 92, 118, 114, 109}, // base cell 108 - {109, 108, 93, 95, 117, 118, 106}, // base cell 109 - {110, 98, 104, 96, 119, 111, 115}, // base cell 110 - {111, 97, 110, 98, 116, 103, 119}, // base cell 111 - {112, 107, 102, 101, 120, 115, 114}, // base cell 112 - {113, 99, 116, 105, 117, 106, 121}, // base cell 113 - {114, 112, 100, 102, 118, 120, 108}, // base cell 114 - {115, 110, 107, 104, 120, 119, 112}, // base cell 115 - {116, 103, 119, 111, 113, 105, 121}, // base cell 116 - {117, INVALID_BASE_CELL, 109, 118, 113, 121, - 106}, // base cell 117 (pentagon) - {118, 120, 108, 114, 117, 121, 109}, // base cell 118 - {119, 111, 115, 110, 121, 116, 120}, // base cell 119 - {120, 115, 114, 112, 121, 119, 118}, // base cell 120 - {121, 116, 120, 119, 117, 113, 118}, // base cell 121 -}; - -/** @brief Neighboring base cell rotations in each IJK direction. - * - * For each base cell, for each direction, the number of 60 degree - * CCW rotations to the coordinate system of the neighbor is given. - * -1 indicates there is no neighbor in that direction. - */ -const int baseCellNeighbor60CCWRots[NUM_BASE_CELLS][7] = { - {0, 5, 0, 0, 1, 5, 1}, // base cell 0 - {0, 0, 1, 0, 1, 0, 1}, // base cell 1 - {0, 0, 0, 0, 0, 5, 0}, // base cell 2 - {0, 5, 0, 0, 2, 5, 1}, // base cell 3 - {0, -1, 1, 0, 3, 4, 2}, // base cell 4 (pentagon) - {0, 0, 1, 0, 1, 0, 1}, // base cell 5 - {0, 0, 0, 3, 5, 5, 0}, // base cell 6 - {0, 0, 0, 0, 0, 5, 0}, // base cell 7 - {0, 5, 0, 0, 0, 5, 1}, // base cell 8 - {0, 0, 1, 3, 0, 0, 1}, // base cell 9 - {0, 0, 1, 3, 0, 0, 1}, // base cell 10 - {0, 3, 3, 3, 0, 0, 0}, // base cell 11 - {0, 5, 0, 0, 3, 5, 1}, // base cell 12 - {0, 0, 1, 0, 1, 0, 1}, // base cell 13 - {0, -1, 3, 0, 5, 2, 0}, // base cell 14 (pentagon) - {0, 5, 0, 0, 4, 5, 1}, // base cell 15 - {0, 0, 0, 0, 0, 5, 0}, // base cell 16 - {0, 3, 3, 3, 3, 0, 3}, // base cell 17 - {0, 0, 0, 3, 5, 5, 0}, // base cell 18 - {0, 3, 3, 3, 0, 0, 0}, // base cell 19 - {0, 3, 3, 3, 0, 3, 0}, // base cell 20 - {0, 0, 0, 3, 5, 5, 0}, // base cell 21 - {0, 0, 1, 0, 1, 0, 1}, // base cell 22 - {0, 3, 3, 3, 0, 3, 0}, // base cell 23 - {0, -1, 3, 0, 5, 2, 0}, // base cell 24 (pentagon) - {0, 0, 0, 3, 0, 0, 3}, // base cell 25 - {0, 0, 0, 0, 0, 5, 0}, // base cell 26 - {0, 3, 0, 0, 0, 3, 3}, // base cell 27 - {0, 0, 1, 0, 1, 0, 1}, // base cell 28 - {0, 0, 1, 3, 0, 0, 1}, // base cell 29 - {0, 3, 3, 3, 0, 0, 0}, // base cell 30 - {0, 0, 0, 0, 0, 5, 0}, // base cell 31 - {0, 3, 3, 3, 3, 0, 3}, // base cell 32 - {0, 0, 1, 3, 0, 0, 1}, // base cell 33 - {0, 3, 3, 3, 3, 0, 3}, // base cell 34 - {0, 0, 3, 0, 3, 0, 3}, // base cell 35 - {0, 0, 0, 3, 0, 0, 3}, // base cell 36 - {0, 3, 0, 0, 0, 3, 3}, // base cell 37 - {0, -1, 3, 0, 5, 2, 0}, // base cell 38 (pentagon) - {0, 3, 0, 0, 3, 3, 0}, // base cell 39 - {0, 3, 0, 0, 3, 3, 0}, // base cell 40 - {0, 0, 0, 3, 5, 5, 0}, // base cell 41 - {0, 0, 0, 3, 5, 5, 0}, // base cell 42 - {0, 3, 3, 3, 0, 0, 0}, // base cell 43 - {0, 0, 1, 3, 0, 0, 1}, // base cell 44 - {0, 0, 3, 0, 0, 3, 3}, // base cell 45 - {0, 0, 0, 3, 0, 3, 0}, // base cell 46 - {0, 3, 3, 3, 0, 3, 0}, // base cell 47 - {0, 3, 3, 3, 0, 3, 0}, // base cell 48 - {0, -1, 3, 0, 5, 2, 0}, // base cell 49 (pentagon) - {0, 0, 0, 3, 0, 0, 3}, // base cell 50 - {0, 3, 0, 0, 0, 3, 3}, // base cell 51 - {0, 0, 3, 0, 3, 0, 3}, // base cell 52 - {0, 3, 3, 3, 0, 0, 0}, // base cell 53 - {0, 0, 3, 0, 3, 0, 3}, // base cell 54 - {0, 0, 3, 0, 0, 3, 3}, // base cell 55 - {0, 3, 3, 3, 0, 0, 3}, // base cell 56 - {0, 0, 0, 3, 0, 3, 0}, // base cell 57 - {0, -1, 3, 0, 5, 2, 0}, // base cell 58 (pentagon) - {0, 3, 3, 3, 3, 3, 0}, // base cell 59 - {0, 3, 3, 3, 3, 3, 0}, // base cell 60 - {0, 3, 3, 3, 3, 0, 3}, // base cell 61 - {0, 3, 3, 3, 3, 0, 3}, // base cell 62 - {0, -1, 3, 0, 5, 2, 0}, // base cell 63 (pentagon) - {0, 0, 0, 3, 0, 0, 3}, // base cell 64 - {0, 3, 3, 3, 0, 3, 0}, // base cell 65 - {0, 3, 0, 0, 0, 3, 3}, // base cell 66 - {0, 3, 0, 0, 3, 3, 0}, // base cell 67 - {0, 3, 3, 3, 0, 0, 0}, // base cell 68 - {0, 3, 0, 0, 3, 3, 0}, // base cell 69 - {0, 0, 3, 0, 0, 3, 3}, // base cell 70 - {0, 0, 0, 3, 0, 3, 0}, // base cell 71 - {0, -1, 3, 0, 5, 2, 0}, // base cell 72 (pentagon) - {0, 3, 3, 3, 0, 0, 3}, // base cell 73 - {0, 3, 3, 3, 0, 0, 3}, // base cell 74 - {0, 0, 0, 3, 0, 0, 3}, // base cell 75 - {0, 3, 0, 0, 0, 3, 3}, // base cell 76 - {0, 0, 0, 3, 0, 5, 0}, // base cell 77 - {0, 3, 3, 3, 0, 0, 0}, // base cell 78 - {0, 0, 1, 3, 1, 0, 1}, // base cell 79 - {0, 0, 1, 3, 1, 0, 1}, // base cell 80 - {0, 0, 3, 0, 3, 0, 3}, // base cell 81 - {0, 0, 3, 0, 3, 0, 3}, // base cell 82 - {0, -1, 3, 0, 5, 2, 0}, // base cell 83 (pentagon) - {0, 0, 3, 0, 0, 3, 3}, // base cell 84 - {0, 0, 0, 3, 0, 3, 0}, // base cell 85 - {0, 3, 0, 0, 3, 3, 0}, // base cell 86 - {0, 3, 3, 3, 3, 3, 0}, // base cell 87 - {0, 0, 0, 3, 0, 5, 0}, // base cell 88 - {0, 3, 3, 3, 3, 3, 0}, // base cell 89 - {0, 0, 0, 0, 0, 0, 1}, // base cell 90 - {0, 3, 3, 3, 0, 0, 0}, // base cell 91 - {0, 0, 0, 3, 0, 5, 0}, // base cell 92 - {0, 5, 0, 0, 5, 5, 0}, // base cell 93 - {0, 0, 3, 0, 0, 3, 3}, // base cell 94 - {0, 0, 0, 0, 0, 0, 1}, // base cell 95 - {0, 0, 0, 3, 0, 3, 0}, // base cell 96 - {0, -1, 3, 0, 5, 2, 0}, // base cell 97 (pentagon) - {0, 3, 3, 3, 0, 0, 3}, // base cell 98 - {0, 5, 0, 0, 5, 5, 0}, // base cell 99 - {0, 0, 1, 3, 1, 0, 1}, // base cell 100 - {0, 3, 3, 3, 0, 0, 3}, // base cell 101 - {0, 3, 3, 3, 0, 0, 0}, // base cell 102 - {0, 0, 1, 3, 1, 0, 1}, // base cell 103 - {0, 3, 3, 3, 3, 3, 0}, // base cell 104 - {0, 0, 0, 0, 0, 0, 1}, // base cell 105 - {0, 0, 1, 0, 3, 5, 1}, // base cell 106 - {0, -1, 3, 0, 5, 2, 0}, // base cell 107 (pentagon) - {0, 5, 0, 0, 5, 5, 0}, // base cell 108 - {0, 0, 1, 0, 4, 5, 1}, // base cell 109 - {0, 3, 3, 3, 0, 0, 0}, // base cell 110 - {0, 0, 0, 3, 0, 5, 0}, // base cell 111 - {0, 0, 0, 3, 0, 5, 0}, // base cell 112 - {0, 0, 1, 0, 2, 5, 1}, // base cell 113 - {0, 0, 0, 0, 0, 0, 1}, // base cell 114 - {0, 0, 1, 3, 1, 0, 1}, // base cell 115 - {0, 5, 0, 0, 5, 5, 0}, // base cell 116 - {0, -1, 1, 0, 3, 4, 2}, // base cell 117 (pentagon) - {0, 0, 1, 0, 0, 5, 1}, // base cell 118 - {0, 0, 0, 0, 0, 0, 1}, // base cell 119 - {0, 5, 0, 0, 5, 5, 0}, // base cell 120 - {0, 0, 1, 0, 1, 5, 1}, // base cell 121 -}; - -/** @brief Resolution 0 base cell lookup table for each face. - * - * Given the face number and a resolution 0 ijk+ coordinate in that face's - * face-centered ijk coordinate system, gives the base cell located at that - * coordinate and the number of 60 ccw rotations to rotate into that base - * cell's orientation. - * - * Valid lookup coordinates are from (0, 0, 0) to (2, 2, 2). - * - * This table can be accessed using the functions `_faceIjkToBaseCell` and - * `_faceIjkToBaseCellCCWrot60` - */ -static const BaseCellRotation faceIjkBaseCells[NUM_ICOSA_FACES][3][3][3] = { - {// face 0 - { - // i 0 - {{16, 0}, {18, 0}, {24, 0}}, // j 0 - {{33, 0}, {30, 0}, {32, 3}}, // j 1 - {{49, 1}, {48, 3}, {50, 3}} // j 2 - }, - { - // i 1 - {{8, 0}, {5, 5}, {10, 5}}, // j 0 - {{22, 0}, {16, 0}, {18, 0}}, // j 1 - {{41, 1}, {33, 0}, {30, 0}} // j 2 - }, - { - // i 2 - {{4, 0}, {0, 5}, {2, 5}}, // j 0 - {{15, 1}, {8, 0}, {5, 5}}, // j 1 - {{31, 1}, {22, 0}, {16, 0}} // j 2 - }}, - {// face 1 - { - // i 0 - {{2, 0}, {6, 0}, {14, 0}}, // j 0 - {{10, 0}, {11, 0}, {17, 3}}, // j 1 - {{24, 1}, {23, 3}, {25, 3}} // j 2 - }, - { - // i 1 - {{0, 0}, {1, 5}, {9, 5}}, // j 0 - {{5, 0}, {2, 0}, {6, 0}}, // j 1 - {{18, 1}, {10, 0}, {11, 0}} // j 2 - }, - { - // i 2 - {{4, 1}, {3, 5}, {7, 5}}, // j 0 - {{8, 1}, {0, 0}, {1, 5}}, // j 1 - {{16, 1}, {5, 0}, {2, 0}} // j 2 - }}, - {// face 2 - { - // i 0 - {{7, 0}, {21, 0}, {38, 0}}, // j 0 - {{9, 0}, {19, 0}, {34, 3}}, // j 1 - {{14, 1}, {20, 3}, {36, 3}} // j 2 - }, - { - // i 1 - {{3, 0}, {13, 5}, {29, 5}}, // j 0 - {{1, 0}, {7, 0}, {21, 0}}, // j 1 - {{6, 1}, {9, 0}, {19, 0}} // j 2 - }, - { - // i 2 - {{4, 2}, {12, 5}, {26, 5}}, // j 0 - {{0, 1}, {3, 0}, {13, 5}}, // j 1 - {{2, 1}, {1, 0}, {7, 0}} // j 2 - }}, - {// face 3 - { - // i 0 - {{26, 0}, {42, 0}, {58, 0}}, // j 0 - {{29, 0}, {43, 0}, {62, 3}}, // j 1 - {{38, 1}, {47, 3}, {64, 3}} // j 2 - }, - { - // i 1 - {{12, 0}, {28, 5}, {44, 5}}, // j 0 - {{13, 0}, {26, 0}, {42, 0}}, // j 1 - {{21, 1}, {29, 0}, {43, 0}} // j 2 - }, - { - // i 2 - {{4, 3}, {15, 5}, {31, 5}}, // j 0 - {{3, 1}, {12, 0}, {28, 5}}, // j 1 - {{7, 1}, {13, 0}, {26, 0}} // j 2 - }}, - {// face 4 - { - // i 0 - {{31, 0}, {41, 0}, {49, 0}}, // j 0 - {{44, 0}, {53, 0}, {61, 3}}, // j 1 - {{58, 1}, {65, 3}, {75, 3}} // j 2 - }, - { - // i 1 - {{15, 0}, {22, 5}, {33, 5}}, // j 0 - {{28, 0}, {31, 0}, {41, 0}}, // j 1 - {{42, 1}, {44, 0}, {53, 0}} // j 2 - }, - { - // i 2 - {{4, 4}, {8, 5}, {16, 5}}, // j 0 - {{12, 1}, {15, 0}, {22, 5}}, // j 1 - {{26, 1}, {28, 0}, {31, 0}} // j 2 - }}, - {// face 5 - { - // i 0 - {{50, 0}, {48, 0}, {49, 3}}, // j 0 - {{32, 0}, {30, 3}, {33, 3}}, // j 1 - {{24, 3}, {18, 3}, {16, 3}} // j 2 - }, - { - // i 1 - {{70, 0}, {67, 0}, {66, 3}}, // j 0 - {{52, 3}, {50, 0}, {48, 0}}, // j 1 - {{37, 3}, {32, 0}, {30, 3}} // j 2 - }, - { - // i 2 - {{83, 0}, {87, 3}, {85, 3}}, // j 0 - {{74, 3}, {70, 0}, {67, 0}}, // j 1 - {{57, 1}, {52, 3}, {50, 0}} // j 2 - }}, - {// face 6 - { - // i 0 - {{25, 0}, {23, 0}, {24, 3}}, // j 0 - {{17, 0}, {11, 3}, {10, 3}}, // j 1 - {{14, 3}, {6, 3}, {2, 3}} // j 2 - }, - { - // i 1 - {{45, 0}, {39, 0}, {37, 3}}, // j 0 - {{35, 3}, {25, 0}, {23, 0}}, // j 1 - {{27, 3}, {17, 0}, {11, 3}} // j 2 - }, - { - // i 2 - {{63, 0}, {59, 3}, {57, 3}}, // j 0 - {{56, 3}, {45, 0}, {39, 0}}, // j 1 - {{46, 3}, {35, 3}, {25, 0}} // j 2 - }}, - {// face 7 - { - // i 0 - {{36, 0}, {20, 0}, {14, 3}}, // j 0 - {{34, 0}, {19, 3}, {9, 3}}, // j 1 - {{38, 3}, {21, 3}, {7, 3}} // j 2 - }, - { - // i 1 - {{55, 0}, {40, 0}, {27, 3}}, // j 0 - {{54, 3}, {36, 0}, {20, 0}}, // j 1 - {{51, 3}, {34, 0}, {19, 3}} // j 2 - }, - { - // i 2 - {{72, 0}, {60, 3}, {46, 3}}, // j 0 - {{73, 3}, {55, 0}, {40, 0}}, // j 1 - {{71, 3}, {54, 3}, {36, 0}} // j 2 - }}, - {// face 8 - { - // i 0 - {{64, 0}, {47, 0}, {38, 3}}, // j 0 - {{62, 0}, {43, 3}, {29, 3}}, // j 1 - {{58, 3}, {42, 3}, {26, 3}} // j 2 - }, - { - // i 1 - {{84, 0}, {69, 0}, {51, 3}}, // j 0 - {{82, 3}, {64, 0}, {47, 0}}, // j 1 - {{76, 3}, {62, 0}, {43, 3}} // j 2 - }, - { - // i 2 - {{97, 0}, {89, 3}, {71, 3}}, // j 0 - {{98, 3}, {84, 0}, {69, 0}}, // j 1 - {{96, 3}, {82, 3}, {64, 0}} // j 2 - }}, - {// face 9 - { - // i 0 - {{75, 0}, {65, 0}, {58, 3}}, // j 0 - {{61, 0}, {53, 3}, {44, 3}}, // j 1 - {{49, 3}, {41, 3}, {31, 3}} // j 2 - }, - { - // i 1 - {{94, 0}, {86, 0}, {76, 3}}, // j 0 - {{81, 3}, {75, 0}, {65, 0}}, // j 1 - {{66, 3}, {61, 0}, {53, 3}} // j 2 - }, - { - // i 2 - {{107, 0}, {104, 3}, {96, 3}}, // j 0 - {{101, 3}, {94, 0}, {86, 0}}, // j 1 - {{85, 3}, {81, 3}, {75, 0}} // j 2 - }}, - {// face 10 - { - // i 0 - {{57, 0}, {59, 0}, {63, 3}}, // j 0 - {{74, 0}, {78, 3}, {79, 3}}, // j 1 - {{83, 3}, {92, 3}, {95, 3}} // j 2 - }, - { - // i 1 - {{37, 0}, {39, 3}, {45, 3}}, // j 0 - {{52, 0}, {57, 0}, {59, 0}}, // j 1 - {{70, 3}, {74, 0}, {78, 3}} // j 2 - }, - { - // i 2 - {{24, 0}, {23, 3}, {25, 3}}, // j 0 - {{32, 3}, {37, 0}, {39, 3}}, // j 1 - {{50, 3}, {52, 0}, {57, 0}} // j 2 - }}, - {// face 11 - { - // i 0 - {{46, 0}, {60, 0}, {72, 3}}, // j 0 - {{56, 0}, {68, 3}, {80, 3}}, // j 1 - {{63, 3}, {77, 3}, {90, 3}} // j 2 - }, - { - // i 1 - {{27, 0}, {40, 3}, {55, 3}}, // j 0 - {{35, 0}, {46, 0}, {60, 0}}, // j 1 - {{45, 3}, {56, 0}, {68, 3}} // j 2 - }, - { - // i 2 - {{14, 0}, {20, 3}, {36, 3}}, // j 0 - {{17, 3}, {27, 0}, {40, 3}}, // j 1 - {{25, 3}, {35, 0}, {46, 0}} // j 2 - }}, - {// face 12 - { - // i 0 - {{71, 0}, {89, 0}, {97, 3}}, // j 0 - {{73, 0}, {91, 3}, {103, 3}}, // j 1 - {{72, 3}, {88, 3}, {105, 3}} // j 2 - }, - { - // i 1 - {{51, 0}, {69, 3}, {84, 3}}, // j 0 - {{54, 0}, {71, 0}, {89, 0}}, // j 1 - {{55, 3}, {73, 0}, {91, 3}} // j 2 - }, - { - // i 2 - {{38, 0}, {47, 3}, {64, 3}}, // j 0 - {{34, 3}, {51, 0}, {69, 3}}, // j 1 - {{36, 3}, {54, 0}, {71, 0}} // j 2 - }}, - {// face 13 - { - // i 0 - {{96, 0}, {104, 0}, {107, 3}}, // j 0 - {{98, 0}, {110, 3}, {115, 3}}, // j 1 - {{97, 3}, {111, 3}, {119, 3}} // j 2 - }, - { - // i 1 - {{76, 0}, {86, 3}, {94, 3}}, // j 0 - {{82, 0}, {96, 0}, {104, 0}}, // j 1 - {{84, 3}, {98, 0}, {110, 3}} // j 2 - }, - { - // i 2 - {{58, 0}, {65, 3}, {75, 3}}, // j 0 - {{62, 3}, {76, 0}, {86, 3}}, // j 1 - {{64, 3}, {82, 0}, {96, 0}} // j 2 - }}, - {// face 14 - { - // i 0 - {{85, 0}, {87, 0}, {83, 3}}, // j 0 - {{101, 0}, {102, 3}, {100, 3}}, // j 1 - {{107, 3}, {112, 3}, {114, 3}} // j 2 - }, - { - // i 1 - {{66, 0}, {67, 3}, {70, 3}}, // j 0 - {{81, 0}, {85, 0}, {87, 0}}, // j 1 - {{94, 3}, {101, 0}, {102, 3}} // j 2 - }, - { - // i 2 - {{49, 0}, {48, 3}, {50, 3}}, // j 0 - {{61, 3}, {66, 0}, {67, 3}}, // j 1 - {{75, 3}, {81, 0}, {85, 0}} // j 2 - }}, - {// face 15 - { - // i 0 - {{95, 0}, {92, 0}, {83, 0}}, // j 0 - {{79, 0}, {78, 0}, {74, 3}}, // j 1 - {{63, 1}, {59, 3}, {57, 3}} // j 2 - }, - { - // i 1 - {{109, 0}, {108, 0}, {100, 5}}, // j 0 - {{93, 1}, {95, 0}, {92, 0}}, // j 1 - {{77, 1}, {79, 0}, {78, 0}} // j 2 - }, - { - // i 2 - {{117, 4}, {118, 5}, {114, 5}}, // j 0 - {{106, 1}, {109, 0}, {108, 0}}, // j 1 - {{90, 1}, {93, 1}, {95, 0}} // j 2 - }}, - {// face 16 - { - // i 0 - {{90, 0}, {77, 0}, {63, 0}}, // j 0 - {{80, 0}, {68, 0}, {56, 3}}, // j 1 - {{72, 1}, {60, 3}, {46, 3}} // j 2 - }, - { - // i 1 - {{106, 0}, {93, 0}, {79, 5}}, // j 0 - {{99, 1}, {90, 0}, {77, 0}}, // j 1 - {{88, 1}, {80, 0}, {68, 0}} // j 2 - }, - { - // i 2 - {{117, 3}, {109, 5}, {95, 5}}, // j 0 - {{113, 1}, {106, 0}, {93, 0}}, // j 1 - {{105, 1}, {99, 1}, {90, 0}} // j 2 - }}, - {// face 17 - { - // i 0 - {{105, 0}, {88, 0}, {72, 0}}, // j 0 - {{103, 0}, {91, 0}, {73, 3}}, // j 1 - {{97, 1}, {89, 3}, {71, 3}} // j 2 - }, - { - // i 1 - {{113, 0}, {99, 0}, {80, 5}}, // j 0 - {{116, 1}, {105, 0}, {88, 0}}, // j 1 - {{111, 1}, {103, 0}, {91, 0}} // j 2 - }, - { - // i 2 - {{117, 2}, {106, 5}, {90, 5}}, // j 0 - {{121, 1}, {113, 0}, {99, 0}}, // j 1 - {{119, 1}, {116, 1}, {105, 0}} // j 2 - }}, - {// face 18 - { - // i 0 - {{119, 0}, {111, 0}, {97, 0}}, // j 0 - {{115, 0}, {110, 0}, {98, 3}}, // j 1 - {{107, 1}, {104, 3}, {96, 3}} // j 2 - }, - { - // i 1 - {{121, 0}, {116, 0}, {103, 5}}, // j 0 - {{120, 1}, {119, 0}, {111, 0}}, // j 1 - {{112, 1}, {115, 0}, {110, 0}} // j 2 - }, - { - // i 2 - {{117, 1}, {113, 5}, {105, 5}}, // j 0 - {{118, 1}, {121, 0}, {116, 0}}, // j 1 - {{114, 1}, {120, 1}, {119, 0}} // j 2 - }}, - {// face 19 - { - // i 0 - {{114, 0}, {112, 0}, {107, 0}}, // j 0 - {{100, 0}, {102, 0}, {101, 3}}, // j 1 - {{83, 1}, {87, 3}, {85, 3}} // j 2 - }, - { - // i 1 - {{118, 0}, {120, 0}, {115, 5}}, // j 0 - {{108, 1}, {114, 0}, {112, 0}}, // j 1 - {{92, 1}, {100, 0}, {102, 0}} // j 2 - }, - { - // i 2 - {{117, 0}, {121, 5}, {119, 5}}, // j 0 - {{109, 1}, {118, 0}, {120, 0}}, // j 1 - {{95, 1}, {108, 1}, {114, 0}} // j 2 - }}}; - -/** @brief Resolution 0 base cell data table. - * - * For each base cell, gives the "home" face and ijk+ coordinates on that face, - * whether or not the base cell is a pentagon. Additionally, if the base cell - * is a pentagon, the two cw offset rotation adjacent faces are given (-1 - * indicates that no cw offset rotation faces exist for this base cell). - */ -const BaseCellData baseCellData[NUM_BASE_CELLS] = { - - {{1, {1, 0, 0}}, 0, {0, 0}}, // base cell 0 - {{2, {1, 1, 0}}, 0, {0, 0}}, // base cell 1 - {{1, {0, 0, 0}}, 0, {0, 0}}, // base cell 2 - {{2, {1, 0, 0}}, 0, {0, 0}}, // base cell 3 - {{0, {2, 0, 0}}, 1, {-1, -1}}, // base cell 4 - {{1, {1, 1, 0}}, 0, {0, 0}}, // base cell 5 - {{1, {0, 0, 1}}, 0, {0, 0}}, // base cell 6 - {{2, {0, 0, 0}}, 0, {0, 0}}, // base cell 7 - {{0, {1, 0, 0}}, 0, {0, 0}}, // base cell 8 - {{2, {0, 1, 0}}, 0, {0, 0}}, // base cell 9 - {{1, {0, 1, 0}}, 0, {0, 0}}, // base cell 10 - {{1, {0, 1, 1}}, 0, {0, 0}}, // base cell 11 - {{3, {1, 0, 0}}, 0, {0, 0}}, // base cell 12 - {{3, {1, 1, 0}}, 0, {0, 0}}, // base cell 13 - {{11, {2, 0, 0}}, 1, {2, 6}}, // base cell 14 - {{4, {1, 0, 0}}, 0, {0, 0}}, // base cell 15 - {{0, {0, 0, 0}}, 0, {0, 0}}, // base cell 16 - {{6, {0, 1, 0}}, 0, {0, 0}}, // base cell 17 - {{0, {0, 0, 1}}, 0, {0, 0}}, // base cell 18 - {{2, {0, 1, 1}}, 0, {0, 0}}, // base cell 19 - {{7, {0, 0, 1}}, 0, {0, 0}}, // base cell 20 - {{2, {0, 0, 1}}, 0, {0, 0}}, // base cell 21 - {{0, {1, 1, 0}}, 0, {0, 0}}, // base cell 22 - {{6, {0, 0, 1}}, 0, {0, 0}}, // base cell 23 - {{10, {2, 0, 0}}, 1, {1, 5}}, // base cell 24 - {{6, {0, 0, 0}}, 0, {0, 0}}, // base cell 25 - {{3, {0, 0, 0}}, 0, {0, 0}}, // base cell 26 - {{11, {1, 0, 0}}, 0, {0, 0}}, // base cell 27 - {{4, {1, 1, 0}}, 0, {0, 0}}, // base cell 28 - {{3, {0, 1, 0}}, 0, {0, 0}}, // base cell 29 - {{0, {0, 1, 1}}, 0, {0, 0}}, // base cell 30 - {{4, {0, 0, 0}}, 0, {0, 0}}, // base cell 31 - {{5, {0, 1, 0}}, 0, {0, 0}}, // base cell 32 - {{0, {0, 1, 0}}, 0, {0, 0}}, // base cell 33 - {{7, {0, 1, 0}}, 0, {0, 0}}, // base cell 34 - {{11, {1, 1, 0}}, 0, {0, 0}}, // base cell 35 - {{7, {0, 0, 0}}, 0, {0, 0}}, // base cell 36 - {{10, {1, 0, 0}}, 0, {0, 0}}, // base cell 37 - {{12, {2, 0, 0}}, 1, {3, 7}}, // base cell 38 - {{6, {1, 0, 1}}, 0, {0, 0}}, // base cell 39 - {{7, {1, 0, 1}}, 0, {0, 0}}, // base cell 40 - {{4, {0, 0, 1}}, 0, {0, 0}}, // base cell 41 - {{3, {0, 0, 1}}, 0, {0, 0}}, // base cell 42 - {{3, {0, 1, 1}}, 0, {0, 0}}, // base cell 43 - {{4, {0, 1, 0}}, 0, {0, 0}}, // base cell 44 - {{6, {1, 0, 0}}, 0, {0, 0}}, // base cell 45 - {{11, {0, 0, 0}}, 0, {0, 0}}, // base cell 46 - {{8, {0, 0, 1}}, 0, {0, 0}}, // base cell 47 - {{5, {0, 0, 1}}, 0, {0, 0}}, // base cell 48 - {{14, {2, 0, 0}}, 1, {0, 9}}, // base cell 49 - {{5, {0, 0, 0}}, 0, {0, 0}}, // base cell 50 - {{12, {1, 0, 0}}, 0, {0, 0}}, // base cell 51 - {{10, {1, 1, 0}}, 0, {0, 0}}, // base cell 52 - {{4, {0, 1, 1}}, 0, {0, 0}}, // base cell 53 - {{12, {1, 1, 0}}, 0, {0, 0}}, // base cell 54 - {{7, {1, 0, 0}}, 0, {0, 0}}, // base cell 55 - {{11, {0, 1, 0}}, 0, {0, 0}}, // base cell 56 - {{10, {0, 0, 0}}, 0, {0, 0}}, // base cell 57 - {{13, {2, 0, 0}}, 1, {4, 8}}, // base cell 58 - {{10, {0, 0, 1}}, 0, {0, 0}}, // base cell 59 - {{11, {0, 0, 1}}, 0, {0, 0}}, // base cell 60 - {{9, {0, 1, 0}}, 0, {0, 0}}, // base cell 61 - {{8, {0, 1, 0}}, 0, {0, 0}}, // base cell 62 - {{6, {2, 0, 0}}, 1, {11, 15}}, // base cell 63 - {{8, {0, 0, 0}}, 0, {0, 0}}, // base cell 64 - {{9, {0, 0, 1}}, 0, {0, 0}}, // base cell 65 - {{14, {1, 0, 0}}, 0, {0, 0}}, // base cell 66 - {{5, {1, 0, 1}}, 0, {0, 0}}, // base cell 67 - {{16, {0, 1, 1}}, 0, {0, 0}}, // base cell 68 - {{8, {1, 0, 1}}, 0, {0, 0}}, // base cell 69 - {{5, {1, 0, 0}}, 0, {0, 0}}, // base cell 70 - {{12, {0, 0, 0}}, 0, {0, 0}}, // base cell 71 - {{7, {2, 0, 0}}, 1, {12, 16}}, // base cell 72 - {{12, {0, 1, 0}}, 0, {0, 0}}, // base cell 73 - {{10, {0, 1, 0}}, 0, {0, 0}}, // base cell 74 - {{9, {0, 0, 0}}, 0, {0, 0}}, // base cell 75 - {{13, {1, 0, 0}}, 0, {0, 0}}, // base cell 76 - {{16, {0, 0, 1}}, 0, {0, 0}}, // base cell 77 - {{15, {0, 1, 1}}, 0, {0, 0}}, // base cell 78 - {{15, {0, 1, 0}}, 0, {0, 0}}, // base cell 79 - {{16, {0, 1, 0}}, 0, {0, 0}}, // base cell 80 - {{14, {1, 1, 0}}, 0, {0, 0}}, // base cell 81 - {{13, {1, 1, 0}}, 0, {0, 0}}, // base cell 82 - {{5, {2, 0, 0}}, 1, {10, 19}}, // base cell 83 - {{8, {1, 0, 0}}, 0, {0, 0}}, // base cell 84 - {{14, {0, 0, 0}}, 0, {0, 0}}, // base cell 85 - {{9, {1, 0, 1}}, 0, {0, 0}}, // base cell 86 - {{14, {0, 0, 1}}, 0, {0, 0}}, // base cell 87 - {{17, {0, 0, 1}}, 0, {0, 0}}, // base cell 88 - {{12, {0, 0, 1}}, 0, {0, 0}}, // base cell 89 - {{16, {0, 0, 0}}, 0, {0, 0}}, // base cell 90 - {{17, {0, 1, 1}}, 0, {0, 0}}, // base cell 91 - {{15, {0, 0, 1}}, 0, {0, 0}}, // base cell 92 - {{16, {1, 0, 1}}, 0, {0, 0}}, // base cell 93 - {{9, {1, 0, 0}}, 0, {0, 0}}, // base cell 94 - {{15, {0, 0, 0}}, 0, {0, 0}}, // base cell 95 - {{13, {0, 0, 0}}, 0, {0, 0}}, // base cell 96 - {{8, {2, 0, 0}}, 1, {13, 17}}, // base cell 97 - {{13, {0, 1, 0}}, 0, {0, 0}}, // base cell 98 - {{17, {1, 0, 1}}, 0, {0, 0}}, // base cell 99 - {{19, {0, 1, 0}}, 0, {0, 0}}, // base cell 100 - {{14, {0, 1, 0}}, 0, {0, 0}}, // base cell 101 - {{19, {0, 1, 1}}, 0, {0, 0}}, // base cell 102 - {{17, {0, 1, 0}}, 0, {0, 0}}, // base cell 103 - {{13, {0, 0, 1}}, 0, {0, 0}}, // base cell 104 - {{17, {0, 0, 0}}, 0, {0, 0}}, // base cell 105 - {{16, {1, 0, 0}}, 0, {0, 0}}, // base cell 106 - {{9, {2, 0, 0}}, 1, {14, 18}}, // base cell 107 - {{15, {1, 0, 1}}, 0, {0, 0}}, // base cell 108 - {{15, {1, 0, 0}}, 0, {0, 0}}, // base cell 109 - {{18, {0, 1, 1}}, 0, {0, 0}}, // base cell 110 - {{18, {0, 0, 1}}, 0, {0, 0}}, // base cell 111 - {{19, {0, 0, 1}}, 0, {0, 0}}, // base cell 112 - {{17, {1, 0, 0}}, 0, {0, 0}}, // base cell 113 - {{19, {0, 0, 0}}, 0, {0, 0}}, // base cell 114 - {{18, {0, 1, 0}}, 0, {0, 0}}, // base cell 115 - {{18, {1, 0, 1}}, 0, {0, 0}}, // base cell 116 - {{19, {2, 0, 0}}, 1, {-1, -1}}, // base cell 117 - {{19, {1, 0, 0}}, 0, {0, 0}}, // base cell 118 - {{18, {0, 0, 0}}, 0, {0, 0}}, // base cell 119 - {{19, {1, 0, 1}}, 0, {0, 0}}, // base cell 120 - {{18, {1, 0, 0}}, 0, {0, 0}} // base cell 121 -}; - -/** @brief Return whether or not the indicated base cell is a pentagon. */ -int _isBaseCellPentagon(int baseCell) { - return baseCellData[baseCell].isPentagon; -} - -/** @brief Return whether the indicated base cell is a pentagon where all - * neighbors are oriented towards it. */ -bool _isBaseCellPolarPentagon(int baseCell) { - return baseCell == 4 || baseCell == 117; -} - -/** @brief Find base cell given FaceIJK. - * - * Given the face number and a resolution 0 ijk+ coordinate in that face's - * face-centered ijk coordinate system, return the base cell located at that - * coordinate. - * - * Valid ijk+ lookup coordinates are from (0, 0, 0) to (2, 2, 2). - */ -int _faceIjkToBaseCell(const FaceIJK* h) { - return faceIjkBaseCells[h->face][h->coord.i][h->coord.j][h->coord.k] - .baseCell; -} - -/** @brief Find base cell given FaceIJK. - * - * Given the face number and a resolution 0 ijk+ coordinate in that face's - * face-centered ijk coordinate system, return the number of 60' ccw rotations - * to rotate into the coordinate system of the base cell at that coordinates. - * - * Valid ijk+ lookup coordinates are from (0, 0, 0) to (2, 2, 2). - */ -int _faceIjkToBaseCellCCWrot60(const FaceIJK* h) { - return faceIjkBaseCells[h->face][h->coord.i][h->coord.j][h->coord.k] - .ccwRot60; -} - -/** @brief Find the FaceIJK given a base cell. - */ -void _baseCellToFaceIjk(int baseCell, FaceIJK* h) { - *h = baseCellData[baseCell].homeFijk; -} - -/** - * @brief Given a base cell and the face it appears on, return - * the number of 60' ccw rotations for that base cell's - * coordinate system. - * @returns The number of rotations, or INVALID_ROTATIONS if the base - * cell is not found on the given face - */ -int _baseCellToCCWrot60(int baseCell, int face) { - if (face < 0 || face > NUM_ICOSA_FACES) return INVALID_ROTATIONS; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - for (int k = 0; k < 3; k++) { - if (faceIjkBaseCells[face][i][j][k].baseCell == baseCell) { - return faceIjkBaseCells[face][i][j][k].ccwRot60; - } - } - } - } - return INVALID_ROTATIONS; -} - -/** @brief Return whether or not the tested face is a cw offset face. - */ -bool _baseCellIsCwOffset(int baseCell, int testFace) { - return baseCellData[baseCell].cwOffsetPent[0] == testFace || - baseCellData[baseCell].cwOffsetPent[1] == testFace; -} - -/** @brief Return the neighboring base cell in the given direction. - */ -int _getBaseCellNeighbor(int baseCell, Direction dir) { - return baseCellNeighbors[baseCell][dir]; -} - -/** @brief Return the direction from the origin base cell to the neighbor. - * Returns INVALID_DIGIT if the base cells are not neighbors. - */ -Direction _getBaseCellDirection(int originBaseCell, int neighboringBaseCell) { - for (Direction dir = CENTER_DIGIT; dir < NUM_DIGITS; dir++) { - int testBaseCell = _getBaseCellNeighbor(originBaseCell, dir); - if (testBaseCell == neighboringBaseCell) { - return dir; - } - } - return INVALID_DIGIT; -} - -/** - * res0IndexCount returns the number of resolution 0 indexes - * - * @return int count of resolution 0 indexes - */ -int H3_EXPORT(res0IndexCount)() { return NUM_BASE_CELLS; } - -/** - * getRes0Indexes generates all base cells storing them into the provided - * memory pointer. Buffer must be of size NUM_BASE_CELLS * sizeof(H3Index). - * - * @param out H3Index* the memory to store the resulting base cells in - */ -void H3_EXPORT(getRes0Indexes)(H3Index* out) { - for (int bc = 0; bc < NUM_BASE_CELLS; bc++) { - H3Index baseCell = H3_INIT; - H3_SET_MODE(baseCell, H3_HEXAGON_MODE); - H3_SET_BASE_CELL(baseCell, bc); - out[bc] = baseCell; - } -} diff --git a/v3/h3_baseCells.h b/v3/h3_baseCells.h deleted file mode 100644 index 4895b95..0000000 --- a/v3/h3_baseCells.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2016-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. - */ -/** @file baseCells.h - * @brief Base cell related lookup tables and access functions. - */ - -#ifndef BASECELLS_H -#define BASECELLS_H - -#include "h3_constants.h" -#include "h3_coordijk.h" -#include "h3_faceijk.h" - -/** @struct BaseCellData - * @brief information on a single base cell - */ -typedef struct { - FaceIJK - homeFijk; ///< "home" face and normalized ijk coordinates on that face - int isPentagon; ///< is this base cell a pentagon? - int cwOffsetPent[2]; ///< if a pentagon, what are its two clockwise offset - /// faces? -} BaseCellData; - -#define INVALID_BASE_CELL 127 -extern const int baseCellNeighbors[NUM_BASE_CELLS][7]; -extern const int baseCellNeighbor60CCWRots[NUM_BASE_CELLS][7]; - -// resolution 0 base cell data lookup-table (global) -extern const BaseCellData baseCellData[NUM_BASE_CELLS]; - -/** Maximum input for any component to face-to-base-cell lookup functions */ -#define MAX_FACE_COORD 2 - -/** Invalid number of rotations */ -#define INVALID_ROTATIONS -1 - -// Internal functions -int _isBaseCellPentagon(int baseCell); -bool _isBaseCellPolarPentagon(int baseCell); -int _faceIjkToBaseCell(const FaceIJK* h); -int _faceIjkToBaseCellCCWrot60(const FaceIJK* h); -int _baseCellToCCWrot60(int baseCell, int face); -void _baseCellToFaceIjk(int baseCell, FaceIJK* h); -bool _baseCellIsCwOffset(int baseCell, int testFace); -int _getBaseCellNeighbor(int baseCell, Direction dir); -Direction _getBaseCellDirection(int originBaseCell, int destinationBaseCell); - -#endif diff --git a/v3/h3_bbox.c b/v3/h3_bbox.c deleted file mode 100644 index 78bc320..0000000 --- a/v3/h3_bbox.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2016-2020 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. - */ -/** @file bbox.c - * @brief Geographic bounding box functions - */ - -#include "h3_bbox.h" - -#include -#include -#include - -#include "h3_constants.h" -#include "h3_geoCoord.h" -#include "h3_h3Index.h" - -/** - * Whether the given bounding box crosses the antimeridian - * @param bbox Bounding box to inspect - * @return is transmeridian - */ -bool bboxIsTransmeridian(const BBox* bbox) { return bbox->east < bbox->west; } - -/** - * Get the center of a bounding box - * @param bbox Input bounding box - * @param center Output center coordinate - */ -void bboxCenter(const BBox* bbox, GeoCoord* center) { - center->lat = (bbox->north + bbox->south) / 2.0; - // If the bbox crosses the antimeridian, shift east 360 degrees - double east = bboxIsTransmeridian(bbox) ? bbox->east + M_2PI : bbox->east; - center->lon = constrainLng((east + bbox->west) / 2.0); -} - -/** - * Whether the bounding box contains a given point - * @param bbox Bounding box - * @param point Point to test - * @return Whether the point is contained - */ -bool bboxContains(const BBox* bbox, const GeoCoord* point) { - return point->lat >= bbox->south && point->lat <= bbox->north && - (bboxIsTransmeridian(bbox) ? - // transmeridian case - (point->lon >= bbox->west || point->lon <= bbox->east) - : - // standard case - (point->lon >= bbox->west && point->lon <= bbox->east)); -} - -/** - * Whether two bounding boxes are strictly equal - * @param b1 Bounding box 1 - * @param b2 Bounding box 2 - * @return Whether the boxes are equal - */ -bool bboxEquals(const BBox* b1, const BBox* b2) { - return b1->north == b2->north && b1->south == b2->south && - b1->east == b2->east && b1->west == b2->west; -} - -/** - * _hexRadiusKm returns the radius of a given hexagon in Km - * - * @param h3Index the index of the hexagon - * @return the radius of the hexagon in Km - */ -double _hexRadiusKm(H3Index h3Index) { - // There is probably a cheaper way to determine the radius of a - // hexagon, but this way is conceptually simple - GeoCoord h3Center; - GeoBoundary h3Boundary; - H3_EXPORT(h3ToGeo)(h3Index, &h3Center); - H3_EXPORT(h3ToGeoBoundary)(h3Index, &h3Boundary); - return H3_EXPORT(pointDistKm)(&h3Center, h3Boundary.verts); -} - -/** - * bboxHexEstimate returns an estimated number of hexagons that fit - * within the cartesian-projected bounding box - * - * @param bbox the bounding box to estimate the hexagon fill level - * @param res the resolution of the H3 hexagons to fill the bounding box - * @return the estimated number of hexagons to fill the bounding box - */ -int bboxHexEstimate(const BBox* bbox, int res) { - // Get the area of the pentagon as the maximally-distorted area possible - H3Index pentagons[12] = {0}; - H3_EXPORT(getPentagonIndexes)(res, pentagons); - double pentagonRadiusKm = _hexRadiusKm(pentagons[0]); - // Area of a regular hexagon is 3/2*sqrt(3) * r * r - // The pentagon has the most distortion (smallest edges) and shares its - // edges with hexagons, so the most-distorted hexagons have this area, - // shrunk by 20% off chance that the bounding box perfectly bounds a - // pentagon. - double pentagonAreaKm2 = - 0.8 * (2.59807621135 * pentagonRadiusKm * pentagonRadiusKm); - - // Then get the area of the bounding box of the geofence in question - GeoCoord p1, p2; - p1.lat = bbox->north; - p1.lon = bbox->east; - p2.lat = bbox->south; - p2.lon = bbox->west; - double d = H3_EXPORT(pointDistKm)(&p1, &p2); - // Derived constant based on: https://math.stackexchange.com/a/1921940 - // Clamped to 3 as higher values tend to rapidly drag the estimate to zero. - double a = d * d / fmin(3.0, fabs((p1.lon - p2.lon) / (p1.lat - p2.lat))); - - // Divide the two to get an estimate of the number of hexagons needed - int estimate = (int)ceil(a / pentagonAreaKm2); - if (estimate == 0) estimate = 1; - return estimate; -} - -/** - * lineHexEstimate returns an estimated number of hexagons that trace - * the cartesian-projected line - * - * @param origin the origin coordinates - * @param destination the destination coordinates - * @param res the resolution of the H3 hexagons to trace the line - * @return the estimated number of hexagons required to trace the line - */ -int lineHexEstimate(const GeoCoord* origin, const GeoCoord* destination, - int res) { - // Get the area of the pentagon as the maximally-distorted area possible - H3Index pentagons[12] = {0}; - H3_EXPORT(getPentagonIndexes)(res, pentagons); - double pentagonRadiusKm = _hexRadiusKm(pentagons[0]); - - double dist = H3_EXPORT(pointDistKm)(origin, destination); - int estimate = (int)ceil(dist / (2 * pentagonRadiusKm)); - if (estimate == 0) estimate = 1; - return estimate; -} diff --git a/v3/h3_bbox.h b/v3/h3_bbox.h deleted file mode 100644 index c3ab6fd..0000000 --- a/v3/h3_bbox.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2016-2017 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. - */ -/** @file bbox.h - * @brief Geographic bounding box functions - */ - -#ifndef BBOX_H -#define BBOX_H - -#include - -#include "h3_geoCoord.h" - -/** @struct BBox - * @brief Geographic bounding box with coordinates defined in radians - */ -typedef struct { - double north; ///< north latitude - double south; ///< south latitude - double east; ///< east longitude - double west; ///< west longitude -} BBox; - -bool bboxIsTransmeridian(const BBox* bbox); -void bboxCenter(const BBox* bbox, GeoCoord* center); -bool bboxContains(const BBox* bbox, const GeoCoord* point); -bool bboxEquals(const BBox* b1, const BBox* b2); -int bboxHexEstimate(const BBox* bbox, int res); -int lineHexEstimate(const GeoCoord* origin, const GeoCoord* destination, - int res); - -#endif diff --git a/v3/h3_constants.h b/v3/h3_constants.h deleted file mode 100644 index 9328c77..0000000 --- a/v3/h3_constants.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2016-2017 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. - */ -/** @file constants.h - * @brief Constants used by more than one source code file. - */ - -#ifndef CONSTANTS_H -#define CONSTANTS_H - -#ifndef M_PI -/** pi */ -#define M_PI 3.14159265358979323846 -#endif - -#ifndef M_PI_2 -/** pi / 2.0 */ -#define M_PI_2 1.5707963267948966 -#endif - -/** 2.0 * PI */ -#define M_2PI 6.28318530717958647692528676655900576839433L - -/** pi / 180 */ -#define M_PI_180 0.0174532925199432957692369076848861271111L -/** pi * 180 */ -#define M_180_PI 57.29577951308232087679815481410517033240547L - -/** threshold epsilon */ -#define EPSILON 0.0000000000000001L -/** sqrt(3) / 2.0 */ -#define M_SQRT3_2 0.8660254037844386467637231707529361834714L -/** sin(60') */ -#define M_SIN60 M_SQRT3_2 - -/** rotation angle between Class II and Class III resolution axes - * (asin(sqrt(3.0 / 28.0))) */ -#define M_AP7_ROT_RADS 0.333473172251832115336090755351601070065900389L - -/** sin(M_AP7_ROT_RADS) */ -#define M_SIN_AP7_ROT 0.3273268353539885718950318L - -/** cos(M_AP7_ROT_RADS) */ -#define M_COS_AP7_ROT 0.9449111825230680680167902L - -/** earth radius in kilometers using WGS84 authalic radius */ -#define EARTH_RADIUS_KM 6371.007180918475L - -/** scaling factor from hex2d resolution 0 unit length - * (or distance between adjacent cell center points - * on the plane) to gnomonic unit length. */ -#define RES0_U_GNOMONIC 0.38196601125010500003L - -/** max H3 resolution; H3 version 1 has 16 resolutions, numbered 0 through 15 */ -#define MAX_H3_RES 15 - -/** The number of faces on an icosahedron */ -#define NUM_ICOSA_FACES 20 -/** The number of H3 base cells */ -#define NUM_BASE_CELLS 122 -/** The number of vertices in a hexagon */ -#define NUM_HEX_VERTS 6 -/** The number of vertices in a pentagon */ -#define NUM_PENT_VERTS 5 -/** The number of pentagons per resolution **/ -#define NUM_PENTAGONS 12 - -/** H3 index modes */ -#define H3_HEXAGON_MODE 1 -#define H3_UNIEDGE_MODE 2 - -#endif diff --git a/v3/h3_coordijk.c b/v3/h3_coordijk.c deleted file mode 100644 index b08f204..0000000 --- a/v3/h3_coordijk.c +++ /dev/null @@ -1,556 +0,0 @@ -/* - * Copyright 2016-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. - */ -/** @file coordijk.c - * @brief Hex IJK coordinate systems functions including conversions to/from - * lat/lon. - */ - -#include "h3_coordijk.h" - -#include -#include -#include -#include - -#include "h3_constants.h" -#include "h3_geoCoord.h" -#include "h3_mathExtensions.h" - -/** - * Sets an IJK coordinate to the specified component values. - * - * @param ijk The IJK coordinate to set. - * @param i The desired i component value. - * @param j The desired j component value. - * @param k The desired k component value. - */ -void _setIJK(CoordIJK* ijk, int i, int j, int k) { - ijk->i = i; - ijk->j = j; - ijk->k = k; -} - -/** - * Determine the containing hex in ijk+ coordinates for a 2D cartesian - * coordinate vector (from DGGRID). - * - * @param v The 2D cartesian coordinate vector. - * @param h The ijk+ coordinates of the containing hex. - */ -void _hex2dToCoordIJK(const Vec2d* v, CoordIJK* h) { - double a1, a2; - double x1, x2; - int m1, m2; - double r1, r2; - - // quantize into the ij system and then normalize - h->k = 0; - - a1 = fabsl(v->x); - a2 = fabsl(v->y); - - // first do a reverse conversion - x2 = a2 / M_SIN60; - x1 = a1 + x2 / 2.0L; - - // check if we have the center of a hex - m1 = x1; - m2 = x2; - - // otherwise round correctly - r1 = x1 - m1; - r2 = x2 - m2; - - if (r1 < 0.5L) { - if (r1 < 1.0L / 3.0L) { - if (r2 < (1.0L + r1) / 2.0L) { - h->i = m1; - h->j = m2; - } else { - h->i = m1; - h->j = m2 + 1; - } - } else { - if (r2 < (1.0L - r1)) { - h->j = m2; - } else { - h->j = m2 + 1; - } - - if ((1.0L - r1) <= r2 && r2 < (2.0 * r1)) { - h->i = m1 + 1; - } else { - h->i = m1; - } - } - } else { - if (r1 < 2.0L / 3.0L) { - if (r2 < (1.0L - r1)) { - h->j = m2; - } else { - h->j = m2 + 1; - } - - if ((2.0L * r1 - 1.0L) < r2 && r2 < (1.0L - r1)) { - h->i = m1; - } else { - h->i = m1 + 1; - } - } else { - if (r2 < (r1 / 2.0L)) { - h->i = m1 + 1; - h->j = m2; - } else { - h->i = m1 + 1; - h->j = m2 + 1; - } - } - } - - // now fold across the axes if necessary - - if (v->x < 0.0L) { - if ((h->j % 2) == 0) // even - { - long long int axisi = h->j / 2; - long long int diff = h->i - axisi; - h->i = h->i - 2.0 * diff; - } else { - long long int axisi = (h->j + 1) / 2; - long long int diff = h->i - axisi; - h->i = h->i - (2.0 * diff + 1); - } - } - - if (v->y < 0.0L) { - h->i = h->i - (2 * h->j + 1) / 2; - h->j = -1 * h->j; - } - - _ijkNormalize(h); -} - -/** - * Find the center point in 2D cartesian coordinates of a hex. - * - * @param h The ijk coordinates of the hex. - * @param v The 2D cartesian coordinates of the hex center point. - */ -void _ijkToHex2d(const CoordIJK* h, Vec2d* v) { - int i = h->i - h->k; - int j = h->j - h->k; - - v->x = i - 0.5L * j; - v->y = j * M_SQRT3_2; -} - -/** - * Returns whether or not two ijk coordinates contain exactly the same - * component values. - * - * @param c1 The first set of ijk coordinates. - * @param c2 The second set of ijk coordinates. - * @return 1 if the two addresses match, 0 if they do not. - */ -int _ijkMatches(const CoordIJK* c1, const CoordIJK* c2) { - return (c1->i == c2->i && c1->j == c2->j && c1->k == c2->k); -} - -/** - * Add two ijk coordinates. - * - * @param h1 The first set of ijk coordinates. - * @param h2 The second set of ijk coordinates. - * @param sum The sum of the two sets of ijk coordinates. - */ -void _ijkAdd(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* sum) { - sum->i = h1->i + h2->i; - sum->j = h1->j + h2->j; - sum->k = h1->k + h2->k; -} - -/** - * Subtract two ijk coordinates. - * - * @param h1 The first set of ijk coordinates. - * @param h2 The second set of ijk coordinates. - * @param diff The difference of the two sets of ijk coordinates (h1 - h2). - */ -void _ijkSub(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* diff) { - diff->i = h1->i - h2->i; - diff->j = h1->j - h2->j; - diff->k = h1->k - h2->k; -} - -/** - * Uniformly scale ijk coordinates by a scalar. Works in place. - * - * @param c The ijk coordinates to scale. - * @param factor The scaling factor. - */ -void _ijkScale(CoordIJK* c, int factor) { - c->i *= factor; - c->j *= factor; - c->k *= factor; -} - -/** - * Normalizes ijk coordinates by setting the components to the smallest possible - * values. Works in place. - * - * @param c The ijk coordinates to normalize. - */ -void _ijkNormalize(CoordIJK* c) { - // remove any negative values - if (c->i < 0) { - c->j -= c->i; - c->k -= c->i; - c->i = 0; - } - - if (c->j < 0) { - c->i -= c->j; - c->k -= c->j; - c->j = 0; - } - - if (c->k < 0) { - c->i -= c->k; - c->j -= c->k; - c->k = 0; - } - - // remove the min value if needed - int min = c->i; - if (c->j < min) min = c->j; - if (c->k < min) min = c->k; - if (min > 0) { - c->i -= min; - c->j -= min; - c->k -= min; - } -} - -/** - * Determines the H3 digit corresponding to a unit vector in ijk coordinates. - * - * @param ijk The ijk coordinates; must be a unit vector. - * @return The H3 digit (0-6) corresponding to the ijk unit vector, or - * INVALID_DIGIT on failure. - */ -Direction _unitIjkToDigit(const CoordIJK* ijk) { - CoordIJK c = *ijk; - _ijkNormalize(&c); - - Direction digit = INVALID_DIGIT; - for (Direction i = CENTER_DIGIT; i < NUM_DIGITS; i++) { - if (_ijkMatches(&c, &UNIT_VECS[i])) { - digit = i; - break; - } - } - - return digit; -} - -/** - * Find the normalized ijk coordinates of the indexing parent of a cell in a - * counter-clockwise aperture 7 grid. Works in place. - * - * @param ijk The ijk coordinates. - */ -void _upAp7(CoordIJK* ijk) { - // convert to CoordIJ - int i = ijk->i - ijk->k; - int j = ijk->j - ijk->k; - - ijk->i = (int)lroundl((3 * i - j) / 7.0L); - ijk->j = (int)lroundl((i + 2 * j) / 7.0L); - ijk->k = 0; - _ijkNormalize(ijk); -} - -/** - * Find the normalized ijk coordinates of the indexing parent of a cell in a - * clockwise aperture 7 grid. Works in place. - * - * @param ijk The ijk coordinates. - */ -void _upAp7r(CoordIJK* ijk) { - // convert to CoordIJ - int i = ijk->i - ijk->k; - int j = ijk->j - ijk->k; - - ijk->i = (int)lroundl((2 * i + j) / 7.0L); - ijk->j = (int)lroundl((3 * j - i) / 7.0L); - ijk->k = 0; - _ijkNormalize(ijk); -} - -/** - * Find the normalized ijk coordinates of the hex centered on the indicated - * hex at the next finer aperture 7 counter-clockwise resolution. Works in - * place. - * - * @param ijk The ijk coordinates. - */ -void _downAp7(CoordIJK* ijk) { - // res r unit vectors in res r+1 - CoordIJK iVec = {3, 0, 1}; - CoordIJK jVec = {1, 3, 0}; - CoordIJK kVec = {0, 1, 3}; - - _ijkScale(&iVec, ijk->i); - _ijkScale(&jVec, ijk->j); - _ijkScale(&kVec, ijk->k); - - _ijkAdd(&iVec, &jVec, ijk); - _ijkAdd(ijk, &kVec, ijk); - - _ijkNormalize(ijk); -} - -/** - * Find the normalized ijk coordinates of the hex centered on the indicated - * hex at the next finer aperture 7 clockwise resolution. Works in place. - * - * @param ijk The ijk coordinates. - */ -void _downAp7r(CoordIJK* ijk) { - // res r unit vectors in res r+1 - CoordIJK iVec = {3, 1, 0}; - CoordIJK jVec = {0, 3, 1}; - CoordIJK kVec = {1, 0, 3}; - - _ijkScale(&iVec, ijk->i); - _ijkScale(&jVec, ijk->j); - _ijkScale(&kVec, ijk->k); - - _ijkAdd(&iVec, &jVec, ijk); - _ijkAdd(ijk, &kVec, ijk); - - _ijkNormalize(ijk); -} - -/** - * Find the normalized ijk coordinates of the hex in the specified digit - * direction from the specified ijk coordinates. Works in place. - * - * @param ijk The ijk coordinates. - * @param digit The digit direction from the original ijk coordinates. - */ -void _neighbor(CoordIJK* ijk, Direction digit) { - if (digit > CENTER_DIGIT && digit < NUM_DIGITS) { - _ijkAdd(ijk, &UNIT_VECS[digit], ijk); - _ijkNormalize(ijk); - } -} - -/** - * Rotates ijk coordinates 60 degrees counter-clockwise. Works in place. - * - * @param ijk The ijk coordinates. - */ -void _ijkRotate60ccw(CoordIJK* ijk) { - // unit vector rotations - CoordIJK iVec = {1, 1, 0}; - CoordIJK jVec = {0, 1, 1}; - CoordIJK kVec = {1, 0, 1}; - - _ijkScale(&iVec, ijk->i); - _ijkScale(&jVec, ijk->j); - _ijkScale(&kVec, ijk->k); - - _ijkAdd(&iVec, &jVec, ijk); - _ijkAdd(ijk, &kVec, ijk); - - _ijkNormalize(ijk); -} - -/** - * Rotates ijk coordinates 60 degrees clockwise. Works in place. - * - * @param ijk The ijk coordinates. - */ -void _ijkRotate60cw(CoordIJK* ijk) { - // unit vector rotations - CoordIJK iVec = {1, 0, 1}; - CoordIJK jVec = {1, 1, 0}; - CoordIJK kVec = {0, 1, 1}; - - _ijkScale(&iVec, ijk->i); - _ijkScale(&jVec, ijk->j); - _ijkScale(&kVec, ijk->k); - - _ijkAdd(&iVec, &jVec, ijk); - _ijkAdd(ijk, &kVec, ijk); - - _ijkNormalize(ijk); -} - -/** - * Rotates indexing digit 60 degrees counter-clockwise. Returns result. - * - * @param digit Indexing digit (between 1 and 6 inclusive) - */ -Direction _rotate60ccw(Direction digit) { - switch (digit) { - case K_AXES_DIGIT: - return IK_AXES_DIGIT; - case IK_AXES_DIGIT: - return I_AXES_DIGIT; - case I_AXES_DIGIT: - return IJ_AXES_DIGIT; - case IJ_AXES_DIGIT: - return J_AXES_DIGIT; - case J_AXES_DIGIT: - return JK_AXES_DIGIT; - case JK_AXES_DIGIT: - return K_AXES_DIGIT; - default: - return digit; - } -} - -/** - * Rotates indexing digit 60 degrees clockwise. Returns result. - * - * @param digit Indexing digit (between 1 and 6 inclusive) - */ -Direction _rotate60cw(Direction digit) { - switch (digit) { - case K_AXES_DIGIT: - return JK_AXES_DIGIT; - case JK_AXES_DIGIT: - return J_AXES_DIGIT; - case J_AXES_DIGIT: - return IJ_AXES_DIGIT; - case IJ_AXES_DIGIT: - return I_AXES_DIGIT; - case I_AXES_DIGIT: - return IK_AXES_DIGIT; - case IK_AXES_DIGIT: - return K_AXES_DIGIT; - default: - return digit; - } -} - -/** - * Find the normalized ijk coordinates of the hex centered on the indicated - * hex at the next finer aperture 3 counter-clockwise resolution. Works in - * place. - * - * @param ijk The ijk coordinates. - */ -void _downAp3(CoordIJK* ijk) { - // res r unit vectors in res r+1 - CoordIJK iVec = {2, 0, 1}; - CoordIJK jVec = {1, 2, 0}; - CoordIJK kVec = {0, 1, 2}; - - _ijkScale(&iVec, ijk->i); - _ijkScale(&jVec, ijk->j); - _ijkScale(&kVec, ijk->k); - - _ijkAdd(&iVec, &jVec, ijk); - _ijkAdd(ijk, &kVec, ijk); - - _ijkNormalize(ijk); -} - -/** - * Find the normalized ijk coordinates of the hex centered on the indicated - * hex at the next finer aperture 3 clockwise resolution. Works in place. - * - * @param ijk The ijk coordinates. - */ -void _downAp3r(CoordIJK* ijk) { - // res r unit vectors in res r+1 - CoordIJK iVec = {2, 1, 0}; - CoordIJK jVec = {0, 2, 1}; - CoordIJK kVec = {1, 0, 2}; - - _ijkScale(&iVec, ijk->i); - _ijkScale(&jVec, ijk->j); - _ijkScale(&kVec, ijk->k); - - _ijkAdd(&iVec, &jVec, ijk); - _ijkAdd(ijk, &kVec, ijk); - - _ijkNormalize(ijk); -} - -/** - * Finds the distance between the two coordinates. Returns result. - * - * @param c1 The first set of ijk coordinates. - * @param c2 The second set of ijk coordinates. - */ -int ijkDistance(const CoordIJK* c1, const CoordIJK* c2) { - CoordIJK diff; - _ijkSub(c1, c2, &diff); - _ijkNormalize(&diff); - CoordIJK absDiff = {abs(diff.i), abs(diff.j), abs(diff.k)}; - return MAX(absDiff.i, MAX(absDiff.j, absDiff.k)); -} - -/** - * Transforms coordinates from the IJK+ coordinate system to the IJ coordinate - * system. - * - * @param ijk The input IJK+ coordinates - * @param ij The output IJ coordinates - */ -void ijkToIj(const CoordIJK* ijk, CoordIJ* ij) { - ij->i = ijk->i - ijk->k; - ij->j = ijk->j - ijk->k; -} - -/** - * Transforms coordinates from the IJ coordinate system to the IJK+ coordinate - * system. - * - * @param ij The input IJ coordinates - * @param ijk The output IJK+ coordinates - */ -void ijToIjk(const CoordIJ* ij, CoordIJK* ijk) { - ijk->i = ij->i; - ijk->j = ij->j; - ijk->k = 0; - - _ijkNormalize(ijk); -} - -/** - * Convert IJK coordinates to cube coordinates, in place - * @param ijk Coordinate to convert - */ -void ijkToCube(CoordIJK* ijk) { - ijk->i = -ijk->i + ijk->k; - ijk->j = ijk->j - ijk->k; - ijk->k = -ijk->i - ijk->j; -} - -/** - * Convert cube coordinates to IJK coordinates, in place - * @param ijk Coordinate to convert - */ -void cubeToIjk(CoordIJK* ijk) { - ijk->i = -ijk->i; - ijk->k = 0; - _ijkNormalize(ijk); -} diff --git a/v3/h3_coordijk.h b/v3/h3_coordijk.h deleted file mode 100644 index e847fc7..0000000 --- a/v3/h3_coordijk.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2016-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. - */ -/** @file coordijk.h - * @brief Header file for CoordIJK functions including conversion from lat/lon - * - * References two Vec2d cartesian coordinate systems: - * - * 1. gnomonic: face-centered polyhedral gnomonic projection space with - * traditional scaling and x-axes aligned with the face Class II - * i-axes. - * - * 2. hex2d: local face-centered coordinate system scaled a specific H3 grid - * resolution unit length and with x-axes aligned with the local - * i-axes - */ - -#ifndef COORDIJK_H -#define COORDIJK_H - -#include "h3_geoCoord.h" -#include "h3_h3api.h" -#include "h3_vec2d.h" - -/** @struct CoordIJK - * @brief IJK hexagon coordinates - * - * Each axis is spaced 120 degrees apart. - */ -typedef struct { - int i; ///< i component - int j; ///< j component - int k; ///< k component -} CoordIJK; - -/** @brief CoordIJK unit vectors corresponding to the 7 H3 digits. - */ -static const CoordIJK UNIT_VECS[] = { - {0, 0, 0}, // direction 0 - {0, 0, 1}, // direction 1 - {0, 1, 0}, // direction 2 - {0, 1, 1}, // direction 3 - {1, 0, 0}, // direction 4 - {1, 0, 1}, // direction 5 - {1, 1, 0} // direction 6 -}; - -/** @brief H3 digit representing ijk+ axes direction. - * Values will be within the lowest 3 bits of an integer. - */ -typedef enum { - /** H3 digit in center */ - CENTER_DIGIT = 0, - /** H3 digit in k-axes direction */ - K_AXES_DIGIT = 1, - /** H3 digit in j-axes direction */ - J_AXES_DIGIT = 2, - /** H3 digit in j == k direction */ - JK_AXES_DIGIT = J_AXES_DIGIT | K_AXES_DIGIT, /* 3 */ - /** H3 digit in i-axes direction */ - I_AXES_DIGIT = 4, - /** H3 digit in i == k direction */ - IK_AXES_DIGIT = I_AXES_DIGIT | K_AXES_DIGIT, /* 5 */ - /** H3 digit in i == j direction */ - IJ_AXES_DIGIT = I_AXES_DIGIT | J_AXES_DIGIT, /* 6 */ - /** H3 digit in the invalid direction */ - INVALID_DIGIT = 7, - /** Valid digits will be less than this value. Same value as INVALID_DIGIT. - */ - NUM_DIGITS = INVALID_DIGIT -} Direction; - -// Internal functions - -void _setIJK(CoordIJK* ijk, int i, int j, int k); -void _hex2dToCoordIJK(const Vec2d* v, CoordIJK* h); -void _ijkToHex2d(const CoordIJK* h, Vec2d* v); -int _ijkMatches(const CoordIJK* c1, const CoordIJK* c2); -void _ijkAdd(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* sum); -void _ijkSub(const CoordIJK* h1, const CoordIJK* h2, CoordIJK* diff); -void _ijkScale(CoordIJK* c, int factor); -void _ijkNormalize(CoordIJK* c); -Direction _unitIjkToDigit(const CoordIJK* ijk); -void _upAp7(CoordIJK* ijk); -void _upAp7r(CoordIJK* ijk); -void _downAp7(CoordIJK* ijk); -void _downAp7r(CoordIJK* ijk); -void _downAp3(CoordIJK* ijk); -void _downAp3r(CoordIJK* ijk); -void _neighbor(CoordIJK* ijk, Direction digit); -void _ijkRotate60ccw(CoordIJK* ijk); -void _ijkRotate60cw(CoordIJK* ijk); -Direction _rotate60ccw(Direction digit); -Direction _rotate60cw(Direction digit); -int ijkDistance(const CoordIJK* a, const CoordIJK* b); -void ijkToIj(const CoordIJK* ijk, CoordIJ* ij); -void ijToIjk(const CoordIJ* ij, CoordIJK* ijk); -void ijkToCube(CoordIJK* ijk); -void cubeToIjk(CoordIJK* ijk); - -#endif diff --git a/v3/h3_faceijk.c b/v3/h3_faceijk.c deleted file mode 100644 index 9979828..0000000 --- a/v3/h3_faceijk.c +++ /dev/null @@ -1,936 +0,0 @@ -/* - * Copyright 2016-2020 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. - */ -/** @file faceijk.c - * @brief Functions for working with icosahedral face-centered hex IJK - * coordinate systems. - */ - -#include "h3_faceijk.h" - -#include -#include -#include -#include -#include - -#include "h3_constants.h" -#include "h3_coordijk.h" -#include "h3_geoCoord.h" -#include "h3_h3Index.h" -#include "h3_vec3d.h" - -/** square root of 7 */ -#define M_SQRT7 2.6457513110645905905016157536392604257102L - -/** @brief icosahedron face centers in lat/lon radians */ -const GeoCoord faceCenterGeo[NUM_ICOSA_FACES] = { - {0.803582649718989942, 1.248397419617396099}, // face 0 - {1.307747883455638156, 2.536945009877921159}, // face 1 - {1.054751253523952054, -1.347517358900396623}, // face 2 - {0.600191595538186799, -0.450603909469755746}, // face 3 - {0.491715428198773866, 0.401988202911306943}, // face 4 - {0.172745327415618701, 1.678146885280433686}, // face 5 - {0.605929321571350690, 2.953923329812411617}, // face 6 - {0.427370518328979641, -1.888876200336285401}, // face 7 - {-0.079066118549212831, -0.733429513380867741}, // face 8 - {-0.230961644455383637, 0.506495587332349035}, // face 9 - {0.079066118549212831, 2.408163140208925497}, // face 10 - {0.230961644455383637, -2.635097066257444203}, // face 11 - {-0.172745327415618701, -1.463445768309359553}, // face 12 - {-0.605929321571350690, -0.187669323777381622}, // face 13 - {-0.427370518328979641, 1.252716453253507838}, // face 14 - {-0.600191595538186799, 2.690988744120037492}, // face 15 - {-0.491715428198773866, -2.739604450678486295}, // face 16 - {-0.803582649718989942, -1.893195233972397139}, // face 17 - {-1.307747883455638156, -0.604647643711872080}, // face 18 - {-1.054751253523952054, 1.794075294689396615}, // face 19 -}; - -/** @brief icosahedron face centers in x/y/z on the unit sphere */ -static const Vec3d faceCenterPoint[NUM_ICOSA_FACES] = { - {0.2199307791404606, 0.6583691780274996, 0.7198475378926182}, // face 0 - {-0.2139234834501421, 0.1478171829550703, 0.9656017935214205}, // face 1 - {0.1092625278784797, -0.4811951572873210, 0.8697775121287253}, // face 2 - {0.7428567301586791, -0.3593941678278028, 0.5648005936517033}, // face 3 - {0.8112534709140969, 0.3448953237639384, 0.4721387736413930}, // face 4 - {-0.1055498149613921, 0.9794457296411413, 0.1718874610009365}, // face 5 - {-0.8075407579970092, 0.1533552485898818, 0.5695261994882688}, // face 6 - {-0.2846148069787907, -0.8644080972654206, 0.4144792552473539}, // face 7 - {0.7405621473854482, -0.6673299564565524, -0.0789837646326737}, // face 8 - {0.8512303986474293, 0.4722343788582681, -0.2289137388687808}, // face 9 - {-0.7405621473854481, 0.6673299564565524, 0.0789837646326737}, // face 10 - {-0.8512303986474292, -0.4722343788582682, 0.2289137388687808}, // face 11 - {0.1055498149613919, -0.9794457296411413, -0.1718874610009365}, // face 12 - {0.8075407579970092, -0.1533552485898819, -0.5695261994882688}, // face 13 - {0.2846148069787908, 0.8644080972654204, -0.4144792552473539}, // face 14 - {-0.7428567301586791, 0.3593941678278027, -0.5648005936517033}, // face 15 - {-0.8112534709140971, -0.3448953237639382, -0.4721387736413930}, // face 16 - {-0.2199307791404607, -0.6583691780274996, -0.7198475378926182}, // face 17 - {0.2139234834501420, -0.1478171829550704, -0.9656017935214205}, // face 18 - {-0.1092625278784796, 0.4811951572873210, -0.8697775121287253}, // face 19 -}; - -/** @brief icosahedron face ijk axes as azimuth in radians from face center to - * vertex 0/1/2 respectively - */ -static const double faceAxesAzRadsCII[NUM_ICOSA_FACES][3] = { - {5.619958268523939882, 3.525563166130744542, - 1.431168063737548730}, // face 0 - {5.760339081714187279, 3.665943979320991689, - 1.571548876927796127}, // face 1 - {0.780213654393430055, 4.969003859179821079, - 2.874608756786625655}, // face 2 - {0.430469363979999913, 4.619259568766391033, - 2.524864466373195467}, // face 3 - {6.130269123335111400, 4.035874020941915804, - 1.941478918548720291}, // face 4 - {2.692877706530642877, 0.598482604137447119, - 4.787272808923838195}, // face 5 - {2.982963003477243874, 0.888567901084048369, - 5.077358105870439581}, // face 6 - {3.532912002790141181, 1.438516900396945656, - 5.627307105183336758}, // face 7 - {3.494305004259568154, 1.399909901866372864, - 5.588700106652763840}, // face 8 - {3.003214169499538391, 0.908819067106342928, - 5.097609271892733906}, // face 9 - {5.930472956509811562, 3.836077854116615875, - 1.741682751723420374}, // face 10 - {0.138378484090254847, 4.327168688876645809, - 2.232773586483450311}, // face 11 - {0.448714947059150361, 4.637505151845541521, - 2.543110049452346120}, // face 12 - {0.158629650112549365, 4.347419854898940135, - 2.253024752505744869}, // face 13 - {5.891865957979238535, 3.797470855586042958, - 1.703075753192847583}, // face 14 - {2.711123289609793325, 0.616728187216597771, - 4.805518392002988683}, // face 15 - {3.294508837434268316, 1.200113735041072948, - 5.388903939827463911}, // face 16 - {3.804819692245439833, 1.710424589852244509, - 5.899214794638635174}, // face 17 - {3.664438879055192436, 1.570043776661997111, - 5.758833981448388027}, // face 18 - {2.361378999196363184, 0.266983896803167583, - 4.455774101589558636}, // face 19 -}; - -/** @brief Definition of which faces neighbor each other. */ -static const FaceOrientIJK faceNeighbors[NUM_ICOSA_FACES][4] = { - { - // face 0 - {0, {0, 0, 0}, 0}, // central face - {4, {2, 0, 2}, 1}, // ij quadrant - {1, {2, 2, 0}, 5}, // ki quadrant - {5, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 1 - {1, {0, 0, 0}, 0}, // central face - {0, {2, 0, 2}, 1}, // ij quadrant - {2, {2, 2, 0}, 5}, // ki quadrant - {6, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 2 - {2, {0, 0, 0}, 0}, // central face - {1, {2, 0, 2}, 1}, // ij quadrant - {3, {2, 2, 0}, 5}, // ki quadrant - {7, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 3 - {3, {0, 0, 0}, 0}, // central face - {2, {2, 0, 2}, 1}, // ij quadrant - {4, {2, 2, 0}, 5}, // ki quadrant - {8, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 4 - {4, {0, 0, 0}, 0}, // central face - {3, {2, 0, 2}, 1}, // ij quadrant - {0, {2, 2, 0}, 5}, // ki quadrant - {9, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 5 - {5, {0, 0, 0}, 0}, // central face - {10, {2, 2, 0}, 3}, // ij quadrant - {14, {2, 0, 2}, 3}, // ki quadrant - {0, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 6 - {6, {0, 0, 0}, 0}, // central face - {11, {2, 2, 0}, 3}, // ij quadrant - {10, {2, 0, 2}, 3}, // ki quadrant - {1, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 7 - {7, {0, 0, 0}, 0}, // central face - {12, {2, 2, 0}, 3}, // ij quadrant - {11, {2, 0, 2}, 3}, // ki quadrant - {2, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 8 - {8, {0, 0, 0}, 0}, // central face - {13, {2, 2, 0}, 3}, // ij quadrant - {12, {2, 0, 2}, 3}, // ki quadrant - {3, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 9 - {9, {0, 0, 0}, 0}, // central face - {14, {2, 2, 0}, 3}, // ij quadrant - {13, {2, 0, 2}, 3}, // ki quadrant - {4, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 10 - {10, {0, 0, 0}, 0}, // central face - {5, {2, 2, 0}, 3}, // ij quadrant - {6, {2, 0, 2}, 3}, // ki quadrant - {15, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 11 - {11, {0, 0, 0}, 0}, // central face - {6, {2, 2, 0}, 3}, // ij quadrant - {7, {2, 0, 2}, 3}, // ki quadrant - {16, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 12 - {12, {0, 0, 0}, 0}, // central face - {7, {2, 2, 0}, 3}, // ij quadrant - {8, {2, 0, 2}, 3}, // ki quadrant - {17, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 13 - {13, {0, 0, 0}, 0}, // central face - {8, {2, 2, 0}, 3}, // ij quadrant - {9, {2, 0, 2}, 3}, // ki quadrant - {18, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 14 - {14, {0, 0, 0}, 0}, // central face - {9, {2, 2, 0}, 3}, // ij quadrant - {5, {2, 0, 2}, 3}, // ki quadrant - {19, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 15 - {15, {0, 0, 0}, 0}, // central face - {16, {2, 0, 2}, 1}, // ij quadrant - {19, {2, 2, 0}, 5}, // ki quadrant - {10, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 16 - {16, {0, 0, 0}, 0}, // central face - {17, {2, 0, 2}, 1}, // ij quadrant - {15, {2, 2, 0}, 5}, // ki quadrant - {11, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 17 - {17, {0, 0, 0}, 0}, // central face - {18, {2, 0, 2}, 1}, // ij quadrant - {16, {2, 2, 0}, 5}, // ki quadrant - {12, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 18 - {18, {0, 0, 0}, 0}, // central face - {19, {2, 0, 2}, 1}, // ij quadrant - {17, {2, 2, 0}, 5}, // ki quadrant - {13, {0, 2, 2}, 3} // jk quadrant - }, - { - // face 19 - {19, {0, 0, 0}, 0}, // central face - {15, {2, 0, 2}, 1}, // ij quadrant - {18, {2, 2, 0}, 5}, // ki quadrant - {14, {0, 2, 2}, 3} // jk quadrant - }}; - -/** @brief direction from the origin face to the destination face, relative to - * the origin face's coordinate system, or -1 if not adjacent. - */ -static const int adjacentFaceDir[NUM_ICOSA_FACES][NUM_ICOSA_FACES] = { - {0, KI, -1, -1, IJ, JK, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 0 - {IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 1 - {-1, IJ, 0, KI, -1, -1, -1, JK, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 2 - {-1, -1, IJ, 0, KI, -1, -1, -1, JK, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 3 - {KI, -1, -1, IJ, 0, -1, -1, -1, -1, JK, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 4 - {JK, -1, -1, -1, -1, 0, -1, -1, -1, -1, - IJ, -1, -1, -1, KI, -1, -1, -1, -1, -1}, // face 5 - {-1, JK, -1, -1, -1, -1, 0, -1, -1, -1, - KI, IJ, -1, -1, -1, -1, -1, -1, -1, -1}, // face 6 - {-1, -1, JK, -1, -1, -1, -1, 0, -1, -1, - -1, KI, IJ, -1, -1, -1, -1, -1, -1, -1}, // face 7 - {-1, -1, -1, JK, -1, -1, -1, -1, 0, -1, - -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1}, // face 8 - {-1, -1, -1, -1, JK, -1, -1, -1, -1, 0, - -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1}, // face 9 - {-1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, - 0, -1, -1, -1, -1, JK, -1, -1, -1, -1}, // face 10 - {-1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, - -1, 0, -1, -1, -1, -1, JK, -1, -1, -1}, // face 11 - {-1, -1, -1, -1, -1, -1, -1, IJ, KI, -1, - -1, -1, 0, -1, -1, -1, -1, JK, -1, -1}, // face 12 - {-1, -1, -1, -1, -1, -1, -1, -1, IJ, KI, - -1, -1, -1, 0, -1, -1, -1, -1, JK, -1}, // face 13 - {-1, -1, -1, -1, -1, KI, -1, -1, -1, IJ, - -1, -1, -1, -1, 0, -1, -1, -1, -1, JK}, // face 14 - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - JK, -1, -1, -1, -1, 0, IJ, -1, -1, KI}, // face 15 - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, JK, -1, -1, -1, KI, 0, IJ, -1, -1}, // face 16 - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, JK, -1, -1, -1, KI, 0, IJ, -1}, // face 17 - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ}, // face 18 - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, JK, IJ, -1, -1, KI, 0} // face 19 -}; - -/** @brief overage distance table */ -static const int maxDimByCIIres[] = { - 2, // res 0 - -1, // res 1 - 14, // res 2 - -1, // res 3 - 98, // res 4 - -1, // res 5 - 686, // res 6 - -1, // res 7 - 4802, // res 8 - -1, // res 9 - 33614, // res 10 - -1, // res 11 - 235298, // res 12 - -1, // res 13 - 1647086, // res 14 - -1, // res 15 - 11529602 // res 16 -}; - -/** @brief unit scale distance table */ -static const int unitScaleByCIIres[] = { - 1, // res 0 - -1, // res 1 - 7, // res 2 - -1, // res 3 - 49, // res 4 - -1, // res 5 - 343, // res 6 - -1, // res 7 - 2401, // res 8 - -1, // res 9 - 16807, // res 10 - -1, // res 11 - 117649, // res 12 - -1, // res 13 - 823543, // res 14 - -1, // res 15 - 5764801 // res 16 -}; - -/** - * Encodes a coordinate on the sphere to the FaceIJK address of the containing - * cell at the specified resolution. - * - * @param g The spherical coordinates to encode. - * @param res The desired H3 resolution for the encoding. - * @param h The FaceIJK address of the containing cell at resolution res. - */ -void _geoToFaceIjk(const GeoCoord* g, int res, FaceIJK* h) { - // first convert to hex2d - Vec2d v; - _geoToHex2d(g, res, &h->face, &v); - - // then convert to ijk+ - _hex2dToCoordIJK(&v, &h->coord); -} - -/** - * Encodes a coordinate on the sphere to the corresponding icosahedral face and - * containing 2D hex coordinates relative to that face center. - * - * @param g The spherical coordinates to encode. - * @param res The desired H3 resolution for the encoding. - * @param face The icosahedral face containing the spherical coordinates. - * @param v The 2D hex coordinates of the cell containing the point. - */ -void _geoToHex2d(const GeoCoord* g, int res, int* face, Vec2d* v) { - Vec3d v3d; - _geoToVec3d(g, &v3d); - - // determine the icosahedron face - *face = 0; - double sqd = _pointSquareDist(&faceCenterPoint[0], &v3d); - for (int f = 1; f < NUM_ICOSA_FACES; f++) { - double sqdT = _pointSquareDist(&faceCenterPoint[f], &v3d); - if (sqdT < sqd) { - *face = f; - sqd = sqdT; - } - } - - // cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2 - double r = acos(1 - sqd / 2); - - if (r < EPSILON) { - v->x = v->y = 0.0L; - return; - } - - // now have face and r, now find CCW theta from CII i-axis - double theta = - _posAngleRads(faceAxesAzRadsCII[*face][0] - - _posAngleRads(_geoAzimuthRads(&faceCenterGeo[*face], g))); - - // adjust theta for Class III (odd resolutions) - if (isResClassIII(res)) theta = _posAngleRads(theta - M_AP7_ROT_RADS); - - // perform gnomonic scaling of r - r = tan(r); - - // scale for current resolution length u - r /= RES0_U_GNOMONIC; - for (int i = 0; i < res; i++) r *= M_SQRT7; - - // we now have (r, theta) in hex2d with theta ccw from x-axes - - // convert to local x,y - v->x = r * cos(theta); - v->y = r * sin(theta); -} - -/** - * Determines the center point in spherical coordinates of a cell given by 2D - * hex coordinates on a particular icosahedral face. - * - * @param v The 2D hex coordinates of the cell. - * @param face The icosahedral face upon which the 2D hex coordinate system is - * centered. - * @param res The H3 resolution of the cell. - * @param substrate Indicates whether or not this grid is actually a substrate - * grid relative to the specified resolution. - * @param g The spherical coordinates of the cell center point. - */ -void _hex2dToGeo(const Vec2d* v, int face, int res, int substrate, - GeoCoord* g) { - // calculate (r, theta) in hex2d - double r = _v2dMag(v); - - if (r < EPSILON) { - *g = faceCenterGeo[face]; - return; - } - - double theta = atan2(v->y, v->x); - - // scale for current resolution length u - for (int i = 0; i < res; i++) r /= M_SQRT7; - - // scale accordingly if this is a substrate grid - if (substrate) { - r /= 3.0; - if (isResClassIII(res)) r /= M_SQRT7; - } - - r *= RES0_U_GNOMONIC; - - // perform inverse gnomonic scaling of r - r = atan(r); - - // adjust theta for Class III - // if a substrate grid, then it's already been adjusted for Class III - if (!substrate && isResClassIII(res)) - theta = _posAngleRads(theta + M_AP7_ROT_RADS); - - // find theta as an azimuth - theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta); - - // now find the point at (r,theta) from the face center - _geoAzDistanceRads(&faceCenterGeo[face], theta, r, g); -} - -/** - * Determines the center point in spherical coordinates of a cell given by - * a FaceIJK address at a specified resolution. - * - * @param h The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. - * @param g The spherical coordinates of the cell center point. - */ -void _faceIjkToGeo(const FaceIJK* h, int res, GeoCoord* g) { - Vec2d v; - _ijkToHex2d(&h->coord, &v); - _hex2dToGeo(&v, h->face, res, 0, g); -} - -/** - * Generates the cell boundary in spherical coordinates for a pentagonal cell - * given by a FaceIJK address at a specified resolution. - * - * @param h The FaceIJK address of the pentagonal cell. - * @param res The H3 resolution of the cell. - * @param start The first topological vertex to return. - * @param length The number of topological vertexes to return. - * @param g The spherical coordinates of the cell boundary. - */ -void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, int start, int length, - GeoBoundary* g) { - int adjRes = res; - FaceIJK centerIJK = *h; - FaceIJK fijkVerts[NUM_PENT_VERTS]; - _faceIjkPentToVerts(¢erIJK, &adjRes, fijkVerts); - - // If we're returning the entire loop, we need one more iteration in case - // of a distortion vertex on the last edge - int additionalIteration = length == NUM_PENT_VERTS ? 1 : 0; - - // convert each vertex to lat/lon - // adjust the face of each vertex as appropriate and introduce - // edge-crossing vertices as needed - g->numVerts = 0; - FaceIJK lastFijk; - for (int vert = start; vert < start + length + additionalIteration; - vert++) { - int v = vert % NUM_PENT_VERTS; - - FaceIJK fijk = fijkVerts[v]; - - _adjustPentVertOverage(&fijk, adjRes); - - // all Class III pentagon edges cross icosa edges - // note that Class II pentagons have vertices on the edge, - // not edge intersections - if (isResClassIII(res) && vert > start) { - // find hex2d of the two vertexes on the last face - - FaceIJK tmpFijk = fijk; - - Vec2d orig2d0; - _ijkToHex2d(&lastFijk.coord, &orig2d0); - - int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; - - const FaceOrientIJK* fijkOrient = - &faceNeighbors[tmpFijk.face][currentToLastDir]; - - tmpFijk.face = fijkOrient->face; - CoordIJK* ijk = &tmpFijk.coord; - - // rotate and translate for adjacent face - for (int i = 0; i < fijkOrient->ccwRot60; i++) _ijkRotate60ccw(ijk); - - CoordIJK transVec = fijkOrient->translate; - _ijkScale(&transVec, unitScaleByCIIres[adjRes] * 3); - _ijkAdd(ijk, &transVec, ijk); - _ijkNormalize(ijk); - - Vec2d orig2d1; - _ijkToHex2d(ijk, &orig2d1); - - // find the appropriate icosa face edge vertexes - int maxDim = maxDimByCIIres[adjRes]; - Vec2d v0 = {3.0 * maxDim, 0.0}; - Vec2d v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; - Vec2d v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; - - Vec2d* edge0; - Vec2d* edge1; - switch (adjacentFaceDir[tmpFijk.face][fijk.face]) { - case IJ: - edge0 = &v0; - edge1 = &v1; - break; - case JK: - edge0 = &v1; - edge1 = &v2; - break; - case KI: - default: - assert(adjacentFaceDir[tmpFijk.face][fijk.face] == KI); - edge0 = &v2; - edge1 = &v0; - break; - } - - // find the intersection and add the lat/lon point to the result - Vec2d inter; - _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); - _hex2dToGeo(&inter, tmpFijk.face, adjRes, 1, - &g->verts[g->numVerts]); - g->numVerts++; - } - - // convert vertex to lat/lon and add to the result - // vert == start + NUM_PENT_VERTS is only used to test for possible - // intersection on last edge - if (vert < start + NUM_PENT_VERTS) { - Vec2d vec; - _ijkToHex2d(&fijk.coord, &vec); - _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); - g->numVerts++; - } - - lastFijk = fijk; - } -} - -/** - * Get the vertices of a pentagon cell as substrate FaceIJK addresses - * - * @param fijk The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. This may be adjusted if - * necessary for the substrate grid resolution. - * @param fijkVerts Output array for the vertices - */ -void _faceIjkPentToVerts(FaceIJK* fijk, int* res, FaceIJK* fijkVerts) { - // the vertexes of an origin-centered pentagon in a Class II resolution on a - // substrate grid with aperture sequence 33r. The aperture 3 gets us the - // vertices, and the 3r gets us back to Class II. - // vertices listed ccw from the i-axes - CoordIJK vertsCII[NUM_PENT_VERTS] = { - {2, 1, 0}, // 0 - {1, 2, 0}, // 1 - {0, 2, 1}, // 2 - {0, 1, 2}, // 3 - {1, 0, 2}, // 4 - }; - - // the vertexes of an origin-centered pentagon in a Class III resolution on - // a substrate grid with aperture sequence 33r7r. The aperture 3 gets us the - // vertices, and the 3r7r gets us to Class II. vertices listed ccw from the - // i-axes - CoordIJK vertsCIII[NUM_PENT_VERTS] = { - {5, 4, 0}, // 0 - {1, 5, 0}, // 1 - {0, 5, 4}, // 2 - {0, 1, 5}, // 3 - {4, 0, 5}, // 4 - }; - - // get the correct set of substrate vertices for this resolution - CoordIJK* verts; - if (isResClassIII(*res)) - verts = vertsCIII; - else - verts = vertsCII; - - // adjust the center point to be in an aperture 33r substrate grid - // these should be composed for speed - _downAp3(&fijk->coord); - _downAp3r(&fijk->coord); - - // if res is Class III we need to add a cw aperture 7 to get to - // icosahedral Class II - if (isResClassIII(*res)) { - _downAp7r(&fijk->coord); - *res += 1; - } - - // The center point is now in the same substrate grid as the origin - // cell vertices. Add the center point substate coordinates - // to each vertex to translate the vertices to that cell. - for (int v = 0; v < NUM_PENT_VERTS; v++) { - fijkVerts[v].face = fijk->face; - _ijkAdd(&fijk->coord, &verts[v], &fijkVerts[v].coord); - _ijkNormalize(&fijkVerts[v].coord); - } -} - -/** - * Generates the cell boundary in spherical coordinates for a cell given by a - * FaceIJK address at a specified resolution. - * - * @param h The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. - * @param start The first topological vertex to return. - * @param length The number of topological vertexes to return. - * @param g The spherical coordinates of the cell boundary. - */ -void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int start, int length, - GeoBoundary* g) { - int adjRes = res; - FaceIJK centerIJK = *h; - FaceIJK fijkVerts[NUM_HEX_VERTS]; - _faceIjkToVerts(¢erIJK, &adjRes, fijkVerts); - - // If we're returning the entire loop, we need one more iteration in case - // of a distortion vertex on the last edge - int additionalIteration = length == NUM_HEX_VERTS ? 1 : 0; - - // convert each vertex to lat/lon - // adjust the face of each vertex as appropriate and introduce - // edge-crossing vertices as needed - g->numVerts = 0; - int lastFace = -1; - Overage lastOverage = NO_OVERAGE; - for (int vert = start; vert < start + length + additionalIteration; - vert++) { - int v = vert % NUM_HEX_VERTS; - - FaceIJK fijk = fijkVerts[v]; - - const int pentLeading4 = 0; - Overage overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); - - /* - Check for edge-crossing. Each face of the underlying icosahedron is a - different projection plane. So if an edge of the hexagon crosses an - icosahedron edge, an additional vertex must be introduced at that - intersection point. Then each half of the cell edge can be projected - to geographic coordinates using the appropriate icosahedron face - projection. Note that Class II cell edges have vertices on the face - edge, with no edge line intersections. - */ - if (isResClassIII(res) && vert > start && fijk.face != lastFace && - lastOverage != FACE_EDGE) { - // find hex2d of the two vertexes on original face - int lastV = (v + 5) % NUM_HEX_VERTS; - Vec2d orig2d0; - _ijkToHex2d(&fijkVerts[lastV].coord, &orig2d0); - - Vec2d orig2d1; - _ijkToHex2d(&fijkVerts[v].coord, &orig2d1); - - // find the appropriate icosa face edge vertexes - int maxDim = maxDimByCIIres[adjRes]; - Vec2d v0 = {3.0 * maxDim, 0.0}; - Vec2d v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; - Vec2d v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; - - int face2 = ((lastFace == centerIJK.face) ? fijk.face : lastFace); - Vec2d* edge0; - Vec2d* edge1; - switch (adjacentFaceDir[centerIJK.face][face2]) { - case IJ: - edge0 = &v0; - edge1 = &v1; - break; - case JK: - edge0 = &v1; - edge1 = &v2; - break; - // case KI: - default: - assert(adjacentFaceDir[centerIJK.face][face2] == KI); - edge0 = &v2; - edge1 = &v0; - break; - } - - // find the intersection and add the lat/lon point to the result - Vec2d inter; - _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); - /* - If a point of intersection occurs at a hexagon vertex, then each - adjacent hexagon edge will lie completely on a single icosahedron - face, and no additional vertex is required. - */ - bool isIntersectionAtVertex = - _v2dEquals(&orig2d0, &inter) || _v2dEquals(&orig2d1, &inter); - if (!isIntersectionAtVertex) { - _hex2dToGeo(&inter, centerIJK.face, adjRes, 1, - &g->verts[g->numVerts]); - g->numVerts++; - } - } - - // convert vertex to lat/lon and add to the result - // vert == start + NUM_HEX_VERTS is only used to test for possible - // intersection on last edge - if (vert < start + NUM_HEX_VERTS) { - Vec2d vec; - _ijkToHex2d(&fijk.coord, &vec); - _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); - g->numVerts++; - } - - lastFace = fijk.face; - lastOverage = overage; - } -} - -/** - * Get the vertices of a cell as substrate FaceIJK addresses - * - * @param fijk The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. This may be adjusted if - * necessary for the substrate grid resolution. - * @param fijkVerts Output array for the vertices - */ -void _faceIjkToVerts(FaceIJK* fijk, int* res, FaceIJK* fijkVerts) { - // the vertexes of an origin-centered cell in a Class II resolution on a - // substrate grid with aperture sequence 33r. The aperture 3 gets us the - // vertices, and the 3r gets us back to Class II. - // vertices listed ccw from the i-axes - CoordIJK vertsCII[NUM_HEX_VERTS] = { - {2, 1, 0}, // 0 - {1, 2, 0}, // 1 - {0, 2, 1}, // 2 - {0, 1, 2}, // 3 - {1, 0, 2}, // 4 - {2, 0, 1} // 5 - }; - - // the vertexes of an origin-centered cell in a Class III resolution on a - // substrate grid with aperture sequence 33r7r. The aperture 3 gets us the - // vertices, and the 3r7r gets us to Class II. - // vertices listed ccw from the i-axes - CoordIJK vertsCIII[NUM_HEX_VERTS] = { - {5, 4, 0}, // 0 - {1, 5, 0}, // 1 - {0, 5, 4}, // 2 - {0, 1, 5}, // 3 - {4, 0, 5}, // 4 - {5, 0, 1} // 5 - }; - - // get the correct set of substrate vertices for this resolution - CoordIJK* verts; - if (isResClassIII(*res)) - verts = vertsCIII; - else - verts = vertsCII; - - // adjust the center point to be in an aperture 33r substrate grid - // these should be composed for speed - _downAp3(&fijk->coord); - _downAp3r(&fijk->coord); - - // if res is Class III we need to add a cw aperture 7 to get to - // icosahedral Class II - if (isResClassIII(*res)) { - _downAp7r(&fijk->coord); - *res += 1; - } - - // The center point is now in the same substrate grid as the origin - // cell vertices. Add the center point substate coordinates - // to each vertex to translate the vertices to that cell. - for (int v = 0; v < NUM_HEX_VERTS; v++) { - fijkVerts[v].face = fijk->face; - _ijkAdd(&fijk->coord, &verts[v], &fijkVerts[v].coord); - _ijkNormalize(&fijkVerts[v].coord); - } -} - -/** - * Adjusts a FaceIJK address in place so that the resulting cell address is - * relative to the correct icosahedral face. - * - * @param fijk The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. - * @param pentLeading4 Whether or not the cell is a pentagon with a leading - * digit 4. - * @param substrate Whether or not the cell is in a substrate grid. - * @return 0 if on original face (no overage); 1 if on face edge (only occurs - * on substrate grids); 2 if overage on new face interior - */ -Overage _adjustOverageClassII(FaceIJK* fijk, int res, int pentLeading4, - int substrate) { - Overage overage = NO_OVERAGE; - - CoordIJK* ijk = &fijk->coord; - - // get the maximum dimension value; scale if a substrate grid - int maxDim = maxDimByCIIres[res]; - if (substrate) maxDim *= 3; - - // check for overage - if (substrate && ijk->i + ijk->j + ijk->k == maxDim) // on edge - overage = FACE_EDGE; - else if (ijk->i + ijk->j + ijk->k > maxDim) // overage - { - overage = NEW_FACE; - - const FaceOrientIJK* fijkOrient; - if (ijk->k > 0) { - if (ijk->j > 0) // jk "quadrant" - fijkOrient = &faceNeighbors[fijk->face][JK]; - else // ik "quadrant" - { - fijkOrient = &faceNeighbors[fijk->face][KI]; - - // adjust for the pentagonal missing sequence - if (pentLeading4) { - // translate origin to center of pentagon - CoordIJK origin; - _setIJK(&origin, maxDim, 0, 0); - CoordIJK tmp; - _ijkSub(ijk, &origin, &tmp); - // rotate to adjust for the missing sequence - _ijkRotate60cw(&tmp); - // translate the origin back to the center of the triangle - _ijkAdd(&tmp, &origin, ijk); - } - } - } else // ij "quadrant" - fijkOrient = &faceNeighbors[fijk->face][IJ]; - - fijk->face = fijkOrient->face; - - // rotate and translate for adjacent face - for (int i = 0; i < fijkOrient->ccwRot60; i++) _ijkRotate60ccw(ijk); - - CoordIJK transVec = fijkOrient->translate; - int unitScale = unitScaleByCIIres[res]; - if (substrate) unitScale *= 3; - _ijkScale(&transVec, unitScale); - _ijkAdd(ijk, &transVec, ijk); - _ijkNormalize(ijk); - - // overage points on pentagon boundaries can end up on edges - if (substrate && ijk->i + ijk->j + ijk->k == maxDim) // on edge - overage = FACE_EDGE; - } - - return overage; -} - -/** - * Adjusts a FaceIJK address for a pentagon vertex in a substrate grid in - * place so that the resulting cell address is relative to the correct - * icosahedral face. - * - * @param fijk The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. - */ -Overage _adjustPentVertOverage(FaceIJK* fijk, int res) { - int pentLeading4 = 0; - Overage overage; - do { - overage = _adjustOverageClassII(fijk, res, pentLeading4, 1); - } while (overage == NEW_FACE); - return overage; -} diff --git a/v3/h3_faceijk.h b/v3/h3_faceijk.h deleted file mode 100644 index eb18990..0000000 --- a/v3/h3_faceijk.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2016-2019 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. - */ -/** @file faceijk.h - * @brief FaceIJK functions including conversion to/from lat/lon. - * - * References the Vec2d cartesian coordinate systems hex2d: local face-centered - * coordinate system scaled a specific H3 grid resolution unit length and - * with x-axes aligned with the local i-axes - */ - -#ifndef FACEIJK_H -#define FACEIJK_H - -#include "h3_coordijk.h" -#include "h3_geoCoord.h" -#include "h3_vec2d.h" - -/** @struct FaceIJK - * @brief Face number and ijk coordinates on that face-centered coordinate - * system - */ -typedef struct { - int face; ///< face number - CoordIJK coord; ///< ijk coordinates on that face -} FaceIJK; - -/** @struct FaceOrientIJK - * @brief Information to transform into an adjacent face IJK system - */ -typedef struct { - int face; ///< face number - CoordIJK translate; ///< res 0 translation relative to primary face - int ccwRot60; ///< number of 60 degree ccw rotations relative to primary - /// face -} FaceOrientIJK; - -extern const GeoCoord faceCenterGeo[NUM_ICOSA_FACES]; - -// indexes for faceNeighbors table -/** IJ quadrant faceNeighbors table direction */ -#define IJ 1 -/** KI quadrant faceNeighbors table direction */ -#define KI 2 -/** JK quadrant faceNeighbors table direction */ -#define JK 3 - -/** Invalid face index */ -#define INVALID_FACE -1 - -/** Digit representing overage type */ -typedef enum { - /** No overage (on original face) */ - NO_OVERAGE = 0, - /** On face edge (only occurs on substrate grids) */ - FACE_EDGE = 1, - /** Overage on new face interior */ - NEW_FACE = 2 -} Overage; - -// Internal functions - -void _geoToFaceIjk(const GeoCoord* g, int res, FaceIJK* h); -void _geoToHex2d(const GeoCoord* g, int res, int* face, Vec2d* v); -void _faceIjkToGeo(const FaceIJK* h, int res, GeoCoord* g); -void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int start, int length, - GeoBoundary* g); -void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, int start, int length, - GeoBoundary* g); -void _faceIjkToVerts(FaceIJK* fijk, int* res, FaceIJK* fijkVerts); -void _faceIjkPentToVerts(FaceIJK* fijk, int* res, FaceIJK* fijkVerts); -void _hex2dToGeo(const Vec2d* v, int face, int res, int substrate, GeoCoord* g); -Overage _adjustOverageClassII(FaceIJK* fijk, int res, int pentLeading4, - int substrate); -Overage _adjustPentVertOverage(FaceIJK* fijk, int res); - -#endif diff --git a/v3/h3_h3Index.c b/v3/h3_h3Index.c deleted file mode 100644 index 31cdab6..0000000 --- a/v3/h3_h3Index.c +++ /dev/null @@ -1,974 +0,0 @@ -/* - * Copyright 2016-2019 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. - */ -/** @file h3Index.c - * @brief H3Index utility functions - * (see h3api.h for the main library entry functions) - */ -#include "h3_h3Index.h" - - -#include -#include -#include -#include - -#include "h3_alloc.h" -#include "h3_baseCells.h" -#include "h3_faceijk.h" -#include "h3_mathExtensions.h" - -/** - * Returns the H3 resolution of an H3 index. - * @param h The H3 index. - * @return The resolution of the H3 index argument. - */ -int H3_EXPORT(h3GetResolution)(H3Index h) { return H3_GET_RESOLUTION(h); } - -/** - * Returns the H3 base cell "number" of an H3 cell (hexagon or pentagon). - * - * Note: Technically works on H3 edges, but will return base cell of the - * origin cell. - * - * @param h The H3 cell. - * @return The base cell "number" of the H3 cell argument. - */ -int H3_EXPORT(h3GetBaseCell)(H3Index h) { return H3_GET_BASE_CELL(h); } - -/** - * Converts a string representation of an H3 index into an H3 index. - * @param str The string representation of an H3 index. - * @return The H3 index corresponding to the string argument, or H3_NULL if - * invalid. - */ -H3Index H3_EXPORT(stringToH3)(const char* str) { - H3Index h = H3_NULL; - // If failed, h will be unmodified and we should return H3_NULL anyways. - sscanf(str, "%" PRIx64, &h); - return h; -} - -/** - * Converts an H3 index into a string representation. - * @param h The H3 index to convert. - * @param str The string representation of the H3 index. - * @param sz Size of the buffer `str` - */ -void H3_EXPORT(h3ToString)(H3Index h, char* str, size_t sz) { - // An unsigned 64 bit integer will be expressed in at most - // 16 digits plus 1 for the null terminator. - if (sz < 17) { - // Buffer is potentially not large enough. - return; - } - sprintf(str, "%" PRIx64, h); -} - -/** - * Returns whether or not an H3 index is a valid cell (hexagon or pentagon). - * @param h The H3 index to validate. - * @return 1 if the H3 index if valid, and 0 if it is not. - */ -int H3_EXPORT(h3IsValid)(H3Index h) { - if (H3_GET_HIGH_BIT(h) != 0) return 0; - - if (H3_GET_MODE(h) != H3_HEXAGON_MODE) return 0; - - if (H3_GET_RESERVED_BITS(h) != 0) return 0; - - int baseCell = H3_GET_BASE_CELL(h); - if (baseCell < 0 || baseCell >= NUM_BASE_CELLS) return 0; - - int res = H3_GET_RESOLUTION(h); - if (res < 0 || res > MAX_H3_RES) return 0; - - bool foundFirstNonZeroDigit = false; - for (int r = 1; r <= res; r++) { - Direction digit = H3_GET_INDEX_DIGIT(h, r); - - if (!foundFirstNonZeroDigit && digit != CENTER_DIGIT) { - foundFirstNonZeroDigit = true; - if (_isBaseCellPentagon(baseCell) && digit == K_AXES_DIGIT) { - return 0; - } - } - - if (digit < CENTER_DIGIT || digit >= NUM_DIGITS) return 0; - } - - for (int r = res + 1; r <= MAX_H3_RES; r++) { - Direction digit = H3_GET_INDEX_DIGIT(h, r); - if (digit != INVALID_DIGIT) return 0; - } - - return 1; -} - -/** - * Initializes an H3 index. - * @param hp The H3 index to initialize. - * @param res The H3 resolution to initialize the index to. - * @param baseCell The H3 base cell to initialize the index to. - * @param initDigit The H3 digit (0-7) to initialize all of the index digits to. - */ -void setH3Index(H3Index* hp, int res, int baseCell, Direction initDigit) { - H3Index h = H3_INIT; - H3_SET_MODE(h, H3_HEXAGON_MODE); - H3_SET_RESOLUTION(h, res); - H3_SET_BASE_CELL(h, baseCell); - for (int r = 1; r <= res; r++) H3_SET_INDEX_DIGIT(h, r, initDigit); - *hp = h; -} - -/** - * h3ToParent produces the parent index for a given H3 index - * - * @param h H3Index to find parent of - * @param parentRes The resolution to switch to (parent, grandparent, etc) - * - * @return H3Index of the parent, or H3_NULL if you actually asked for a child - */ -H3Index H3_EXPORT(h3ToParent)(H3Index h, int parentRes) { - int childRes = H3_GET_RESOLUTION(h); - if (parentRes > childRes) { - return H3_NULL; - } else if (parentRes == childRes) { - return h; - } else if (parentRes < 0 || parentRes > MAX_H3_RES) { - return H3_NULL; - } - H3Index parentH = H3_SET_RESOLUTION(h, parentRes); - for (int i = parentRes + 1; i <= childRes; i++) { - H3_SET_INDEX_DIGIT(parentH, i, H3_DIGIT_MASK); - } - return parentH; -} - -/** - * Determines whether one resolution is a valid child resolution of another. - * Each resolution is considered a valid child resolution of itself. - * - * @param parentRes int resolution of the parent - * @param childRes int resolution of the child - * - * @return The validity of the child resolution - */ -static bool _isValidChildRes(int parentRes, int childRes) { - if (childRes < parentRes || childRes > MAX_H3_RES) { - return false; - } - return true; -} - -/** - * maxH3ToChildrenSize returns the maximum number of children possible for a - * given child level. - * - * @param h H3Index to find the number of children of - * @param childRes The resolution of the child level you're interested in - * - * @return int count of maximum number of children (equal for hexagons, less for - * pentagons - */ -int H3_EXPORT(maxH3ToChildrenSize)(H3Index h, int childRes) { - int parentRes = H3_GET_RESOLUTION(h); - if (!_isValidChildRes(parentRes, childRes)) { - return 0; - } - return _ipow(7, (childRes - parentRes)); -} - -/** - * makeDirectChild takes an index and immediately returns the immediate child - * index based on the specified cell number. Bit operations only, could generate - * invalid indexes if not careful (deleted cell under a pentagon). - * - * @param h H3Index to find the direct child of - * @param cellNumber int id of the direct child (0-6) - * - * @return The new H3Index for the child - */ -H3Index makeDirectChild(H3Index h, int cellNumber) { - int childRes = H3_GET_RESOLUTION(h) + 1; - H3Index childH = H3_SET_RESOLUTION(h, childRes); - H3_SET_INDEX_DIGIT(childH, childRes, cellNumber); - return childH; -} - -/** - * h3ToChildren takes the given hexagon id and generates all of the children - * at the specified resolution storing them into the provided memory pointer. - * It's assumed that maxH3ToChildrenSize was used to determine the allocation. - * - * @param h H3Index to find the children of - * @param childRes int the child level to produce - * @param children H3Index* the memory to store the resulting addresses in - */ -void H3_EXPORT(h3ToChildren)(H3Index h, int childRes, H3Index* children) { - int parentRes = H3_GET_RESOLUTION(h); - if (!_isValidChildRes(parentRes, childRes)) { - return; - } else if (parentRes == childRes) { - *children = h; - return; - } - int bufferSize = H3_EXPORT(maxH3ToChildrenSize)(h, childRes); - int bufferChildStep = (bufferSize / 7); - int isAPentagon = H3_EXPORT(h3IsPentagon)(h); - for (int i = 0; i < 7; i++) { - if (isAPentagon && i == K_AXES_DIGIT) { - H3Index* nextChild = children + bufferChildStep; - while (children < nextChild) { - *children = H3_NULL; - children++; - } - } else { - H3_EXPORT(h3ToChildren)(makeDirectChild(h, i), childRes, children); - children += bufferChildStep; - } - } -} - -/** - * h3ToCenterChild produces the center child index for a given H3 index at - * the specified resolution - * - * @param h H3Index to find center child of - * @param childRes The resolution to switch to - * - * @return H3Index of the center child, or H3_NULL if you actually asked for a - * parent - */ -H3Index H3_EXPORT(h3ToCenterChild)(H3Index h, int childRes) { - int parentRes = H3_GET_RESOLUTION(h); - if (!_isValidChildRes(parentRes, childRes)) { - return H3_NULL; - } else if (childRes == parentRes) { - return h; - } - H3Index child = H3_SET_RESOLUTION(h, childRes); - for (int i = parentRes + 1; i <= childRes; i++) { - H3_SET_INDEX_DIGIT(child, i, 0); - } - return child; -} - -/** - * compact takes a set of hexagons all at the same resolution and compresses - * them by pruning full child branches to the parent level. This is also done - * for all parents recursively to get the minimum number of hex addresses that - * perfectly cover the defined space. - * @param h3Set Set of hexagons - * @param compactedSet The output array of compressed hexagons (preallocated) - * @param numHexes The size of the input and output arrays (possible that no - * contiguous regions exist in the set at all and no compression possible) - * @return an error code on bad input data - */ -int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet, - const int numHexes) { - if (numHexes == 0) { - return COMPACT_SUCCESS; - } - int res = H3_GET_RESOLUTION(h3Set[0]); - if (res == 0) { - // No compaction possible, just copy the set to output - for (int i = 0; i < numHexes; i++) { - compactedSet[i] = h3Set[i]; - } - return COMPACT_SUCCESS; - } - H3Index* remainingHexes = H3_MEMORY(malloc)(numHexes * sizeof(H3Index)); - if (!remainingHexes) { - return COMPACT_ALLOC_FAILED; - } - memcpy(remainingHexes, h3Set, numHexes * sizeof(H3Index)); - H3Index* hashSetArray = H3_MEMORY(calloc)(numHexes, sizeof(H3Index)); - if (!hashSetArray) { - H3_MEMORY(free)(remainingHexes); - return COMPACT_ALLOC_FAILED; - } - H3Index* compactedSetOffset = compactedSet; - int numRemainingHexes = numHexes; - while (numRemainingHexes) { - res = H3_GET_RESOLUTION(remainingHexes[0]); - int parentRes = res - 1; - // Put the parents of the hexagons into the temp array - // via a hashing mechanism, and use the reserved bits - // to track how many times a parent is duplicated - for (int i = 0; i < numRemainingHexes; i++) { - H3Index currIndex = remainingHexes[i]; - if (currIndex != 0) { - H3Index parent = H3_EXPORT(h3ToParent)(currIndex, parentRes); - // Modulus hash the parent into the temp array - int loc = (int)(parent % numRemainingHexes); - int loopCount = 0; - while (hashSetArray[loc] != 0) { - if (loopCount > numRemainingHexes) { // LCOV_EXCL_BR_LINE - // LCOV_EXCL_START - // This case should not be possible because at most one - // index is placed into hashSetArray per - // numRemainingHexes. - H3_MEMORY(free)(remainingHexes); - H3_MEMORY(free)(hashSetArray); - return COMPACT_LOOP_EXCEEDED; - // LCOV_EXCL_STOP - } - H3Index tempIndex = - hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; - if (tempIndex == parent) { - int count = H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; - int limitCount = 7; - if (H3_EXPORT(h3IsPentagon)( - tempIndex & H3_RESERVED_MASK_NEGATIVE)) { - limitCount--; - } - // One is added to count for this check to match one - // being added to count later in this function when - // checking for all children being present. - if (count + 1 > limitCount) { - // Only possible on duplicate input - H3_MEMORY(free)(remainingHexes); - H3_MEMORY(free)(hashSetArray); - return COMPACT_DUPLICATE; - } - H3_SET_RESERVED_BITS(parent, count); - hashSetArray[loc] = H3_NULL; - } else { - loc = (loc + 1) % numRemainingHexes; - } - loopCount++; - } - hashSetArray[loc] = parent; - } - } - // Determine which parent hexagons have a complete set - // of children and put them in the compactableHexes array - int compactableCount = 0; - int maxCompactableCount = - numRemainingHexes / 6; // Somehow all pentagons; conservative - if (maxCompactableCount == 0) { - memcpy(compactedSetOffset, remainingHexes, - numRemainingHexes * sizeof(remainingHexes[0])); - break; - } - H3Index* compactableHexes = - H3_MEMORY(calloc)(maxCompactableCount, sizeof(H3Index)); - if (!compactableHexes) { - H3_MEMORY(free)(remainingHexes); - H3_MEMORY(free)(hashSetArray); - return COMPACT_ALLOC_FAILED; - } - for (int i = 0; i < numRemainingHexes; i++) { - if (hashSetArray[i] == 0) continue; - int count = H3_GET_RESERVED_BITS(hashSetArray[i]) + 1; - // Include the deleted direction for pentagons as implicitly "there" - if (H3_EXPORT(h3IsPentagon)(hashSetArray[i] & - H3_RESERVED_MASK_NEGATIVE)) { - // We need this later on, no need to recalculate - H3_SET_RESERVED_BITS(hashSetArray[i], count); - // Increment count after setting the reserved bits, - // since count is already incremented above, so it - // will be the expected value for a complete hexagon. - count++; - } - if (count == 7) { - // Bingo! Full set! - compactableHexes[compactableCount] = - hashSetArray[i] & H3_RESERVED_MASK_NEGATIVE; - compactableCount++; - } - } - // Uncompactable hexes are immediately copied into the - // output compactedSetOffset - int uncompactableCount = 0; - for (int i = 0; i < numRemainingHexes; i++) { - H3Index currIndex = remainingHexes[i]; - if (currIndex != H3_NULL) { - H3Index parent = H3_EXPORT(h3ToParent)(currIndex, parentRes); - // Modulus hash the parent into the temp array - // to determine if this index was included in - // the compactableHexes array - int loc = (int)(parent % numRemainingHexes); - int loopCount = 0; - bool isUncompactable = true; - do { - if (loopCount > numRemainingHexes) { // LCOV_EXCL_BR_LINE - // LCOV_EXCL_START - // This case should not be possible because at most one - // index is placed into hashSetArray per input hexagon. - H3_MEMORY(free)(compactableHexes); - H3_MEMORY(free)(remainingHexes); - H3_MEMORY(free)(hashSetArray); - return COMPACT_LOOP_EXCEEDED; - // LCOV_EXCL_STOP - } - H3Index tempIndex = - hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; - if (tempIndex == parent) { - int count = H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; - if (count == 7) { - isUncompactable = false; - } - break; - } else { - loc = (loc + 1) % numRemainingHexes; - } - loopCount++; - } while (hashSetArray[loc] != parent); - if (isUncompactable) { - compactedSetOffset[uncompactableCount] = remainingHexes[i]; - uncompactableCount++; - } - } - } - // Set up for the next loop - memset(hashSetArray, 0, numHexes * sizeof(H3Index)); - compactedSetOffset += uncompactableCount; - memcpy(remainingHexes, compactableHexes, - compactableCount * sizeof(H3Index)); - numRemainingHexes = compactableCount; - H3_MEMORY(free)(compactableHexes); - } - H3_MEMORY(free)(remainingHexes); - H3_MEMORY(free)(hashSetArray); - return COMPACT_SUCCESS; -} - -/** - * uncompact takes a compressed set of hexagons and expands back to the - * original set of hexagons. - * @param compactedSet Set of hexagons - * @param numHexes The number of hexes in the input set - * @param h3Set Output array of decompressed hexagons (preallocated) - * @param maxHexes The size of the output array to bound check against - * @param res The hexagon resolution to decompress to - * @return An error code if output array is too small or any hexagon is - * smaller than the output resolution. - */ -int H3_EXPORT(uncompact)(const H3Index* compactedSet, const int numHexes, - H3Index* h3Set, const int maxHexes, const int res) { - int outOffset = 0; - for (int i = 0; i < numHexes; i++) { - if (compactedSet[i] == 0) continue; - if (outOffset >= maxHexes) { - // We went too far, abort! - return -1; - } - int currentRes = H3_GET_RESOLUTION(compactedSet[i]); - if (!_isValidChildRes(currentRes, res)) { - // Nonsensical. Abort. - return -2; - } - if (currentRes == res) { - // Just copy and move along - h3Set[outOffset] = compactedSet[i]; - outOffset++; - } else { - // Bigger hexagon to reduce in size - int numHexesToGen = - H3_EXPORT(maxH3ToChildrenSize)(compactedSet[i], res); - if (outOffset + numHexesToGen > maxHexes) { - // We're about to go too far, abort! - return -1; - } - H3_EXPORT(h3ToChildren)(compactedSet[i], res, h3Set + outOffset); - outOffset += numHexesToGen; - } - } - return 0; -} - -/** - * maxUncompactSize takes a compacted set of hexagons are provides an - * upper-bound estimate of the size of the uncompacted set of hexagons. - * @param compactedSet Set of hexagons - * @param numHexes The number of hexes in the input set - * @param res The hexagon resolution to decompress to - * @return The number of hexagons to allocate memory for, or a negative - * number if an error occurs. - */ -int H3_EXPORT(maxUncompactSize)(const H3Index* compactedSet, const int numHexes, - const int res) { - int maxNumHexagons = 0; - for (int i = 0; i < numHexes; i++) { - if (compactedSet[i] == 0) continue; - int currentRes = H3_GET_RESOLUTION(compactedSet[i]); - if (!_isValidChildRes(currentRes, res)) { - // Nonsensical. Abort. - return -1; - } - if (currentRes == res) { - maxNumHexagons++; - } else { - // Bigger hexagon to reduce in size - int numHexesToGen = - H3_EXPORT(maxH3ToChildrenSize)(compactedSet[i], res); - maxNumHexagons += numHexesToGen; - } - } - return maxNumHexagons; -} - -/** - * h3IsResClassIII takes a hexagon ID and determines if it is in a - * Class III resolution (rotated versus the icosahedron and subject - * to shape distortion adding extra points on icosahedron edges, making - * them not true hexagons). - * @param h The H3Index to check. - * @return Returns 1 if the hexagon is class III, otherwise 0. - */ -int H3_EXPORT(h3IsResClassIII)(H3Index h) { return H3_GET_RESOLUTION(h) % 2; } - -/** - * h3IsPentagon takes an H3Index and determines if it is actually a - * pentagon. - * @param h The H3Index to check. - * @return Returns 1 if it is a pentagon, otherwise 0. - */ -int H3_EXPORT(h3IsPentagon)(H3Index h) { - return _isBaseCellPentagon(H3_GET_BASE_CELL(h)) && - !_h3LeadingNonZeroDigit(h); -} - -/** - * Returns the highest resolution non-zero digit in an H3Index. - * @param h The H3Index. - * @return The highest resolution non-zero digit in the H3Index. - */ -Direction _h3LeadingNonZeroDigit(H3Index h) { - for (int r = 1; r <= H3_GET_RESOLUTION(h); r++) - if (H3_GET_INDEX_DIGIT(h, r)) return H3_GET_INDEX_DIGIT(h, r); - - // if we're here it's all 0's - return CENTER_DIGIT; -} - -/** - * Rotate an H3Index 60 degrees counter-clockwise about a pentagonal center. - * @param h The H3Index. - */ -H3Index _h3RotatePent60ccw(H3Index h) { - // rotate in place; skips any leading 1 digits (k-axis) - - int foundFirstNonZeroDigit = 0; - for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { - // rotate this digit - H3_SET_INDEX_DIGIT(h, r, _rotate60ccw(H3_GET_INDEX_DIGIT(h, r))); - - // look for the first non-zero digit so we - // can adjust for deleted k-axes sequence - // if necessary - if (!foundFirstNonZeroDigit && H3_GET_INDEX_DIGIT(h, r) != 0) { - foundFirstNonZeroDigit = 1; - - // adjust for deleted k-axes sequence - if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) - h = _h3Rotate60ccw(h); - } - } - return h; -} - -/** - * Rotate an H3Index 60 degrees clockwise about a pentagonal center. - * @param h The H3Index. - */ -H3Index _h3RotatePent60cw(H3Index h) { - // rotate in place; skips any leading 1 digits (k-axis) - - int foundFirstNonZeroDigit = 0; - for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { - // rotate this digit - H3_SET_INDEX_DIGIT(h, r, _rotate60cw(H3_GET_INDEX_DIGIT(h, r))); - - // look for the first non-zero digit so we - // can adjust for deleted k-axes sequence - // if necessary - if (!foundFirstNonZeroDigit && H3_GET_INDEX_DIGIT(h, r) != 0) { - foundFirstNonZeroDigit = 1; - - // adjust for deleted k-axes sequence - if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) h = _h3Rotate60cw(h); - } - } - return h; -} - -/** - * Rotate an H3Index 60 degrees counter-clockwise. - * @param h The H3Index. - */ -H3Index _h3Rotate60ccw(H3Index h) { - for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { - Direction oldDigit = H3_GET_INDEX_DIGIT(h, r); - H3_SET_INDEX_DIGIT(h, r, _rotate60ccw(oldDigit)); - } - - return h; -} - -/** - * Rotate an H3Index 60 degrees clockwise. - * @param h The H3Index. - */ -H3Index _h3Rotate60cw(H3Index h) { - for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { - H3_SET_INDEX_DIGIT(h, r, _rotate60cw(H3_GET_INDEX_DIGIT(h, r))); - } - - return h; -} - -/** - * Convert an FaceIJK address to the corresponding H3Index. - * @param fijk The FaceIJK address. - * @param res The cell resolution. - * @return The encoded H3Index (or H3_NULL on failure). - */ -H3Index _faceIjkToH3(const FaceIJK* fijk, int res) { - // initialize the index - H3Index h = H3_INIT; - H3_SET_MODE(h, H3_HEXAGON_MODE); - H3_SET_RESOLUTION(h, res); - - // check for res 0/base cell - if (res == 0) { - if (fijk->coord.i > MAX_FACE_COORD || fijk->coord.j > MAX_FACE_COORD || - fijk->coord.k > MAX_FACE_COORD) { - // out of range input - return H3_NULL; - } - - H3_SET_BASE_CELL(h, _faceIjkToBaseCell(fijk)); - return h; - } - - // we need to find the correct base cell FaceIJK for this H3 index; - // start with the passed in face and resolution res ijk coordinates - // in that face's coordinate system - FaceIJK fijkBC = *fijk; - - // build the H3Index from finest res up - // adjust r for the fact that the res 0 base cell offsets the indexing - // digits - CoordIJK* ijk = &fijkBC.coord; - for (int r = res - 1; r >= 0; r--) { - CoordIJK lastIJK = *ijk; - CoordIJK lastCenter; - if (isResClassIII(r + 1)) { - // rotate ccw - _upAp7(ijk); - lastCenter = *ijk; - _downAp7(&lastCenter); - } else { - // rotate cw - _upAp7r(ijk); - lastCenter = *ijk; - _downAp7r(&lastCenter); - } - - CoordIJK diff; - _ijkSub(&lastIJK, &lastCenter, &diff); - _ijkNormalize(&diff); - - H3_SET_INDEX_DIGIT(h, r + 1, _unitIjkToDigit(&diff)); - } - - // fijkBC should now hold the IJK of the base cell in the - // coordinate system of the current face - - if (fijkBC.coord.i > MAX_FACE_COORD || fijkBC.coord.j > MAX_FACE_COORD || - fijkBC.coord.k > MAX_FACE_COORD) { - // out of range input - return H3_NULL; - } - - // lookup the correct base cell - int baseCell = _faceIjkToBaseCell(&fijkBC); - H3_SET_BASE_CELL(h, baseCell); - - // rotate if necessary to get canonical base cell orientation - // for this base cell - int numRots = _faceIjkToBaseCellCCWrot60(&fijkBC); - if (_isBaseCellPentagon(baseCell)) { - // force rotation out of missing k-axes sub-sequence - if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) { - // check for a cw/ccw offset face; default is ccw - if (_baseCellIsCwOffset(baseCell, fijkBC.face)) { - h = _h3Rotate60cw(h); - } else { - h = _h3Rotate60ccw(h); - } - } - - for (int i = 0; i < numRots; i++) h = _h3RotatePent60ccw(h); - } else { - for (int i = 0; i < numRots; i++) { - h = _h3Rotate60ccw(h); - } - } - - return h; -} - -/** - * Encodes a coordinate on the sphere to the H3 index of the containing cell at - * the specified resolution. - * - * Returns 0 on invalid input. - * - * @param g The spherical coordinates to encode. - * @param res The desired H3 resolution for the encoding. - * @return The encoded H3Index (or H3_NULL on failure). - */ -H3Index H3_EXPORT(geoToH3)(const GeoCoord* g, int res) { - if (res < 0 || res > MAX_H3_RES) { - return H3_NULL; - } - if (!isfinite(g->lat) || !isfinite(g->lon)) { - return H3_NULL; - } - - FaceIJK fijk; - _geoToFaceIjk(g, res, &fijk); - return _faceIjkToH3(&fijk, res); -} - -/** - * Convert an H3Index to the FaceIJK address on a specified icosahedral face. - * @param h The H3Index. - * @param fijk The FaceIJK address, initialized with the desired face - * and normalized base cell coordinates. - * @return Returns 1 if the possibility of overage exists, otherwise 0. - */ -int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK* fijk) { - CoordIJK* ijk = &fijk->coord; - int res = H3_GET_RESOLUTION(h); - - // center base cell hierarchy is entirely on this face - int possibleOverage = 1; - if (!_isBaseCellPentagon(H3_GET_BASE_CELL(h)) && - (res == 0 || - (fijk->coord.i == 0 && fijk->coord.j == 0 && fijk->coord.k == 0))) - possibleOverage = 0; - - for (int r = 1; r <= res; r++) { - if (isResClassIII(r)) { - // Class III == rotate ccw - _downAp7(ijk); - } else { - // Class II == rotate cw - _downAp7r(ijk); - } - - _neighbor(ijk, H3_GET_INDEX_DIGIT(h, r)); - } - - return possibleOverage; -} - -/** - * Convert an H3Index to a FaceIJK address. - * @param h The H3Index. - * @param fijk The corresponding FaceIJK address. - */ -void _h3ToFaceIjk(H3Index h, FaceIJK* fijk) { - int baseCell = H3_GET_BASE_CELL(h); - // adjust for the pentagonal missing sequence; all of sub-sequence 5 needs - // to be adjusted (and some of sub-sequence 4 below) - if (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 5) - h = _h3Rotate60cw(h); - - // start with the "home" face and ijk+ coordinates for the base cell of c - *fijk = baseCellData[baseCell].homeFijk; - if (!_h3ToFaceIjkWithInitializedFijk(h, fijk)) - return; // no overage is possible; h lies on this face - - // if we're here we have the potential for an "overage"; i.e., it is - // possible that c lies on an adjacent face - - CoordIJK origIJK = fijk->coord; - - // if we're in Class III, drop into the next finer Class II grid - int res = H3_GET_RESOLUTION(h); - if (isResClassIII(res)) { - // Class III - _downAp7r(&fijk->coord); - res++; - } - - // adjust for overage if needed - // a pentagon base cell with a leading 4 digit requires special handling - int pentLeading4 = - (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 4); - if (_adjustOverageClassII(fijk, res, pentLeading4, 0) != NO_OVERAGE) { - // if the base cell is a pentagon we have the potential for secondary - // overages - if (_isBaseCellPentagon(baseCell)) { - while (_adjustOverageClassII(fijk, res, 0, 0) != NO_OVERAGE) - continue; - } - - if (res != H3_GET_RESOLUTION(h)) _upAp7r(&fijk->coord); - } else if (res != H3_GET_RESOLUTION(h)) { - fijk->coord = origIJK; - } -} - -/** - * Determines the spherical coordinates of the center point of an H3 index. - * - * @param h3 The H3 index. - * @param g The spherical coordinates of the H3 cell center. - */ -void H3_EXPORT(h3ToGeo)(H3Index h3, GeoCoord* g) { - FaceIJK fijk; - _h3ToFaceIjk(h3, &fijk); - _faceIjkToGeo(&fijk, H3_GET_RESOLUTION(h3), g); -} - -/** - * Determines the cell boundary in spherical coordinates for an H3 index. - * - * @param h3 The H3 index. - * @param gb The boundary of the H3 cell in spherical coordinates. - */ -void H3_EXPORT(h3ToGeoBoundary)(H3Index h3, GeoBoundary* gb) { - FaceIJK fijk; - _h3ToFaceIjk(h3, &fijk); - if (H3_EXPORT(h3IsPentagon)(h3)) { - _faceIjkPentToGeoBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, - NUM_PENT_VERTS, gb); - } else { - _faceIjkToGeoBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_HEX_VERTS, - gb); - } -} - -/** - * Returns the max number of possible icosahedron faces an H3 index - * may intersect. - * - * @return int count of faces - */ -int H3_EXPORT(maxFaceCount)(H3Index h3) { - // a pentagon always intersects 5 faces, a hexagon never intersects more - // than 2 (but may only intersect 1) - return H3_EXPORT(h3IsPentagon)(h3) ? 5 : 2; -} - -/** - * Find all icosahedron faces intersected by a given H3 index, represented - * as integers from 0-19. The array is sparse; since 0 is a valid value, - * invalid array values are represented as -1. It is the responsibility of - * the caller to filter out invalid values. - * - * @param h3 The H3 index - * @param out Output array. Must be of size maxFaceCount(h3). - */ -void H3_EXPORT(h3GetFaces)(H3Index h3, int* out) { - int res = H3_GET_RESOLUTION(h3); - int isPentagon = H3_EXPORT(h3IsPentagon)(h3); - - // We can't use the vertex-based approach here for class II pentagons, - // because all their vertices are on the icosahedron edges. Their - // direct child pentagons cross the same faces, so use those instead. - if (isPentagon && !isResClassIII(res)) { - // Note that this would not work for res 15, but this is only run on - // Class II pentagons, it should never be invoked for a res 15 index. - H3Index childPentagon = makeDirectChild(h3, 0); - H3_EXPORT(h3GetFaces)(childPentagon, out); - return; - } - - // convert to FaceIJK - FaceIJK fijk; - _h3ToFaceIjk(h3, &fijk); - - // Get all vertices as FaceIJK addresses. For simplicity, always - // initialize the array with 6 verts, ignoring the last one for pentagons - FaceIJK fijkVerts[NUM_HEX_VERTS]; - int vertexCount; - - if (isPentagon) { - vertexCount = NUM_PENT_VERTS; - _faceIjkPentToVerts(&fijk, &res, fijkVerts); - } else { - vertexCount = NUM_HEX_VERTS; - _faceIjkToVerts(&fijk, &res, fijkVerts); - } - - // We may not use all of the slots in the output array, - // so fill with invalid values to indicate unused slots - int faceCount = H3_EXPORT(maxFaceCount)(h3); - for (int i = 0; i < faceCount; i++) { - out[i] = INVALID_FACE; - } - - // add each vertex face, using the output array as a hash set - for (int i = 0; i < vertexCount; i++) { - FaceIJK* vert = &fijkVerts[i]; - - // Adjust overage, determining whether this vertex is - // on another face - if (isPentagon) { - _adjustPentVertOverage(vert, res); - } else { - _adjustOverageClassII(vert, res, 0, 1); - } - - // Save the face to the output array - int face = vert->face; - int pos = 0; - // Find the first empty output position, or the first position - // matching the current face - while (out[pos] != INVALID_FACE && out[pos] != face) pos++; - out[pos] = face; - } -} - -/** - * pentagonIndexCount returns the number of pentagons (same at any resolution) - * - * @return int count of pentagon indexes - */ -int H3_EXPORT(pentagonIndexCount)() { return NUM_PENTAGONS; } - -/** - * Generates all pentagons at the specified resolution - * - * @param res The resolution to produce pentagons at. - * @param out Output array. Must be of size pentagonIndexCount(). - */ -void H3_EXPORT(getPentagonIndexes)(int res, H3Index* out) { - int i = 0; - for (int bc = 0; bc < NUM_BASE_CELLS; bc++) { - if (_isBaseCellPentagon(bc)) { - H3Index pentagon; - setH3Index(&pentagon, res, bc, 0); - out[i++] = pentagon; - } - } -} - -/** - * Returns whether or not a resolution is a Class III grid. Note that odd - * resolutions are Class III and even resolutions are Class II. - * @param res The H3 resolution. - * @return 1 if the resolution is a Class III grid, and 0 if the resolution is - * a Class II grid. - */ -int isResClassIII(int res) { return res % 2; } diff --git a/v3/h3_h3Index.h b/v3/h3_h3Index.h deleted file mode 100644 index c91191a..0000000 --- a/v3/h3_h3Index.h +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2016-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. - */ -/** @file h3Index.h - * @brief H3Index functions. - */ - -#ifndef H3INDEX_H -#define H3INDEX_H - -#include "h3_faceijk.h" -#include "h3_h3api.h" - -// define's of constants and macros for bitwise manipulation of H3Index's. - -/** The number of bits in an H3 index. */ -#define H3_NUM_BITS 64 - -/** The bit offset of the max resolution digit in an H3 index. */ -#define H3_MAX_OFFSET 63 - -/** The bit offset of the mode in an H3 index. */ -#define H3_MODE_OFFSET 59 - -/** The bit offset of the base cell in an H3 index. */ -#define H3_BC_OFFSET 45 - -/** The bit offset of the resolution in an H3 index. */ -#define H3_RES_OFFSET 52 - -/** The bit offset of the reserved bits in an H3 index. */ -#define H3_RESERVED_OFFSET 56 - -/** The number of bits in a single H3 resolution digit. */ -#define H3_PER_DIGIT_OFFSET 3 - -/** 1 in the highest bit, 0's everywhere else. */ -#define H3_HIGH_BIT_MASK ((uint64_t)(1) << H3_MAX_OFFSET) - -/** 0 in the highest bit, 1's everywhere else. */ -#define H3_HIGH_BIT_MASK_NEGATIVE (~H3_HIGH_BIT_MASK) - -/** 1's in the 4 mode bits, 0's everywhere else. */ -#define H3_MODE_MASK ((uint64_t)(15) << H3_MODE_OFFSET) - -/** 0's in the 4 mode bits, 1's everywhere else. */ -#define H3_MODE_MASK_NEGATIVE (~H3_MODE_MASK) - -/** 1's in the 7 base cell bits, 0's everywhere else. */ -#define H3_BC_MASK ((uint64_t)(127) << H3_BC_OFFSET) - -/** 0's in the 7 base cell bits, 1's everywhere else. */ -#define H3_BC_MASK_NEGATIVE (~H3_BC_MASK) - -/** 1's in the 4 resolution bits, 0's everywhere else. */ -#define H3_RES_MASK (UINT64_C(15) << H3_RES_OFFSET) - -/** 0's in the 4 resolution bits, 1's everywhere else. */ -#define H3_RES_MASK_NEGATIVE (~H3_RES_MASK) - -/** 1's in the 3 reserved bits, 0's everywhere else. */ -#define H3_RESERVED_MASK ((uint64_t)(7) << H3_RESERVED_OFFSET) - -/** 0's in the 3 reserved bits, 1's everywhere else. */ -#define H3_RESERVED_MASK_NEGATIVE (~H3_RESERVED_MASK) - -/** 1's in the 3 bits of res 15 digit bits, 0's everywhere else. */ -#define H3_DIGIT_MASK ((uint64_t)(7)) - -/** 0's in the 7 base cell bits, 1's everywhere else. */ -#define H3_DIGIT_MASK_NEGATIVE (~H3_DIGIT_MASK) - -/** - * H3 index with mode 0, res 0, base cell 0, and 7 for all index digits. - * Typically used to initialize the creation of an H3 cell index, which - * expects all direction digits to be 7 beyond the cell's resolution. - */ -#define H3_INIT (UINT64_C(35184372088831)) - -/** - * Gets the highest bit of the H3 index. - */ -#define H3_GET_HIGH_BIT(h3) ((int)((((h3)&H3_HIGH_BIT_MASK) >> H3_MAX_OFFSET))) - -/** - * Sets the highest bit of the h3 to v. - */ -#define H3_SET_HIGH_BIT(h3, v) \ - (h3) = (((h3)&H3_HIGH_BIT_MASK_NEGATIVE) | \ - (((uint64_t)(v)) << H3_MAX_OFFSET)) - -/** - * Gets the integer mode of h3. - */ -#define H3_GET_MODE(h3) ((int)((((h3)&H3_MODE_MASK) >> H3_MODE_OFFSET))) - -/** - * Sets the integer mode of h3 to v. - */ -#define H3_SET_MODE(h3, v) \ - (h3) = (((h3)&H3_MODE_MASK_NEGATIVE) | (((uint64_t)(v)) << H3_MODE_OFFSET)) - -/** - * Gets the integer base cell of h3. - */ -#define H3_GET_BASE_CELL(h3) ((int)((((h3)&H3_BC_MASK) >> H3_BC_OFFSET))) - -/** - * Sets the integer base cell of h3 to bc. - */ -#define H3_SET_BASE_CELL(h3, bc) \ - (h3) = (((h3)&H3_BC_MASK_NEGATIVE) | (((uint64_t)(bc)) << H3_BC_OFFSET)) - -/** - * Gets the integer resolution of h3. - */ -#define H3_GET_RESOLUTION(h3) ((int)((((h3)&H3_RES_MASK) >> H3_RES_OFFSET))) - -/** - * Sets the integer resolution of h3. - */ -#define H3_SET_RESOLUTION(h3, res) \ - (h3) = (((h3)&H3_RES_MASK_NEGATIVE) | (((uint64_t)(res)) << H3_RES_OFFSET)) - -/** - * Gets the resolution res integer digit (0-7) of h3. - */ -#define H3_GET_INDEX_DIGIT(h3, res) \ - ((Direction)((((h3) >> ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET)) & \ - H3_DIGIT_MASK))) - -/** - * Sets a value in the reserved space. Setting to non-zero may produce invalid - * indexes. - */ -#define H3_SET_RESERVED_BITS(h3, v) \ - (h3) = (((h3)&H3_RESERVED_MASK_NEGATIVE) | \ - (((uint64_t)(v)) << H3_RESERVED_OFFSET)) - -/** - * Gets a value in the reserved space. Should always be zero for valid indexes. - */ -#define H3_GET_RESERVED_BITS(h3) \ - ((int)((((h3)&H3_RESERVED_MASK) >> H3_RESERVED_OFFSET))) - -/** - * Sets the resolution res digit of h3 to the integer digit (0-7) - */ -#define H3_SET_INDEX_DIGIT(h3, res, digit) \ - (h3) = (((h3) & ~((H3_DIGIT_MASK \ - << ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET)))) | \ - (((uint64_t)(digit)) \ - << ((MAX_H3_RES - (res)) * H3_PER_DIGIT_OFFSET))) - -/** - * Invalid index used to indicate an error from geoToH3 and related functions - * or missing data in arrays of h3 indices. Analogous to NaN in floating point. - */ -#define H3_NULL 0 - -/* - * Return codes for compact - */ - -#define COMPACT_SUCCESS 0 -#define COMPACT_LOOP_EXCEEDED -1 -#define COMPACT_DUPLICATE -2 -#define COMPACT_ALLOC_FAILED -3 - -void setH3Index(H3Index* h, int res, int baseCell, Direction initDigit); -int isResClassIII(int res); - -// Internal functions - -int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK* fijk); -void _h3ToFaceIjk(H3Index h, FaceIJK* fijk); -H3Index _faceIjkToH3(const FaceIJK* fijk, int res); -Direction _h3LeadingNonZeroDigit(H3Index h); -H3Index _h3RotatePent60ccw(H3Index h); -H3Index _h3RotatePent60cw(H3Index h); -H3Index _h3Rotate60ccw(H3Index h); -H3Index _h3Rotate60cw(H3Index h); - -#endif diff --git a/v3/h3_h3UniEdge.c b/v3/h3_h3UniEdge.c deleted file mode 100644 index e72e46e..0000000 --- a/v3/h3_h3UniEdge.c +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2017-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. - */ -/** @file h3UniEdge.c - * @brief H3UniEdge functions for manipulating unidirectional edge indexes. - */ - -#include -#include - -#include "h3_algos.h" -#include "h3_constants.h" -#include "h3_coordijk.h" -#include "h3_geoCoord.h" -#include "h3_h3Index.h" -#include "h3_vertex.h" - -/** - * Returns whether or not the provided H3Indexes are neighbors. - * @param origin The origin H3 index. - * @param destination The destination H3 index. - * @return 1 if the indexes are neighbors, 0 otherwise; - */ -int H3_EXPORT(h3IndexesAreNeighbors)(H3Index origin, H3Index destination) { - // Make sure they're hexagon indexes - if (H3_GET_MODE(origin) != H3_HEXAGON_MODE || - H3_GET_MODE(destination) != H3_HEXAGON_MODE) { - return 0; - } - - // Hexagons cannot be neighbors with themselves - if (origin == destination) { - return 0; - } - - // Only hexagons in the same resolution can be neighbors - if (H3_GET_RESOLUTION(origin) != H3_GET_RESOLUTION(destination)) { - return 0; - } - - // H3 Indexes that share the same parent are very likely to be neighbors - // Child 0 is neighbor with all of its parent's 'offspring', the other - // children are neighbors with 3 of the 7 children. So a simple comparison - // of origin and destination parents and then a lookup table of the children - // is a super-cheap way to possibly determine they are neighbors. - int parentRes = H3_GET_RESOLUTION(origin) - 1; - if (parentRes > 0 && (H3_EXPORT(h3ToParent)(origin, parentRes) == - H3_EXPORT(h3ToParent)(destination, parentRes))) { - Direction originResDigit = H3_GET_INDEX_DIGIT(origin, parentRes + 1); - Direction destinationResDigit = - H3_GET_INDEX_DIGIT(destination, parentRes + 1); - if (originResDigit == CENTER_DIGIT || - destinationResDigit == CENTER_DIGIT) { - return 1; - } - // These sets are the relevant neighbors in the clockwise - // and counter-clockwise - const Direction neighborSetClockwise[] = { - CENTER_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, J_AXES_DIGIT, - IK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT}; - const Direction neighborSetCounterclockwise[] = { - CENTER_DIGIT, IK_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, - IJ_AXES_DIGIT, I_AXES_DIGIT, J_AXES_DIGIT}; - if (neighborSetClockwise[originResDigit] == destinationResDigit || - neighborSetCounterclockwise[originResDigit] == - destinationResDigit) { - return 1; - } - } - - // Otherwise, we have to determine the neighbor relationship the "hard" way. - H3Index neighborRing[7] = {0}; - H3_EXPORT(kRing)(origin, 1, neighborRing); - for (int i = 0; i < 7; i++) { - if (neighborRing[i] == destination) { - return 1; - } - } - - // Made it here, they definitely aren't neighbors - return 0; -} - -/** - * Returns a unidirectional edge H3 index based on the provided origin and - * destination - * @param origin The origin H3 hexagon index - * @param destination The destination H3 hexagon index - * @return The unidirectional edge H3Index, or H3_NULL on failure. - */ -H3Index H3_EXPORT(getH3UnidirectionalEdge)(H3Index origin, - H3Index destination) { - // Short-circuit and return an invalid index value if they are not neighbors - if (H3_EXPORT(h3IndexesAreNeighbors)(origin, destination) == 0) { - return H3_NULL; - } - - // Otherwise, determine the IJK direction from the origin to the destination - H3Index output = origin; - H3_SET_MODE(output, H3_UNIEDGE_MODE); - - bool isPentagon = H3_EXPORT(h3IsPentagon)(origin); - - // Checks each neighbor, in order, to determine which direction the - // destination neighbor is located. Skips CENTER_DIGIT since that - // would be this index. - H3Index neighbor; - // Excluding from branch coverage as we never hit the end condition - // LCOV_EXCL_BR_START - for (Direction direction = isPentagon ? J_AXES_DIGIT : K_AXES_DIGIT; - direction < NUM_DIGITS; direction++) { - // LCOV_EXCL_BR_STOP - int rotations = 0; - neighbor = h3NeighborRotations(origin, direction, &rotations); - if (neighbor == destination) { - H3_SET_RESERVED_BITS(output, direction); - return output; - } - } - - // This should be impossible, return H3_NULL in this case; - return H3_NULL; // LCOV_EXCL_LINE -} - -/** - * Returns the origin hexagon from the unidirectional edge H3Index - * @param edge The edge H3 index - * @return The origin H3 hexagon index, or H3_NULL on failure - */ -H3Index H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(H3Index edge) { - if (H3_GET_MODE(edge) != H3_UNIEDGE_MODE) { - return H3_NULL; - } - H3Index origin = edge; - H3_SET_MODE(origin, H3_HEXAGON_MODE); - H3_SET_RESERVED_BITS(origin, 0); - return origin; -} - -/** - * Returns the destination hexagon from the unidirectional edge H3Index - * @param edge The edge H3 index - * @return The destination H3 hexagon index, or H3_NULL on failure - */ -H3Index H3_EXPORT(getDestinationH3IndexFromUnidirectionalEdge)(H3Index edge) { - if (H3_GET_MODE(edge) != H3_UNIEDGE_MODE) { - return H3_NULL; - } - Direction direction = H3_GET_RESERVED_BITS(edge); - int rotations = 0; - H3Index destination = h3NeighborRotations( - H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(edge), direction, - &rotations); - return destination; -} - -/** - * Determines if the provided H3Index is a valid unidirectional edge index - * @param edge The unidirectional edge H3Index - * @return 1 if it is a unidirectional edge H3Index, otherwise 0. - */ -int H3_EXPORT(h3UnidirectionalEdgeIsValid)(H3Index edge) { - if (H3_GET_MODE(edge) != H3_UNIEDGE_MODE) { - return 0; - } - - Direction neighborDirection = H3_GET_RESERVED_BITS(edge); - if (neighborDirection <= CENTER_DIGIT || neighborDirection >= NUM_DIGITS) { - return 0; - } - - H3Index origin = H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(edge); - if (H3_EXPORT(h3IsPentagon)(origin) && neighborDirection == K_AXES_DIGIT) { - return 0; - } - - return H3_EXPORT(h3IsValid)(origin); -} - -/** - * Returns the origin, destination pair of hexagon IDs for the given edge ID - * @param edge The unidirectional edge H3Index - * @param originDestination Pointer to memory to store origin and destination - * IDs - */ -void H3_EXPORT(getH3IndexesFromUnidirectionalEdge)(H3Index edge, - H3Index* originDestination) { - originDestination[0] = - H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(edge); - originDestination[1] = - H3_EXPORT(getDestinationH3IndexFromUnidirectionalEdge)(edge); -} - -/** - * Provides all of the unidirectional edges from the current H3Index. - * @param origin The origin hexagon H3Index to find edges for. - * @param edges The memory to store all of the edges inside. - */ -void H3_EXPORT(getH3UnidirectionalEdgesFromHexagon)(H3Index origin, - H3Index* edges) { - // Determine if the origin is a pentagon and special treatment needed. - int isPentagon = H3_EXPORT(h3IsPentagon)(origin); - - // This is actually quite simple. Just modify the bits of the origin - // slightly for each direction, except the 'k' direction in pentagons, - // which is zeroed. - for (int i = 0; i < 6; i++) { - if (isPentagon && i == 0) { - edges[i] = H3_NULL; - } else { - edges[i] = origin; - H3_SET_MODE(edges[i], H3_UNIEDGE_MODE); - H3_SET_RESERVED_BITS(edges[i], i + 1); - } - } -} - -/** - * Provides the coordinates defining the unidirectional edge. - * @param edge The unidirectional edge H3Index - * @param gb The geoboundary object to store the edge coordinates. - */ -void H3_EXPORT(getH3UnidirectionalEdgeBoundary)(H3Index edge, GeoBoundary* gb) { - // Get the origin and neighbor direction from the edge - Direction direction = H3_GET_RESERVED_BITS(edge); - H3Index origin = H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(edge); - - // Get the start vertex for the edge - int startVertex = vertexNumForDirection(origin, direction); - if (startVertex == INVALID_VERTEX_NUM) { - // This is not actually an edge (i.e. no valid direction), - // so return no vertices. - gb->numVerts = 0; - return; - } - - // Get the geo boundary for the appropriate vertexes of the origin. Note - // that while there are always 2 topological vertexes per edge, the - // resulting edge boundary may have an additional distortion vertex if it - // crosses an edge of the icosahedron. - FaceIJK fijk; - _h3ToFaceIjk(origin, &fijk); - int res = H3_GET_RESOLUTION(origin); - int isPentagon = H3_EXPORT(h3IsPentagon)(origin); - - if (isPentagon) { - _faceIjkPentToGeoBoundary(&fijk, res, startVertex, 2, gb); - } else { - _faceIjkToGeoBoundary(&fijk, res, startVertex, 2, gb); - } -} diff --git a/v3/h3_h3api.h b/v3/h3_h3api.h deleted file mode 100644 index 187f843..0000000 --- a/v3/h3_h3api.h +++ /dev/null @@ -1,592 +0,0 @@ -/* - * Copyright 2016-2020 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. - */ -/** @file h3api.h - * @brief Primary H3 core library entry points. - * - * This file defines the public API of the H3 library. Incompatible changes to - * these functions require the library's major version be increased. - */ - -#ifndef H3API_H -#define H3API_H - -/* - * Preprocessor code to support renaming (prefixing) the public API. - * All public functions should be wrapped in H3_EXPORT so they can be - * renamed. - */ -#ifdef H3_PREFIX -#define XTJOIN(a, b) a##b -#define TJOIN(a, b) XTJOIN(a, b) - -/* export joins the user provided prefix with our exported function name */ -#define H3_EXPORT(name) TJOIN(H3_PREFIX, name) -#else -#define H3_EXPORT(name) name -#endif - -/* For uint64_t */ -#include -/* For size_t */ -#include - -/* - * H3 is compiled as C, not C++ code. `extern "C"` is needed for C++ code - * to be able to use the library. - */ -#ifdef __cplusplus -extern "C" { -#endif - -/** @brief the H3Index fits within a 64-bit unsigned integer */ -typedef uint64_t H3Index; - -/* library version numbers generated from VERSION file */ -// clang-format off -#define H3_VERSION_MAJOR @H3_VERSION_MAJOR@ -#define H3_VERSION_MINOR @H3_VERSION_MINOR@ -#define H3_VERSION_PATCH @H3_VERSION_PATCH@ -// clang-format on - -/** Maximum number of cell boundary vertices; worst case is pentagon: - * 5 original verts + 5 edge crossings - */ -#define MAX_CELL_BNDRY_VERTS 10 - -/** @struct GeoCoord - @brief latitude/longitude in radians -*/ -typedef struct { - double lat; ///< latitude in radians - double lon; ///< longitude in radians -} GeoCoord; - -/** @struct GeoBoundary - @brief cell boundary in latitude/longitude -*/ -typedef struct { - int numVerts; ///< number of vertices - GeoCoord verts[MAX_CELL_BNDRY_VERTS]; ///< vertices in ccw order -} GeoBoundary; - -/** @struct Geofence - * @brief similar to GeoBoundary, but requires more alloc work - */ -typedef struct { - int numVerts; - GeoCoord *verts; -} Geofence; - -/** @struct GeoPolygon - * @brief Simplified core of GeoJSON Polygon coordinates definition - */ -typedef struct { - Geofence geofence; ///< exterior boundary of the polygon - int numHoles; ///< number of elements in the array pointed to by holes - Geofence *holes; ///< interior boundaries (holes) in the polygon -} GeoPolygon; - -/** @struct GeoMultiPolygon - * @brief Simplified core of GeoJSON MultiPolygon coordinates definition - */ -typedef struct { - int numPolygons; - GeoPolygon *polygons; -} GeoMultiPolygon; - -/** @struct LinkedGeoCoord - * @brief A coordinate node in a linked geo structure, part of a linked list - */ -typedef struct LinkedGeoCoord LinkedGeoCoord; -struct LinkedGeoCoord { - GeoCoord vertex; - LinkedGeoCoord *next; -}; - -/** @struct LinkedGeoLoop - * @brief A loop node in a linked geo structure, part of a linked list - */ -typedef struct LinkedGeoLoop LinkedGeoLoop; -struct LinkedGeoLoop { - LinkedGeoCoord *first; - LinkedGeoCoord *last; - LinkedGeoLoop *next; -}; - -/** @struct LinkedGeoPolygon - * @brief A polygon node in a linked geo structure, part of a linked list. - */ -typedef struct LinkedGeoPolygon LinkedGeoPolygon; -struct LinkedGeoPolygon { - LinkedGeoLoop *first; - LinkedGeoLoop *last; - LinkedGeoPolygon *next; -}; - -/** @struct CoordIJ - * @brief IJ hexagon coordinates - * - * Each axis is spaced 120 degrees apart. - */ -typedef struct { - int i; ///< i component - int j; ///< j component -} CoordIJ; - -/** @defgroup geoToH3 geoToH3 - * Functions for geoToH3 - * @{ - */ -/** @brief find the H3 index of the resolution res cell containing the lat/lng - */ -H3Index H3_EXPORT(geoToH3)(const GeoCoord *g, int res); -/** @} */ - -/** @defgroup h3ToGeo h3ToGeo - * Functions for h3ToGeo - * @{ - */ -/** @brief find the lat/lon center point g of the cell h3 */ -void H3_EXPORT(h3ToGeo)(H3Index h3, GeoCoord *g); -/** @} */ - -/** @defgroup h3ToGeoBoundary h3ToGeoBoundary - * Functions for h3ToGeoBoundary - * @{ - */ -/** @brief give the cell boundary in lat/lon coordinates for the cell h3 */ -void H3_EXPORT(h3ToGeoBoundary)(H3Index h3, GeoBoundary *gp); -/** @} */ - -/** @defgroup kRing kRing - * Functions for kRing - * @{ - */ -/** @brief maximum number of hexagons in k-ring */ -int H3_EXPORT(maxKringSize)(int k); - -/** @brief hexagons neighbors in all directions, assuming no pentagons */ -int H3_EXPORT(hexRange)(H3Index origin, int k, H3Index *out); -/** @} */ - -/** @brief hexagons neighbors in all directions, assuming no pentagons, - * reporting distance from origin */ -int H3_EXPORT(hexRangeDistances)(H3Index origin, int k, H3Index *out, - int *distances); - -/** @brief collection of hex rings sorted by ring for all given hexagons */ -int H3_EXPORT(hexRanges)(H3Index *h3Set, int length, int k, H3Index *out); - -/** @brief hexagon neighbors in all directions */ -void H3_EXPORT(kRing)(H3Index origin, int k, H3Index *out); -/** @} */ - -/** @defgroup kRingDistances kRingDistances - * Functions for kRingDistances - * @{ - */ -/** @brief hexagon neighbors in all directions, reporting distance from origin - */ -void H3_EXPORT(kRingDistances)(H3Index origin, int k, H3Index *out, - int *distances); -/** @} */ - -/** @defgroup hexRing hexRing - * Functions for hexRing - * @{ - */ -/** @brief hollow hexagon ring at some origin */ -int H3_EXPORT(hexRing)(H3Index origin, int k, H3Index *out); -/** @} */ - -/** @defgroup polyfill polyfill - * Functions for polyfill - * @{ - */ -/** @brief maximum number of hexagons in the geofence */ -int H3_EXPORT(maxPolyfillSize)(const GeoPolygon *geoPolygon, int res); - -/** @brief hexagons within the given geofence */ -void H3_EXPORT(polyfill)(const GeoPolygon *geoPolygon, int res, H3Index *out); -/** @} */ - -/** @defgroup h3SetToMultiPolygon h3SetToMultiPolygon - * Functions for h3SetToMultiPolygon (currently a binding-only concept) - * @{ - */ -/** @brief Create a LinkedGeoPolygon from a set of contiguous hexagons */ -void H3_EXPORT(h3SetToLinkedGeo)(const H3Index *h3Set, const int numHexes, - LinkedGeoPolygon *out); - -/** @brief Free all memory created for a LinkedGeoPolygon */ -void H3_EXPORT(destroyLinkedPolygon)(LinkedGeoPolygon *polygon); -/** @} */ - -/** @defgroup degsToRads degsToRads - * Functions for degsToRads - * @{ - */ -/** @brief converts degrees to radians */ -double H3_EXPORT(degsToRads)(double degrees); -/** @} */ - -/** @defgroup radsToDegs radsToDegs - * Functions for radsToDegs - * @{ - */ -/** @brief converts radians to degrees */ -double H3_EXPORT(radsToDegs)(double radians); -/** @} */ - -/** @defgroup pointDist pointDist - * Functions for pointDist - * @{ - */ -/** @brief "great circle distance" between pairs of GeoCoord points in radians*/ -double H3_EXPORT(pointDistRads)(const GeoCoord *a, const GeoCoord *b); - -/** @brief "great circle distance" between pairs of GeoCoord points in - * kilometers*/ -double H3_EXPORT(pointDistKm)(const GeoCoord *a, const GeoCoord *b); - -/** @brief "great circle distance" between pairs of GeoCoord points in meters*/ -double H3_EXPORT(pointDistM)(const GeoCoord *a, const GeoCoord *b); -/** @} */ - -/** @defgroup hexArea hexArea - * Functions for hexArea - * @{ - */ -/** @brief average hexagon area in square kilometers (excludes pentagons) */ -double H3_EXPORT(hexAreaKm2)(int res); - -/** @brief average hexagon area in square meters (excludes pentagons) */ -double H3_EXPORT(hexAreaM2)(int res); -/** @} */ - -/** @defgroup cellArea cellArea - * Functions for cellArea - * @{ - */ -/** @brief exact area for a specific cell (hexagon or pentagon) in radians^2 */ -double H3_EXPORT(cellAreaRads2)(H3Index h); - -/** @brief exact area for a specific cell (hexagon or pentagon) in kilometers^2 - */ -double H3_EXPORT(cellAreaKm2)(H3Index h); - -/** @brief exact area for a specific cell (hexagon or pentagon) in meters^2 */ -double H3_EXPORT(cellAreaM2)(H3Index h); -/** @} */ - -/** @defgroup edgeLength edgeLength - * Functions for edgeLength - * @{ - */ -/** @brief average hexagon edge length in kilometers (excludes pentagons) */ -double H3_EXPORT(edgeLengthKm)(int res); - -/** @brief average hexagon edge length in meters (excludes pentagons) */ -double H3_EXPORT(edgeLengthM)(int res); -/** @} */ - -/** @defgroup exactEdgeLength exactEdgeLength - * Functions for exactEdgeLength - * @{ - */ -/** @brief exact length for a specific unidirectional edge in radians*/ -double H3_EXPORT(exactEdgeLengthRads)(H3Index edge); - -/** @brief exact length for a specific unidirectional edge in kilometers*/ -double H3_EXPORT(exactEdgeLengthKm)(H3Index edge); - -/** @brief exact length for a specific unidirectional edge in meters*/ -double H3_EXPORT(exactEdgeLengthM)(H3Index edge); -/** @} */ - -/** @defgroup numHexagons numHexagons - * Functions for numHexagons - * @{ - */ -/** @brief number of cells (hexagons and pentagons) for a given resolution */ -int64_t H3_EXPORT(numHexagons)(int res); -/** @} */ - -/** @defgroup getRes0Indexes getRes0Indexes - * Functions for getRes0Indexes - * @{ - */ -/** @brief returns the number of resolution 0 cells (hexagons and pentagons) */ -int H3_EXPORT(res0IndexCount)(); - -/** @brief provides all base cells in H3Index format*/ -void H3_EXPORT(getRes0Indexes)(H3Index *out); -/** @} */ - -/** @defgroup getPentagonIndexes getPentagonIndexes - * Functions for getPentagonIndexes - * @{ - */ -/** @brief returns the number of pentagons per resolution */ -int H3_EXPORT(pentagonIndexCount)(); - -/** @brief generates all pentagons at the specified resolution */ -void H3_EXPORT(getPentagonIndexes)(int res, H3Index *out); -/** @} */ - -/** @defgroup h3GetResolution h3GetResolution - * Functions for h3GetResolution - * @{ - */ -/** @brief returns the resolution of the provided H3 index - * Works on both cells and unidirectional edges. */ -int H3_EXPORT(h3GetResolution)(H3Index h); -/** @} */ - -/** @defgroup h3GetBaseCell h3GetBaseCell - * Functions for h3GetBaseCell - * @{ - */ -/** @brief returns the base cell "number" (0 to 121) of the provided H3 cell - * - * Note: Technically works on H3 edges, but will return base cell of the - * origin cell. */ -int H3_EXPORT(h3GetBaseCell)(H3Index h); -/** @} */ - -/** @defgroup stringToH3 stringToH3 - * Functions for stringToH3 - * @{ - */ -/** @brief converts the canonical string format to H3Index format */ -H3Index H3_EXPORT(stringToH3)(const char *str); -/** @} */ - -/** @defgroup h3ToString h3ToString - * Functions for h3ToString - * @{ - */ -/** @brief converts an H3Index to a canonical string */ -void H3_EXPORT(h3ToString)(H3Index h, char *str, size_t sz); -/** @} */ - -/** @defgroup h3IsValid h3IsValid - * Functions for h3IsValid - * @{ - */ -/** @brief confirms if an H3Index is a valid cell (hexagon or pentagon) - * In particular, returns 0 (False) for H3 unidirectional edges or invalid data - */ -int H3_EXPORT(h3IsValid)(H3Index h); -/** @} */ - -/** @defgroup h3ToParent h3ToParent - * Functions for h3ToParent - * @{ - */ -/** @brief returns the parent (or grandparent, etc) hexagon of the given hexagon - */ -H3Index H3_EXPORT(h3ToParent)(H3Index h, int parentRes); -/** @} */ - -/** @defgroup h3ToChildren h3ToChildren - * Functions for h3ToChildren - * @{ - */ -/** @brief determines the maximum number of children (or grandchildren, etc) - * that could be returned for the given hexagon */ -int H3_EXPORT(maxH3ToChildrenSize)(H3Index h, int childRes); - -/** @brief provides the children (or grandchildren, etc) of the given hexagon */ -void H3_EXPORT(h3ToChildren)(H3Index h, int childRes, H3Index *children); -/** @} */ - -/** @defgroup h3ToCenterChild h3ToCenterChild - * Functions for h3ToCenterChild - * @{ - */ -/** @brief returns the center child of the given hexagon at the specified - * resolution */ -H3Index H3_EXPORT(h3ToCenterChild)(H3Index h, int childRes); -/** @} */ - -/** @defgroup compact compact - * Functions for compact - * @{ - */ -/** @brief compacts the given set of hexagons as best as possible */ -int H3_EXPORT(compact)(const H3Index *h3Set, H3Index *compactedSet, - const int numHexes); -/** @} */ - -/** @defgroup uncompact uncompact - * Functions for uncompact - * @{ - */ -/** @brief determines the maximum number of hexagons that could be uncompacted - * from the compacted set */ -int H3_EXPORT(maxUncompactSize)(const H3Index *compactedSet, const int numHexes, - const int res); - -/** @brief uncompacts the compacted hexagon set */ -int H3_EXPORT(uncompact)(const H3Index *compactedSet, const int numHexes, - H3Index *h3Set, const int maxHexes, const int res); -/** @} */ - -/** @defgroup h3IsResClassIII h3IsResClassIII - * Functions for h3IsResClassIII - * @{ - */ -/** @brief determines if a hexagon is Class III (or Class II) */ -int H3_EXPORT(h3IsResClassIII)(H3Index h); -/** @} */ - -/** @defgroup h3IsPentagon h3IsPentagon - * Functions for h3IsPentagon - * @{ - */ -/** @brief determines if an H3 cell is a pentagon */ -int H3_EXPORT(h3IsPentagon)(H3Index h); -/** @} */ - -/** @defgroup h3GetFaces h3GetFaces - * Functions for h3GetFaces - * @{ - */ -/** @brief Max number of icosahedron faces intersected by an index */ -int H3_EXPORT(maxFaceCount)(H3Index h3); - -/** @brief Find all icosahedron faces intersected by a given H3 index */ -void H3_EXPORT(h3GetFaces)(H3Index h3, int *out); -/** @} */ - -/** @defgroup h3IndexesAreNeighbors h3IndexesAreNeighbors - * Functions for h3IndexesAreNeighbors - * @{ - */ -/** @brief returns whether or not the provided hexagons border */ -int H3_EXPORT(h3IndexesAreNeighbors)(H3Index origin, H3Index destination); -/** @} */ - -/** @defgroup getH3UnidirectionalEdge getH3UnidirectionalEdge - * Functions for getH3UnidirectionalEdge - * @{ - */ -/** @brief returns the unidirectional edge H3Index for the specified origin and - * destination */ -H3Index H3_EXPORT(getH3UnidirectionalEdge)(H3Index origin, H3Index destination); -/** @} */ - -/** @defgroup h3UnidirectionalEdgeIsValid h3UnidirectionalEdgeIsValid - * Functions for h3UnidirectionalEdgeIsValid - * @{ - */ -/** @brief returns whether the H3Index is a valid unidirectional edge */ -int H3_EXPORT(h3UnidirectionalEdgeIsValid)(H3Index edge); -/** @} */ - -/** @defgroup getOriginH3IndexFromUnidirectionalEdge \ - * getOriginH3IndexFromUnidirectionalEdge - * Functions for getOriginH3IndexFromUnidirectionalEdge - * @{ - */ -/** @brief Returns the origin hexagon H3Index from the unidirectional edge - * H3Index */ -H3Index H3_EXPORT(getOriginH3IndexFromUnidirectionalEdge)(H3Index edge); -/** @} */ - -/** @defgroup getDestinationH3IndexFromUnidirectionalEdge \ - * getDestinationH3IndexFromUnidirectionalEdge - * Functions for getDestinationH3IndexFromUnidirectionalEdge - * @{ - */ -/** @brief Returns the destination hexagon H3Index from the unidirectional edge - * H3Index */ -H3Index H3_EXPORT(getDestinationH3IndexFromUnidirectionalEdge)(H3Index edge); -/** @} */ - -/** @defgroup getH3IndexesFromUnidirectionalEdge \ - * getH3IndexesFromUnidirectionalEdge - * Functions for getH3IndexesFromUnidirectionalEdge - * @{ - */ -/** @brief Returns the origin and destination hexagons from the unidirectional - * edge H3Index */ -void H3_EXPORT(getH3IndexesFromUnidirectionalEdge)(H3Index edge, - H3Index *originDestination); -/** @} */ - -/** @defgroup getH3UnidirectionalEdgesFromHexagon \ - * getH3UnidirectionalEdgesFromHexagon - * Functions for getH3UnidirectionalEdgesFromHexagon - * @{ - */ -/** @brief Returns the 6 (or 5 for pentagons) edges associated with the H3Index - */ -void H3_EXPORT(getH3UnidirectionalEdgesFromHexagon)(H3Index origin, - H3Index *edges); -/** @} */ - -/** @defgroup getH3UnidirectionalEdgeBoundary getH3UnidirectionalEdgeBoundary - * Functions for getH3UnidirectionalEdgeBoundary - * @{ - */ -/** @brief Returns the GeoBoundary containing the coordinates of the edge */ -void H3_EXPORT(getH3UnidirectionalEdgeBoundary)(H3Index edge, GeoBoundary *gb); -/** @} */ - -/** @defgroup h3Distance h3Distance - * Functions for h3Distance - * @{ - */ -/** @brief Returns grid distance between two indexes */ -int H3_EXPORT(h3Distance)(H3Index origin, H3Index h3); -/** @} */ - -/** @defgroup h3Line h3Line - * Functions for h3Line - * @{ - */ -/** @brief Number of indexes in a line connecting two indexes */ -int H3_EXPORT(h3LineSize)(H3Index start, H3Index end); - -/** @brief Line of h3 indexes connecting two indexes */ -int H3_EXPORT(h3Line)(H3Index start, H3Index end, H3Index *out); -/** @} */ - -/** @defgroup experimentalH3ToLocalIj experimentalH3ToLocalIj - * Functions for experimentalH3ToLocalIj - * @{ - */ -/** @brief Returns two dimensional coordinates for the given index */ -int H3_EXPORT(experimentalH3ToLocalIj)(H3Index origin, H3Index h3, - CoordIJ *out); -/** @} */ - -/** @defgroup experimentalLocalIjToH3 experimentalLocalIjToH3 - * Functions for experimentalLocalIjToH3 - * @{ - */ -/** @brief Returns index for the given two dimensional coordinates */ -int H3_EXPORT(experimentalLocalIjToH3)(H3Index origin, const CoordIJ *ij, - H3Index *out); -/** @} */ - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif diff --git a/v3/h3_linkedGeo.c b/v3/h3_linkedGeo.c deleted file mode 100644 index 4c44634..0000000 --- a/v3/h3_linkedGeo.c +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright 2017-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. - */ -/** @file linkedGeo.c - * @brief Linked data structure for geo data - */ - -#include "h3_linkedGeo.h" - -#include -#include - -#include "h3_alloc.h" -#include "h3_geoCoord.h" -#include "h3_h3api.h" - -/** - * Add a linked polygon to the current polygon - * @param polygon Polygon to add link to - * @return Pointer to new polygon - */ -LinkedGeoPolygon* addNewLinkedPolygon(LinkedGeoPolygon* polygon) { - assert(polygon->next == NULL); - LinkedGeoPolygon* next = H3_MEMORY(calloc)(1, sizeof(*next)); - assert(next != NULL); - polygon->next = next; - return next; -} - -/** - * Add a new linked loop to the current polygon - * @param polygon Polygon to add loop to - * @return Pointer to loop - */ -LinkedGeoLoop* addNewLinkedLoop(LinkedGeoPolygon* polygon) { - LinkedGeoLoop* loop = H3_MEMORY(calloc)(1, sizeof(*loop)); - assert(loop != NULL); - return addLinkedLoop(polygon, loop); -} - -/** - * Add an existing linked loop to the current polygon - * @param polygon Polygon to add loop to - * @return Pointer to loop - */ -LinkedGeoLoop* addLinkedLoop(LinkedGeoPolygon* polygon, LinkedGeoLoop* loop) { - LinkedGeoLoop* last = polygon->last; - if (last == NULL) { - assert(polygon->first == NULL); - polygon->first = loop; - } else { - last->next = loop; - } - polygon->last = loop; - return loop; -} - -/** - * Add a new linked coordinate to the current loop - * @param loop Loop to add coordinate to - * @param vertex Coordinate to add - * @return Pointer to the coordinate - */ -LinkedGeoCoord* addLinkedCoord(LinkedGeoLoop* loop, const GeoCoord* vertex) { - LinkedGeoCoord* coord = H3_MEMORY(malloc)(sizeof(*coord)); - assert(coord != NULL); - *coord = (LinkedGeoCoord){.vertex = *vertex, .next = NULL}; - LinkedGeoCoord* last = loop->last; - if (last == NULL) { - assert(loop->first == NULL); - loop->first = coord; - } else { - last->next = coord; - } - loop->last = coord; - return coord; -} - -/** - * Free all allocated memory for a linked geo loop. The caller is - * responsible for freeing memory allocated to input loop struct. - * @param loop Loop to free - */ -void destroyLinkedGeoLoop(LinkedGeoLoop* loop) { - LinkedGeoCoord* nextCoord; - for (LinkedGeoCoord* currentCoord = loop->first; currentCoord != NULL; - currentCoord = nextCoord) { - nextCoord = currentCoord->next; - H3_MEMORY(free)(currentCoord); - } -} - -/** - * Free all allocated memory for a linked geo structure. The caller is - * responsible for freeing memory allocated to input polygon struct. - * @param polygon Pointer to the first polygon in the structure - */ -void H3_EXPORT(destroyLinkedPolygon)(LinkedGeoPolygon* polygon) { - // flag to skip the input polygon - bool skip = true; - LinkedGeoPolygon* nextPolygon; - LinkedGeoLoop* nextLoop; - for (LinkedGeoPolygon* currentPolygon = polygon; currentPolygon != NULL; - currentPolygon = nextPolygon) { - for (LinkedGeoLoop* currentLoop = currentPolygon->first; - currentLoop != NULL; currentLoop = nextLoop) { - destroyLinkedGeoLoop(currentLoop); - nextLoop = currentLoop->next; - H3_MEMORY(free)(currentLoop); - } - nextPolygon = currentPolygon->next; - if (skip) { - // do not free the input polygon - skip = false; - } else { - H3_MEMORY(free)(currentPolygon); - } - } -} - -/** - * Count the number of polygons in a linked list - * @param polygon Starting polygon - * @return Count - */ -int countLinkedPolygons(LinkedGeoPolygon* polygon) { - int count = 0; - while (polygon != NULL) { - count++; - polygon = polygon->next; - } - return count; -} - -/** - * Count the number of linked loops in a polygon - * @param polygon Polygon to count loops for - * @return Count - */ -int countLinkedLoops(LinkedGeoPolygon* polygon) { - LinkedGeoLoop* loop = polygon->first; - int count = 0; - while (loop != NULL) { - count++; - loop = loop->next; - } - return count; -} - -/** - * Count the number of coordinates in a loop - * @param loop Loop to count coordinates for - * @return Count - */ -int countLinkedCoords(LinkedGeoLoop* loop) { - LinkedGeoCoord* coord = loop->first; - int count = 0; - while (coord != NULL) { - count++; - coord = coord->next; - } - return count; -} - -/** - * Count the number of polygons containing a given loop. - * @param loop Loop to count containers for - * @param polygons Polygons to test - * @param bboxes Bounding boxes for polygons, used in point-in-poly check - * @param polygonCount Number of polygons in the test array - * @return Number of polygons containing the loop - */ -static int countContainers(const LinkedGeoLoop* loop, - const LinkedGeoPolygon** polygons, - const BBox** bboxes, const int polygonCount) { - int containerCount = 0; - for (int i = 0; i < polygonCount; i++) { - if (loop != polygons[i]->first && - pointInsideLinkedGeoLoop(polygons[i]->first, bboxes[i], - &loop->first->vertex)) { - containerCount++; - } - } - return containerCount; -} - -/** - * Given a list of nested containers, find the one most deeply nested. - * @param polygons Polygon containers to check - * @param bboxes Bounding boxes for polygons, used in point-in-poly check - * @param polygonCount Number of polygons in the list - * @return Deepest container, or null if list is empty - */ -static const LinkedGeoPolygon* findDeepestContainer( - const LinkedGeoPolygon** polygons, const BBox** bboxes, - const int polygonCount) { - // Set the initial return value to the first candidate - const LinkedGeoPolygon* parent = polygonCount > 0 ? polygons[0] : NULL; - - // If we have multiple polygons, they must be nested inside each other. - // Find the innermost polygon by taking the one with the most containers - // in the list. - if (polygonCount > 1) { - int max = -1; - for (int i = 0; i < polygonCount; i++) { - int count = countContainers(polygons[i]->first, polygons, bboxes, - polygonCount); - if (count > max) { - parent = polygons[i]; - max = count; - } - } - } - - return parent; -} - -/** - * Find the polygon to which a given hole should be allocated. Note that this - * function will return null if no parent is found. - * @param loop Inner loop describing a hole - * @param polygon Head of a linked list of polygons to check - * @param bboxes Bounding boxes for polygons, used in point-in-poly check - * @param polygonCount Number of polygons to check - * @return Pointer to parent polygon, or null if not found - */ -static const LinkedGeoPolygon* findPolygonForHole( - const LinkedGeoLoop* loop, const LinkedGeoPolygon* polygon, - const BBox* bboxes, const int polygonCount) { - // Early exit with no polygons - if (polygonCount == 0) { - return NULL; - } - // Initialize arrays for candidate loops and their bounding boxes - const LinkedGeoPolygon** candidates = - H3_MEMORY(malloc)(polygonCount * sizeof(LinkedGeoPolygon*)); - assert(candidates != NULL); - const BBox** candidateBBoxes = - H3_MEMORY(malloc)(polygonCount * sizeof(BBox*)); - assert(candidateBBoxes != NULL); - - // Find all polygons that contain the loop - int candidateCount = 0; - int index = 0; - while (polygon) { - // We are guaranteed not to overlap, so just test the first point - if (pointInsideLinkedGeoLoop(polygon->first, &bboxes[index], - &loop->first->vertex)) { - candidates[candidateCount] = polygon; - candidateBBoxes[candidateCount] = &bboxes[index]; - candidateCount++; - } - polygon = polygon->next; - index++; - } - - // The most deeply nested container is the immediate parent - const LinkedGeoPolygon* parent = - findDeepestContainer(candidates, candidateBBoxes, candidateCount); - - // Free allocated memory - H3_MEMORY(free)(candidates); - H3_MEMORY(free)(candidateBBoxes); - - return parent; -} - -/** - * Normalize a LinkedGeoPolygon in-place into a structure following GeoJSON - * MultiPolygon rules: Each polygon must have exactly one outer loop, which - * must be first in the list, followed by any holes. Holes in this algorithm - * are identified by winding order (holes are clockwise), which is guaranteed - * by the h3SetToVertexGraph algorithm. - * - * Input to this function is assumed to be a single polygon including all - * loops to normalize. It's assumed that a valid arrangement is possible. - * - * @param root Root polygon including all loops - * @return 0 on success, or an error code > 0 for invalid input - */ -int normalizeMultiPolygon(LinkedGeoPolygon* root) { - // We assume that the input is a single polygon with loops; - // if it has multiple polygons, don't touch it - if (root->next) { - return NORMALIZATION_ERR_MULTIPLE_POLYGONS; - } - - // Count loops, exiting early if there's only one - int loopCount = countLinkedLoops(root); - if (loopCount <= 1) { - return NORMALIZATION_SUCCESS; - } - - int resultCode = NORMALIZATION_SUCCESS; - LinkedGeoPolygon* polygon = NULL; - LinkedGeoLoop* next; - int innerCount = 0; - int outerCount = 0; - - // Create an array to hold all of the inner loops. Note that - // this array will never be full, as there will always be fewer - // inner loops than outer loops. - LinkedGeoLoop** innerLoops = - H3_MEMORY(malloc)(loopCount * sizeof(LinkedGeoLoop*)); - assert(innerLoops != NULL); - - // Create an array to hold the bounding boxes for the outer loops - BBox* bboxes = H3_MEMORY(malloc)(loopCount * sizeof(BBox)); - assert(bboxes != NULL); - - // Get the first loop and unlink it from root - LinkedGeoLoop* loop = root->first; - *root = (LinkedGeoPolygon){0}; - - // Iterate over all loops, moving inner loops into an array and - // assigning outer loops to new polygons - while (loop) { - if (isClockwiseLinkedGeoLoop(loop)) { - innerLoops[innerCount] = loop; - innerCount++; - } else { - polygon = polygon == NULL ? root : addNewLinkedPolygon(polygon); - addLinkedLoop(polygon, loop); - bboxFromLinkedGeoLoop(loop, &bboxes[outerCount]); - outerCount++; - } - // get the next loop and unlink it from this one - next = loop->next; - loop->next = NULL; - loop = next; - } - - // Find polygon for each inner loop and assign the hole to it - for (int i = 0; i < innerCount; i++) { - polygon = (LinkedGeoPolygon*)findPolygonForHole(innerLoops[i], root, - bboxes, outerCount); - if (polygon) { - addLinkedLoop(polygon, innerLoops[i]); - } else { - // If we can't find a polygon (possible with invalid input), then - // we need to release the memory for the hole, because the loop has - // been unlinked from the root and the caller will no longer have - // a way to destroy it with destroyLinkedPolygon. - destroyLinkedGeoLoop(innerLoops[i]); - H3_MEMORY(free)(innerLoops[i]); - resultCode = NORMALIZATION_ERR_UNASSIGNED_HOLES; - } - } - - // Free allocated memory - H3_MEMORY(free)(innerLoops); - H3_MEMORY(free)(bboxes); - - return resultCode; -} - -// Define macros used in polygon algos for LinkedGeoLoop -#define TYPE LinkedGeoLoop -#define INIT_ITERATION INIT_ITERATION_LINKED_LOOP -#define ITERATE ITERATE_LINKED_LOOP -#define IS_EMPTY IS_EMPTY_LINKED_LOOP - -#include "h3_polygonAlgos.h" - -#undef TYPE -#undef IS_EMPTY -#undef INIT_ITERATION -#undef ITERATE diff --git a/v3/h3_linkedGeo.h b/v3/h3_linkedGeo.h deleted file mode 100644 index c3d9b15..0000000 --- a/v3/h3_linkedGeo.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2017-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. - */ -/** @file linkedGeo.h - * @brief Linked data structure for geo data - */ - -#ifndef LINKED_GEO_H -#define LINKED_GEO_H - -#include - -#include "h3_bbox.h" -#include "h3_geoCoord.h" -#include "h3_h3api.h" - -// Error codes for normalizeMultiPolygon -#define NORMALIZATION_SUCCESS 0 -#define NORMALIZATION_ERR_MULTIPLE_POLYGONS 1 -#define NORMALIZATION_ERR_UNASSIGNED_HOLES 2 - -// Macros for use with polygonAlgos.h -/** Macro: Init iteration vars for LinkedGeoLoop */ -#define INIT_ITERATION_LINKED_LOOP \ - LinkedGeoCoord* currentCoord = NULL; \ - LinkedGeoCoord* nextCoord = NULL - -/** Macro: Get the next coord in a linked loop, wrapping if needed */ -#define GET_NEXT_COORD(loop, coordToCheck) \ - coordToCheck == NULL ? loop->first : currentCoord->next - -/** Macro: Increment LinkedGeoLoop iteration, or break if done. */ -#define ITERATE_LINKED_LOOP(loop, vertexA, vertexB) \ - currentCoord = GET_NEXT_COORD(loop, currentCoord); \ - if (currentCoord == NULL) break; \ - vertexA = currentCoord->vertex; \ - nextCoord = GET_NEXT_COORD(loop, currentCoord->next); \ - vertexB = nextCoord->vertex - -/** Macro: Whether a LinkedGeoLoop is empty */ -#define IS_EMPTY_LINKED_LOOP(loop) loop->first == NULL - -int normalizeMultiPolygon(LinkedGeoPolygon* root); -LinkedGeoPolygon* addNewLinkedPolygon(LinkedGeoPolygon* polygon); -LinkedGeoLoop* addNewLinkedLoop(LinkedGeoPolygon* polygon); -LinkedGeoLoop* addLinkedLoop(LinkedGeoPolygon* polygon, LinkedGeoLoop* loop); -LinkedGeoCoord* addLinkedCoord(LinkedGeoLoop* loop, const GeoCoord* vertex); -int countLinkedPolygons(LinkedGeoPolygon* polygon); -int countLinkedLoops(LinkedGeoPolygon* polygon); -int countLinkedCoords(LinkedGeoLoop* loop); -void destroyLinkedGeoLoop(LinkedGeoLoop* loop); - -// The following functions are created via macro in polygonAlgos.h, -// so their signatures are documented here: - -/** - * Create a bounding box from a LinkedGeoLoop - * @param geofence Input Geofence - * @param bbox Output bbox - */ -void bboxFromLinkedGeoLoop(const LinkedGeoLoop* loop, BBox* bbox); - -/** - * Take a given LinkedGeoLoop data structure and check if it - * contains a given geo coordinate. - * @param loop The linked loop - * @param bbox The bbox for the loop - * @param coord The coordinate to check - * @return Whether the point is contained - */ -bool pointInsideLinkedGeoLoop(const LinkedGeoLoop* loop, const BBox* bbox, - const GeoCoord* coord); - -/** - * Whether the winding order of a given LinkedGeoLoop is clockwise - * @param loop The loop to check - * @return Whether the loop is clockwise - */ -bool isClockwiseLinkedGeoLoop(const LinkedGeoLoop* loop); - -#endif diff --git a/v3/h3_localij.c b/v3/h3_localij.c deleted file mode 100644 index 2ec453c..0000000 --- a/v3/h3_localij.c +++ /dev/null @@ -1,650 +0,0 @@ -/* - * Copyright 2018-2019 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. - */ -/** @file localij.c - * @brief Local IJ coordinate space functions - * - * These functions try to provide a useful coordinate space in the vicinity of - * an origin index. - */ -#include -#include -#include -#include -#include - -#include "h3_baseCells.h" -#include "h3_faceijk.h" -#include "h3_h3Index.h" -#include "h3_mathExtensions.h" - -/** - * Origin leading digit -> index leading digit -> rotations 60 cw - * Either being 1 (K axis) is invalid. - * No good default at 0. - */ -const int PENTAGON_ROTATIONS[7][7] = { - {0, -1, 0, 0, 0, 0, 0}, // 0 - {-1, -1, -1, -1, -1, -1, -1}, // 1 - {0, -1, 0, 0, 0, 1, 0}, // 2 - {0, -1, 0, 0, 1, 1, 0}, // 3 - {0, -1, 0, 5, 0, 0, 0}, // 4 - {0, -1, 5, 5, 0, 0, 0}, // 5 - {0, -1, 0, 0, 0, 0, 0}, // 6 -}; -/** - * Reverse base cell direction -> leading index digit -> rotations 60 ccw. - * For reversing the rotation introduced in PENTAGON_ROTATIONS when - * the origin is on a pentagon (regardless of the base cell of the index.) - */ -const int PENTAGON_ROTATIONS_REVERSE[7][7] = { - {0, 0, 0, 0, 0, 0, 0}, // 0 - {-1, -1, -1, -1, -1, -1, -1}, // 1 - {0, 1, 0, 0, 0, 0, 0}, // 2 - {0, 1, 0, 0, 0, 1, 0}, // 3 - {0, 5, 0, 0, 0, 0, 0}, // 4 - {0, 5, 0, 5, 0, 0, 0}, // 5 - {0, 0, 0, 0, 0, 0, 0}, // 6 -}; -/** - * Reverse base cell direction -> leading index digit -> rotations 60 ccw. - * For reversing the rotation introduced in PENTAGON_ROTATIONS when the index is - * on a pentagon and the origin is not. - */ -const int PENTAGON_ROTATIONS_REVERSE_NONPOLAR[7][7] = { - {0, 0, 0, 0, 0, 0, 0}, // 0 - {-1, -1, -1, -1, -1, -1, -1}, // 1 - {0, 1, 0, 0, 0, 0, 0}, // 2 - {0, 1, 0, 0, 0, 1, 0}, // 3 - {0, 5, 0, 0, 0, 0, 0}, // 4 - {0, 1, 0, 5, 1, 1, 0}, // 5 - {0, 0, 0, 0, 0, 0, 0}, // 6 -}; -/** - * Reverse base cell direction -> leading index digit -> rotations 60 ccw. - * For reversing the rotation introduced in PENTAGON_ROTATIONS when the index is - * on a polar pentagon and the origin is not. - */ -const int PENTAGON_ROTATIONS_REVERSE_POLAR[7][7] = { - {0, 0, 0, 0, 0, 0, 0}, // 0 - {-1, -1, -1, -1, -1, -1, -1}, // 1 - {0, 1, 1, 1, 1, 1, 1}, // 2 - {0, 1, 0, 0, 0, 1, 0}, // 3 - {0, 1, 0, 0, 1, 1, 1}, // 4 - {0, 1, 0, 5, 1, 1, 0}, // 5 - {0, 1, 1, 0, 1, 1, 1}, // 6 -}; - -/** - * Prohibited directions when unfolding a pentagon. - * - * Indexes by two directions, both relative to the pentagon base cell. The first - * is the direction of the origin index and the second is the direction of the - * index to unfold. Direction refers to the direction from base cell to base - * cell if the indexes are on different base cells, or the leading digit if - * within the pentagon base cell. - * - * This previously included a Class II/Class III check but these were removed - * due to failure cases. It's possible this could be restricted to a narrower - * set of a failure cases. Currently, the logic is any unfolding across more - * than one icosahedron face is not permitted. - */ -const bool FAILED_DIRECTIONS[7][7] = { - {false, false, false, false, false, false, false}, // 0 - {false, false, false, false, false, false, false}, // 1 - {false, false, false, false, true, true, false}, // 2 - {false, false, false, false, true, false, true}, // 3 - {false, false, true, true, false, false, false}, // 4 - {false, false, true, false, false, false, true}, // 5 - {false, false, false, true, false, true, false}, // 6 -}; - -/** - * Produces ijk+ coordinates for an index anchored by an origin. - * - * The coordinate space used by this function may have deleted - * regions or warping due to pentagonal distortion. - * - * Coordinates are only comparable if they come from the same - * origin index. - * - * Failure may occur if the index is too far away from the origin - * or if the index is on the other side of a pentagon. - * - * @param origin An anchoring index for the ijk+ coordinate system. - * @param index Index to find the coordinates of - * @param out ijk+ coordinates of the index will be placed here on success - * @return 0 on success, or another value on failure. - */ -int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out) { - int res = H3_GET_RESOLUTION(origin); - - if (res != H3_GET_RESOLUTION(h3)) { - return 1; - } - - int originBaseCell = H3_GET_BASE_CELL(origin); - int baseCell = H3_GET_BASE_CELL(h3); - - // Direction from origin base cell to index base cell - Direction dir = CENTER_DIGIT; - Direction revDir = CENTER_DIGIT; - if (originBaseCell != baseCell) { - dir = _getBaseCellDirection(originBaseCell, baseCell); - if (dir == INVALID_DIGIT) { - // Base cells are not neighbors, can't unfold. - return 2; - } - revDir = _getBaseCellDirection(baseCell, originBaseCell); - assert(revDir != INVALID_DIGIT); - } - - int originOnPent = _isBaseCellPentagon(originBaseCell); - int indexOnPent = _isBaseCellPentagon(baseCell); - - FaceIJK indexFijk = {0}; - if (dir != CENTER_DIGIT) { - // Rotate index into the orientation of the origin base cell. - // cw because we are undoing the rotation into that base cell. - int baseCellRotations = baseCellNeighbor60CCWRots[originBaseCell][dir]; - if (indexOnPent) { - for (int i = 0; i < baseCellRotations; i++) { - h3 = _h3RotatePent60cw(h3); - - revDir = _rotate60cw(revDir); - if (revDir == K_AXES_DIGIT) revDir = _rotate60cw(revDir); - } - } else { - for (int i = 0; i < baseCellRotations; i++) { - h3 = _h3Rotate60cw(h3); - - revDir = _rotate60cw(revDir); - } - } - } - // Face is unused. This produces coordinates in base cell coordinate space. - _h3ToFaceIjkWithInitializedFijk(h3, &indexFijk); - - if (dir != CENTER_DIGIT) { - assert(baseCell != originBaseCell); - assert(!(originOnPent && indexOnPent)); - - int pentagonRotations = 0; - int directionRotations = 0; - - if (originOnPent) { - int originLeadingDigit = _h3LeadingNonZeroDigit(origin); - - if (FAILED_DIRECTIONS[originLeadingDigit][dir]) { - // TODO: We may be unfolding the pentagon incorrectly in this - // case; return an error code until this is guaranteed to be - // correct. - return 3; - } - - directionRotations = PENTAGON_ROTATIONS[originLeadingDigit][dir]; - pentagonRotations = directionRotations; - } else if (indexOnPent) { - int indexLeadingDigit = _h3LeadingNonZeroDigit(h3); - - if (FAILED_DIRECTIONS[indexLeadingDigit][revDir]) { - // TODO: We may be unfolding the pentagon incorrectly in this - // case; return an error code until this is guaranteed to be - // correct. - return 4; - } - - pentagonRotations = PENTAGON_ROTATIONS[revDir][indexLeadingDigit]; - } - - assert(pentagonRotations >= 0); - assert(directionRotations >= 0); - - for (int i = 0; i < pentagonRotations; i++) { - _ijkRotate60cw(&indexFijk.coord); - } - - CoordIJK offset = {0}; - _neighbor(&offset, dir); - // Scale offset based on resolution - for (int r = res - 1; r >= 0; r--) { - if (isResClassIII(r + 1)) { - // rotate ccw - _downAp7(&offset); - } else { - // rotate cw - _downAp7r(&offset); - } - } - - for (int i = 0; i < directionRotations; i++) { - _ijkRotate60cw(&offset); - } - - // Perform necessary translation - _ijkAdd(&indexFijk.coord, &offset, &indexFijk.coord); - _ijkNormalize(&indexFijk.coord); - } else if (originOnPent && indexOnPent) { - // If the origin and index are on pentagon, and we checked that the base - // cells are the same or neighboring, then they must be the same base - // cell. - assert(baseCell == originBaseCell); - - int originLeadingDigit = _h3LeadingNonZeroDigit(origin); - int indexLeadingDigit = _h3LeadingNonZeroDigit(h3); - - if (FAILED_DIRECTIONS[originLeadingDigit][indexLeadingDigit]) { - // TODO: We may be unfolding the pentagon incorrectly in this case; - // return an error code until this is guaranteed to be correct. - return 5; - } - - int withinPentagonRotations = - PENTAGON_ROTATIONS[originLeadingDigit][indexLeadingDigit]; - - for (int i = 0; i < withinPentagonRotations; i++) { - _ijkRotate60cw(&indexFijk.coord); - } - } - - *out = indexFijk.coord; - return 0; -} - -/** - * Produces an index for ijk+ coordinates anchored by an origin. - * - * The coordinate space used by this function may have deleted - * regions or warping due to pentagonal distortion. - * - * Failure may occur if the coordinates are too far away from the origin - * or if the index is on the other side of a pentagon. - * - * @param origin An anchoring index for the ijk+ coordinate system. - * @param ijk IJK+ Coordinates to find the index of - * @param out The index will be placed here on success - * @return 0 on success, or another value on failure. - */ -int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) { - int res = H3_GET_RESOLUTION(origin); - int originBaseCell = H3_GET_BASE_CELL(origin); - int originOnPent = _isBaseCellPentagon(originBaseCell); - - // This logic is very similar to faceIjkToH3 - // initialize the index - *out = H3_INIT; - H3_SET_MODE(*out, H3_HEXAGON_MODE); - H3_SET_RESOLUTION(*out, res); - - // check for res 0/base cell - if (res == 0) { - if (ijk->i > 1 || ijk->j > 1 || ijk->k > 1) { - // out of range input - return 1; - } - - const Direction dir = _unitIjkToDigit(ijk); - const int newBaseCell = _getBaseCellNeighbor(originBaseCell, dir); - if (newBaseCell == INVALID_BASE_CELL) { - // Moving in an invalid direction off a pentagon. - return 1; - } - H3_SET_BASE_CELL(*out, newBaseCell); - return 0; - } - - // we need to find the correct base cell offset (if any) for this H3 index; - // start with the passed in base cell and resolution res ijk coordinates - // in that base cell's coordinate system - CoordIJK ijkCopy = *ijk; - - // build the H3Index from finest res up - // adjust r for the fact that the res 0 base cell offsets the indexing - // digits - for (int r = res - 1; r >= 0; r--) { - CoordIJK lastIJK = ijkCopy; - CoordIJK lastCenter; - if (isResClassIII(r + 1)) { - // rotate ccw - _upAp7(&ijkCopy); - lastCenter = ijkCopy; - _downAp7(&lastCenter); - } else { - // rotate cw - _upAp7r(&ijkCopy); - lastCenter = ijkCopy; - _downAp7r(&lastCenter); - } - - CoordIJK diff; - _ijkSub(&lastIJK, &lastCenter, &diff); - _ijkNormalize(&diff); - - H3_SET_INDEX_DIGIT(*out, r + 1, _unitIjkToDigit(&diff)); - } - - // ijkCopy should now hold the IJK of the base cell in the - // coordinate system of the current base cell - - if (ijkCopy.i > 1 || ijkCopy.j > 1 || ijkCopy.k > 1) { - // out of range input - return 2; - } - - // lookup the correct base cell - Direction dir = _unitIjkToDigit(&ijkCopy); - int baseCell = _getBaseCellNeighbor(originBaseCell, dir); - // If baseCell is invalid, it must be because the origin base cell is a - // pentagon, and because pentagon base cells do not border each other, - // baseCell must not be a pentagon. - int indexOnPent = - (baseCell == INVALID_BASE_CELL ? 0 : _isBaseCellPentagon(baseCell)); - - if (dir != CENTER_DIGIT) { - // If the index is in a warped direction, we need to unwarp the base - // cell direction. There may be further need to rotate the index digits. - int pentagonRotations = 0; - if (originOnPent) { - const Direction originLeadingDigit = _h3LeadingNonZeroDigit(origin); - pentagonRotations = - PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][dir]; - for (int i = 0; i < pentagonRotations; i++) { - dir = _rotate60ccw(dir); - } - // The pentagon rotations are being chosen so that dir is not the - // deleted direction. If it still happens, it means we're moving - // into a deleted subsequence, so there is no index here. - if (dir == K_AXES_DIGIT) { - return 3; - } - baseCell = _getBaseCellNeighbor(originBaseCell, dir); - - // indexOnPent does not need to be checked again since no pentagon - // base cells border each other. - assert(baseCell != INVALID_BASE_CELL); - assert(!_isBaseCellPentagon(baseCell)); - } - - // Now we can determine the relation between the origin and target base - // cell. - const int baseCellRotations = - baseCellNeighbor60CCWRots[originBaseCell][dir]; - assert(baseCellRotations >= 0); - - // Adjust for pentagon warping within the base cell. The base cell - // should be in the right location, so now we need to rotate the index - // back. We might not need to check for errors since we would just be - // double mapping. - if (indexOnPent) { - const Direction revDir = - _getBaseCellDirection(baseCell, originBaseCell); - assert(revDir != INVALID_DIGIT); - - // Adjust for the different coordinate space in the two base cells. - // This is done first because we need to do the pentagon rotations - // based on the leading digit in the pentagon's coordinate system. - for (int i = 0; i < baseCellRotations; i++) { - *out = _h3Rotate60ccw(*out); - } - - const Direction indexLeadingDigit = _h3LeadingNonZeroDigit(*out); - if (_isBaseCellPolarPentagon(baseCell)) { - pentagonRotations = - PENTAGON_ROTATIONS_REVERSE_POLAR[revDir][indexLeadingDigit]; - } else { - pentagonRotations = - PENTAGON_ROTATIONS_REVERSE_NONPOLAR[revDir] - [indexLeadingDigit]; - } - - assert(pentagonRotations >= 0); - for (int i = 0; i < pentagonRotations; i++) { - *out = _h3RotatePent60ccw(*out); - } - } else { - assert(pentagonRotations >= 0); - for (int i = 0; i < pentagonRotations; i++) { - *out = _h3Rotate60ccw(*out); - } - - // Adjust for the different coordinate space in the two base cells. - for (int i = 0; i < baseCellRotations; i++) { - *out = _h3Rotate60ccw(*out); - } - } - } else if (originOnPent && indexOnPent) { - const int originLeadingDigit = _h3LeadingNonZeroDigit(origin); - const int indexLeadingDigit = _h3LeadingNonZeroDigit(*out); - - const int withinPentagonRotations = - PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][indexLeadingDigit]; - assert(withinPentagonRotations >= 0); - - for (int i = 0; i < withinPentagonRotations; i++) { - *out = _h3Rotate60ccw(*out); - } - } - - if (indexOnPent) { - // TODO: There are cases in h3ToLocalIjk which are failed but not - // accounted for here - instead just fail if the recovered index is - // invalid. - if (_h3LeadingNonZeroDigit(*out) == K_AXES_DIGIT) { - return 4; - } - } - - H3_SET_BASE_CELL(*out, baseCell); - return 0; -} - -/** - * Produces ij coordinates for an index anchored by an origin. - * - * The coordinate space used by this function may have deleted - * regions or warping due to pentagonal distortion. - * - * Coordinates are only comparable if they come from the same - * origin index. - * - * Failure may occur if the index is too far away from the origin - * or if the index is on the other side of a pentagon. - * - * This function is experimental, and its output is not guaranteed - * to be compatible across different versions of H3. - * - * @param origin An anchoring index for the ij coordinate system. - * @param index Index to find the coordinates of - * @param out ij coordinates of the index will be placed here on success - * @return 0 on success, or another value on failure. - */ -int H3_EXPORT(experimentalH3ToLocalIj)(H3Index origin, H3Index h3, - CoordIJ* out) { - // This function is currently experimental. Once ready to be part of the - // non-experimental API, this function (with the experimental prefix) will - // be marked as deprecated and to be removed in the next major version. It - // will be replaced with a non-prefixed function name. - CoordIJK ijk; - int failed = h3ToLocalIjk(origin, h3, &ijk); - if (failed) { - return failed; - } - - ijkToIj(&ijk, out); - - return 0; -} - -/** - * Produces an index for ij coordinates anchored by an origin. - * - * The coordinate space used by this function may have deleted - * regions or warping due to pentagonal distortion. - * - * Failure may occur if the index is too far away from the origin - * or if the index is on the other side of a pentagon. - * - * This function is experimental, and its output is not guaranteed - * to be compatible across different versions of H3. - * - * @param origin An anchoring index for the ij coordinate system. - * @param out ij coordinates to index. - * @param index Index will be placed here on success. - * @return 0 on success, or another value on failure. - */ -int H3_EXPORT(experimentalLocalIjToH3)(H3Index origin, const CoordIJ* ij, - H3Index* out) { - // This function is currently experimental. Once ready to be part of the - // non-experimental API, this function (with the experimental prefix) will - // be marked as deprecated and to be removed in the next major version. It - // will be replaced with a non-prefixed function name. - CoordIJK ijk; - ijToIjk(ij, &ijk); - - return localIjkToH3(origin, &ijk, out); -} - -/** - * Produces the grid distance between the two indexes. - * - * This function may fail to find the distance between two indexes, for - * example if they are very far apart. It may also fail when finding - * distances for indexes on opposite sides of a pentagon. - * - * @param origin Index to find the distance from. - * @param index Index to find the distance to. - * @return The distance, or a negative number if the library could not - * compute the distance. - */ -int H3_EXPORT(h3Distance)(H3Index origin, H3Index h3) { - CoordIJK originIjk, h3Ijk; - if (h3ToLocalIjk(origin, origin, &originIjk)) { - // Currently there are no tests that would cause getting the coordinates - // for an index the same as the origin to fail. - return -1; // LCOV_EXCL_LINE - } - if (h3ToLocalIjk(origin, h3, &h3Ijk)) { - return -1; - } - - return ijkDistance(&originIjk, &h3Ijk); -} - -/** - * Number of indexes in a line from the start index to the end index, - * to be used for allocating memory. Returns a negative number if the - * line cannot be computed. - * - * @param start Start index of the line - * @param end End index of the line - * @return Size of the line, or a negative number if the line cannot - * be computed. - */ -int H3_EXPORT(h3LineSize)(H3Index start, H3Index end) { - int distance = H3_EXPORT(h3Distance)(start, end); - return distance >= 0 ? distance + 1 : distance; -} - -/** - * Given cube coords as doubles, round to valid integer coordinates. Algorithm - * from https://www.redblobgames.com/grids/hexagons/#rounding - * @param i Floating-point I coord - * @param j Floating-point J coord - * @param k Floating-point K coord - * @param ijk IJK coord struct, modified in place - */ -static void cubeRound(double i, double j, double k, CoordIJK* ijk) { - int ri = round(i); - int rj = round(j); - int rk = round(k); - - double iDiff = fabs((double)ri - i); - double jDiff = fabs((double)rj - j); - double kDiff = fabs((double)rk - k); - - // Round, maintaining valid cube coords - if (iDiff > jDiff && iDiff > kDiff) { - ri = -rj - rk; - } else if (jDiff > kDiff) { - rj = -ri - rk; - } else { - rk = -ri - rj; - } - - ijk->i = ri; - ijk->j = rj; - ijk->k = rk; -} - -/** - * Given two H3 indexes, return the line of indexes between them (inclusive). - * - * This function may fail to find the line between two indexes, for - * example if they are very far apart. It may also fail when finding - * distances for indexes on opposite sides of a pentagon. - * - * Notes: - * - * - The specific output of this function should not be considered stable - * across library versions. The only guarantees the library provides are - * that the line length will be `h3Distance(start, end) + 1` and that - * every index in the line will be a neighbor of the preceding index. - * - Lines are drawn in grid space, and may not correspond exactly to either - * Cartesian lines or great arcs. - * - * @param start Start index of the line - * @param end End index of the line - * @param out Output array, which must be of size h3LineSize(start, end) - * @return 0 on success, or another value on failure. - */ -int H3_EXPORT(h3Line)(H3Index start, H3Index end, H3Index* out) { - int distance = H3_EXPORT(h3Distance)(start, end); - // Early exit if we can't calculate the line - if (distance < 0) { - return distance; - } - - // Get IJK coords for the start and end. We've already confirmed - // that these can be calculated with the distance check above. - CoordIJK startIjk = {0}; - CoordIJK endIjk = {0}; - - // Convert H3 addresses to IJK coords - h3ToLocalIjk(start, start, &startIjk); - h3ToLocalIjk(start, end, &endIjk); - - // Convert IJK to cube coordinates suitable for linear interpolation - ijkToCube(&startIjk); - ijkToCube(&endIjk); - - double iStep = - distance ? (double)(endIjk.i - startIjk.i) / (double)distance : 0; - double jStep = - distance ? (double)(endIjk.j - startIjk.j) / (double)distance : 0; - double kStep = - distance ? (double)(endIjk.k - startIjk.k) / (double)distance : 0; - - CoordIJK currentIjk = {startIjk.i, startIjk.j, startIjk.k}; - for (int n = 0; n <= distance; n++) { - cubeRound((double)startIjk.i + iStep * n, - (double)startIjk.j + jStep * n, - (double)startIjk.k + kStep * n, ¤tIjk); - // Convert cube -> ijk -> h3 index - cubeToIjk(¤tIjk); - localIjkToH3(start, ¤tIjk, &out[n]); - } - - return 0; -} diff --git a/v3/h3_localij.h b/v3/h3_localij.h deleted file mode 100644 index fa2ab5c..0000000 --- a/v3/h3_localij.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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. - */ -/** @file localij.h - * @brief Local IJ coordinate space functions. - */ - -#ifndef LOCALIJ_H -#define LOCALIJ_H - -#include "h3_coordijk.h" -#include "h3_h3api.h" - -int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out); -int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out); - -#endif diff --git a/v3/h3_mathExtensions.c b/v3/h3_mathExtensions.c deleted file mode 100644 index 1c544a1..0000000 --- a/v3/h3_mathExtensions.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2017 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. - */ -/** @file mathExtensions.c - * @brief Math functions that should've been in math.h but aren't - */ - -#include "h3_mathExtensions.h" - -/** - * _ipow does integer exponentiation efficiently. Taken from StackOverflow. - * - * @param base the integer base - * @param exp the integer exponent - * - * @return the exponentiated value - */ -int _ipow(int base, int exp) { - int result = 1; - while (exp) { - if (exp & 1) result *= base; - exp >>= 1; - base *= base; - } - - return result; -} diff --git a/v3/h3_mathExtensions.h b/v3/h3_mathExtensions.h deleted file mode 100644 index 650d6a9..0000000 --- a/v3/h3_mathExtensions.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2017-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. - */ -/** @file mathExtensions.h - * @brief Math functions that should've been in math.h but aren't - */ - -#ifndef MATHEXTENSIONS_H -#define MATHEXTENSIONS_H - -/** - * MAX returns the maximum of two values. - */ -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) - -// Internal functions -int _ipow(int base, int exp); - -#endif diff --git a/v3/h3_polygon.c b/v3/h3_polygon.c deleted file mode 100644 index 72f38c8..0000000 --- a/v3/h3_polygon.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2018-2019 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. - */ -/** @file polygon.c - * @brief Polygon (Geofence) algorithms - */ - -#include "h3_polygon.h" - -#include -#include -#include - -#include "h3_bbox.h" -#include "h3_constants.h" -#include "h3_geoCoord.h" -#include "h3_h3api.h" -#include "h3_linkedGeo.h" - -// Define macros used in polygon algos for Geofence -#define TYPE Geofence -#define INIT_ITERATION INIT_ITERATION_GEOFENCE -#define ITERATE ITERATE_GEOFENCE -#define IS_EMPTY IS_EMPTY_GEOFENCE - -#include "h3_polygonAlgos.h" - -#undef TYPE -#undef INIT_ITERATION -#undef ITERATE -#undef IS_EMPTY - -/** - * Create a bounding box from a GeoPolygon - * @param polygon Input GeoPolygon - * @param bboxes Output bboxes, one for the outer loop and one for each hole - */ -void bboxesFromGeoPolygon(const GeoPolygon* polygon, BBox* bboxes) { - bboxFromGeofence(&polygon->geofence, &bboxes[0]); - for (int i = 0; i < polygon->numHoles; i++) { - bboxFromGeofence(&polygon->holes[i], &bboxes[i + 1]); - } -} - -/** - * pointInsidePolygon takes a given GeoPolygon data structure and - * checks if it contains a given geo coordinate. - * - * @param geoPolygon The geofence and holes defining the relevant area - * @param bboxes The bboxes for the main geofence and each of its holes - * @param coord The coordinate to check - * @return Whether the point is contained - */ -bool pointInsidePolygon(const GeoPolygon* geoPolygon, const BBox* bboxes, - const GeoCoord* coord) { - // Start with contains state of primary geofence - bool contains = - pointInsideGeofence(&(geoPolygon->geofence), &bboxes[0], coord); - - // If the point is contained in the primary geofence, but there are holes in - // the geofence iterate through all holes and return false if the point is - // contained in any hole - if (contains && geoPolygon->numHoles > 0) { - for (int i = 0; i < geoPolygon->numHoles; i++) { - if (pointInsideGeofence(&(geoPolygon->holes[i]), &bboxes[i + 1], - coord)) { - return false; - } - } - } - - return contains; -} diff --git a/v3/h3_polygon.h b/v3/h3_polygon.h deleted file mode 100644 index ec375da..0000000 --- a/v3/h3_polygon.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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. - */ -/** @file polygon.h - * @brief Polygon algorithms - */ - -#ifndef POLYGON_H -#define POLYGON_H - -#include - -#include "h3_bbox.h" -#include "h3_geoCoord.h" -#include "h3_h3api.h" -#include "h3_linkedGeo.h" - -// Macros for use with polygonAlgos.h -/** Macro: Init iteration vars for Geofence */ -#define INIT_ITERATION_GEOFENCE int loopIndex = -1 - -/** Macro: Increment Geofence loop iteration, or break if done. */ -#define ITERATE_GEOFENCE(geofence, vertexA, vertexB) \ - if (++loopIndex >= geofence->numVerts) break; \ - vertexA = geofence->verts[loopIndex]; \ - vertexB = geofence->verts[(loopIndex + 1) % geofence->numVerts] - -/** Macro: Whether a Geofence is empty */ -#define IS_EMPTY_GEOFENCE(geofence) geofence->numVerts == 0 - -// Defined directly in polygon.c: -void bboxesFromGeoPolygon(const GeoPolygon* polygon, BBox* bboxes); -bool pointInsidePolygon(const GeoPolygon* geoPolygon, const BBox* bboxes, - const GeoCoord* coord); - -// The following functions are created via macro in polygonAlgos.h, -// so their signatures are documented here: - -/** - * Create a bounding box from a Geofence - * @param geofence Input Geofence - * @param bbox Output bbox - */ -void bboxFromGeofence(const Geofence* loop, BBox* bbox); - -/** - * Take a given Geofence data structure and check if it - * contains a given geo coordinate. - * @param loop The geofence - * @param bbox The bbox for the loop - * @param coord The coordinate to check - * @return Whether the point is contained - */ -bool pointInsideGeofence(const Geofence* loop, const BBox* bbox, - const GeoCoord* coord); - -/** - * Whether the winding order of a given Geofence is clockwise - * @param loop The loop to check - * @return Whether the loop is clockwise - */ -bool isClockwiseGeofence(const Geofence* geofence); - -#endif diff --git a/v3/h3_polygonAlgos.h b/v3/h3_polygonAlgos.h deleted file mode 100644 index 170abf4..0000000 --- a/v3/h3_polygonAlgos.h +++ /dev/null @@ -1,221 +0,0 @@ -/* - * 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. - */ -/** @file - * @brief Include file for poylgon algorithms. This includes the core - * logic for algorithms acting over loops of coordinates, - * allowing them to be reused for both Geofence and - * LinkegGeoLoop structures. This file is intended to be - * included inline in a file that defines the type-specific - * macros required for iteration. - */ - -#include -#include -#include - -#include "h3_bbox.h" -#include "h3_constants.h" -#include "h3_geoCoord.h" -#include "h3_h3api.h" -#include "h3_linkedGeo.h" -#include "h3_polygon.h" - -#ifndef TYPE -#error "TYPE must be defined before including this header" -#endif - -#ifndef IS_EMPTY -#error "IS_EMPTY must be defined before including this header" -#endif - -#ifndef INIT_ITERATION -#error "INIT_ITERATION must be defined before including this header" -#endif - -#ifndef ITERATE -#error "ITERATE must be defined before including this header" -#endif - -#define LOOP_ALGO_XTJOIN(a, b) a##b -#define LOOP_ALGO_TJOIN(a, b) LOOP_ALGO_XTJOIN(a, b) -#define GENERIC_LOOP_ALGO(func) LOOP_ALGO_TJOIN(func, TYPE) - -/** Macro: Normalize longitude, dealing with transmeridian arcs */ -#define NORMALIZE_LON(lon, isTransmeridian) \ - (isTransmeridian && lon < 0 ? lon + (double)M_2PI : lon) - -/** - * pointInside is the core loop of the point-in-poly algorithm - * @param loop The loop to check - * @param bbox The bbox for the loop being tested - * @param coord The coordinate to check - * @return Whether the point is contained - */ -bool GENERIC_LOOP_ALGO(pointInside)(const TYPE* loop, const BBox* bbox, - const GeoCoord* coord) { - // fail fast if we're outside the bounding box - if (!bboxContains(bbox, coord)) { - return false; - } - bool isTransmeridian = bboxIsTransmeridian(bbox); - bool contains = false; - - double lat = coord->lat; - double lng = NORMALIZE_LON(coord->lon, isTransmeridian); - - GeoCoord a; - GeoCoord b; - - INIT_ITERATION; - - while (true) { - ITERATE(loop, a, b); - - // Ray casting algo requires the second point to always be higher - // than the first, so swap if needed - if (a.lat > b.lat) { - GeoCoord tmp = a; - a = b; - b = tmp; - } - - // If we're totally above or below the latitude ranges, the test - // ray cannot intersect the line segment, so let's move on - if (lat < a.lat || lat > b.lat) { - continue; - } - - double aLng = NORMALIZE_LON(a.lon, isTransmeridian); - double bLng = NORMALIZE_LON(b.lon, isTransmeridian); - - // Rays are cast in the longitudinal direction, in case a point - // exactly matches, to decide tiebreakers, bias westerly - if (aLng == lng || bLng == lng) { - lng -= DBL_EPSILON; - } - - // For the latitude of the point, compute the longitude of the - // point that lies on the line segment defined by a and b - // This is done by computing the percent above a the lat is, - // and traversing the same percent in the longitudinal direction - // of a to b - double ratio = (lat - a.lat) / (b.lat - a.lat); - double testLng = - NORMALIZE_LON(aLng + (bLng - aLng) * ratio, isTransmeridian); - - // Intersection of the ray - if (testLng > lng) { - contains = !contains; - } - } - - return contains; -} - -/** - * Create a bounding box from a simple polygon loop. - * Known limitations: - * - Does not support polygons with two adjacent points > 180 degrees of - * longitude apart. These will be interpreted as crossing the antimeridian. - * - Does not currently support polygons containing a pole. - * @param loop Loop of coordinates - * @param bbox Output bbox - */ -void GENERIC_LOOP_ALGO(bboxFrom)(const TYPE* loop, BBox* bbox) { - // Early exit if there are no vertices - if (IS_EMPTY(loop)) { - *bbox = (BBox){0}; - return; - } - - bbox->south = DBL_MAX; - bbox->west = DBL_MAX; - bbox->north = -DBL_MAX; - bbox->east = -DBL_MAX; - double minPosLon = DBL_MAX; - double maxNegLon = -DBL_MAX; - bool isTransmeridian = false; - - double lat; - double lon; - GeoCoord coord; - GeoCoord next; - - INIT_ITERATION; - - while (true) { - ITERATE(loop, coord, next); - - lat = coord.lat; - lon = coord.lon; - if (lat < bbox->south) bbox->south = lat; - if (lon < bbox->west) bbox->west = lon; - if (lat > bbox->north) bbox->north = lat; - if (lon > bbox->east) bbox->east = lon; - // Save the min positive and max negative longitude for - // use in the transmeridian case - if (lon > 0 && lon < minPosLon) minPosLon = lon; - if (lon < 0 && lon > maxNegLon) maxNegLon = lon; - // check for arcs > 180 degrees longitude, flagging as transmeridian - if (fabs(lon - next.lon) > M_PI) { - isTransmeridian = true; - } - } - // Swap east and west if transmeridian - if (isTransmeridian) { - bbox->east = maxNegLon; - bbox->west = minPosLon; - } -} - -/** - * Whether the winding order of a given loop is clockwise, with normalization - * for loops crossing the antimeridian. - * @param loop The loop to check - * @param isTransmeridian Whether the loop crosses the antimeridian - * @return Whether the loop is clockwise - */ -static bool GENERIC_LOOP_ALGO(isClockwiseNormalized)(const TYPE* loop, - bool isTransmeridian) { - double sum = 0; - GeoCoord a; - GeoCoord b; - - INIT_ITERATION; - while (true) { - ITERATE(loop, a, b); - // If we identify a transmeridian arc (> 180 degrees longitude), - // start over with the transmeridian flag set - if (!isTransmeridian && fabs(a.lon - b.lon) > M_PI) { - return GENERIC_LOOP_ALGO(isClockwiseNormalized)(loop, true); - } - sum += ((NORMALIZE_LON(b.lon, isTransmeridian) - - NORMALIZE_LON(a.lon, isTransmeridian)) * - (b.lat + a.lat)); - } - - return sum > 0; -} - -/** - * Whether the winding order of a given loop is clockwise. In GeoJSON, - * clockwise loops are always inner loops (holes). - * @param loop The loop to check - * @return Whether the loop is clockwise - */ -bool GENERIC_LOOP_ALGO(isClockwise)(const TYPE* loop) { - return GENERIC_LOOP_ALGO(isClockwiseNormalized)(loop, false); -} diff --git a/v3/h3_test.go b/v3/h3_test.go deleted file mode 100644 index 721163b..0000000 --- a/v3/h3_test.go +++ /dev/null @@ -1,862 +0,0 @@ -/* - * 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. - */ -package h3 - -import ( - "fmt" - "sort" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const eps = 1e-4 - -// validH3Index resolution 5 -const ( - validH3Index = H3Index(0x850dab63fffffff) - pentagonH3Index = H3Index(0x821c07fffffffff) - validLineStartIndex = H3Index(0x89283082803ffff) - validLineEndIndex = H3Index(0x8929a5653c3ffff) -) - -var ( - validH3Rings1 = [][]H3Index{ - { - validH3Index, - }, - { - 0x850dab73fffffff, - 0x850dab7bfffffff, - 0x850dab6bfffffff, - 0x850dab6ffffffff, - 0x850dab67fffffff, - 0x850dab77fffffff, - }, - { - 0x850dab0bfffffff, - 0x850dab47fffffff, - 0x850dab4ffffffff, - 0x850d8cb7fffffff, - 0x850d8ca7fffffff, - 0x850d8dd3fffffff, - 0x850d8dd7fffffff, - 0x850d8d9bfffffff, - 0x850d8d93fffffff, - 0x850dab2bfffffff, - 0x850dab3bfffffff, - 0x850dab0ffffffff, - }, - } - validH3Rings2 = [][]H3Index{ - { - 0x8928308280fffff, - }, { - 0x8928308280bffff, - 0x89283082873ffff, - 0x89283082877ffff, - 0x8928308283bffff, - 0x89283082807ffff, - 0x89283082803ffff, - }, - { - 0x8928308281bffff, - 0x89283082857ffff, - 0x89283082847ffff, - 0x8928308287bffff, - 0x89283082863ffff, - 0x89283082867ffff, - 0x8928308282bffff, - 0x89283082823ffff, - 0x89283082833ffff, - 0x892830828abffff, - 0x89283082817ffff, - 0x89283082813ffff, - }, - } - - validGeoCoord = GeoCoord{ - Latitude: 67.1509268640, - Longitude: -168.3908885810, - } - - validGeofence = GeoBoundary{ - {Latitude: 67.224749856, Longitude: -168.523006585}, - {Latitude: 67.140938355, Longitude: -168.626914333}, - {Latitude: 67.067252558, Longitude: -168.494913285}, - {Latitude: 67.077062918, Longitude: -168.259695931}, - {Latitude: 67.160561948, Longitude: -168.154801171}, - {Latitude: 67.234563187, Longitude: -168.286102782}, - } - - validGeofenceHole1 = GeoBoundary{ - {Latitude: 67.2, Longitude: -168.4}, - {Latitude: 67.1, Longitude: -168.4}, - {Latitude: 67.1, Longitude: -168.3}, - {Latitude: 67.2, Longitude: -168.3}, - } - - validGeofenceHole2 = GeoBoundary{ - {Latitude: 67.21, Longitude: -168.41}, - {Latitude: 67.22, Longitude: -168.41}, - {Latitude: 67.22, Longitude: -168.42}, - } - - validGeopolygonWithoutHoles = GeoPolygon{ - Geofence: validGeofence, - } - - validGeopolygonWithHoles = GeoPolygon{ - Geofence: validGeofence, - Holes: [][]GeoCoord{ - validGeofenceHole1, - validGeofenceHole2, - }, - } - - validGeoRing = []GeoCoord{{}} - - validLinkedIndexes = [][]H3Index{ - { - 0x872f5a328ffffff, - 0x872f5a32cffffff, - 0x872f5aad3ffffff, - 0x872f5a32dffffff, - }, - { - 0x872f5a328ffffff, - 0x872f5a32cffffff, - 0x872f5aad3ffffff, - 0x872f5aadeffffff, - 0x872f5aad8ffffff, - 0x872f5aadbffffff, - 0x872f5a374ffffff, - 0x872f5a329ffffff, - }, - { - 0x89194ad36a3ffff, - 0x89194ad3263ffff, - 0x89194ad326fffff, - 0x89194ad149bffff, - 0x89194ad1493ffff, - 0x89194ad322bffff, - 0x89194ad3277ffff, - 0x89194ad338bffff, - 0x89194ad3313ffff, - 0x89194ad3317ffff, - 0x89194ad33bbffff, - 0x89194ad3387ffff, - 0x89194ad3383ffff, - 0x89194ad3397ffff, - 0x89194ad33b3ffff, - 0x89194ad314fffff, - 0x89194ad3143ffff, - 0x89194ad315bffff, - 0x88194ad369fffff, - 0x88194ad361fffff, - 0x88194ad363fffff, - 0x88194ad30dfffff, - 0x88194ad347fffff, - 0x88194ad345fffff, - }, - } - - validGeoCoordB = GeoCoord{ - Latitude: 37.775705522929044, - Longitude: -122.41812765598296, - } - - validEdge = H3Index(0x1250dab73fffffff) -) - -func TestFromGeo(t *testing.T) { - t.Parallel() - h := FromGeo(GeoCoord{ - Latitude: 67.194013596, - Longitude: 191.598258018, - }, 5) - assert.Equal(t, validH3Index, h, "expected %x but got %x", validH3Index, h) - assert.Equal(t, validH3Index, h) -} - -func TestToGeo(t *testing.T) { - t.Parallel() - g := ToGeo(validH3Index) - assertGeoCoord(t, validGeoCoord, g) -} - -func TestToGeoBoundary(t *testing.T) { - t.Parallel() - boundary := ToGeoBoundary(validH3Index) - assertGeoCoords(t, validGeofence[:], boundary[:]) -} - -func TestHexRing(t *testing.T) { - t.Parallel() - for k, expected := range validH3Rings1 { - t.Run(fmt.Sprintf("ring size %d", k), func(t *testing.T) { - actual, err := HexRing(validH3Index, k) - require.NoError(t, err) - assert.ElementsMatch(t, expected, actual) - }) - } - t.Run("pentagon err", func(t *testing.T) { - t.Parallel() - _, err := HexRing(pentagonH3Index, 1) - assert.Error(t, err) - }) -} - -func TestKRing(t *testing.T) { - t.Parallel() - t.Run("no pentagon", func(t *testing.T) { - t.Parallel() - assertHexRange(t, validH3Rings1, KRing(validH3Index, len(validH3Rings1)-1)) - }) - t.Run("pentagon ok", func(t *testing.T) { - t.Parallel() - assert.NotPanics(t, func() { - KRing(pentagonH3Index, len(validH3Rings1)-1) - }) - }) -} - -func TestKRingDistances(t *testing.T) { - t.Parallel() - t.Run("no pentagon", func(t *testing.T) { - t.Parallel() - rings := KRingDistances(validH3Index, len(validH3Rings1)-1) - for i, ring := range validH3Rings1 { - assert.ElementsMatch(t, ring, rings[i]) - } - }) - t.Run("pentagon ok", func(t *testing.T) { - t.Parallel() - assert.NotPanics(t, func() { - KRingDistances(pentagonH3Index, len(validH3Rings1)-1) - }) - }) -} - -func TestHexRange(t *testing.T) { - t.Parallel() - t.Run("no pentagon", func(t *testing.T) { - t.Parallel() - hexes, err := HexRange(validH3Index, len(validH3Rings1)-1) - require.NoError(t, err) - assertHexRange(t, validH3Rings1, hexes) - }) - t.Run("pentagon err", func(t *testing.T) { - t.Parallel() - _, err := HexRange(pentagonH3Index, len(validH3Rings1)-1) - assert.Error(t, err) - }) -} - -func TestHexRangeDistances(t *testing.T) { - t.Parallel() - t.Run("no pentagon", func(t *testing.T) { - t.Parallel() - rings, err := HexRangeDistances(validH3Index, len(validH3Rings1)-1) - require.NoError(t, err) - for i, ring := range validH3Rings1 { - assert.ElementsMatch(t, ring, rings[i]) - } - }) - t.Run("pentagon err", func(t *testing.T) { - t.Parallel() - _, err := HexRangeDistances(pentagonH3Index, len(validH3Rings1)-1) - assert.Error(t, err) - }) -} - -func TestHexRanges(t *testing.T) { - t.Parallel() - t.Run("no pentagon", func(t *testing.T) { - t.Parallel() - hexranges, err := HexRanges( - []H3Index{ - validH3Rings1[0][0], - validH3Rings2[0][0], - }, len(validH3Rings2)-1) - require.NoError(t, err) - require.Len(t, hexranges, 2) - assertHexRange(t, validH3Rings1, hexranges[0]) - assertHexRange(t, validH3Rings2, hexranges[1]) - }) - t.Run("pentagon err", func(t *testing.T) { - _, err := HexRanges( - []H3Index{ - validH3Rings1[0][0], - pentagonH3Index, - }, len(validH3Rings2)-1) - assert.Error(t, err) - t.Parallel() - }) -} - -func TestIsValid(t *testing.T) { - t.Parallel() - assert.True(t, IsValid(validH3Index)) - assert.False(t, IsValid(0)) -} - -func TestFromGeoToGeo(t *testing.T) { - t.Parallel() - expectedGeo := GeoCoord{Latitude: 1, Longitude: 2} - h := FromGeo(expectedGeo, 15) - actualGeo := ToGeo(h) - assertGeoCoord(t, expectedGeo, actualGeo) -} - -func TestResolution(t *testing.T) { - t.Parallel() - for i := 1; i <= 15; i++ { - h := FromGeo(validGeoCoord, i) - assert.Equal(t, i, Resolution(h)) - } -} - -func TestBaseCell(t *testing.T) { - t.Parallel() - bcID := BaseCell(validH3Index) - assert.Equal(t, 6, bcID) -} - -func TestToParent(t *testing.T) { - t.Parallel() - // get the index's parent by requesting that index's resolution+1 - parent := ToParent(validH3Index, Resolution(validH3Index)-1) - - // get the children at the resolution of the original index - children := ToChildren(parent, Resolution(validH3Index)) - - assertHexIn(t, validH3Index, children) -} - -func TestCompact(t *testing.T) { - t.Parallel() - in := append([]H3Index{}, validH3Rings1[0][0]) - in = append(in, validH3Rings1[1]...) - out := Compact(in) - require.Len(t, out, 1) - assert.Equal(t, ToParent(validH3Rings1[0][0], Resolution(validH3Rings1[0][0])-1), out[0]) -} - -func TestUncompact(t *testing.T) { - t.Parallel() - // get the index's parent by requesting that index's resolution+1 - res := Resolution(validH3Index) - 1 - parent := ToParent(validH3Index, res) - - out, err := Uncompact([]H3Index{parent}, res+1) - assert.NoError(t, err) - assertHexIn(t, validH3Index, out) -} - -func TestUncompactError(t *testing.T) { - t.Parallel() - res := Resolution(validH3Index) - 1 - parent := ToParent(validH3Index, res) - - // use a resolution that is too small - out, err := Uncompact([]H3Index{parent}, res-1) - assert.Nil(t, out) - assert.Equal(t, ErrInvalidResolution, err) -} - -func TestIsResClassIII(t *testing.T) { - t.Parallel() - res := Resolution(validH3Index) - 1 - parent := ToParent(validH3Index, res) - - assert.True(t, IsResClassIII(validH3Index)) - assert.False(t, IsResClassIII(parent)) -} - -func TestIsPentagon(t *testing.T) { - t.Parallel() - assert.False(t, IsPentagon(validH3Index)) - assert.True(t, IsPentagon(pentagonH3Index)) -} - -func TestAreNeighbors(t *testing.T) { - t.Parallel() - assert.False(t, AreNeighbors(pentagonH3Index, validH3Index)) - assert.True(t, AreNeighbors(validH3Rings1[1][0], validH3Rings1[1][1])) -} - -func TestUnidirectionalEdge(t *testing.T) { - t.Parallel() - origin := validH3Rings1[1][0] - destination := validH3Rings1[1][1] - edge := UnidirectionalEdge(origin, destination) - - t.Run("is valid", func(t *testing.T) { - t.Parallel() - assert.True(t, UnidirectionalEdgeIsValid(edge)) - assert.False(t, UnidirectionalEdgeIsValid(validH3Index)) - }) - t.Run("get origin/destination from edge", func(t *testing.T) { - t.Parallel() - assert.Equal(t, origin, OriginFromUnidirectionalEdge(edge)) - assert.Equal(t, destination, DestinationFromUnidirectionalEdge(edge)) - - // shadow origin/destination - origin, destination := FromUnidirectionalEdge(edge) - assert.Equal(t, origin, OriginFromUnidirectionalEdge(edge)) - assert.Equal(t, destination, DestinationFromUnidirectionalEdge(edge)) - }) - t.Run("get edges from hexagon", func(t *testing.T) { - t.Parallel() - edges := ToUnidirectionalEdges(validH3Index) - assert.Len(t, edges, 6, "hexagon has 6 edges") - }) - t.Run("get edges from pentagon", func(t *testing.T) { - t.Parallel() - edges := ToUnidirectionalEdges(pentagonH3Index) - require.Len(t, edges, 5, "pentagon has 5 edges") - }) - t.Run("get boundary from edge", func(t *testing.T) { - t.Parallel() - gb := UnidirectionalEdgeBoundary(edge) - assert.Len(t, gb, 2) - }) -} - -func TestString(t *testing.T) { - t.Parallel() - t.Run("bad string", func(t *testing.T) { - t.Parallel() - h := FromString("oops") - assert.Equal(t, H3Index(0), h) - }) - t.Run("good string round trip", func(t *testing.T) { - t.Parallel() - h := FromString(ToString(validH3Index)) - assert.Equal(t, validH3Index, h) - }) - t.Run("no 0x prefix", func(t *testing.T) { - t.Parallel() - h3addr := ToString(validH3Index) - assert.Equal(t, "850dab63fffffff", h3addr) - }) -} - -func TestPolyfill(t *testing.T) { - t.Parallel() - t.Run("empty", func(t *testing.T) { - t.Parallel() - indexes := Polyfill(GeoPolygon{}, 6) - assert.Len(t, indexes, 0) - }) - t.Run("without holes", func(t *testing.T) { - t.Parallel() - indexes := Polyfill(validGeopolygonWithoutHoles, 6) - assert.Len(t, indexes, 7) - expectedIndexes := []H3Index{ - 0x860dab607ffffff, - 0x860dab60fffffff, - 0x860dab617ffffff, - 0x860dab61fffffff, - 0x860dab627ffffff, - 0x860dab62fffffff, - 0x860dab637ffffff, - } - assert.ElementsMatch(t, expectedIndexes, indexes) - }) - t.Run("with hole", func(t *testing.T) { - t.Parallel() - indexes := Polyfill(validGeopolygonWithHoles, 6) - assert.Len(t, indexes, 6) - expectedIndexes := []H3Index{ - 0x860dab60fffffff, - 0x860dab617ffffff, - 0x860dab61fffffff, - 0x860dab627ffffff, - 0x860dab62fffffff, - 0x860dab637ffffff, - } - assert.ElementsMatch(t, expectedIndexes, indexes) - }) -} - -func TestSetToLinkedGeo(t *testing.T) { - t.Parallel() - t.Run("empty", func(t *testing.T) { - t.Parallel() - polygon := SetToLinkedGeo([]H3Index{}) - assert.Nil(t, polygon.First) - assert.Nil(t, polygon.Last) - assert.Nil(t, polygon.Next) - }) - t.Run("without holes", func(t *testing.T) { - t.Parallel() - polygon := SetToLinkedGeo(validLinkedIndexes[0]) - assert.NotNil(t, polygon.First) - assert.NotNil(t, polygon.Last) - assert.Nil(t, polygon.Next) - - assert.Equal(t, float64(35.6937211669055), polygon.First.First.Vertex.Latitude) - assert.Equal(t, float64(139.75068495599942), polygon.First.First.Vertex.Longitude) - }) - t.Run("with hole", func(t *testing.T) { - t.Parallel() - polygon := SetToLinkedGeo(validLinkedIndexes[1]) - - assert.NotNil(t, polygon.First) - assert.NotNil(t, polygon.Last) - - assert.Equal(t, float64(35.66254233421934), polygon.First.First.Vertex.Latitude) - assert.Equal(t, float64(139.78638935216594), polygon.First.First.Vertex.Longitude) - - assert.Equal(t, float64(35.68805899195678), polygon.Last.First.Vertex.Latitude) - assert.Equal(t, float64(139.7268573711303), polygon.Last.First.Vertex.Longitude) - }) - t.Run("Multipolygon", func(t *testing.T) { - t.Parallel() - polygon := SetToLinkedGeo(validLinkedIndexes[2]) - - assert.NotNil(t, polygon.First) - assert.NotNil(t, polygon.Last) - assert.NotNil(t, polygon.Next) - - assert.Equal(t, float64(51.49710134101195), polygon.First.First.Vertex.Latitude) - assert.Equal(t, float64(-0.09036943363144687), polygon.First.First.Vertex.Longitude) - - assert.Equal(t, float64(51.49794867871118), polygon.Last.First.Vertex.Latitude) - assert.Equal(t, float64(-0.07191264615040037), polygon.Last.First.Vertex.Longitude) - - assert.Equal(t, float64(51.53058149905774), polygon.Next.First.First.Vertex.Latitude) - assert.Equal(t, float64(-0.07297959470277668), polygon.Next.First.First.Vertex.Longitude) - }) -} - -func TestLine(t *testing.T) { - t.Parallel() - line := Line(validLineStartIndex, validLineEndIndex) - assert.Equal(t, validLineStartIndex, line[0]) - assert.Equal(t, validLineEndIndex, line[len(line)-1]) - for i := 0; i < len(line)-1; i++ { - assert.True(t, AreNeighbors(line[i], line[i+1])) - } -} - -func TestHexAreaKm2(t *testing.T) { - t.Parallel() - t.Run("min resolution", func(t *testing.T) { - t.Parallel() - area := HexAreaKm2(0) - - assert.Equal(t, float64(4250546.848), area) - }) - t.Run("max resolution", func(t *testing.T) { - t.Parallel() - area := HexAreaKm2(15) - - assert.Equal(t, float64(0.0000009), area) - }) - t.Run("mid resolution", func(t *testing.T) { - t.Parallel() - area := HexAreaKm2(8) - - assert.Equal(t, float64(0.7373276), area) - }) -} - -func TestHexAreaM2(t *testing.T) { - t.Parallel() - t.Run("min resolution", func(t *testing.T) { - t.Parallel() - area := HexAreaM2(0) - - assert.Equal(t, float64(4250550000000), area) - }) - t.Run("max resolution", func(t *testing.T) { - t.Parallel() - area := HexAreaM2(15) - - assert.Equal(t, float64(0.9), area) - }) - t.Run("mid resolution", func(t *testing.T) { - t.Parallel() - area := HexAreaM2(8) - - assert.Equal(t, float64(737327.6), area) - }) -} - -func TestPointDistRads(t *testing.T) { - t.Parallel() - distance := PointDistRads(validGeoCoord, validGeoCoordB) - assert.Equal(t, float64(0.6796147656451452), distance) -} - -func TestPointDistKm(t *testing.T) { - t.Parallel() - distance := PointDistKm(validGeoCoord, validGeoCoordB) - assert.Equal(t, float64(4329.830552183446), distance) -} - -func TestPointDistM(t *testing.T) { - t.Parallel() - distance := PointDistM(validGeoCoord, validGeoCoordB) - assert.Equal(t, float64(4329830.5521834465), distance) -} - -func TestCellAreaRads2(t *testing.T) { - t.Parallel() - distance := CellAreaRads2(validH3Index) - assert.Equal(t, float64(0.000006643967854567278), distance) -} - -func TestCellAreaKm2(t *testing.T) { - t.Parallel() - distance := CellAreaKm2(validH3Index) - assert.Equal(t, float64(269.6768779509321), distance) -} - -func TestCellAreaM2(t *testing.T) { - t.Parallel() - distance := CellAreaM2(validH3Index) - assert.Equal(t, float64(269676877.95093215), distance) -} - -func TestEdgeLengthKm(t *testing.T) { - t.Parallel() - t.Run("min resolution", func(t *testing.T) { - t.Parallel() - area := EdgeLengthKm(0) - - assert.Equal(t, float64(1107.712591), area) - }) - t.Run("max resolution", func(t *testing.T) { - t.Parallel() - area := EdgeLengthKm(15) - - assert.Equal(t, float64(0.000509713), area) - }) - t.Run("mid resolution", func(t *testing.T) { - t.Parallel() - area := EdgeLengthKm(8) - - assert.Equal(t, float64(0.461354684), area) - }) -} - -func TestEdgeLengthM(t *testing.T) { - t.Parallel() - t.Run("min resolution", func(t *testing.T) { - t.Parallel() - area := EdgeLengthM(0) - - assert.Equal(t, float64(1107712.591), area) - }) - t.Run("max resolution", func(t *testing.T) { - t.Parallel() - area := EdgeLengthM(15) - - assert.Equal(t, float64(0.509713273), area) - }) - t.Run("mid resolution", func(t *testing.T) { - t.Parallel() - area := EdgeLengthM(8) - - assert.Equal(t, float64(461.3546837), area) - }) -} - -func TestExactEdgeLengthRads(t *testing.T) { - t.Parallel() - - distance := ExactEdgeLengthRads(validEdge) - assert.Equal(t, float64(0.001569665746947077), distance) -} - -func TestExactEdgeLengthKm(t *testing.T) { - t.Parallel() - - distance := ExactEdgeLengthKm(validEdge) - assert.Equal(t, float64(10.00035174544159), distance) -} - -func TestExactEdgeLengthM(t *testing.T) { - t.Parallel() - - distance := ExactEdgeLengthM(validEdge) - assert.Equal(t, float64(10000.351745441589), distance) -} - -func TestNumHexagons(t *testing.T) { - t.Parallel() - t.Run("min resolution", func(t *testing.T) { - t.Parallel() - count := NumHexagons(0) - - assert.Equal(t, 122, count) - }) - t.Run("max resolution", func(t *testing.T) { - t.Parallel() - count := NumHexagons(15) - - assert.Equal(t, 569707381193162, count) - }) - t.Run("mid resolution", func(t *testing.T) { - t.Parallel() - count := NumHexagons(8) - - assert.Equal(t, 691776122, count) - }) -} - -func TestRes0IndexCount(t *testing.T) { - t.Parallel() - count := Res0IndexCount() - - assert.Equal(t, 122, count) -} - -func TestGetRes0Indexes(t *testing.T) { - t.Parallel() - indexes := GetRes0Indexes() - - assert.Equal(t, 122, len(indexes)) - assert.Equal(t, H3Index(0x8001fffffffffff), indexes[0]) - assert.Equal(t, H3Index(0x80f3fffffffffff), indexes[121]) -} - -func TestDistanceBetween(t *testing.T) { - t.Parallel() - distance := DistanceBetween(validLineStartIndex, validLineEndIndex) - - assert.Equal(t, 1823, distance) -} - -func TestToCenterChild(t *testing.T) { - t.Parallel() - - child := ToCenterChild(validH3Index, 15) - - assert.Equal(t, H3Index(0x8f0dab600000000), child) -} - -func TestMaxFaceCount(t *testing.T) { - t.Parallel() - - faces := MaxFaceCount(validH3Index) - - assert.Equal(t, 2, faces) -} - -func TestGetFaces(t *testing.T) { - t.Parallel() - - faces := GetFaces(validH3Rings1[1][1]) - - assert.Equal(t, 1, len(faces)) - assert.Equal(t, 1, faces[0]) -} - -func TestPentagonIndexCount(t *testing.T) { - t.Parallel() - - pentagonCount := PentagonIndexCount() - - assert.Equal(t, 12, pentagonCount) -} - -func TestGetPentagonIndexes(t *testing.T) { - t.Parallel() - t.Run("min resolution", func(t *testing.T) { - t.Parallel() - pentagons := GetPentagonIndexes(0) - - assert.Equal(t, 12, len(pentagons)) - }) - t.Run("max resolution", func(t *testing.T) { - t.Parallel() - pentagons := GetPentagonIndexes(15) - - assert.Equal(t, 12, len(pentagons)) - }) - t.Run("mid resolution", func(t *testing.T) { - t.Parallel() - pentagons := GetPentagonIndexes(8) - - assert.Equal(t, 12, len(pentagons)) - }) -} - -func almostEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { - assert.InEpsilon(t, expected, actual, eps, msgAndArgs...) -} - -func assertGeoCoord(t *testing.T, expected, actual GeoCoord) { - almostEqual(t, expected.Latitude, actual.Latitude, "latitude mismatch") - almostEqual(t, expected.Longitude, actual.Longitude, "longitude mismatch") -} - -func assertGeoCoords(t *testing.T, expected, actual []GeoCoord) { - for i, gc := range expected { - assertGeoCoord(t, gc, actual[i]) - } -} - -func assertHexRange(t *testing.T, expected [][]H3Index, actual []H3Index) { - for i, ring := range expected { - // each ring should be sorted by value because the order of a ring is - // undefined. - lower := rangeSize(i) - ringSize(i) - upper := rangeSize(i) - assert.ElementsMatch(t, ring, actual[lower:upper]) - } -} - -func assertHexIn(t *testing.T, needle H3Index, haystack []H3Index) { - var found bool - for _, h := range haystack { - found = needle == h - if found { - break - } - } - assert.True(t, found, - "expected %+v in %+v", - needle, haystack) -} - -func validHexRange() []H3Index { - out := []H3Index{} - for _, ring := range validH3Rings1 { - out = append(out, ring...) - } - return out -} - -func sortHexes(s []H3Index) []H3Index { - sort.SliceStable(s, func(i, j int) bool { - return s[i] < s[j] - }) - return s -} - -func max(x, y int) int { - if x > y { - return x - } - return y -} diff --git a/v3/h3_vec2d.c b/v3/h3_vec2d.c deleted file mode 100644 index d32b4c8..0000000 --- a/v3/h3_vec2d.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2016-2017 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. - */ -/** @file vec2d.c - * @brief 2D floating point vector functions. - */ - -#include "h3_vec2d.h" - -#include -#include - -/** - * Calculates the magnitude of a 2D cartesian vector. - * @param v The 2D cartesian vector. - * @return The magnitude of the vector. - */ -double _v2dMag(const Vec2d* v) { return sqrt(v->x * v->x + v->y * v->y); } - -/** - * Finds the intersection between two lines. Assumes that the lines intersect - * and that the intersection is not at an endpoint of either line. - * @param p0 The first endpoint of the first line. - * @param p1 The second endpoint of the first line. - * @param p2 The first endpoint of the second line. - * @param p3 The second endpoint of the second line. - * @param inter The intersection point. - */ -void _v2dIntersect(const Vec2d* p0, const Vec2d* p1, const Vec2d* p2, - const Vec2d* p3, Vec2d* inter) { - Vec2d s1, s2; - s1.x = p1->x - p0->x; - s1.y = p1->y - p0->y; - s2.x = p3->x - p2->x; - s2.y = p3->y - p2->y; - - float t; - t = (s2.x * (p0->y - p2->y) - s2.y * (p0->x - p2->x)) / - (-s2.x * s1.y + s1.x * s2.y); - - inter->x = p0->x + (t * s1.x); - inter->y = p0->y + (t * s1.y); -} - -/** - * Whether two 2D vectors are equal. Does not consider possible false - * negatives due to floating-point errors. - * @param v1 First vector to compare - * @param v2 Second vector to compare - * @return Whether the vectors are equal - */ -bool _v2dEquals(const Vec2d* v1, const Vec2d* v2) { - return v1->x == v2->x && v1->y == v2->y; -} diff --git a/v3/h3_vec2d.h b/v3/h3_vec2d.h deleted file mode 100644 index 44030cc..0000000 --- a/v3/h3_vec2d.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2016-2017 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. - */ -/** @file vec2d.h - * @brief 2D floating point vector functions. - */ - -#ifndef VEC2D_H -#define VEC2D_H - -#include - -/** @struct Vec2d - * @brief 2D floating-point vector - */ -typedef struct { - double x; ///< x component - double y; ///< y component -} Vec2d; - -// Internal functions - -double _v2dMag(const Vec2d* v); -void _v2dIntersect(const Vec2d* p0, const Vec2d* p1, const Vec2d* p2, - const Vec2d* p3, Vec2d* inter); -bool _v2dEquals(const Vec2d* p0, const Vec2d* p1); - -#endif diff --git a/v3/h3_vec3d.c b/v3/h3_vec3d.c deleted file mode 100644 index cc05b9f..0000000 --- a/v3/h3_vec3d.c +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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. - */ -/** @file vec3d.c - * @brief 3D floating point vector functions. - */ - -#include "h3_vec3d.h" - -#include - -/** - * Square of a number - * - * @param x The input number. - * @return The square of the input number. - */ -double _square(double x) { return x * x; } - -/** - * Calculate the square of the distance between two 3D coordinates. - * - * @param v1 The first 3D coordinate. - * @param v2 The second 3D coordinate. - * @return The square of the distance between the given points. - */ -double _pointSquareDist(const Vec3d* v1, const Vec3d* v2) { - return _square(v1->x - v2->x) + _square(v1->y - v2->y) + - _square(v1->z - v2->z); -} - -/** - * Calculate the 3D coordinate on unit sphere from the latitude and longitude. - * - * @param geo The latitude and longitude of the point. - * @param v The 3D coordinate of the point. - */ -void _geoToVec3d(const GeoCoord* geo, Vec3d* v) { - double r = cos(geo->lat); - - v->z = sin(geo->lat); - v->x = cos(geo->lon) * r; - v->y = sin(geo->lon) * r; -} diff --git a/v3/h3_vec3d.h b/v3/h3_vec3d.h deleted file mode 100644 index 555464d..0000000 --- a/v3/h3_vec3d.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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. - */ -/** @file vec3d.h - * @brief 3D floating point vector functions. - */ - -#ifndef VEC3D_H -#define VEC3D_H - -#include "h3_geoCoord.h" - -/** @struct Vec3D - * @brief 3D floating point structure - */ -typedef struct { - double x; ///< x component - double y; ///< y component - double z; ///< z component -} Vec3d; - -void _geoToVec3d(const GeoCoord* geo, Vec3d* point); -double _pointSquareDist(const Vec3d* p1, const Vec3d* p2); - -#endif diff --git a/v3/h3_vertex.c b/v3/h3_vertex.c deleted file mode 100644 index ffe5fd7..0000000 --- a/v3/h3_vertex.c +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2020 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. - */ -/** @file vertex.h - * @brief Functions for working with cell vertexes. - */ - -#include "h3_vertex.h" - -#include "h3_baseCells.h" -#include "h3_faceijk.h" -#include "h3_geoCoord.h" -#include "h3_h3Index.h" - -#define DIRECTION_INDEX_OFFSET 2 - -/** @brief Table of direction-to-face mapping for each pentagon - * - * Note that faces are in directional order, starting at J_AXES_DIGIT. - * This table is generated by the generatePentagonDirectionFaces script. - */ -static const PentagonDirectionFaces pentagonDirectionFaces[NUM_PENTAGONS] = { - {4, {4, 0, 2, 1, 3}}, {14, {6, 11, 2, 7, 1}}, - {24, {5, 10, 1, 6, 0}}, {38, {7, 12, 3, 8, 2}}, - {49, {9, 14, 0, 5, 4}}, {58, {8, 13, 4, 9, 3}}, - {63, {11, 6, 15, 10, 16}}, {72, {12, 7, 16, 11, 17}}, - {83, {10, 5, 19, 14, 15}}, {97, {13, 8, 17, 12, 18}}, - {107, {14, 9, 18, 13, 19}}, {117, {15, 19, 17, 18, 16}}, -}; - -/** - * Get the number of CCW rotations of the cell's vertex numbers - * compared to the directional layout of its neighbors. - * @return Number of CCW rotations for the cell - */ -int vertexRotations(H3Index cell) { - // Get the face and other info for the origin - FaceIJK fijk; - _h3ToFaceIjk(cell, &fijk); - int baseCell = H3_EXPORT(h3GetBaseCell)(cell); - int cellLeadingDigit = _h3LeadingNonZeroDigit(cell); - - // get the base cell face - FaceIJK baseFijk; - _baseCellToFaceIjk(baseCell, &baseFijk); - - int ccwRot60 = _baseCellToCCWrot60(baseCell, fijk.face); - - if (_isBaseCellPentagon(baseCell)) { - // Find the appropriate direction-to-face mapping - PentagonDirectionFaces dirFaces; - for (int p = 0; p < NUM_PENTAGONS; p++) { - if (pentagonDirectionFaces[p].baseCell == baseCell) { - dirFaces = pentagonDirectionFaces[p]; - break; - } - } - - // additional CCW rotation for polar neighbors or IK neighbors - if (fijk.face != baseFijk.face && - (_isBaseCellPolarPentagon(baseCell) || - fijk.face == - dirFaces.faces[IK_AXES_DIGIT - DIRECTION_INDEX_OFFSET])) { - ccwRot60 = (ccwRot60 + 1) % 6; - } - - // Check whether the cell crosses a deleted pentagon subsequence - if (cellLeadingDigit == JK_AXES_DIGIT && - fijk.face == - dirFaces.faces[IK_AXES_DIGIT - DIRECTION_INDEX_OFFSET]) { - // Crosses from JK to IK: Rotate CW - ccwRot60 = (ccwRot60 + 5) % 6; - } else if (cellLeadingDigit == IK_AXES_DIGIT && - fijk.face == - dirFaces.faces[JK_AXES_DIGIT - DIRECTION_INDEX_OFFSET]) { - // Crosses from IK to JK: Rotate CCW - ccwRot60 = (ccwRot60 + 1) % 6; - } - } - return ccwRot60; -} - -/** @brief Hexagon direction to vertex number relationships (same face). - * Note that we don't use direction 0 (center). - */ -static const int directionToVertexNumHex[NUM_DIGITS] = { - INVALID_DIGIT, 3, 1, 2, 5, 4, 0}; - -/** @brief Pentagon direction to vertex number relationships (same face). - * Note that we don't use directions 0 (center) or 1 (deleted K axis). - */ -static const int directionToVertexNumPent[NUM_DIGITS] = { - INVALID_DIGIT, INVALID_DIGIT, 1, 2, 4, 3, 0}; - -/** - * Get the first vertex number for a given direction. The neighbor in this - * direction is located between this vertex number and the next number in - * sequence. - * @returns The number for the first topological vertex, or INVALID_VERTEX_NUM - * if the direction is not valid for this cell - */ -int vertexNumForDirection(const H3Index origin, const Direction direction) { - int isPentagon = H3_EXPORT(h3IsPentagon)(origin); - // Check for invalid directions - if (direction == CENTER_DIGIT || direction >= INVALID_DIGIT || - (isPentagon && direction == K_AXES_DIGIT)) - return INVALID_VERTEX_NUM; - - // Determine the vertex rotations for this cell - int rotations = vertexRotations(origin); - - // Find the appropriate vertex, rotating CCW if necessary - if (isPentagon) { - return (directionToVertexNumPent[direction] + NUM_PENT_VERTS - - rotations) % - NUM_PENT_VERTS; - } else { - return (directionToVertexNumHex[direction] + NUM_HEX_VERTS - - rotations) % - NUM_HEX_VERTS; - } -} diff --git a/v3/h3_vertexGraph.c b/v3/h3_vertexGraph.c deleted file mode 100644 index 0f7a875..0000000 --- a/v3/h3_vertexGraph.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2017-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. - */ -/** @file vertexGraph.c - * @brief Data structure for storing a graph of vertices - */ - -#include "h3_vertexGraph.h" - -#include -#include -#include -#include -#include - -#include "h3_alloc.h" -#include "h3_geoCoord.h" - -/** - * Initialize a new VertexGraph - * @param graph Graph to initialize - * @param numBuckets Number of buckets to include in the graph - * @param res Resolution of the hexagons whose vertices we're storing - */ -void initVertexGraph(VertexGraph* graph, int numBuckets, int res) { - if (numBuckets > 0) { - graph->buckets = H3_MEMORY(calloc)(numBuckets, sizeof(VertexNode*)); - assert(graph->buckets != NULL); - } else { - graph->buckets = NULL; - } - graph->numBuckets = numBuckets; - graph->size = 0; - graph->res = res; -} - -/** - * Destroy a VertexGraph's sub-objects, freeing their memory. The caller is - * responsible for freeing memory allocated to the VertexGraph struct itself. - * @param graph Graph to destroy - */ -void destroyVertexGraph(VertexGraph* graph) { - VertexNode* node; - while ((node = firstVertexNode(graph)) != NULL) { - removeVertexNode(graph, node); - } - H3_MEMORY(free)(graph->buckets); -} - -/** - * Get an integer hash for a lat/lon point, at a precision determined - * by the current hexagon resolution. - * TODO: Light testing suggests this might not be sufficient at resolutions - * finer than 10. Design a better hash function if performance and collisions - * seem to be an issue here. - * @param vertex Lat/lon vertex to hash - * @param res Resolution of the hexagon the vertex belongs to - * @param numBuckets Number of buckets in the graph - * @return Integer hash - */ -uint32_t _hashVertex(const GeoCoord* vertex, int res, int numBuckets) { - // Simple hash: Take the sum of the lat and lon with a precision level - // determined by the resolution, converted to int, modulo bucket count. - return (uint32_t)fmod(fabs((vertex->lat + vertex->lon) * pow(10, 15 - res)), - numBuckets); -} - -void _initVertexNode(VertexNode* node, const GeoCoord* fromVtx, - const GeoCoord* toVtx) { - node->from = *fromVtx; - node->to = *toVtx; - node->next = NULL; -} - -/** - * Add a edge to the graph - * @param graph Graph to add node to - * @param fromVtx Start vertex - * @param toVtx End vertex - * @return Pointer to the new node - */ -VertexNode* addVertexNode(VertexGraph* graph, const GeoCoord* fromVtx, - const GeoCoord* toVtx) { - // Make the new node - VertexNode* node = H3_MEMORY(malloc)(sizeof(VertexNode)); - assert(node != NULL); - _initVertexNode(node, fromVtx, toVtx); - // Determine location - uint32_t index = _hashVertex(fromVtx, graph->res, graph->numBuckets); - // Check whether there's an existing node in that spot - VertexNode* currentNode = graph->buckets[index]; - if (currentNode == NULL) { - // Set bucket to the new node - graph->buckets[index] = node; - } else { - // Find the end of the list - do { - // Check the the edge we're adding doesn't already exist - if (geoAlmostEqual(¤tNode->from, fromVtx) && - geoAlmostEqual(¤tNode->to, toVtx)) { - // already exists, bail - H3_MEMORY(free)(node); - return currentNode; - } - if (currentNode->next != NULL) { - currentNode = currentNode->next; - } - } while (currentNode->next != NULL); - // Add the new node to the end of the list - currentNode->next = node; - } - graph->size++; - return node; -} - -/** - * Remove a node from the graph. The input node will be freed, and should - * not be used after removal. - * @param graph Graph to mutate - * @param node Node to remove - * @return 0 on success, 1 on failure (node not found) - */ -int removeVertexNode(VertexGraph* graph, VertexNode* node) { - // Determine location - uint32_t index = _hashVertex(&node->from, graph->res, graph->numBuckets); - VertexNode* currentNode = graph->buckets[index]; - int found = 0; - if (currentNode != NULL) { - if (currentNode == node) { - graph->buckets[index] = node->next; - found = 1; - } - // Look through the list - while (!found && currentNode->next != NULL) { - if (currentNode->next == node) { - // splice the node out - currentNode->next = node->next; - found = 1; - } - currentNode = currentNode->next; - } - } - if (found) { - H3_MEMORY(free)(node); - graph->size--; - return 0; - } - // Failed to find the node - return 1; -} - -/** - * Find the Vertex node for a given edge, if it exists - * @param graph Graph to look in - * @param fromVtx Start vertex - * @param toVtx End vertex, or NULL if we don't care - * @return Pointer to the vertex node, if found - */ -VertexNode* findNodeForEdge(const VertexGraph* graph, const GeoCoord* fromVtx, - const GeoCoord* toVtx) { - // Determine location - uint32_t index = _hashVertex(fromVtx, graph->res, graph->numBuckets); - // Check whether there's an existing node in that spot - VertexNode* node = graph->buckets[index]; - if (node != NULL) { - // Look through the list and see if we find the edge - do { - if (geoAlmostEqual(&node->from, fromVtx) && - (toVtx == NULL || geoAlmostEqual(&node->to, toVtx))) { - return node; - } - node = node->next; - } while (node != NULL); - } - // Iteration lookup fail - return NULL; -} - -/** - * Find a Vertex node starting at the given vertex - * @param graph Graph to look in - * @param fromVtx Start vertex - * @return Pointer to the vertex node, if found - */ -VertexNode* findNodeForVertex(const VertexGraph* graph, - const GeoCoord* fromVtx) { - return findNodeForEdge(graph, fromVtx, NULL); -} - -/** - * Get the next vertex node in the graph. - * @param graph Graph to iterate - * @return Vertex node, or NULL if at the end - */ -VertexNode* firstVertexNode(const VertexGraph* graph) { - VertexNode* node = NULL; - int currentIndex = 0; - while (node == NULL) { - if (currentIndex < graph->numBuckets) { - // find the first node in the next bucket - node = graph->buckets[currentIndex]; - } else { - // end of iteration - return NULL; - } - currentIndex++; - } - return node; -} diff --git a/v3/h3_vertexGraph.h b/v3/h3_vertexGraph.h deleted file mode 100644 index 6e403bf..0000000 --- a/v3/h3_vertexGraph.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2017 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. - */ -/** @file vertexGraph.h - * @brief Data structure for storing a graph of vertices - */ - -#ifndef VERTEX_GRAPH_H -#define VERTEX_GRAPH_H - -#include -#include - -#include "h3_geoCoord.h" - -/** @struct VertexNode - * @brief A single node in a vertex graph, part of a linked list - */ -typedef struct VertexNode VertexNode; -struct VertexNode { - GeoCoord from; - GeoCoord to; - VertexNode* next; -}; - -/** @struct VertexGraph - * @brief A data structure to store a graph of vertices - */ -typedef struct { - VertexNode** buckets; - int numBuckets; - int size; - int res; -} VertexGraph; - -void initVertexGraph(VertexGraph* graph, int numBuckets, int res); - -void destroyVertexGraph(VertexGraph* graph); - -VertexNode* addVertexNode(VertexGraph* graph, const GeoCoord* fromVtx, - const GeoCoord* toVtx); - -int removeVertexNode(VertexGraph* graph, VertexNode* node); - -VertexNode* findNodeForEdge(const VertexGraph* graph, const GeoCoord* fromVtx, - const GeoCoord* toVtx); - -VertexNode* findNodeForVertex(const VertexGraph* graph, - const GeoCoord* fromVtx); - -VertexNode* firstVertexNode(const VertexGraph* graph); - -// Internal functions -uint32_t _hashVertex(const GeoCoord* vertex, int res, int numBuckets); -void _initVertexNode(VertexNode* node, const GeoCoord* fromVtx, - const GeoCoord* toVtx); - -#endif diff --git a/v3/update-h3.sh b/v3/update-h3.sh deleted file mode 100755 index 69dd27c..0000000 --- a/v3/update-h3.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash -# -# 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. -# - -# Arguments: [git-remote] -# -# git-remote - The git remote to pull from. An existing cloned repository will -# not be deleted if a new remote is specified. Defaults to -# "github.com/uber/h3" -# -# Will fetch the version of H3 specified in the file `H3_VERSION`, copy the -# source files into the working directory with `h3_` prefix, and headers files -# into `H3_INC_DIR`. - -# -- quiet pushd/popd --- -pushd () { - command pushd "$@" > /dev/null -} - -popd () { - command popd > /dev/null -} -# -- -- -- -- -- -- -- -- - -badexit () { - echo "something went wrong" - exit 1 -} - -cleanup () { - echo "Cleaning up!" - rm -rf "$H3_SRC_DIR" -} -trap cleanup EXIT - -GIT_REMOTE=${1:-"https://github.com/uber/h3.git"} -H3_SRC_DIR="src" - -# hold onto the current working directory to copy source files into. -CWD=$(pwd) - -# clean up existing C source code. -find . -name "*.c" -depth 1 -exec rm {} \; -# clean up existing C headers. -find . -name "*.h" -depth 1 -exec rm {} \; - -echo Downloading H3 from "$GIT_REMOTE" - -if [ -d "$H3_SRC_DIR" ]; then - echo Replacing existing src at "$H3_SRC_DIR" - rm -rf "$H3_SRC_DIR" -fi - -H3_VERSION=$(< H3_VERSION) -echo "Checking out $H3_VERSION (found in file H3_VERSION)" - -git clone "$GIT_REMOTE" "$H3_SRC_DIR" - -pushd "$H3_SRC_DIR" || badexit - git checkout -q tags/"$H3_VERSION" - - echo Copying source files into working directory - pushd ./src/h3lib/lib/ || badexit - for f in *.c; do - sed -E 's/#include "(.*)"/#include "h3_\1"/; s/#include / /' "$f" > "$CWD/h3_$f" || badexit - done - popd || badexit - - echo Copying header files into working directory - pushd ./src/h3lib/include/ || badexit - for f in *.h; do - sed -E 's/#include "(.*)"/#include "h3_\1"/' "$f" > "$CWD/h3_$f" || badexit - done - popd || badexit - - echo Copying api header file into working directory - pushd ./src/h3lib/include/ || badexit - sed -E 's/#include "(.*)"/#include "h3_\1"/' "h3api.h.in" > "$CWD/h3_h3api.h" || badexit - popd || badexit -popd || badexit