From 7d392e082e31437706a59773257b8ec35edca494 Mon Sep 17 00:00:00 2001 From: Joseph Gilley Date: Mon, 13 Jan 2020 18:02:21 -0800 Subject: [PATCH 1/2] add support for GOMODULES This repo follows a modified version of semver, where the major version of this repository tracks the major version of the C core repository. This doesn't play well with GOMODULES by default, so we create our first major version go module that is just a duplication of master version. Solves #24 --- go.mod | 5 + go.sum | 11 + v3/bench_test.go | 59 +++ v3/example_test.go | 16 + v3/go.mod | 5 + v3/go.sum | 10 + v3/h3.go | 509 ++++++++++++++++++++ v3/h3_algos.c | 807 ++++++++++++++++++++++++++++++++ v3/h3_baseCells.c | 911 ++++++++++++++++++++++++++++++++++++ v3/h3_bbox.c | 121 +++++ v3/h3_coordijk.c | 554 ++++++++++++++++++++++ v3/h3_faceijk.c | 893 +++++++++++++++++++++++++++++++++++ v3/h3_geoCoord.c | 322 +++++++++++++ v3/h3_h3Index.c | 790 +++++++++++++++++++++++++++++++ v3/h3_h3UniEdge.c | 278 +++++++++++ v3/h3_linkedGeo.c | 375 +++++++++++++++ v3/h3_localij.c | 654 ++++++++++++++++++++++++++ v3/h3_mathExtensions.c | 39 ++ v3/h3_polygon.c | 84 ++++ v3/h3_test.go | 500 ++++++++++++++++++++ v3/h3_vec2d.c | 65 +++ v3/h3_vec3d.c | 55 +++ v3/h3_vertexGraph.c | 218 +++++++++ v3/include/algos.h | 43 ++ v3/include/baseCells.h | 58 +++ v3/include/bbox.h | 42 ++ v3/include/constants.h | 82 ++++ v3/include/coordijk.h | 113 +++++ v3/include/faceijk.h | 76 +++ v3/include/geoCoord.h | 52 ++ v3/include/h3Index.h | 163 +++++++ v3/include/h3UniEdge.h | 28 ++ v3/include/h3api.h | 513 ++++++++++++++++++++ v3/include/linkedGeo.h | 92 ++++ v3/include/localij.h | 29 ++ v3/include/mathExtensions.h | 31 ++ v3/include/polygon.h | 75 +++ v3/include/polygonAlgos.h | 220 +++++++++ v3/include/stackAlloc.h | 64 +++ v3/include/vec2d.h | 40 ++ v3/include/vec3d.h | 37 ++ v3/include/vertexGraph.h | 69 +++ v3/update-h3.sh | 86 ++++ 43 files changed, 9194 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 v3/bench_test.go create mode 100644 v3/example_test.go create mode 100644 v3/go.mod create mode 100644 v3/go.sum create mode 100644 v3/h3.go create mode 100644 v3/h3_algos.c create mode 100644 v3/h3_baseCells.c create mode 100644 v3/h3_bbox.c create mode 100644 v3/h3_coordijk.c create mode 100644 v3/h3_faceijk.c create mode 100644 v3/h3_geoCoord.c create mode 100644 v3/h3_h3Index.c create mode 100644 v3/h3_h3UniEdge.c create mode 100644 v3/h3_linkedGeo.c create mode 100644 v3/h3_localij.c create mode 100644 v3/h3_mathExtensions.c create mode 100644 v3/h3_polygon.c create mode 100644 v3/h3_test.go create mode 100644 v3/h3_vec2d.c create mode 100644 v3/h3_vec3d.c create mode 100644 v3/h3_vertexGraph.c create mode 100644 v3/include/algos.h create mode 100644 v3/include/baseCells.h create mode 100644 v3/include/bbox.h create mode 100644 v3/include/constants.h create mode 100644 v3/include/coordijk.h create mode 100644 v3/include/faceijk.h create mode 100644 v3/include/geoCoord.h create mode 100644 v3/include/h3Index.h create mode 100644 v3/include/h3UniEdge.h create mode 100644 v3/include/h3api.h create mode 100644 v3/include/linkedGeo.h create mode 100644 v3/include/localij.h create mode 100644 v3/include/mathExtensions.h create mode 100644 v3/include/polygon.h create mode 100644 v3/include/polygonAlgos.h create mode 100644 v3/include/stackAlloc.h create mode 100644 v3/include/vec2d.h create mode 100644 v3/include/vec3d.h create mode 100644 v3/include/vertexGraph.h create mode 100755 v3/update-h3.sh diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0b6634c --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/uber/h3-go + +go 1.13 + +require github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8fdee58 --- /dev/null +++ b/go.sum @@ -0,0 +1,11 @@ +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/v3/bench_test.go b/v3/bench_test.go new file mode 100644 index 0000000..43633d1 --- /dev/null +++ b/v3/bench_test.go @@ -0,0 +1,59 @@ +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 new file mode 100644 index 0000000..ec8e1ab --- /dev/null +++ b/v3/example_test.go @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..60f9e1c --- /dev/null +++ b/v3/go.mod @@ -0,0 +1,5 @@ +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 new file mode 100644 index 0000000..e863f51 --- /dev/null +++ b/v3/go.sum @@ -0,0 +1,10 @@ +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 new file mode 100644 index 0000000..c22d5bf --- /dev/null +++ b/v3/h3.go @@ -0,0 +1,509 @@ +/* + * 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}/include +#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_INVALID_INDEX +) + +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 +} + +// --- 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) +} + +// --- 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) +} + +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 ringSize(k int) int { + if k == 0 { + return 1 + } + return 6 * k +} + +func rangeSize(k int) int { + return int(C.maxKringSize(C.int(k))) +} + +// 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 new file mode 100644 index 0000000..7f45991 --- /dev/null +++ b/v3/h3_algos.c @@ -0,0 +1,807 @@ +/* + * 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.c + * @brief Hexagon grid algorithms + */ + +#include "algos.h" +#include +#include +#include +#include +#include "baseCells.h" +#include "bbox.h" +#include "faceijk.h" +#include "geoCoord.h" +#include "h3Index.h" +#include "h3api.h" +#include "linkedGeo.h" +#include "polygon.h" +#include "stackAlloc.h" +#include "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 + +/** + * 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 indices 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; } + +/** + * k-rings produces indices within k distance of the origin index. + * + * 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. + * + * 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). + */ +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); +} + +/** + * k-rings produces indices within k distance of the origin index. + * + * 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. + * + * 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). + */ +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); + if (failed) { + // 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); + } +} + +/** + * Internal helper function called recursively for kRingDistances. + * + * Adds the origin index 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. + */ +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 0 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 (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_INVALID_INDEX; + } 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_INVALID_INDEX; // 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, 0); +} + +/** + * 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) { + // 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) { + // 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 hexagonal ring centered at origin with sides of length k. + * + * @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. + */ +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) { + // 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) { + // 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. + * + * 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? ;) + * + * @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; + bboxFromGeofence(&geoPolygon->geofence, &bbox); + int minK = bboxHexRadius(&bbox, res); + + // The total number of hexagons to allocate can now be determined by + // the k-ring hex allocation helper function. + return H3_EXPORT(maxKringSize)(minK); +} + +/** + * 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. + * + * 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. + * + * @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) { + // 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 = malloc((geoPolygon->numHoles + 1) * sizeof(BBox)); + assert(bboxes != NULL); + 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; + } + // 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; + } + } + free(bboxes); +} + +/** + * 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_baseCells.c b/v3/h3_baseCells.c new file mode 100644 index 0000000..5677d88 --- /dev/null +++ b/v3/h3_baseCells.c @@ -0,0 +1,911 @@ +/* + * 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.c + * @brief Base cell related lookup tables and access functions. + */ + +#include "baseCells.h" +#include "h3Index.h" + +/** @struct BaseCellOrient + * @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; + +/** @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 BaseCellOrient 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 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; + } +} \ No newline at end of file diff --git a/v3/h3_bbox.c b/v3/h3_bbox.c new file mode 100644 index 0000000..90196b3 --- /dev/null +++ b/v3/h3_bbox.c @@ -0,0 +1,121 @@ +/* + * 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.c + * @brief Geographic bounding box functions + */ + +#include "bbox.h" +#include +#include +#include +#include "constants.h" +#include "geoCoord.h" +#include "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 _geoDistKm(&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 + */ +int bboxHexRadius(const BBox* bbox, int res) { + // Determine the center of the bounding box + GeoCoord center; + bboxCenter(bbox, ¢er); + + // 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}; + + // 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); + + // Determine the radius of the center hexagon + double centerHexRadiusKm = _hexRadiusKm(H3_EXPORT(geoToH3)(¢er, res)); + + // 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)); +} diff --git a/v3/h3_coordijk.c b/v3/h3_coordijk.c new file mode 100644 index 0000000..b7107e8 --- /dev/null +++ b/v3/h3_coordijk.c @@ -0,0 +1,554 @@ +/* + * 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 "coordijk.h" +#include +#include +#include +#include +#include "constants.h" +#include "geoCoord.h" +#include "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); +} \ No newline at end of file diff --git a/v3/h3_faceijk.c b/v3/h3_faceijk.c new file mode 100644 index 0000000..27dc37c --- /dev/null +++ b/v3/h3_faceijk.c @@ -0,0 +1,893 @@ +/* + * 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 faceijk.c + * @brief Functions for working with icosahedral face-centered hex IJK + * coordinate systems. + */ + +#include "faceijk.h" +#include +#include +#include +#include +#include +#include "constants.h" +#include "coordijk.h" +#include "geoCoord.h" +#include "h3Index.h" +#include "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 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 + 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 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); + } + + // 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 = 0; vert < NUM_PENT_VERTS + 1; 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; + } + } + + // 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) { + // 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 == NUM_PENT_VERTS is only used to test for possible intersection + // on last edge + if (vert < NUM_PENT_VERTS) { + Vec2d vec; + _ijkToHex2d(&fijk.coord, &vec); + _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); + g->numVerts++; + } + + lastFijk = fijk; + } +} + +/** + * 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 isPentagon Whether or not the cell is a pentagon. + * @param g The spherical coordinates of the cell boundary. + */ +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 + // 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 + 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 + 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 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); + _ijkNormalize(&fijkVerts[v].coord); + } + + // 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; + int lastOverage = 0; // 0: none; 1: edge; 2: overage + for (int vert = 0; vert < NUM_HEX_VERTS + 1; vert++) { + int v = vert % NUM_HEX_VERTS; + + FaceIJK fijk = fijkVerts[v]; + + int pentLeading4 = 0; + int 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 > 0 && fijk.face != lastFace && + lastOverage != 1) { + // 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 == NUM_HEX_VERTS is only used to test for possible intersection + // on last edge + if (vert < 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; + } +} + +/** + * 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 + */ +int _adjustOverageClassII(FaceIJK* fijk, int res, int pentLeading4, + int substrate) { + int overage = 0; + + 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 = 1; + else if (ijk->i + ijk->j + ijk->k > maxDim) // overage + { + overage = 2; + + 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 = 1; + } + + return overage; +} diff --git a/v3/h3_geoCoord.c b/v3/h3_geoCoord.c new file mode 100644 index 0000000..3652ffd --- /dev/null +++ b/v3/h3_geoCoord.c @@ -0,0 +1,322 @@ +/* + * 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 "geoCoord.h" +#include +#include +#include "constants.h" +#include "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/v3/h3_h3Index.c b/v3/h3_h3Index.c new file mode 100644 index 0000000..cf41023 --- /dev/null +++ b/v3/h3_h3Index.c @@ -0,0 +1,790 @@ +/* + * 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.c + * @brief H3Index utility functions + * (see h3api.h for the main library entry functions) + */ +#include "h3Index.h" +#include +#include +#include +#include +#include +#include "baseCells.h" +#include "faceijk.h" +#include "mathExtensions.h" +#include "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); } + +/** + * Returns the H3 base cell number of an H3 index. + * @param h The H3 index. + * @return The base cell of the H3 index 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 0 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; +} + +/** + * 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 valid. + * @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 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 0 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_INVALID_INDEX; + } else if (parentRes == childRes) { + return h; + } else if (parentRes < 0 || parentRes > MAX_H3_RES) { + return H3_INVALID_INDEX; + } + 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; +} + +/** + * 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 (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 (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; + } + } +} + +/** + * 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) { + 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; + } + H3Index* remainingHexes = malloc(numHexes * sizeof(H3Index)); + memcpy(remainingHexes, h3Set, numHexes * sizeof(H3Index)); + H3Index* hashSetArray = calloc(numHexes, sizeof(H3Index)); + 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_START + // This case should not be possible because at most one + // index is placed into hashSetArray per + // numRemainingHexes. + free(remainingHexes); + free(hashSetArray); + return -1; + // 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) { + // Only possible on duplicate input + free(remainingHexes); + free(hashSetArray); + return -2; + } + H3_SET_RESERVED_BITS(parent, count); + hashSetArray[loc] = H3_INVALID_INDEX; + } 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 = + malloc(maxCompactableCount * sizeof(H3Index)); + 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_INVALID_INDEX) { + 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_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 + // 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; + free(compactableHexes); + } + free(remainingHexes); + free(hashSetArray); + return 0; +} + +/** + * 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; + } + } + 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 (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 0 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_INVALID_INDEX; + } + + 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_INVALID_INDEX; + } + + // 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 0 on failure). + */ +H3Index H3_EXPORT(geoToH3)(const GeoCoord* g, int res) { + if (res < 0 || res > MAX_H3_RES) { + return H3_INVALID_INDEX; + } + if (!isfinite(g->lat) || !isfinite(g->lon)) { + return H3_INVALID_INDEX; + } + + 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)) { + // 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; + } + } + + 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); + _faceIjkToGeoBoundary(&fijk, H3_GET_RESOLUTION(h3), + H3_EXPORT(h3IsPentagon)(h3), gb); +} + +/** + * 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_h3UniEdge.c b/v3/h3_h3UniEdge.c new file mode 100644 index 0000000..c36453e --- /dev/null +++ b/v3/h3_h3UniEdge.c @@ -0,0 +1,278 @@ +/* + * 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 "algos.h" +#include "constants.h" +#include "coordijk.h" +#include "geoCoord.h" +#include "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/v3/h3_linkedGeo.c b/v3/h3_linkedGeo.c new file mode 100644 index 0000000..4fe24b1 --- /dev/null +++ b/v3/h3_linkedGeo.c @@ -0,0 +1,375 @@ +/* + * 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 "linkedGeo.h" +#include +#include +#include "geoCoord.h" +#include "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 = 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 = 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 = 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; + 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; + free(currentLoop); + } + nextPolygon = currentPolygon->next; + if (skip) { + // do not free the input polygon + skip = false; + } else { + 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 = + malloc(polygonCount * sizeof(LinkedGeoPolygon*)); + assert(candidates != NULL); + const BBox** candidateBBoxes = 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 + free(candidates); + 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 = 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)); + 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]); + free(innerLoops[i]); + resultCode = NORMALIZATION_ERR_UNASSIGNED_HOLES; + } + } + + // Free allocated memory + free(innerLoops); + 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 "polygonAlgos.h" + +#undef TYPE +#undef IS_EMPTY +#undef INIT_ITERATION +#undef ITERATE diff --git a/v3/h3_localij.c b/v3/h3_localij.c new file mode 100644 index 0000000..38392d0 --- /dev/null +++ b/v3/h3_localij.c @@ -0,0 +1,654 @@ +/* + * 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.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 "baseCells.h" +#include "faceijk.h" +#include "h3Index.h" +#include "mathExtensions.h" +#include "stackAlloc.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 +}; + +// 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] = { + {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 +}; + +/** + * 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 = 0; + Direction revDir = 0; + 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); + + // 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; + } + + directionRotations = PENTAGON_ROTATIONS[originLeadingDigit][dir]; + pentagonRotations = directionRotations; + } 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; + } + + 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_III[originLeadingDigit][indexLeadingDigit] || + FAILED_DIRECTIONS_II[originLeadingDigit][indexLeadingDigit]) { + // TODO this part of the pentagon might not be unfolded + // correctly. + 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->i > 1 || ijk->i > 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_mathExtensions.c b/v3/h3_mathExtensions.c new file mode 100644 index 0000000..52c2f59 --- /dev/null +++ b/v3/h3_mathExtensions.c @@ -0,0 +1,39 @@ +/* + * 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 "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_polygon.c b/v3/h3_polygon.c new file mode 100644 index 0000000..ad462d5 --- /dev/null +++ b/v3/h3_polygon.c @@ -0,0 +1,84 @@ +/* + * 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.c + * @brief Polygon (Geofence) algorithms + */ + +#include "polygon.h" +#include +#include +#include +#include +#include "bbox.h" +#include "constants.h" +#include "geoCoord.h" +#include "h3api.h" +#include "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 "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_test.go b/v3/h3_test.go new file mode 100644 index 0000000..b9e2aa1 --- /dev/null +++ b/v3/h3_test.go @@ -0,0 +1,500 @@ +/* + * 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) +const pentagonH3Index = H3Index(0x821c07fffffffff) + +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{{}} +) + +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 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 new file mode 100644 index 0000000..0632717 --- /dev/null +++ b/v3/h3_vec2d.c @@ -0,0 +1,65 @@ +/* + * 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 "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_vec3d.c b/v3/h3_vec3d.c new file mode 100644 index 0000000..405591b --- /dev/null +++ b/v3/h3_vec3d.c @@ -0,0 +1,55 @@ +/* + * 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 "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_vertexGraph.c b/v3/h3_vertexGraph.c new file mode 100644 index 0000000..2aaae6f --- /dev/null +++ b/v3/h3_vertexGraph.c @@ -0,0 +1,218 @@ +/* + * 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 "vertexGraph.h" +#include +#include +#include +#include +#include +#include "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 = 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); + } + 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 = 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 + 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) { + 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/include/algos.h b/v3/include/algos.h new file mode 100644 index 0000000..029800d --- /dev/null +++ b/v3/include/algos.h @@ -0,0 +1,43 @@ +/* + * 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 "bbox.h" +#include "coordijk.h" +#include "h3api.h" +#include "linkedGeo.h" +#include "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); + +#endif diff --git a/v3/include/baseCells.h b/v3/include/baseCells.h new file mode 100644 index 0000000..eadf3cf --- /dev/null +++ b/v3/include/baseCells.h @@ -0,0 +1,58 @@ +/* + * 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 "constants.h" +#include "coordijk.h" +#include "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 + +// 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); +bool _baseCellIsCwOffset(int baseCell, int testFace); +int _getBaseCellNeighbor(int baseCell, Direction dir); +Direction _getBaseCellDirection(int originBaseCell, int destinationBaseCell); + +#endif diff --git a/v3/include/bbox.h b/v3/include/bbox.h new file mode 100644 index 0000000..c5f918c --- /dev/null +++ b/v3/include/bbox.h @@ -0,0 +1,42 @@ +/* + * 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 "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 bboxHexRadius(const BBox* bbox, int res); + +#endif diff --git a/v3/include/constants.h b/v3/include/constants.h new file mode 100644 index 0000000..d6ef3ae --- /dev/null +++ b/v3/include/constants.h @@ -0,0 +1,82 @@ +/* + * 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 + +/** H3 index modes */ +#define H3_HEXAGON_MODE 1 +#define H3_UNIEDGE_MODE 2 + +#endif diff --git a/v3/include/coordijk.h b/v3/include/coordijk.h new file mode 100644 index 0000000..db877bf --- /dev/null +++ b/v3/include/coordijk.h @@ -0,0 +1,113 @@ +/* + * 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 "geoCoord.h" +#include "h3api.h" +#include "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/include/faceijk.h b/v3/include/faceijk.h new file mode 100644 index 0000000..4224b51 --- /dev/null +++ b/v3/include/faceijk.h @@ -0,0 +1,76 @@ +/* + * 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 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 "coordijk.h" +#include "geoCoord.h" +#include "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 +/** 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 */ +#define KI 2 +/** JK quadrant faceNeighbors table direction */ +#define JK 3 + +// 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); + +#endif diff --git a/v3/include/geoCoord.h b/v3/include/geoCoord.h new file mode 100644 index 0000000..74daf17 --- /dev/null +++ b/v3/include/geoCoord.h @@ -0,0 +1,52 @@ +/* + * 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 "constants.h" +#include "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/v3/include/h3Index.h b/v3/include/h3Index.h new file mode 100644 index 0000000..9b7fc91 --- /dev/null +++ b/v3/include/h3Index.h @@ -0,0 +1,163 @@ +/* + * 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 "faceijk.h" +#include "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'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_NEGATIVE) + +/** H3 index with mode 0, res 0, base cell 0, and 7 for all index digits. */ +#define H3_INIT (UINT64_C(35184372088831)) + +/** + * 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. + */ +#define H3_INVALID_INDEX 0 + +void setH3Index(H3Index* h, int res, int baseCell, Direction initDigit); +int isResClassIII(int res); + +// Internal functions + +int _h3ToFaceIjkWithInitializedFijk(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/include/h3UniEdge.h b/v3/include/h3UniEdge.h new file mode 100644 index 0000000..5d62ea5 --- /dev/null +++ b/v3/include/h3UniEdge.h @@ -0,0 +1,28 @@ +/* + * 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 "algos.h" +#include "h3Index.h" + +// nothing non-public in this file + +#endif diff --git a/v3/include/h3api.h b/v3/include/h3api.h new file mode 100644 index 0000000..8db158c --- /dev/null +++ b/v3/include/h3api.h @@ -0,0 +1,513 @@ +/* + * 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 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/lon g + */ +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 hexArea hexArea + * Functions for hexArea + * @{ + */ +/** @brief hexagon area in square kilometers */ +double H3_EXPORT(hexAreaKm2)(int res); + +/** @brief hexagon area in square meters */ +double H3_EXPORT(hexAreaM2)(int res); +/** @} */ + +/** @defgroup edgeLength edgeLength + * Functions for edgeLength + * @{ + */ +/** @brief hexagon edge length in kilometers */ +double H3_EXPORT(edgeLengthKm)(int res); + +/** @brief hexagon edge length in meters */ +double H3_EXPORT(edgeLengthM)(int res); +/** @} */ + +/** @defgroup numHexagons numHexagons + * Functions for numHexagons + * @{ + */ +/** @brief number of hexagons for a given resolution */ +int64_t H3_EXPORT(numHexagons)(int res); +/** @} */ + +/** @defgroup getRes0Indexes getRes0Indexes + * Functions for getRes0Indexes + * @{ + */ +/** @brief returns the number of resolution 0 indexes */ +int H3_EXPORT(res0IndexCount)(); + +/** @brief provides all base cells */ +void H3_EXPORT(getRes0Indexes)(H3Index *out); +/** @} */ + +/** @defgroup h3GetResolution h3GetResolution + * Functions for h3GetResolution + * @{ + */ +/** @brief returns the resolution of the provided hexagon */ +int H3_EXPORT(h3GetResolution)(H3Index h); +/** @} */ + +/** @defgroup h3GetBaseCell h3GetBaseCell + * Functions for h3GetBaseCell + * @{ + */ +/** @brief returns the base cell of the provided hexagon */ +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 valid */ +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 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 a hexagon is actually a pentagon */ +int H3_EXPORT(h3IsPentagon)(H3Index h); +/** @} */ + +/** @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/include/linkedGeo.h b/v3/include/linkedGeo.h new file mode 100644 index 0000000..bcf6dc6 --- /dev/null +++ b/v3/include/linkedGeo.h @@ -0,0 +1,92 @@ +/* + * 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 "bbox.h" +#include "geoCoord.h" +#include "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/include/localij.h b/v3/include/localij.h new file mode 100644 index 0000000..ed0cda5 --- /dev/null +++ b/v3/include/localij.h @@ -0,0 +1,29 @@ +/* + * 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 "coordijk.h" +#include "h3api.h" + +int h3ToLocalIjk(H3Index origin, H3Index h3, CoordIJK* out); +int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out); + +#endif diff --git a/v3/include/mathExtensions.h b/v3/include/mathExtensions.h new file mode 100644 index 0000000..650d6a9 --- /dev/null +++ b/v3/include/mathExtensions.h @@ -0,0 +1,31 @@ +/* + * 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/include/polygon.h b/v3/include/polygon.h new file mode 100644 index 0000000..1ce4804 --- /dev/null +++ b/v3/include/polygon.h @@ -0,0 +1,75 @@ +/* + * 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 "bbox.h" +#include "geoCoord.h" +#include "h3api.h" +#include "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/include/polygonAlgos.h b/v3/include/polygonAlgos.h new file mode 100644 index 0000000..a6f0f57 --- /dev/null +++ b/v3/include/polygonAlgos.h @@ -0,0 +1,220 @@ +/* + * 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 "bbox.h" +#include "constants.h" +#include "geoCoord.h" +#include "h3api.h" +#include "linkedGeo.h" +#include "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/include/stackAlloc.h b/v3/include/stackAlloc.h new file mode 100644 index 0000000..479f66b --- /dev/null +++ b/v3/include/stackAlloc.h @@ -0,0 +1,64 @@ +/* + * 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/v3/include/vec2d.h b/v3/include/vec2d.h new file mode 100644 index 0000000..44030cc --- /dev/null +++ b/v3/include/vec2d.h @@ -0,0 +1,40 @@ +/* + * 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/include/vec3d.h b/v3/include/vec3d.h new file mode 100644 index 0000000..8f9464f --- /dev/null +++ b/v3/include/vec3d.h @@ -0,0 +1,37 @@ +/* + * 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 "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/include/vertexGraph.h b/v3/include/vertexGraph.h new file mode 100644 index 0000000..11894fd --- /dev/null +++ b/v3/include/vertexGraph.h @@ -0,0 +1,69 @@ +/* + * 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 "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 new file mode 100755 index 0000000..a07d896 --- /dev/null +++ b/v3/update-h3.sh @@ -0,0 +1,86 @@ +#!/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" + +# this must match the CGO include path in main.go +H3_INC_DIR="include" + +# 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 {} \; +if [ -d "$H3_INC_DIR" ]; then + rm -rf "$H3_INC_DIR" +fi + +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 + cp -- "$f" "$CWD/h3_$f" || badexit + done + popd || badexit + cp -R ./src/h3lib/include/ "$CWD"/include +popd || badexit From 928363cb4347b4fc20fc75a28ed190c6e9a68d9b Mon Sep 17 00:00:00 2001 From: Joseph Gilley Date: Tue, 14 Jan 2020 20:44:18 -0800 Subject: [PATCH 2/2] add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82893b7..93adf80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ fixes via patches with patch version bumps. * `MaxResolution` * `NumIcosaFaces` * `NumBaseCells` +* Support for GOMODULES (#24) ## 3.0.1